fix(hub): resolve the engine from backend_json for runtime mode/model switching - #570
Merged
Merged
Conversation
… switching
Changing a steward's model or permission mode never worked. Both halves
of the path resolved the engine family from `agents.kind`, which for a
steward is the persona template (`steward.claude-m4`), not the family
(`claude-code`):
- resolveRuntimeModeSwitch looked the family up to read its
`runtime_mode_switch` table, matched nothing, and answered 422
"engine does not support runtime mode switching".
- respawnWithSpecMutation keyed `flagForField` the same way and would
have returned errUnknownFamilyField had a request ever reached it.
So the feature was dead end to end for the agent class this product is
built around, while working fine for a direct engine spawn — which is
exactly why every existing test passed. The seed helper could not even
express a steward: it wrote no `backend_json`, so no test in the file
could exhibit the bug.
Third instance of this class, after the `/compact` marker (R3) and the
desktop's engine-gated affordances (R2). The canonical fix was already
written down, with its rationale, at handlers_sessions.go:1088.
Fixing it armed a trap the repo had already predicted. `spliceResume` is
family-keyed too, and was unreachable for stewards only because the flag
table rejected them first — resume_splice_table_test.go:55 says as much:
"Latent (flagForField gates first), but it would have become a silent
cold-start". Resolving the engine at one site and not the other would
have replaced a loud 422 with a SILENT loss of the resume cursor, so a
steward's model flip would cold-start mid-session. Both lookups moved
together, and a test pins the cursor.
Verified: 5 mutations all killed, including one per site and one per
legacy-row fallback; full `go test ./...` green.
Found while surveying R6's (composer pills) consumer side — building the
pill first would have shipped a control that is disabled for exactly the
agents it exists for, looking like an engine limitation rather than ours.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
physercoe
added a commit
that referenced
this pull request
Aug 17, 2026
…desktop 2026.817.322 (#576) * chore(release): all four lanes — mobile/hub/host 2026.817.322-alpha, desktop 2026.817.322 Four tags go on this commit once merged: mobile-v2026.817.322-alpha APK + IPA, non-prerelease, owns releases/latest hub-v2026.817.322-alpha hub-server tarballs, prerelease host-v2026.817.322-alpha host-runner tarballs, prerelease electron-v2026.817.322-alpha desktop unsigned alpha, prerelease Stamp minted with `date -u` (2026-08-17 03:22 UTC). 46 commits since 2026.805.1022 — the largest gap between cuts so far. **Hub/host, 18 commits.** Pane-state detection lands as a lane: a declarative evaluator over vendored screen manifests (P1), wired to the poll tick (P2), raising and retracting attention on a blocked pane (P3), and explaining its own verdict rule by rule through `pane_explain` (P4). Plus native-resume recipes as shared data (N1), generic pane-input hardening (Q1), and four engine-vocabulary corrections found by measuring real binaries: claude's discarded sub-agent provenance (R5), codex's swallowed command output (E3), the attachment shapes codex rejects (L4c), and a runtime mode/model switch that resolved the engine from the wrong column so it had never worked for a steward (#570) — plus the field mask that makes the three impossible switches say so (R6). Security: an engine-supplied session id could inject shell into the next respawn. **Desktop, 32 commits.** The Companion runs a claude (L3a) or codex (L4c) session locally with no hub, surviving app restart with transcript and memory restored separately (L3b); the transcript grew streamed output and inline media (R4), a sub-agent panel (R5), and model/permission pills (R6). Read became a discovery workspace — six literature sources plus Scholar, cadence monitors, social subscriptions, ratings, citation details. Plus rich Inspect previews, a bundled terminal font, an SFTP transfer queue, and a monochrome theme pass. ⚠ Operators must redeploy the hub and host-runner. Both the 2026.730.1231 and 2026.805.1022 sections said the same about their own 51 and 17 commits; if neither happened this cut compounds both, and every pane-state feature above is host-runner code by construction. **Changelog gaps closed in the same pass.** Neither changelog's Unreleased section covered the ~18 Fleet desktop PRs or four hub-side changes (the Read library's attachments + rating columns, and the hub halves of R5 and R6), and the desktop section had seven feature entries misfiled under `### Fixed` plus a duplicate heading in each file. Written up and restructured rather than shipped as a partial record. Verified: hub `go build ./...` + full `go test ./...` (exit 0, no FAIL), desktop tsc + vite build (4m43s), 900 frontend tests, 726 electron tests (720 pass / 6 skipped / 0 fail), 12 lints, NUL/CJK scans clean. Flutter is absent on this host, so the mobile analyze/test/APK legs are CI-only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(roadmap): reconcile with 2026.805.1022 + 2026.817.322; pane-state plan is in flight, not proposed The roadmap was two releases behind: its Done-this-quarter tables stopped at 2026.730.1231 / 2026.730.1242, and its Now table had no row for either lane that dominated the 2026-08 cycle. - Status block re-stamped to the current cut. - Both Done tables gain their two missing releases, mobile/hub/host and desktop, described from the changelog sections rather than commit subjects. - Now gains two rows: the **hub + host-runner redeploy**, which three consecutive cuts have now deferred (51 + 17 + 18 server-side commits are not live), and the **vision-parity + pane-state** lanes with what actually remains in each — L3c, P5, S1/S2, all deferrable or unstarted. - `plans/pane-state-manifests.md` said "Proposed — for review" while P1–P4, N1 and Q1 had all shipped. Status corrected; `Last verified vs code` left alone, because re-stamping it would claim an anchor-by-anchor re-verification this commit did not do. Release commits do not touch the roadmap in this repo (neither 1f8fc77 nor e60c83f did) — it is maintained by dedicated docs: commits, which is why this is separate from the version bump it accompanies. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Ubuntu <ubuntu@ip-172-26-2-239.agent.ai> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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.
The bug
Changing a steward's model or permission mode never worked. Both halves of
the
set_model/set_modepath resolved the engine family fromagents.kind— which for a steward is the persona template (
steward.claude-m4), not thefamily (
claude-code):resolveRuntimeModeSwitchunsupported→ 422 "engine does not support runtime mode switching"respawnWithSpecMutationflagForFieldthe same way →errUnknownFamilyField, had a request ever reached itSo the feature was dead end to end for the agent class this product is built
around, while working fine for a direct engine spawn.
Why no test caught it.
seedAgentWithSessionwrote nobackend_json, so notest in the file could express a steward at all — every case seeded
Kind: "claude-code", which passes whether the code readskindorbackend_json. The seed now takes aBackend, and the new cases use theseparating input:
Kindis a template id and onlyBackendnames the engine.Third instance of this class, after the
/compactmarker (R3) and thedesktop's engine-gated affordances (R2) — same column, same mistake. The
canonical fix, with its rationale, was already written at
handlers_sessions.go:1088.The trap inside the fix
spliceResumeis also family-keyed, and was unreachable for stewards onlybecause the flag table rejected them first. The repo had already predicted this
exact failure —
resume_splice_table_test.go:55:Resolving the engine at one site and not the other would have replaced a loud
422 with a silent loss of the resume cursor — every steward's model flip
cold-starting mid-session with the transcript visibly jumping. Both lookups
moved together, and
TestRespawnWithSpecMutation_StewardKeepsResumeCursorpinsit.
Kind:on the respawn itself deliberately staysagents.kind— it re-spawnsthe same persona, not a bare engine.
Verification
legacy
{}rows still fall back tokind, and a table over the routing gate(steward/claude, steward/codex, direct spawn, unknown engine).
resume-splice trap.
go test ./...green (0 failures);gofmtclean on changed files; CIdoc lint set passes.
Why now
Found while surveying the consumer side of R6 (composer pills) in the
vision-parity plan. Building the pill first would have shipped a control that is
disabled for exactly the agents it exists for — and read as an engine
limitation rather than our bug. The plan's R6 entry records this along with
three of its own specifics that needed correcting.