Skip to content

fix(agent): scope model.context_length override to config default model - #62153

Closed
TheTom wants to merge 2 commits into
NousResearch:mainfrom
TheTom:fix/scope-config-context-length
Closed

fix(agent): scope model.context_length override to config default model#62153
TheTom wants to merge 2 commits into
NousResearch:mainfrom
TheTom:fix/scope-config-context-length

Conversation

@TheTom

@TheTom TheTom commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Closes #62152

Problem

The explicit model.context_length in config.yaml is written for the config's default model, but init_agent applied it to every agent build unconditionally. A new session created with a per-session model override (desktop/mobile picker choosing a different provider/model) inherited the override and reported the wrong context window: e.g. a Codex gpt-5.6 session (real window 372k) showing a local default model's 131072, with the compressor threshold firing accordingly.

The in-session /model switch path already guards against exactly this by clearing _config_context_length on swap (agent_runtime_helpers.py). The build path had no equivalent, so switching mid-session gave the correct window while picking the same model for a new chat did not.

Fix

New pure helper _scope_config_context_length_to_default_model in agent/agent_init.py, wired in right after the existing int-parse of the override:

  • reads the config default the same way hermes_cli/web_server.py::get_model_info does (default, falling back to name)
  • if the config names a default model and agent.model differs, returns None so get_model_context_length() auto-detects the real window for the actual model
  • conservative otherwise: non-dict model config, no named default, or empty agent model all keep current behavior

Per-model context_length entries under custom_providers are untouched and still resolve after the scoped override falls through to None (that block already keys on model + base_url).

Tests

tests/run_agent/test_config_context_length_model_scoping.py (new, 11 tests):

  • integration (real AIAgent, mirroring test_invalid_context_length_warning.py): override applies when the session model matches the config default; override does not leak onto a session with a different model (auto-detection wins); plain-string model config unchanged
  • unit coverage of the helper: match/mismatch, name fallback, whitespace, missing default, missing agent model, non-dict config

tests/run_agent/test_invalid_context_length_warning.py: two tests incidentally built the agent with a model that mismatched the config default while asserting the override applied; they now pass the matching model explicitly, with a comment noting the scoping constraint.

python3 -m pytest tests/run_agent/test_config_context_length_model_scoping.py tests/run_agent/test_invalid_context_length_warning.py passes 16/16 on this branch. ruff check clean on the touched files.

@TheTom
TheTom force-pushed the fix/scope-config-context-length branch from 52f2128 to cddecbc Compare July 10, 2026 15:32
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint area/config Config system, migrations, profiles sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Jul 10, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Competing-PR cluster for #62152 (model.context_length override leaking onto a session running a different model). This PR and #62124 both scope the override in agent/agent_init.py via the same core mechanism (drop the override when the config's default model differs from the active model), but frame different triggers: this PR targets the per-session model-override / picker leak; #62124 targets the CLI --model/skill/fallback mismatch and additionally adds a provider-prefix normalizer, a visible mismatch warning, and a companion anti-thrashing guard (#62125). #62124 is the earlier and broader entrant. Flagging so a maintainer can pick the canonical one rather than merging both.

