NousResearch/hermes-agent

Model switching routing and provider resolution bugs

Cluster 16857. Data as of

Nothing open on GitHub. Fix merged in #16897 on 28 Apr 2026. Check it covers your case.

The short answer

0open issues
0open PRs
3items grouped
Known?
Yes. 3 related items grouped here, 1 issue already closed.
Fix event
A fix was merged to main in PR #16897. Check it covers your case. No release data is tracked.
Tracker
The maintainers' tracker marks this resolved, deciding PR #16897. That is separate from the fix event.
Still open
Nothing open on GitHub.

These issues and fixes address problems where model-switching commands incorrectly route to native providers instead of configured or custom providers, leak environment/process state across sessions, and fail to honor launch-time or runtime provider overrides.

Open issues0

No open issues.

Open PRs0

No open pull requests.

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

  • #16897 fix(tui): `/model` writes HERMES_TUI_PROVIDER unconditionally (#16857)

    merged to main

    files: tests/test_tui_gateway_server.py, tui_gateway/server.py

    Show the diff
    • tests/test_tui_gateway_server.py
      @@ -1171,6 +1171,61 @@ def switch_model(self, **_kwargs):
           assert os.environ["HERMES_INFERENCE_PROVIDER"] == "anthropic"
       
       
      +def test_config_set_model_syncs_tui_provider_unconditionally(monkeypatch):
      +    """Regression for #16857: /model must set HERMES_TUI_PROVIDER even when
      +    it wasn't pre-set on launch, so a later /new (which re-runs
      +    _resolve_startup_runtime) honours the user's explicit provider choice
      +    instead of falling through to static-catalog detection and picking a
      +    coincidentally-matching native provider.
      +    """
      +
      +    class _Agent:
      +        provider = "openrouter"
      +        model = "old/model"
      +        base_url = ""
      +        api_key = "sk-or"
      +
      +        def switch_model(self, **_kwargs):
      +            return None
      +
      +    result = types.SimpleNamespace(
      +        success=True,
      +        new_model="deepseek-v4-pro",
      +        target_provider="custom:xuanji",
      +        api_key="[redacted:secret-assignment]",
      +        base_url="https://xuanji.example/v1",
      +        api_mode="chat_completions",
      +        warning_message="",
      +    )
      +
      +    server._sessions["sid"] = _session(agent=_Agent())
      +    monkeypatch.delenv("HERMES_TUI_PROVIDER", raising=False)
      +    monkeypatch.delenv("HERMES_INFERENCE_PROVIDER", raising=False)
      +    monkeypatch.setattr(
      +        "hermes_cli.model_switch.switch_model", lambda **_kwargs: result
      +    )
      +    monkeypatch.setattr(server, "_restart_slash_worker", lambda session: None)
      +    monkeypatch.setattr(server, "_emit", lambda *args, **kwargs: None)
      +
      +    server.handle_request(
      +        {
      +            "id": "1",
      +            "method": "config.set",
      +            "params": {
      +                "session_id": "sid",
      +                "key": "model",
      +                "value": "deepseek-v4-pro --provider custom:xuanji",
      +            },
      +        }
      +    )
      +
      +    # Both env vars must reflect the user's choice. HERMES_TUI_PROVIDER is
      +    # the canonical explicit-this-process carrier consumed by
      +    # _resolve_startup_runtime() on /new.
      +    assert os.environ["HERMES_TUI_PROVIDER"] == "custom:xuanji"
      +    assert os.environ["HERMES_INFERENCE_PROVIDER"] == "custom:xuanji"
      +
      +
       def test_config_set_model_syncs_tui_provider_env(monkeypatch):
           class Agent:
               model = "gpt-5.3-codex"
    • tui_gateway/server.py
      @@ -843,14 +843,19 @@ def _apply_model_switch(sid: str, session: dict, raw_input: str) -> dict:
       
           os.environ["HERMES_MODEL"] = result.new_model
           os.environ["HERMES_INFERENCE_MODEL"] = result.new_model
      -    # Keep the process-level provider env var in sync with the user's explicit
      -    # choice so any ambient re-resolution (credential pool refresh, compressor
      -    # rebuild, aux clients) resolves to the new provider instead of the
      -    # original one persisted in config or env.
      +    # Keep the process-level provider env vars in sync with the user's
      +    # explicit choice so any ambient re-resolution (credential pool refresh,
      +    # compressor rebuild, aux clients) and startup re-resolution on /new
      +    # both pick up the new provider instead of the original one persisted
      +    # in config or env.
      +    #
      +    # HERMES_TUI_PROVIDER is the canonical "explicit-this-process" carrier
      +    # consumed by _resolve_startup_runtime() — set it unconditionally on
      +    # /model so /new can't fall through to static-catalog detection and
      +    # pick a coincidentally-matching native provider (fixes #16857).
           if result.target_provider:
               os.environ["HERMES_INFERENCE_PROVIDER"] = result.target_provider
      -        if os.environ.get("HERMES_TUI_PROVIDER"):
      -            os.environ["HERMES_TUI_PROVIDER"] = result.target_provider
      +        os.environ["HERMES_TUI_PROVIDER"] = result.target_provider
           if persist_global:
               _persist_model_switch(result)
           return {"value": result.new_model, "warning": result.warning_message or ""}

1 PR closed without merging

From the close comments: superseded by another PR 1

  • #16873 fix(gateway): honour HERMES_INFERENCE_PROVIDER in _resolve_startup_runtime (#16857) · superseded by another PR → #16897, #16857, #15755
Closed issues · 1
  • Closed#16857TUI: /new after /model switch to custom provider model fails with 'no API key' when model name exists in native provider catalog
How this cluster was grouped
  • Link evidence (structural links throughout): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
  • Pairwise link density 0.67: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. 1.00 means every pair is linked.

Pick up a Lab task

Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.

Open the Lab

At a glance

Merged PRs
#16897
Closed issues
1
Attempts
1 PR closed without merging
Fix link checked
28 Apr 2026. This confirmation is old and may be out of date (a revert or reopen would not have invalidated it here); re-verify.

For your agent

The same facts, typed.