Skip to content

fix: unify route dict or-None discipline in /model persist - #85920

Merged
kshitijk4poor merged 1 commit into
NousResearch:mainfrom
kshitijk4poor:fix/route-dict-asymmetry
Aug 14, 2026
Merged

fix: unify route dict or-None discipline in /model persist#85920
kshitijk4poor merged 1 commit into
NousResearch:mainfrom
kshitijk4poor:fix/route-dict-asymmetry

Conversation

@kshitijk4poor

Copy link
Copy Markdown
Collaborator

Summary

Follow-up to #85261. The /simplify-code 3-reviewer review on #85261 found that the route dict in _persist_model_switch_to_session used if v filtering (omits falsy values) while the top-level keys used or None (writes explicit None to trigger deletion in _merge_model_config_json). This asymmetry meant stale keys from a previous /model switch could still survive in the nested gateway_runtime dict even after #85261's fix that properly deleted them from the top-level keys.

The bug: When a user switches from provider A (with api_mode=anthropic_messages) to provider B (with api_mode=""), the top-level api_mode key was correctly deleted (written as None). But the nested gateway_runtime.api_mode was merely omitted from the new dict — and since gateway_runtime is replaced as a whole dict by the merge (not deep-merged), the previous switch's api_mode value was overwritten. However, None values written to gateway_runtime survived because the reader (session_gateway_runtime) returned dict(runtime) without filtering None.

The fix (2 files + tests):

  • cli.py: Build route with or None and derive top-level from **route so both shapes always use identical deletion semantics. Eliminates the hand-maintained parallel between the falsy-filtered route dict and the None-coalesced top-level dict — the exact divergence that caused the original stale-key bug.
  • hermes_state.py: Filter None values in session_gateway_runtime's reader, since gateway_runtime is replaced as a whole dict (not deep-merged), so None values written by the persist path survive in the nested dict.

Test plan

  • tests/cli/test_resume_model_restore.py — 15 passed (updated 2 existing assertions + added gateway_runtime stale-key assertions to existing test)
  • tests/cli/test_resume_display.py — passed
  • tests/test_hermes_state.py — passed (258 total, 0 failures)

Found by /simplify-code review on #85261 — all 3 reviewers (reuse, quality, efficiency) converged on the route dict asymmetry as the verdict-relevant finding.

The route dict in _persist_model_switch_to_session used  filtering
(omits falsy values) while the top-level keys used  (writes
explicit None to trigger deletion in _merge_model_config_json). This
asymmetry meant stale keys from a previous /model switch survived in
the nested gateway_runtime dict even after the fix in NousResearch#85261 that
properly deleted them from the top-level keys.

Fix: build the route dict with  and derive the top-level keys
from **route so both shapes always use identical deletion semantics.
Also filter None values in session_gateway_runtime's reader since
gateway_runtime is replaced as a whole dict (not deep-merged), so None
values written by the persist path survive in the nested dict.

Found by /simplify-code 3-reviewer review on NousResearch#85261 (all 3 reviewers
converged on the route dict asymmetry as the verdict-relevant finding).
@kshitijk4poor
kshitijk4poor enabled auto-merge (rebase) August 14, 2026 07:48
@kshitijk4poor
kshitijk4poor merged commit 9cb456a into NousResearch:main Aug 14, 2026
5 checks passed
kshitijk4poor pushed a commit that referenced this pull request Aug 14, 2026
Salvage of #79604 (webtecnica) + #85721 (pierrenode), combined and
rebased onto current main with simplify-code findings folded in.

#79604: update_session_model() wrote the model name to sessions.model
but never persisted the provider into model_config. On resume, the
runtime recombined the persisted model with the config.yaml primary
provider (which may not serve that model), producing auth errors.
Fix: add optional provider parameter to update_session_model, merged
into model_config via the shared _merge_model_config_json helper (not
hand-rolled SQL). Wire both gateway /model call sites to pass
result.target_provider.

#85721: session_gateway_runtime() had no billing_provider fallback.
A CLI session that never ran /model has no gateway_runtime or
top-level provider in model_config — billing_provider (written on
every session's first accounted API call) is the only durable record.
Fix: add billing_provider as the last-resort fallback in
session_gateway_runtime(), filtering bare billing buckets (auto/custom)
that are not routable identities.

Simplify-code findings addressed:
- Use _merge_model_config_json instead of 40 lines of branched SQL
- Share _BARE_BILLING_PROVIDERS from hermes_state.py (was duplicated
  as a set in tui_gateway/server.py)
- Merge None-filtering from #85920 with the billing_provider fallback
  into one coherent return path

Co-authored-by: pierrenode <298902573+pierrenode@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant