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.
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.
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:
kind:'chat' — always labeled Deliverable.planDocIds resolves relPaths against the vault index; before the watcher indexes a fresh file (or when a provisional id differs from the embedded htmldocs-id) the list comes back empty.docsHtml finishes loading; planIds filters to empty and falls through.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.
planRelPaths dedupes by path, so no second tab ever appears.ensureRunning() respawns with
--resume --fork-session but without systemPromptAppend — the resumed
agent has no plan-file contract, no deliverable prompt, no design system.ensureRunning throws when sessionId is
null (process died before init); send() errors never reach the chat.stageRelPath = "last plans/ or debriefs/ file written"
— a new plan silently steals the "current deliverable" slot.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.
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.
A two-round session. Every artifact keeps its tab forever; only Status is live. "New plan" opens round 2 without touching round 1.
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.
label = role + (rounds > 1 ? round : ''). No fallback chain — Bug A dies structurally. While a doc's HTML is still loading the tab renders a skeleton, never a different label.role:'plan' and status ≠ approved/superseded.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.
approved → send the execute instruction (existing flow, now per-round).plans/<slug>-r<N>.html from the template, appends a drafting row, and sends "Draft plan round N for: <goal>. The plan file for THIS round is <path>. Never modify earlier plans."planSystemPrompt becomes round-aware — the file it binds is the current round's; frozen plans are listed as read-only context.captureToolIntel — a Write/Edit targeting an approved row's relPath triggers restore-from-snapshot + a system chat message. The agent can't regress round 1 even if it ignores instructions.ensureRunning rebuilds systemPromptAppend from the ledger (current round's plan contract + deliverable prompt) and passes it on resume — a continued session behaves like it never stopped.sessionId === null → no throw; the chat gets a system message with a Restart session chip (spawns fresh, same title/config, ledger intact).send() failure surfaces as a system chat message. Nothing fails silently.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: default / acceptEdits / plan / dangerously-skip ⚠ · model: opus / sonnet / haiku · tools: allowed/disallowed list editor (drives --allowedTools/--disallowedTools)
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?
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.
.htmldocs/sessions/, plans in plans/; zero hooks required (hooks only add richer terminal-session stages, as today).| Phase | Scope | Kind | Files (main) |
|---|---|---|---|
| P0 · Triage | Label from role not fallback (plans/ ⇒ "Plan" for any session kind); loading skeleton instead of fall-through; send/ensureRunning errors → chat; resume passes rebuilt systemPromptAppend. | fix | session-detail.tsx · agent-manager.ts |
| P1 · Ledger | Artifact[] on SessionRecord + migration from the 3 pointers; toSummary emits artifacts; tabs derive 1:1; relPath-keyed lazy docId resolution. | arch | agent-manager.ts · ipc.ts · ipc-contract.ts · session-detail.tsx |
| P2 · Rounds | New-plan action (topbar/chip/card); per-round files -r<N>; round-aware planSystemPrompt; approval freeze + write guard with snapshot restore; per-round Summary rows. | arch | agent-manager.ts · vault-manager.ts |
| P3 · Mode strip | Model switch, permission modes incl. plan & skip ⚠, interrupt, tools editor — all mid-session via fork-respawn. | ux | session-chat.tsx · session-detail.tsx · claude-adapter.ts |
| P4 · Options chips | hd-options comment protocol: prompt append + parser + chip renderer + app-injected standby chips. | ux | prompts.ts · markdown-message.tsx · session-chat.tsx |
| P5 · Launcher | New-session sheet (Plan first / Direct / Ask), round badges on cards, resume affordance for stopped sessions. | ux | mission-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.
| Scenario | Expected |
|---|---|
| Plan session, forked session, chat session each author a plans/ file | Tab always reads "Plan" — three screenshots |
| Approve plan 1 → execute → "New plan" → draft round 2 | Tabs: 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 approval | Guard restores snapshot; chat shows the warning |
| Quit app mid-execution → relaunch → send a message | Session 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-session | Respawn preserves history; next reply on the new model; badge updates |
| Assistant message with hd-options block | Chips render; clicking sends/executes; raw comment never visible |
| All of the above in a second repo containing Claude, no hooks | Identical behavior (ledger + plans/ are repo-local) |
localId, so artifacts are unaffected.Prepared 2026-07-06 · builds on "Session Canvas — the mental model" (rounds extend the artifact-tab model) · implementation not started — awaiting review.