Skip to content

fix(hub): resolve the engine from backend_json for runtime mode/model switching - #570

Merged
physercoe merged 1 commit into
mainfrom
fix-hub-runtime-mode-switch-steward-engine
Aug 16, 2026
Merged

fix(hub): resolve the engine from backend_json for runtime mode/model switching#570
physercoe merged 1 commit into
mainfrom
fix-hub-runtime-mode-switch-steward-engine

Conversation

@physercoe

Copy link
Copy Markdown
Owner

The bug

Changing a steward's model or permission mode never worked. Both halves of
the set_model / set_mode path resolved the engine family from agents.kind
— which for a steward is the persona template (steward.claude-m4), not the
family (claude-code):

site what happened
resolveRuntimeModeSwitch matched no family → unsupported422 "engine does not support runtime mode switching"
respawnWithSpecMutation keyed flagForField the same way → 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.

Why no test caught it. seedAgentWithSession wrote no backend_json, so no
test in the file could express a steward at all — every case seeded
Kind: "claude-code", which passes whether the code reads kind or
backend_json. The seed now takes a Backend, and the new cases use the
separating input: Kind is a template id and only Backend names the engine.

Third instance of this class, after the /compact marker (R3) and the
desktop'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

spliceResume is also family-keyed, and was unreachable for stewards only
because the flag table rejected them first. The repo had already predicted this
exact failure — resume_splice_table_test.go:55:

"Latent (flagForField gates first), but it would have become a silent
cold-start the moment antigravity gained a mode/model flag."

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_StewardKeepsResumeCursor pins
it.

Kind: on the respawn itself deliberately stays agents.kind — it re-spawns
the same persona, not a bare engine.

Verification

  • 4 new tests: steward resolves its engine, steward keeps its resume cursor,
    legacy {} rows still fall back to kind, and a table over the routing gate
    (steward/claude, steward/codex, direct spawn, unknown engine).
  • 5 mutations, all killed — one per site, one per legacy fallback, and the
    resume-splice trap.
  • Full go test ./... green (0 failures); gofmt clean on changed files; CI
    doc 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.

… 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
physercoe merged commit df24415 into main Aug 16, 2026
4 checks passed
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>
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