Description
nemoclaw inference set --provider anthropic-prod (and --provider compatible-anthropic-endpoint) leaves a Hermes-type sandbox unable to run any inference: every agent turn is rejected by the OpenShell L7 policy with
[OCSF] NET:OPEN [MED] DENIED inference.local:443 [reason:connection not allowed by policy: POST /chat/completions]
and the agent surfaces a 403 to the user on every channel message. The same providers work for OpenClaw-type sandboxes.
Expected: after nemoclaw inference set --provider anthropic-prod --model claude-sonnet-4-6, the Hermes agent's inference calls succeed through inference.local.
Root cause
Two related drops of the wire-format information on the Hermes sync path:
-
patchHermesInferenceConfig discards route.inferenceApi (src/lib/actions/inference-set.ts). It writes only model.default, model.base_url, and a hardcoded model.provider: "custom". Compare patchOpenClawInferenceConfig, which propagates api: route.inferenceApi into the provider config — that's why OpenClaw works and Hermes doesn't.
-
getSandboxInferenceConfig defaults compatible-anthropic-endpoint to the OpenAI wire (src/lib/inference/config.ts). The guard reads inferenceApi === "openai-completions", but inferenceApi is initialized to preferredInferenceApi || "openai-completions" — so a null preference (the Hermes sync never passes one) silently selects the managed/OpenAI branch for an Anthropic-wire provider.
Downstream mechanics: Hermes resolves the transport of a custom provider via base-URL heuristics (hermes_cli/providers.py — api.anthropic.com or a /anthropic suffix → anthropic_messages); https://inference.local/v1 matches nothing, so Hermes emits POST /chat/completions. Onboarding registers both providers as providerType: "anthropic" (src/lib/onboard/providers.ts), whose L7 policy only permits the Anthropic wire (/v1/messages). Mismatch → every call denied.
Hermes natively supports the missing key: model.api_mode: anthropic_messages is honored for custom providers (its auth flow even cleans up stale api_mode values on provider switches, confirming it's a supported config surface).
Reproduction Steps
- Onboard a Hermes sandbox (e.g. the NemoHermes path), any initial provider.
openshell provider create --name anthropic-prod --type anthropic --credential ANTHROPIC_API_KEY
nemoclaw inference set --provider anthropic-prod --model claude-sonnet-4-6 (with --no-verify if the gateway host cannot probe api.anthropic.com)
- Message the agent on any channel, or from inside the sandbox:
curl -sk https://inference.local/v1/chat/completions -H 'content-type: application/json' -d '{"model":"claude-sonnet-4-6","max_tokens":10,"messages":[{"role":"user","content":"ping"}]}'
- Observe the L7 denial in
nemoclaw <name> logs and the agent failing to answer.
/sandbox/.hermes/config.yaml after step 3 shows the synced block:
model:
default: claude-sonnet-4-6
base_url: https://inference.local # anthropic route
provider: custom # → Hermes speaks /chat/completions
# (no api_mode — the bug)
Proposed fix
patchHermesInferenceConfig: when route.inferenceApi === "anthropic-messages", also write model.api_mode: "anthropic_messages"; delete the key on non-anthropic routes so a stale wire mode never survives a provider switch.
getSandboxInferenceConfig: keep compatible-anthropic-endpoint on the OpenAI wire only for an explicit preferredInferenceApi === "openai-completions", not the null-default.
PR with both fixes + unit tests to follow.
Environment
- NemoClaw v0.0.55 (also present on current
main), Hermes Agent v2026.5.16 sandbox (nemohermes)
- Host: Linux 5.15 (Jetson, aarch64), Node v22.22.3, Docker + OpenShell gateway
- Workaround in the meantime: use the
compatible-endpoint (OpenAI-wire) profile pointed at Anthropic's OpenAI-compatible endpoint (OPENAI_BASE_URL=https://api.anthropic.com/v1) — consistent on both sides of the gateway, verified working.
Description
nemoclaw inference set --provider anthropic-prod(and--provider compatible-anthropic-endpoint) leaves a Hermes-type sandbox unable to run any inference: every agent turn is rejected by the OpenShell L7 policy withand the agent surfaces a 403 to the user on every channel message. The same providers work for OpenClaw-type sandboxes.
Expected: after
nemoclaw inference set --provider anthropic-prod --model claude-sonnet-4-6, the Hermes agent's inference calls succeed throughinference.local.Root cause
Two related drops of the wire-format information on the Hermes sync path:
patchHermesInferenceConfigdiscardsroute.inferenceApi(src/lib/actions/inference-set.ts). It writes onlymodel.default,model.base_url, and a hardcodedmodel.provider: "custom". ComparepatchOpenClawInferenceConfig, which propagatesapi: route.inferenceApiinto the provider config — that's why OpenClaw works and Hermes doesn't.getSandboxInferenceConfigdefaultscompatible-anthropic-endpointto the OpenAI wire (src/lib/inference/config.ts). The guard readsinferenceApi === "openai-completions", butinferenceApiis initialized topreferredInferenceApi || "openai-completions"— so a null preference (the Hermes sync never passes one) silently selects the managed/OpenAI branch for an Anthropic-wire provider.Downstream mechanics: Hermes resolves the transport of a
customprovider via base-URL heuristics (hermes_cli/providers.py—api.anthropic.comor a/anthropicsuffix →anthropic_messages);https://inference.local/v1matches nothing, so Hermes emitsPOST /chat/completions. Onboarding registers both providers asproviderType: "anthropic"(src/lib/onboard/providers.ts), whose L7 policy only permits the Anthropic wire (/v1/messages). Mismatch → every call denied.Hermes natively supports the missing key:
model.api_mode: anthropic_messagesis honored for custom providers (its auth flow even cleans up staleapi_modevalues on provider switches, confirming it's a supported config surface).Reproduction Steps
openshell provider create --name anthropic-prod --type anthropic --credential ANTHROPIC_API_KEYnemoclaw inference set --provider anthropic-prod --model claude-sonnet-4-6(with--no-verifyif the gateway host cannot probeapi.anthropic.com)curl -sk https://inference.local/v1/chat/completions -H 'content-type: application/json' -d '{"model":"claude-sonnet-4-6","max_tokens":10,"messages":[{"role":"user","content":"ping"}]}'nemoclaw <name> logsand the agent failing to answer./sandbox/.hermes/config.yamlafter step 3 shows the synced block:Proposed fix
patchHermesInferenceConfig: whenroute.inferenceApi === "anthropic-messages", also writemodel.api_mode: "anthropic_messages"; delete the key on non-anthropic routes so a stale wire mode never survives a provider switch.getSandboxInferenceConfig: keepcompatible-anthropic-endpointon the OpenAI wire only for an explicitpreferredInferenceApi === "openai-completions", not the null-default.PR with both fixes + unit tests to follow.
Environment
main), Hermes Agent v2026.5.16 sandbox (nemohermes)compatible-endpoint(OpenAI-wire) profile pointed at Anthropic's OpenAI-compatible endpoint (OPENAI_BASE_URL=https://api.anthropic.com/v1) — consistent on both sides of the gateway, verified working.