TheTom added a commit to TheTom/hermes-go that referenced this pull request Jul 10, 2026
Context-window override leak (NousResearch#62153, fixed here),
session model switches persisting globally (NousResearch#61192),
and bare custom endpoints hidden in the picker (NousResearch#59808,
cherry-pick branch on this fork).

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for isolating the initialization leak; current main still reads the override unconditionally at agent/agent_init.py:1632-1639, so the underlying bug is real.

Problems

  • agent/agent_init.py:280 compares raw identifiers, but current main normalizes agent.model for non-aggregator providers at agent/agent_init.py:464-471. A valid configured default such as zai/glm-4.6 can therefore compare unequal to normalized glm-4.6 and lose its intended override.
  • The gateway hygiene path remains unscoped: gateway/run.py:11050-11056 reads the config override before resolving session runtime at gateway/run.py:11079-11087, then passes it to context resolution at gateway/run.py:11093-11099. This can still size pre-agent compression for an overridden session with the default model's window.

Suggested changes

  • Normalize the configured identifier with the same provider-aware normalization used for agent.model, with a prefixed-model regression test.
  • Scope the gateway hygiene override after session-runtime resolution and cover that path with a regression test.

Automated hermes-sweeper review.

Comment thread agent/agent_init.py Outdated
return config_context_length
_default_model = str(model_cfg.get("default", model_cfg.get("name", "")) or "").strip()
_agent_model = str(agent_model or "").strip()
if _default_model and _agent_model and _agent_model != _default_model:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agent.model has already been normalized for non-aggregator providers in the init path, while _default_model remains raw config text. Normalize the config value with the same provider-aware normalizer before comparing; otherwise a valid value such as zai/glm-4.6 mismatches normalized glm-4.6 and drops the intended override.

TheTom added 2 commits July 11, 2026 06:51
The explicit model.context_length in config.yaml is written for the
config's default model, but init_agent applied it unconditionally to
every agent build. A session created with a per-session model_override
(desktop/mobile picker choosing a different provider/model) inherited
the override and reported the wrong context window, e.g. a Codex
gpt-5.6 session (372k) showing a local model's 131072.

The in-session /model switch path already guards against this by
clearing _config_context_length on swap (agent_runtime_helpers.py).
This applies the same scoping at build time: if the config block names
a default model and the agent's model differs, drop the override so
get_model_context_length auto-detects the real window. Per-model
context_length entries under custom_providers are unaffected and still
resolve after the scoped override falls through to None.
…ssion model

Two follow-ups from review on the model.context_length scoping fix.

_scope_config_context_length_to_default_model compared the raw
config.yaml default against agent.model, which has already been
provider-normalized (e.g. zai/glm-4.6 stripped to glm-4.6) elsewhere in
init_agent. A default written with a provider prefix therefore never
matched and lost its override even when the session was actually on
that model. The helper now runs the same normalize_model_for_provider
logic on the default before comparing.

Separately, gateway hygiene's pre-agent compression sizing read
model.context_length from the raw config before resolving which model
the session was actually running via _resolve_session_agent_runtime.
A session with a per-session model override kept the default model's
context window for hygiene sizing even though it was on a different
model. The override is now re-scoped with the same helper after
runtime resolution, using the resolved session model and provider.
@TheTom

TheTom commented Jul 11, 2026

Copy link
Copy Markdown
Contributor Author

Both findings addressed, thanks for the precise pointers.

  1. Provider-prefix normalization: _scope_config_context_length_to_default_model now takes the provider and runs the configured default through the same normalize_model_for_provider path used on agent.model (same aggregator-provider exclusion), so default: zai/glm-4.6 matches the normalized glm-4.6 and keeps its override. Regression tests cover the prefixed-default keep case, the genuinely-different-model drop case, and the aggregator-provider passthrough.

  2. Gateway hygiene path: gateway/run.py now re-scopes _hyg_config_context_length after _resolve_session_agent_runtime, using the resolved session model and provider, before context resolution consumes it. New tests/gateway/test_session_hygiene.py covers both directions; the leak test fails without the fix.

Also rebased onto current main. On the competing-PR note from triage: #62124 covers the CLI/skill/fallback trigger in agent_init only; this PR now additionally covers the gateway pre-agent hygiene sizing, which neither PR previously did. Happy to converge with #62124 in whichever direction maintainers prefer.

@TheTom
TheTom force-pushed the fix/scope-config-context-length branch from cddecbc to 6d1cc63 Compare July 11, 2026 12:10
@teknium1 teknium1 added the sweeper:blast-broad Sweeper blast radius: broad — a core path most sessions hit label Jul 11, 2026
@kshitijk4poor

Copy link
Copy Markdown
Collaborator

Closing as superseded — the core mechanism here (scope a global model.context_length to the configured default model/route, drop it when the session's actual model or route differs) landed on main on July 21–22 via #69114 (fix(compression): prevent stale-budget retry loops, commit 377244f) and commit 63dd651 (fix(providers): scope route-owned runtime settings):

  • agent/agent_init.py now drops the pin on _model_mismatch or _route_mismatch (normalized model comparison + route-URL comparison, including custom_providers base_url resolution)
  • gateway/run.py's message path applies the same scoping (_msg_model != _msg_configured_model check + should_clear_context_pin_async route check)

This PR was opened before that landed and correctly identified the bug (#62152) and the fix direction — thank you for the detailed analysis and tests; the normalization-aware default-model comparison you implemented is exactly the shape that ended up on main.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/config Config system, migrations, profiles comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P2 Medium — degraded but workaround exists sweeper:blast-broad Sweeper blast radius: broad — a core path most sessions hit sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

model.context_length override leaks onto sessions running a different model

4 participants