#131294·hermes-agent

[Bug]: A channel_overrides model evicts the cached gateway agent after every turn

Author: EloquentBrush0xCreated Oct 2, 2026Updated Oct 2, 2026
Labelstype/bugcomp/gatewayarea/configP0sweeper:risk-session-statesweeper:risk-caching

Bug Description

A chat that uses a channel_overrides model (documented in user-guide/messaging/index.md, "a cheap fast model in #daily and a frontier model in #dev") has its cached gateway agent evicted after every successful turn.

GatewayRunner._run_agent_evict_on_fallback (gateway/run_turn.py) treats "agent model != config model" as a fallback that activated during the run. The comparison uses the global model from _resolve_gateway_model(). _is_intentional_model_switch only excuses a session /model override and the Nous welcome-alias move. A channel_overrides.<id>.model is neither, so the agent is evicted on every turn even though it is running exactly the model configured for that chat.

d6c53dcdcb fixed two other causes of this same per-turn eviction ("preventing the prompt cache from ever warming"); the code comment next to the comparison says the same. Eviction also clears the session's context-prompt pin.

Steps to Reproduce

  1. Configure a channel override, e.g.
    yaml
    platforms:
      discord:
        enabled: true
        channel_overrides:
          "<channel id>":
            model: openai/gpt-5-mini
    with a global model: different from it.
  2. Send a message in that channel and let the turn finish successfully.
  3. The cached agent for the session is gone after the turn, and the next message builds a new one.

Minimal check from a source checkout (a runner built the way the existing tests in tests/gateway/test_model_switch_persistence.py build one):

python
runner.config.platforms[Platform.TELEGRAM].channel_overrides = {"c1": ChannelOverride(model="chan/model")}
# global model resolves to "default/model"
ctx.agent_holder[0] = SimpleNamespace(model="chan/model", provider="openrouter")
ctx.result_holder[0] = {"final_response": "ok"}
runner._run_agent_evict_on_fallback(ctx)   # -> _evict_cached_agent(session_key) is called

Expected Behavior

An agent still on the chat's configured channel_overrides model after a successful turn stays cached. A real fallback off that model is still evicted.

Actual Behavior

The agent is evicted after every successful turn. With the real-runner harness from tests/gateway/test_internal_event_pin_wiring.py on a Discord channel override: the session's agent is gone from _agent_cache after the turn and its ephemeral_pin is None. The same applies to a thread whose parent channel has the override.

Affected Component

Gateway (Telegram/Discord/Slack/WhatsApp)

Messaging Platform (if gateway-related)

Any platform with channel_overrides

Debug Report

Reproduced from source in a fresh temporary HERMES_HOME. A debug share would only upload unrelated config and logs, so none is attached.

Operating System

macOS

Python Version

3.14.5

Hermes Version

main at be5e9f72c6

Root Cause Analysis

_run_agent_evict_on_fallback uses _cfg_model = _resolve_gateway_model() as the baseline, while _resolve_session_agent_runtime applies channel_overrides (chat, thread or parent) on top of it. The two disagree whenever an override names a model.

GatewayConfigLoadersMixin._resolve_model_for_channel already resolves "channel_overrides else global default" through the shared resolve_effective_model (it currently has no callers).

Proposed Fix

Use _resolve_model_for_channel(...) with the same thread/parent lookup as the baseline in _run_agent_evict_on_fallback. Without an override it returns the global model, so nothing changes for chats that have none. PR to follow.

Are you willing to submit a PR for this?

  • I'd like to fix this myself and submit a PR

Source: NousResearch/hermes-agent