Summary
User-defined providers configured under the providers: dict in ~/.hermes/config.yaml are reachable through some CLI paths (top-level oneshot, TUI /model, delegation:) but blocked or ignored by two heavily-used paths:
hermes chat --provider <user-name> is rejected by argparse choices=.
-m <alias> from the CLI doesn't propagate the alias's base_url (only the model name).
Both result in requests silently routing to whatever model.base_url is in config, instead of the user-intended endpoint — verified via log inspection on the targeted endpoint.
Reproducible setup (~/.hermes/config.yaml)
model:
default: "gemma-4-26b"
provider: "custom"
base_url: "http://localhost:8080/v1"
providers:
host-a:
api: "http://localhost:8080/v1"
default_model: "gemma-4-26b"
transport: "openai"
host-b:
api: "http://localhost:11435/v1"
default_model: "gemma-4-31b"
transport: "openai"
model_aliases:
my-31b:
model: "gemma-4-31b"
provider: "custom"
base_url: "http://localhost:11435/v1"
(Two local llama.cpp instances on different ports — common multi-model pattern.)
Bug 1 — hermes chat --provider <user-name> rejected by argparse
$ hermes chat --provider host-b -m gemma-4-31b -q "Hello"
hermes chat: error: argument --provider: invalid choice: 'host-b'
(choose from 'auto', 'openrouter', 'nous', 'openai-codex', ...)
Expected: routes through _resolve_named_custom_runtime (which already handles user providers:) when --provider matches a key in user config.
Root cause: hermes_cli/main.py ~L7765 — chat subcommand argparse hardcodes choices= to built-in providers only. The top-level hermes --provider … flag does NOT have this restriction and routes correctly.
Suggested fix: drop choices= on the chat subparser's --provider, or build the list at runtime as BUILTIN_PROVIDERS | set(config.get("providers", {}).keys()).
Bug 2 — -m <alias> doesn't propagate base_url
$ hermes -m my-31b -z "Hello"
Request silently goes to model.base_url (port 8080) with the literal string my-31b as the model name. Verified by tailing the llama.cpp log on each port — port 11435 records no activity, port 8080 receives the request.
Expected: alias resolves to (model=gemma-4-31b, provider=custom, base_url=http://localhost:11435/v1) and the request hits port 11435.
Root cause (two layers):
-
hermes_cli/models.py:1537 — detect_provider_for_model() only searches built-in static catalogs and the OpenRouter catalog. It does not consult user providers: or DIRECT_ALIASES.
-
hermes_cli/model_switch.py:434–436 — resolve_alias() returns a 3-tuple that drops base_url:
direct = DIRECT_ALIASES.get(key)
if direct is not None:
return (direct.provider, direct.model, key) # drops direct.base_url
Callers in hermes_cli/oneshot.py therefore can't thread base_url through to resolve_runtime_provider(explicit_base_url=...).
Suggested fix: extend detect_provider_for_model() to consult DIRECT_ALIASES (matched by alias name or default_model), and have resolve_alias() return a 4-tuple (or DirectAlias) including base_url; update CLI callers to pass it through.
Workarounds verified to work today
hermes --provider host-b -m gemma-4-31b -z "…" (top-level oneshot, no chat subcommand) ✓
- TUI
/model my-31b (uses a separate path that handles base_url correctly) ✓
- For multi-model parallel use,
delegation.base_url works as designed (tools/delegate_tool.py:2178) ✓
Environment
- Hermes Agent v0.11.0 (2026.4.23)
- Python 3.11.14, uv-managed venv
- Linux / Ubuntu
- Two local llama.cpp servers (Gemma 4 26B and Gemma 4 31B) on different ports
Summary
User-defined providers configured under the
providers:dict in~/.hermes/config.yamlare reachable through some CLI paths (top-level oneshot, TUI/model,delegation:) but blocked or ignored by two heavily-used paths:hermes chat --provider <user-name>is rejected by argparsechoices=.-m <alias>from the CLI doesn't propagate the alias'sbase_url(only the model name).Both result in requests silently routing to whatever
model.base_urlis in config, instead of the user-intended endpoint — verified via log inspection on the targeted endpoint.Reproducible setup (
~/.hermes/config.yaml)(Two local llama.cpp instances on different ports — common multi-model pattern.)
Bug 1 —
hermes chat --provider <user-name>rejected by argparseExpected: routes through
_resolve_named_custom_runtime(which already handles userproviders:) when--providermatches a key in user config.Root cause:
hermes_cli/main.py~L7765 — chat subcommand argparse hardcodeschoices=to built-in providers only. The top-levelhermes --provider …flag does NOT have this restriction and routes correctly.Suggested fix: drop
choices=on the chat subparser's--provider, or build the list at runtime asBUILTIN_PROVIDERS | set(config.get("providers", {}).keys()).Bug 2 —
-m <alias>doesn't propagatebase_urlRequest silently goes to
model.base_url(port 8080) with the literal stringmy-31bas the model name. Verified by tailing the llama.cpp log on each port — port 11435 records no activity, port 8080 receives the request.Expected: alias resolves to
(model=gemma-4-31b, provider=custom, base_url=http://localhost:11435/v1)and the request hits port 11435.Root cause (two layers):
hermes_cli/models.py:1537—detect_provider_for_model()only searches built-in static catalogs and the OpenRouter catalog. It does not consult userproviders:orDIRECT_ALIASES.hermes_cli/model_switch.py:434–436—resolve_alias()returns a 3-tuple that dropsbase_url:Callers in
hermes_cli/oneshot.pytherefore can't threadbase_urlthrough toresolve_runtime_provider(explicit_base_url=...).Suggested fix: extend
detect_provider_for_model()to consultDIRECT_ALIASES(matched by alias name ordefault_model), and haveresolve_alias()return a 4-tuple (orDirectAlias) includingbase_url; update CLI callers to pass it through.Workarounds verified to work today
hermes --provider host-b -m gemma-4-31b -z "…"(top-level oneshot, no chat subcommand) ✓/model my-31b(uses a separate path that handles base_url correctly) ✓delegation.base_urlworks as designed (tools/delegate_tool.py:2178) ✓Environment