Repro
Dogfooded the hermes ↔ devagentic boundary from inside devagentic-duplex-claude. The container exposes DUPLEX_SERVICE_URL=http://devbox:6070. A naive operator following the canvas plugin README sets:
export DEVAGENTIC_BASE_URL=$DUPLEX_SERVICE_URL # http://devbox:6070
The canvas plugin then composes {base}/canvases → http://devbox:6070/canvases, which 404s. The real endpoint is http://devbox:6070/v1/canvases (401 → auth, as expected). Confirmed with curl:
$ curl -o /dev/null -w "%{http_code}\n" http://devbox:6070/canvases
404
$ curl -o /dev/null -w "%{http_code}\n" http://devbox:6070/v1/canvases
401
The user must remember to append /v1 to the base URL. There is no error message that points them at the missing prefix — the plugin just silently returns None from _request() (logged at DEBUG, not surfaced to the slash command).
Scope
Three modules duplicate the same DEVAGENTIC_BASE_URL resolution + {base}/... composition, all with the same /v1-required assumption:
plugins/devagentic-canvas/client.py:36 — http://127.0.0.1:6071/v1
agent/devagentic_skills.py:61 — http://127.0.0.1:6071/v1 → {base}/graphql
agent/devagentic_memory.py:62 — http://127.0.0.1:6071/v1 → {base}/graphql
All three break the same way when /v1 is omitted.
Proposed fix
Normalize the base URL in _base_url(): if the resolved value doesn't already end with /vN for some integer N, append /v1. So:
http://127.0.0.1:6071 → http://127.0.0.1:6071/v1
http://devbox:6070 → http://devbox:6070/v1
http://devbox:6070/v1 → http://devbox:6070/v1 (unchanged)
http://devbox:6070/v2 → http://devbox:6070/v2 (respect explicit version)
Backwards compatible (anyone already setting /v1 is unaffected) and makes the duplex-container case work without the operator memorizing the convention.
Won't extract a shared helper across the three modules in this PR — that's a separate refactor I don't want to bundle. Will land the same 4-line normalizer in each.
Out of scope
- Auto-detection of
DUPLEX_SERVICE_URL / orchestrator-specific env vars. Hermes shouldn't know about duplex-deployment specifics.
- The auth-failure case (
DEVAGENTIC_API_KEY empty → 401 → silent None). That's a separate diagnostic-loudness issue and the current loss-tolerant contract is correct for the pre_llm_call hook.
Filed by hermes-maintainer (PowerCreek) while dogfooding the canvas plugin from the duplex container.
Repro
Dogfooded the hermes ↔ devagentic boundary from inside
devagentic-duplex-claude. The container exposesDUPLEX_SERVICE_URL=http://devbox:6070. A naive operator following the canvas plugin README sets:The canvas plugin then composes
{base}/canvases→http://devbox:6070/canvases, which 404s. The real endpoint ishttp://devbox:6070/v1/canvases(401 → auth, as expected). Confirmed with curl:The user must remember to append
/v1to the base URL. There is no error message that points them at the missing prefix — the plugin just silently returns None from_request()(logged at DEBUG, not surfaced to the slash command).Scope
Three modules duplicate the same
DEVAGENTIC_BASE_URLresolution +{base}/...composition, all with the same/v1-required assumption:plugins/devagentic-canvas/client.py:36—http://127.0.0.1:6071/v1agent/devagentic_skills.py:61—http://127.0.0.1:6071/v1→{base}/graphqlagent/devagentic_memory.py:62—http://127.0.0.1:6071/v1→{base}/graphqlAll three break the same way when
/v1is omitted.Proposed fix
Normalize the base URL in
_base_url(): if the resolved value doesn't already end with/vNfor some integer N, append/v1. So:http://127.0.0.1:6071→http://127.0.0.1:6071/v1http://devbox:6070→http://devbox:6070/v1http://devbox:6070/v1→http://devbox:6070/v1(unchanged)http://devbox:6070/v2→http://devbox:6070/v2(respect explicit version)Backwards compatible (anyone already setting
/v1is unaffected) and makes the duplex-container case work without the operator memorizing the convention.Won't extract a shared helper across the three modules in this PR — that's a separate refactor I don't want to bundle. Will land the same 4-line normalizer in each.
Out of scope
DUPLEX_SERVICE_URL/ orchestrator-specific env vars. Hermes shouldn't know about duplex-deployment specifics.DEVAGENTIC_API_KEYempty → 401 → silent None). That's a separate diagnostic-loudness issue and the current loss-tolerant contract is correct for thepre_llm_callhook.Filed by hermes-maintainer (PowerCreek) while dogfooding the canvas plugin from the duplex container.