hermes - 💡(How to fix) Fix Persist session-scoped model/reasoning switches in SessionEntry instead of in-memory dict

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Code Example

class SessionEntry:
    # ... existing fields ...
    model: Optional[str] = None             # None → config.yaml default
    provider: Optional[str] = None          # None → config.yaml default
    reasoning_effort: Optional[str] = None  # None → config.yaml default

---

model = session_entry.model or config_default_model
provider = session_entry.provider or config_default_provider
runtime_kwargs = resolve_runtime_provider(provider)   # derives api_key/base_url/api_mode
reasoning = parse_reasoning_effort(session_entry.reasoning_effort) or config_reasoning
RAW_BUFFERClick to expand / collapse

Problem

Session-scoped model switches (/model, model_switch tool with scope=session) are stored in an in-memory dict (_session_model_overrides / _session_reasoning_overrides) on the GatewayRunner instance. This means:

  1. Gateway restart → session model switch is lost. The session itself survives via sessions.json, but the model override silently reverts to config.yaml defaults.
  2. SoT is fragmented. Model state lives in a separate dict from the session it belongs to, requiring manual .pop() at 3+ sites (auto-reset, compression-exhausted, /new).
  3. Override layering is opaque. config.yaml default → resolve_runtime_provider → _session_model_overrides → agent instance makes it hard to trace what decided the model.

Proposed Solution

Add 3 nullable fields to SessionEntry:

class SessionEntry:
    # ... existing fields ...
    model: Optional[str] = None             # None → config.yaml default
    provider: Optional[str] = None          # None → config.yaml default
    reasoning_effort: Optional[str] = None  # None → config.yaml default

At agent creation time:

model = session_entry.model or config_default_model
provider = session_entry.provider or config_default_provider
runtime_kwargs = resolve_runtime_provider(provider)   # derives api_key/base_url/api_mode
reasoning = parse_reasoning_effort(session_entry.reasoning_effort) or config_reasoning

What this removes

  • _session_model_overrides dict + 3 manual .pop() sites
  • _session_reasoning_overrides dict + related management code
  • _apply_session_model_override() method
  • _is_intentional_model_switch()session_entry.model == agent_model

What this fixes

  • Persistence: sessions.json already serializes SessionEntry → model survives gateway restarts
  • Single SoT: model is a session attribute, not a side-channel override
  • Lifecycle simplification: /new calls reset_session() which creates a fresh SessionEntry with None fields → automatic cleanup, no manual pop needed

Design notes

  • api_key/base_url/api_mode are not stored — they are derived from the provider name at runtime via resolve_runtime_provider(). No credentials in session store.
  • The runtime_update_callback closure in run.py would write to session_entry.model/.provider/.reasoning_effort + trigger a session store flush instead of mutating the dict.
  • TUI gateway _apply_model_switch follows the same pattern.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING