feat(tui): reach the model picker without wrecking your draft, and switch mid-turn - #74756
Merged
Conversation
Reaching the model picker meant typing /model, which forces you to wipe whatever you'd already drafted. Bind Ctrl+O to open the same picker overlay directly, leaving the composer untouched. Ctrl+O is added to the textInput pass-through allowlist so the composer doesn't swallow it, mirroring the existing Ctrl+X session-switcher path.
…rejecting it Picking a model while a turn was streaming hit a 4009 'session busy' reject: switch_model() mutates the agent's model/provider/base_url/client in place and the worker thread reads those every iteration. Now config.set queues the pick in session[pending_model_switch] and _apply_pending_model_switch applies it on the turn thread at the next turn start, before any model call — no race, no interrupt, no waiting on the client rebuild. The TUI paints the pick optimistically and notes '(applies next turn)'.
OutThisLife
enabled auto-merge (squash)
July 30, 2026 10:10
Contributor
૮ >ﻌ< ა ci reviewran on 88c31fe ℹ️ InfoDesktop E2E visual evidence · View test artifacts · View job1 visual diff. inline evidence is publishing... |
3 tasks
OutThisLife
added a commit
that referenced
this pull request
Jul 30, 2026
The gateway now queues a model switch made during a turn and applies it at the next turn start (#74756), but the desktop still bounced the pill back to the old model: the post-switch refetch answered with the model still running and repainted over the pick. Skip that refetch when the switch was deferred — the apply publishes session.info when it lands, and that is what re-syncs every surface. An older gateway that still refuses with 4009 keeps the pick too rather than rolling back and toasting at a user who did nothing wrong; it is what the next turn runs anyway. Real failures still roll back and report. The 4009 predicate lives beside the other gateway-compat probes in lib/gateway-rpc.
3 tasks
OutThisLife
added a commit
that referenced
this pull request
Jul 30, 2026
) The gateway now queues a model switch made during a turn and applies it at the next turn start (#74756), but the desktop still bounced the pill back to the old model: the post-switch refetch answered with the model still running and repainted over the pick. Skip that refetch when the switch was deferred — the apply publishes session.info when it lands, and that is what re-syncs every surface. An older gateway that still refuses with 4009 keeps the pick too rather than rolling back and toasting at a user who did nothing wrong; it is what the next turn runs anyway. Real failures still roll back and report. The 4009 predicate lives beside the other gateway-compat probes in lib/gateway-rpc.
randlee
pushed a commit
to randlee/hermes-agent
that referenced
this pull request
Aug 11, 2026
…itch mid-turn (NousResearch#74756) * feat(tui): Ctrl+O opens the model picker without clearing your draft Reaching the model picker meant typing /model, which forces you to wipe whatever you'd already drafted. Bind Ctrl+O to open the same picker overlay directly, leaving the composer untouched. Ctrl+O is added to the textInput pass-through allowlist so the composer doesn't swallow it, mirroring the existing Ctrl+X session-switcher path. * feat(tui): apply a mid-turn model switch at the next turn instead of rejecting it Picking a model while a turn was streaming hit a 4009 'session busy' reject: switch_model() mutates the agent's model/provider/base_url/client in place and the worker thread reads those every iteration. Now config.set queues the pick in session[pending_model_switch] and _apply_pending_model_switch applies it on the turn thread at the next turn start, before any model call — no race, no interrupt, no waiting on the client rebuild. The TUI paints the pick optimistically and notes '(applies next turn)'.
randlee
pushed a commit
to randlee/hermes-agent
that referenced
this pull request
Aug 11, 2026
…sResearch#74759) The gateway now queues a model switch made during a turn and applies it at the next turn start (NousResearch#74756), but the desktop still bounced the pill back to the old model: the post-switch refetch answered with the model still running and repainted over the pick. Skip that refetch when the switch was deferred — the apply publishes session.info when it lands, and that is what re-syncs every surface. An older gateway that still refuses with 4009 keeps the pick too rather than rolling back and toasting at a user who did nothing wrong; it is what the next turn runs anyway. Real failures still roll back and report. The 4009 predicate lives beside the other gateway-compat probes in lib/gateway-rpc.
33hodl
pushed a commit
to 33hodl/hermes-agent
that referenced
this pull request
Aug 12, 2026
…itch mid-turn (NousResearch#74756) * feat(tui): Ctrl+O opens the model picker without clearing your draft Reaching the model picker meant typing /model, which forces you to wipe whatever you'd already drafted. Bind Ctrl+O to open the same picker overlay directly, leaving the composer untouched. Ctrl+O is added to the textInput pass-through allowlist so the composer doesn't swallow it, mirroring the existing Ctrl+X session-switcher path. * feat(tui): apply a mid-turn model switch at the next turn instead of rejecting it Picking a model while a turn was streaming hit a 4009 'session busy' reject: switch_model() mutates the agent's model/provider/base_url/client in place and the worker thread reads those every iteration. Now config.set queues the pick in session[pending_model_switch] and _apply_pending_model_switch applies it on the turn thread at the next turn start, before any model call — no race, no interrupt, no waiting on the client rebuild. The TUI paints the pick optimistically and notes '(applies next turn)'.
33hodl
pushed a commit
to 33hodl/hermes-agent
that referenced
this pull request
Aug 12, 2026
…sResearch#74759) The gateway now queues a model switch made during a turn and applies it at the next turn start (NousResearch#74756), but the desktop still bounced the pill back to the old model: the post-switch refetch answered with the model still running and repainted over the pick. Skip that refetch when the switch was deferred — the apply publishes session.info when it lands, and that is what re-syncs every surface. An older gateway that still refuses with 4009 keeps the pick too rather than rolling back and toasting at a user who did nothing wrong; it is what the next turn runs anyway. Real failures still roll back and report. The 4009 predicate lives beside the other gateway-compat probes in lib/gateway-rpc.
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.
Two friction points when changing model in the TUI: you had to type
/model(wiping whatever you'd drafted) to open the picker, and picking a model mid-turn was rejected outright with asession busyerror.Summary
/model. Added to the textInput pass-through allowlist so the composer doesn't eat it, mirroring the existing Ctrl+X session-switcher.4009 session busy.switch_model()mutates the live agent's model/provider/base_url/client in place, which races the worker thread mid-request — so instead of rejecting, the pick is queued (session["pending_model_switch"]) and applied on the turn thread at the next turn start, before any model call. No race, no interrupt, no waiting on the client rebuild. The pick paints optimistically with an(applies next turn)note.Binding is
Ctrl+O(not the desktop's Cmd+Shift+M): Cmd and Ctrl+Shift chords don't reach Terminal.app, plain Ctrl+letter does — same reason voice defaults to Ctrl+B.Test plan
test_config_set_model_defers_while_running— mid-turn pick queues, doesn't switch in placetest_apply_pending_model_switch_runs_queued_pick— queued pick applied once at turn start, then clearedtest_config_set_model_allowed_when_idle— idle switch still immediatetest_tui_gateway_server.py(495) green