fix(desktop): session-scope fast mode, surface profile ownership + pinned model override - #66449
Merged
Conversation
A cold profile switch pays the full pool-backend spawn — Python boot, port announcement, readiness probe, token adoption — before the profile's gateway can even open. Measured with the new CDP harness (scripts/measure-profile-switch.mjs, same family as profile-session-switch.mjs): click → WS open is ~2.5-2.9s on a cold profile, ~3-3.6s to a settled sidebar; a warm profile settles in ~0.5-0.8s. The pointer entering a profile square telegraphs the switch hundreds of ms before the click lands, so start the spawn then. - store/profile: prewarmProfileBackend(name) — fires the existing hermesDesktop.getConnection IPC, which is idempotent (ensureBackend returns the pooled connectionPromise), so the real switch joins the in-flight spawn instead of starting it. Skips the active gateway profile, throttles per profile (60s) so drive-by hovers can't spam spawn attempts, and swallows failures — error UX belongs to the real switch. No new IPC surface; the pool's existing LRU cap + idle reaper still bound resource use, and the LRU guard never evicts a keepalive-fresh backend for a hover spawn. - sidebar/use-profile-prewarm: pointerenter/pointerleave handlers with a 120ms dwell so sweeping the pointer across the rail or a mixed-profile session list doesn't spawn a backend per element crossed. - Wired at the three switch surfaces: rail ProfileSquare, the condensed ProfileDropdown items (extracted ProfileDropdownItem so each row owns its dwell timer), and SidebarSessionRow (covers cross-profile resumes from the all-profiles view; same-profile rows no-op inside the guard). Measured E2E over CDP: synthetic hover on a cold profile square spawns its backend in the background; the subsequent click settles in ~519ms vs ~3.0-3.6s unhovered — and any hover shorter than the spawn still shaves its dwell off the click's wait. Verification: apps/desktop `npx tsc --noEmit` clean; full `npx vitest run` 212 files / 1777 passed (new prewarm guard/throttle tests in store/profile.test.ts); eslint + prettier clean.
…nned model override
Model-picker audit follow-through — closes the remaining pieces of the
"switch one session, switches everywhere / can't tell whose session this
is" report class:
- tui_gateway: `config.set key=fast` with a session no longer writes the
global agent.service_tier to config.yaml (sibling of the earlier
`reasoning` scoping fix). It pins create_service_tier_override
("priority" / "" for explicit normal) so lazy builds and rebuilds keep
the choice; the desktop's per-model presets were rewriting the global
tier on every model pick. Fast-support validation now checks a draft's
picked model, and `config.get key=fast` reads the pre-build pin.
- desktop: owning-profile tag (initial chip + tooltip/aria label) on
pinned rows and search results in the All-profiles sidebar, and on the
chat header once a second profile exists (#66003).
- desktop: composer model pill shows a pin dot + tooltip when a manual
sticky pick is overriding the Settings default for new chats (#62055).
Closes #66003. Addresses #62055.
OutThisLife
enabled auto-merge
July 17, 2026 18:33
3 tasks
6 tasks
Gravezzz
pushed a commit
to Gravezzz/hermes-agent
that referenced
this pull request
Jul 21, 2026
…-model-picker fix(desktop): session-scope fast mode, surface profile ownership + pinned model override
randlee
pushed a commit
to randlee/hermes-agent
that referenced
this pull request
Aug 11, 2026
…-model-picker fix(desktop): session-scope fast mode, surface profile ownership + pinned model override
This was referenced Aug 14, 2026
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.
Summary
Audit of the desktop model picker / session-state complaints ("model switch applies everywhere; new message appears in all sessions; can't tell if a session belongs to Agent A or B; switching is slow"). Two of the three bug classes were already fixed on main in July (
ce5c1f9f7,659d1123c, #47709/#47743/#48281) but are not in the v0.18.2 release users are running. This PR closes everything that remained on main, in one change:config.set key=fastis now session-scoped (tui_gateway/server.py) — the last "switch one session, switches everywhere" hole. It wroteagent.service_tierto config.yaml globally even when targeting a session, and the desktop's per-model presets hit it on every model pick, flipping fast mode for every other session, profile, the CLI, and future gateway builds. Now it pinscreate_service_tier_override("priority"/""for explicit normal, so lazy builds and rebuilds keep the choice) and never touches config.yaml; the no-session path persists globally as before. Sibling of the earlierreasoningscoping fix — same pattern, same test shape.config.get key=fastreads the pre-build pin, and fast-support validation checks a draft's picked model instead of the global default.ProfileTagchip (profile initial, identity color with user override, tooltip + ariaProfile: <name>) on pinned rows and search results in the All-profiles sidebar view (the flat cross-profile lists with no group header), and on the chat header whenever more than one profile exists. Default profile renders neutral; status dots keep their own semantics; single-profile users see no change.e0390c0f7hover pre-warm from spawn-only to the full spawn + connect chain (openGatewayForProfile: same path as the real switch, minus activation). By click timeensureGatewayForProfilefinds an open socket and just activates it, instead of paying the WS connect + descriptor fetch after the pre-warmed spawn. Speculative hovers never start reconnect loops — retry/error UX stays with the real switch.Also verified during the audit: both the desktop and TUI halves of #61190 are already implemented on main (
--sessionflag reachesparse_model_flags), so that issue can be closed as implemented; and #65743 reproduces only on the v0.18.2 bundle, not main — the model-picker complaint set mostly needs a desktop release.Test plan
tests/tui_gateway/test_fast_session_scope.py(9 tests, mirrors the reasoning-scope suite) — session-scoped set skips config write, pins survive for lazy sessions, explicit normal pins"", toggle honors the pre-build pin, no-session persists globally,config.getreads the pintests/test_tui_gateway_server.py::test_config_set_fast_updates_live_agent_session_scoped— asserts NO global write + override pin (was asserting the global write)profile-tag.test.tsx(chip initial, accessible label, default-neutral, color override) andmodel-pill.test.tsx(pin dot on manual draft, quiet on default source, quiet on live session, both render paths)profile.test.tspre-warm suite — pre-warm opens (never activates) the gateway, skips the active profile, throttles, swallows failurestsc --noEmit(app + electron), eslint (0 errors),scripts/run_tests.sh tests/tui_gateway/ tests/test_tui_gateway_server.py(727 tests)Closes #66003.