fix(gateway): thread session overrides through Bot Chat capability rebuild - #88826
Open
pierrenode wants to merge 1 commit into
Open
fix(gateway): thread session overrides through Bot Chat capability rebuild#88826pierrenode wants to merge 1 commit into
pierrenode wants to merge 1 commit into
Conversation
…build _sync_bot_capabilities() rebuilds a Bot Chat session's agent in place when its capability surface changes (skill install, MCP toggle, SOUL edit), via a bare _make_agent(sid, key, session_id=..., platform_ override=...) call. Unlike _make_agent's other caller (the initial session-build path), it never threads model_override / reasoning_config_ override / service_tier_override from the session dict, so _make_agent falls back to the global config default for each of them. Concretely: a bot pinned to a specific model via `/model X` (persisted as session["model_override"]) silently reverted to the config-default model the next time a capability edit landed for that bot -- directly undermining Bot Mode's own premise that each bot keeps its own model. No error, no warning beyond a generic "Capabilities updated" notice. Also guard the call site with the same `if not one_turn_restore:` check that already wraps _apply_pending_model_switch/_sync_agent_model_with_ config two lines above, for the same reason cited there (NousResearch#29923): a /model --once turn mutates the live agent in place and schedules a post-turn restore. A capability-triggered rebuild landing inside that window replaces the agent object outright, discarding the in-place once-switch (and, as the mutation-verify run showed, handing the turn to a freshly built agent that had no run_conversation wired for the mocked test double -- a real capability-sync-during-once-turn race would hand it a normal agent, but still one that silently dropped the once-model). The capability edit is still adopted on the next non-once turn; bot_caps_seen is only advanced once the guarded branch runs. Added 4 regression tests: two exercise _sync_bot_capabilities directly (override threading present/absent), one confirms the no-op path when the capability fingerprint hasn't moved, one drives the full prompt. submit dispatch to prove the rebuild is skipped while a once-restore is pending. Mutation-verified: reverting the fix makes the threading test fail (_make_agent called without the override kwargs) and the once- restore test fail (_make_agent gets called at all during the guarded window).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
tui_gateway/server.py::_sync_bot_capabilities()rebuilds a Bot Chat session's agent in place whenever its capability surface changes (skill install, MCP toggle, SOUL edit). It does so via a bare call:Unlike
_make_agent's other caller (the initial session-build path, which threadscurrent.get("model_override")/create_reasoning_override/create_service_tier_overrideinto the kwargs), this rebuild never passes them — so_make_agentfalls back to the global config default for the model, reasoning effort, and service tier.Why it matters
Bot Mode's own premise is that each bot keeps its own model. Concretely: pin a bot to a specific model via
/model X(persisted assession["model_override"]), then install a skill or toggle an MCP server for that bot later — the very next turn silently reverts it to the config-default model. No error, just a generic "Capabilities updated — this bot's tools and prompt were refreshed." notice.Second, related gap: the
/model --onceraceTwo lines above the
_sync_bot_capabilitiescall,_apply_pending_model_switch/_sync_agent_model_with_configare already wrapped inif not one_turn_restore:, with a comment citing #29923: a/model --onceturn mutates the live agent object in place and schedules a post-turn restore, so a same-turn rebuild must not run or it clobbers the in-place switch._sync_bot_capabilitiessat outside that guard, so a capability-triggered rebuild landing during a pending once-restore replaces the agent object outright — discarding the once-switch, and (per the mutation-verify run below) handing the turn to a brand-new agent object.Fix
model_override/reasoning_config_override/service_tier_overridefrom the session dict into_sync_bot_capabilities's_make_agentcall, mirroring the initial-build caller._sync_bot_capabilities(sid, session)call inside the existingif not one_turn_restore:block. The capability edit is still adopted on the very next non-once turn —bot_caps_seenis only advanced once the guarded branch actually runs.Testing
Added 4 tests to
tests/test_tui_gateway_server.py:test_bot_capability_sync_threads_session_overrides_into_rebuild— a pinned model/reasoning/tier survive a capability-triggered rebuild.test_bot_capability_sync_rebuild_without_pin_omits_override_kwargs— an unpinned bot doesn't get override kwargs manufactured out of nothing.test_bot_capability_sync_noop_when_fingerprint_unchanged— no rebuild when the capability fingerprint hasn't moved.test_prompt_submit_skips_bot_capability_rebuild_during_pending_once_restore— drives the realprompt.submitdispatch with a pendingone_turn_model_restore;_make_agentmust not be called during that turn.Mutation-verified (fix reverted via
git stash):_make_agentgets called without the override kwargs._make_agentgets called at all inside the guarded window (and, revealingly, handing the turn to the freshly rebuilt agent crashes the mocked turn withAttributeError: ... no attribute 'run_conversation', since the fake rebuild returns a bare object — a concrete illustration of what an in-flight rebuild does to a running turn).Full
tests/test_tui_gateway_server.py(589 tests) andtests/tui_gateway/+tests/tools/test_bot_mode_probe.py(499 tests) pass.ruff checkclean on both changed files.Checklist