Mission Control · Architecture Plan · Round 2 of the visual harness

Rounds & the Artifact Ledger

Mission Control becomes the place to fly a session: plans are first-class, append-only artifacts in a ledger — never overwritten, each its own tab — a session carries any number of plan rounds, and the chat grows real harness controls (modes, model, permissions) plus agent-generated buttons for going back and forth.

1What broke, and why

Both reported bugs trace to the same root: the session record has no artifact model — just three ad-hoc pointers (planDocId, stageRelPath, planRelPaths) and a system prompt that hard-binds the agent to a single file forever.

Bug A

"Why does the plan show up as a Deliverable sometimes?"

Tab labels fall out of a fallback chain, not a model: kind==='plan' && planIds.length > 0 → "Plan" tabs, else → "Deliverable". Three ways to hit the wrong branch:

derive labels from the artifact ledger's role field. A plans/ doc is a Plan, period — regardless of session kind, timing, or which pointer found it.

Bug B

"The new plan overwrote the deliverable — and I can't continue the session"

append-only rounds. Approving a plan freezes it; a new plan is a new file, a new ledger row, a new tab. Resume rebuilds the prompt from the ledger. Errors land in chat.

2The mental model: a session is a flight of rounds

One session, one canvas, N rounds. Each round is plan → review → approve → execute → summarize. The Status tab is the only live surface; everything else is an immutable artifact appended to the ledger. You can always scroll back through what was agreed and what happened.

SESSION one canvas · one chat · one ledger STATUS — always live ROUND 1 · frozen Plan 1 approved · read-only Summary 1 diff · outcome executed 2026-07-06 · +199 −84 ROUND 2 · active Plan 2 under review Summary 2 after execution approve → executes · a NEW file, plan 1 untouched canvas tabs derive 1:1 from the ledger → ● Status ● Plan 1 ● Summary 1 ● Plan 2

A two-round session. Every artifact keeps its tab forever; only Status is live. "New plan" opens round 2 without touching round 1.

3Architecture

3.1 · The ledger replaces the three pointers

One append-only array on SessionRecord. Everything else — tabs, labels, the "current" plan, the topbar title, the summary — derives from it.

// SessionRecord gains:
artifacts: Artifact[]

interface Artifact {
  id: string            // stable, survives docId re-resolution
  role: 'plan' | 'deliverable' | 'summary'
  round: number         // 1, 2, 3…
  relPath: string       // plans/fix-toolbar-r2.html — path is truth, docId derived
  status: 'drafting' | 'review' | 'approved' | 'executing' | 'done' | 'superseded'
  createdAt: string
  approvedAt?: string
  versionId?: string    // vault snapshot captured at approval
}

// legacy planDocId / stageRelPath / planRelPaths → migrated into rows on load,
// kept as derived getters until every call site moves over.

3.2 · Round lifecycle & the freeze guard

drafting agent writes file review refine ↔ chat approved snapshot + FREEZE executing → done summary appended WRITE GUARD (captureToolIntel) Write/Edit → an approved plan's relPath ⇒ restore snapshot + warn in chat "New plan" → round N+1, NEW file plans/<slug>-r<N>.html

Approval snapshots a vault version, flips the row to approved, and arms the write guard. Overwriting an approved plan becomes structurally impossible, not merely discouraged.

3.3 · Continuation that never dead-ends

3.4 · Chat = the harness (modes, model, controls)

A mode strip docked above the composer exposes what the terminal offers, switchable mid-session (each switch respawns via --resume --fork-session with the new flags — the pattern the permission switch already proved):

⚙ permissions: auto-accept ▾ ✦ model: opus ▾ ⏸ interrupt + new plan round 🛠 tools…

permissions: default / acceptEdits / plan / dangerously-skip ⚠ · model: opus / sonnet / haiku · tools: allowed/disallowed list editor (drives --allowedTools/--disallowedTools)

3.5 · Generative buttons in chat — the options protocol

The plan docs already embed live data-hd-action buttons. Extend the same idea to every assistant message: the agent ends substantial replies with a one-line HTML comment the renderer parses into chips (invisible in any other viewer, so it degrades cleanly):

<!--hd-options [
  {"label": "Approve & run",   "action": "approve"},
  {"label": "Plan round 2",    "action": "new-plan"},
  {"label": "Show me the diff", "action": "message", "text": "Summarize the working diff"}
]-->

Phase 2 done — the toggle now follows the OS theme. Want me to sweep the remaining hardcoded hexes, or is this good to ship?

Sweep remaining hexes Ship it — summarize Plan round 2 instead

chips render under the message · actions: approve · new-plan · message/refine · summarize · publish — plus app-injected standbys (Approve, New plan) so the UI never depends on the agent remembering.

3.6 · Mission Control as the launcher

4Delivery phases

PhaseScopeKindFiles (main)
P0 · TriageLabel from role not fallback (plans/ ⇒ "Plan" for any session kind); loading skeleton instead of fall-through; send/ensureRunning errors → chat; resume passes rebuilt systemPromptAppend.fixsession-detail.tsx · agent-manager.ts
P1 · LedgerArtifact[] on SessionRecord + migration from the 3 pointers; toSummary emits artifacts; tabs derive 1:1; relPath-keyed lazy docId resolution.archagent-manager.ts · ipc.ts · ipc-contract.ts · session-detail.tsx
P2 · RoundsNew-plan action (topbar/chip/card); per-round files -r<N>; round-aware planSystemPrompt; approval freeze + write guard with snapshot restore; per-round Summary rows.archagent-manager.ts · vault-manager.ts
P3 · Mode stripModel switch, permission modes incl. plan & skip ⚠, interrupt, tools editor — all mid-session via fork-respawn.uxsession-chat.tsx · session-detail.tsx · claude-adapter.ts
P4 · Options chipshd-options comment protocol: prompt append + parser + chip renderer + app-injected standby chips.uxprompts.ts · markdown-message.tsx · session-chat.tsx
P5 · LauncherNew-session sheet (Plan first / Direct / Ask), round badges on cards, resume affordance for stopped sessions.uxmission-control.tsx

Each phase ships independently — typecheck, build, live screenshot verification, commit, push. P0+P1 remove both reported bugs; P2 delivers the multi-plan promise for real.

5Verification matrix

ScenarioExpected
Plan session, forked session, chat session each author a plans/ fileTab always reads "Plan" — three screenshots
Approve plan 1 → execute → "New plan" → draft round 2Tabs: Status · Plan 1 · Summary 1 · Plan 2; plan 1's file byte-identical to its approval snapshot
Agent forced (via test prompt) to Write into plan 1's file after approvalGuard restores snapshot; chat shows the warning
Quit app mid-execution → relaunch → send a messageSession resumes with plan contract intact; reply lands; no silent failure
Session with sessionId:null (killed pre-init)Chat shows Restart chip; clicking it revives the session
Switch model + permissions mid-sessionRespawn preserves history; next reply on the new model; badge updates
Assistant message with hd-options blockChips render; clicking sends/executes; raw comment never visible
All of the above in a second repo containing Claude, no hooksIdentical behavior (ledger + plans/ are repo-local)

6Risks & calls


Prepared 2026-07-06 · builds on "Session Canvas — the mental model" (rounds extend the artifact-tab model) · implementation not started — awaiting review.