Names the wave shape before any implementation. Audits what doctrine
has already settled (ADR-0160 stack pins + read-only trust boundary;
ADR-0162 design system + 15-component must-ship list + no-go list),
catalogs the current state (backend complete, frontend zero), surfaces
the five operator pain points sourced from the 2026-05-27 demo and the
CompositionClaim brief pack, and proposes a five-wave sequence:
W0 (docs) — Trust-boundary ratchet ADR (ADR-0173 working title)
admitting operator ratification through the workbench,
scoped to existing Tier 1.5 handlers (Lexical / Frame /
Composition) and pinning the keyboard contract.
W1 (base) — workbench-ui/ scaffold per ADR-0162 Branch 1.
W2 (read) — ProposalQueue + ProposalCard + ProposalDetailPanel.
W3 (act) — RatificationCommandPanel + handler dispatch (the
throughput multiplier).
W4 (verify)— TraceDrawer + ReplayTheater + EvalCenter.
Guardrails enumerated: CLAUDE.md docs discipline, ADR-0162 no-go list,
determinism, trust boundary, wrong==0, case 0050 hazard pin,
keyboard-first, accessibility, local-first, no engine_state writes
outside the checkpoint path.
Open questions (in-process vs out-of-process ratification, single- vs
multi-operator, telemetry path, font/icon bundling, build artifact
location) listed for W0 resolution. No code in this brief.
18 KiB
Workbench UI — Wave Scoping Brief
Status: Scoping (no code in this brief; names the wave shape and the prerequisite ADRs/decisions before any implementation moves) Date: 2026-05-27 Author: Shay Parent doctrine: ADR-0160, ADR-0162 Related: ADR-0161 (proposal-review trust boundary), ADR-0167 (teaching-corridor), ADR-0168 / 0168.1 (FrameClaim handler), ADR-0169 / 0169.1 (CompositionClaim doctrine), ADR-0172 (math contemplation corridor)
Goal
Stand up the workbench frontend so the math teaching corridor is operable by a single operator at human-realistic throughput. The current bottleneck is not capability — Tier 1 + Tier 1.5 shipped FrameClaim and (pending) CompositionClaim handlers — it is operator ergonomics. Every audit refusal we cannot ratify quickly is a learning event the engine never gets.
This brief does not commit code. It names:
- what doctrine has already settled,
- what the current operator pain points are,
- what needs a fresh decision (ADR or scoping note) before any implementation,
- the proposed wave shape (sequence + dependency DAG),
- the guardrails the wave must not violate,
- the acceptance criteria for the wave overall.
What doctrine has already settled
The frontend has substantial pre-existing doctrine. The scoping brief must respect, not relitigate, the following.
ADR-0160 — Workbench v1
- Frontend stack pinned: React + Vite + TypeScript, TanStack Query, Zustand/Jotai, Tailwind + shadcn primitives, Monaco where structured JSON inspection is necessary.
- No Electron. No heavy design system. No plugin marketplace.
- Local-first, deterministic backend. Backend already exists at
workbench/(stdlib HTTP server, routes for chat/proposals/ math-proposals/ratify/evals/replay/trace). - Trust boundary v1 = read-only by default. Accepting proposals, rejecting proposals as durable state, and pack/corpus mutation were explicitly forbidden in v1.
ADR-0162 — Design System
- Token namespace, typography, color semantics all bound to
ratified enums (
EpistemicState,NormativeClearance,ReviewState, grounding source). No invented color states. - Keyboard contract. Workbench is keyboard-first.
- Layout shell committed.
- Component map v1 must-ship (15 components):
WorkbenchShell,TopBar,LeftNav,StatusFooter,CommandPalette,ChatTurnList,ChatTurnCard,ResponseEvidenceStrip,TraceDrawer, four state badges,ReplayTheater,ReplayComparisonPanel,ReplayDiffViewer,ProposalQueue,ProposalCard,ProposalDetailPanel,RatificationCommandPanel,EvalCenter,EvalLaneList,EvalFailureViewer,StableJsonViewer,StableJsonDiffViewer,ArtifactLink. - No-go list: no chat-clone bubbles, no "AI thinking" affordances, no glassmorphism, no purple-neon-cyberpunk, no graph-builder canvas, no dashboard splash metrics, no color-only state encoding.
- Implementation plan — Branch 1 (pre-W-027) names a single PR
scope:
workbench-ui/directory at repo root with pinned deps and the tokens CSS. No app routes yet.
These two ADRs already constitute the design phase. The wave is implementation, not re-design.
Current state — backend ready, frontend zero
workbench/api.pyexposes the routes ADR-0160 anticipates:/health,/runtime/status,/artifacts,/proposals,/math-proposals,/math-proposals/{id}/ratify,/evals,/evals/run,/chat/turn,/trace/...,/replay/....workbench/server.pyis a stdlibThreadingHTTPServer. No remote network dependency for cognition.workbench/schemas.pydefinesProposalSummary,ProposalDetail,MathProposalSummary,MathProposalDetail,MathRatifyResult,EvalLaneSummary,EvalRunResult,ReplayComparison,ReplayDivergence— the JSON contract the UI will consume.MathProposalDetailalready carriessuggested_ratify_cliandhandler_name, which means the dispatch corridor is already end-to-end on the server side.
What does not exist yet:
workbench-ui/directory (ADR-0162 Branch 1 deliverable — not landed).- Any HTML/CSS/JS asset.
- Any client-side state management.
- Any keyboard binding implementation.
So the wave starts from zero on the client and from a complete contract on the server.
Operator pain points (evidence)
Sourced from the 2026-05-27 end-to-end demo and the CompositionClaim brief pack:
- HITL burden is high. Architecturally clean rejection of handler-mismatched proposals still costs the operator a full round-trip (read row → reason about category → reject → move on). At 47 audit rows × multiple proposal categories, this is the gating cost on the compounding loop.
- No keyboard corridor for ratification. Operator currently
constructs the suggested CLI by reading
MathProposalDetail.suggested_ratify_clifrom JSON output and pasting into a shell — a four-step context-switch per row. - Evidence is text-block JSON.
MathReaderRefusalEvidenceships the audit-row context but reading it from raw JSON requires the operator to mentally render the source sentence, the bound slots, and the missing-operator pair. - Reasoning traces are flat.
MathProposalDetail.reasoning_trace_stepscarries the step-by-step decomposer hypothesis, but with no visualization the operator cannot quickly see where the chain would diverge from a correct admission. - Replay equivalence is a string field.
replay_equivalence_hashis a meaningful invariant but currently surfaces as opaque hex — the operator has no eyes-on confirmation that a proposal genuinely replays. - No "stuck queue" diagnosis. The queue of pending proposals does not currently visualize why an item is pending (replay passed but operator hasn't reviewed? replay didn't run? evidence incomplete?). All three states render identically.
These are not capability problems. They are throughput problems with a deterministic fix.
What needs a fresh decision
Two doctrine updates are prerequisites. Both should land before any frontend code so the wave doesn't burn cycles on a moving target.
D1 — Trust-boundary ratchet (new ADR)
Problem. ADR-0160 v1 explicitly forbids "accepting proposals" in the UI. That was correct when no ratification handlers existed server-side. Tier 1.5 changed the world:
teaching/math_lexical_ratification.py::apply_lexical_claim(W2-D, merged)teaching/math_frame_ratification.py::apply_frame_claim(PR #389, merged)teaching/math_composition_ratification.py::apply_composition_claim(PR-β, pending)
Each handler is replay-gated, hazard-pinned (case 0050), partition- tested, idempotent, and append-only on a reviewed JSONL artifact. The original ADR-0160 prohibition was a safety stance against mutation paths that did not yet have proper gates. Those gates now exist.
Decision needed. Either:
- (a) A new ADR (working title: ADR-0173 — Workbench Ratification Trust Boundary) that explicitly admits operator ratification through the workbench, scoped to the three existing handlers and gated by their existing replay/hazard/partition pins; or
- (b) A scoping note that re-affirms ADR-0160 v1 read-only and defers ratification UI to a v2.
Recommendation: (a). Otherwise the wave reduces to "render proposals you cannot act on," which does not retire the operator- burden pain point at all.
Acceptance: ADR-0173 must enumerate exactly which handlers are admitted, which are not, what mutation paths are still forbidden (corpus, packs, engine_state outside checkpoint path, frame/lexical registry edits outside the handler), and how the UI surfaces the proposal-review trust boundary visually (e.g. RatificationCommandPanel must distinguish "replay passed, you may ratify" from "replay didn't run, ratify is disabled").
D2 — Component-set ratification (sub-decision in D1 or its own scoping note)
ADR-0162's "v1 must-ship" component list pre-dates the ratification
handlers. Specifically, RatificationCommandPanel was named as
must-ship but its semantics were not pinned because no handler
existed. With handlers live, we need to fix:
- which proposal kinds the panel can drive (LexicalClaim, FrameClaim, CompositionClaim — and the deferred SUB_TYPE_FOR_OPERATOR entries remain disabled with a clear "no handler yet" affordance);
- whether the panel executes the suggested CLI in-process via the
existing
apply_*_claim()handlers, or merely renders the CLI for the operator to copy/run (the brief recommends in-process execution under D1's trust boundary, because copy/paste-to-shell is itself an operator-burden axis); - the keyboard binding contract (e.g.
j/kto navigate proposals,rto ratify the focused proposal,xto reject,?for help — must be pinned in D1 or a sibling note before implementation so every panel shares one mental model).
Recommendation: fold D2 into D1 as a single ADR.
Proposed wave shape
Five waves, ordered. Each wave is one PR or one tight stack, not a sprawling branch.
W0 (docs): Trust-boundary ratchet ADR + component-set update
│
▼
W1 (UI base): workbench-ui/ scaffold per ADR-0162 Branch 1
(Vite, TS, Tailwind, shadcn primitives, tokens CSS,
shell, command palette, status footer — no routes yet)
│
▼
W2 (read): ProposalQueue + ProposalCard + ProposalDetailPanel
(math + cognition proposals, read-only;
StableJsonViewer for evidence/replay payloads;
EpistemicState / ReviewState / TraceHash badges)
│
▼
W3 (act): RatificationCommandPanel + handler dispatch
(keyboard corridor: j/k navigate, r ratify, x reject;
in-process apply_*_claim() execution via API;
replay-state visualization;
post-ratify auto-advance to next pending)
│
▼
W4 (verify): TraceDrawer + ReplayTheater + ReplayComparisonPanel +
ReplayDiffViewer + EvalCenter + EvalLaneList +
EvalFailureViewer
(the audit/replay surfaces that close the loop;
StableJsonDiffViewer for replay divergences)
Why this order
- W0 is unblock-everything. Code written against a stale trust boundary either gets thrown out (if doctrine tightens) or builds in a constraint that the rest of the wave can't honor (if doctrine loosens unevenly).
- W1 is the substrate. ADR-0162 already named it; ship it once, every later wave assumes it.
- W2 is the minimum legible workbench. If we ship W0-W2 only, the operator can already inspect every pending proposal at human speed — a real throughput improvement even without ratification UI.
- W3 is the throughput multiplier. The keyboard ratification corridor is the entire reason this wave exists. It must land before W4 because trace/replay surfaces are audit tools, not throughput tools — and we're throughput-bound, not audit-bound.
- W4 closes the loop. Once an operator has ratified a proposal, TraceDrawer + ReplayTheater + EvalCenter let them verify the effect immediately rather than dropping back to the CLI.
What ships in each wave
| Wave | One-line description | Approximate PR count | Approximate test surface |
|---|---|---|---|
| W0 | Trust-boundary ratchet ADR + (sub-decision) component-set update | 1 docs PR | 0 (docs only) |
| W1 | workbench-ui/ scaffold + tokens + shell + palette + status footer |
1 frontend PR | smoke (build green, tokens snapshot, shell renders) |
| W2 | ProposalQueue + ProposalCard + ProposalDetailPanel + StableJsonViewer + badges | 1 PR (math) + 1 PR (cognition) OR 1 bundled | component tests + 1 e2e against backend fixtures |
| W3 | RatificationCommandPanel + keyboard corridor + handler dispatch | 1 PR per handler OR 1 bundled PR (LexicalClaim + FrameClaim + CompositionClaim) | one e2e per handler ratifying a fixture proposal → checking the JSONL artifact change |
| W4 | TraceDrawer + ReplayTheater + ReplayDiff + EvalCenter | 2 PRs (replay surfaces + eval surfaces) | one e2e per surface |
This is the shape, not the exact PR boundaries — operators running W2/W3/W4 should bundle per the project's existing "batch-during-research" pattern when CI cycle time argues for it.
Guardrails (must hold across every wave)
Each is non-negotiable. A PR that violates one is reworked, not waived.
- CLAUDE.md docs discipline. No standalone HTML artifacts as substrate (CSS regen ordering / SVG element ordering break determinism). The workbench is allowed to render HTML because it is a read-only view of deterministic JSONL artifacts, not a substitute for them.
- ADR-0162 no-go list. Reread every PR. No chat-clone bubbles, no "AI thinking" theater, no glassmorphism, no dashboard splash pages, no graph-builder canvas, no auto-dismiss toast for audit events, no color-only state encoding.
- Determinism preserved. Every UI state derived from a deterministic backend payload. No client-side randomness that affects rendered content. (Loading skeletons are fine; "shuffle proposal queue" is not.)
- Trust boundary respected. W2 ships read-only. W3 ships ratification only for handlers admitted by D1's ADR. Pack / corpus / engine_state mutation paths remain forbidden through the UI.
wrong == 0invariant unchanged. No UI wave touches the server-side handlers' hazard pins. Every UI PR runs the affected server suites green (core test --suite teaching -q,core test --suite runtime -q).- Case 0050 protected. UI must not introduce a fast-path that
bypasses the handler's pre-conditions. Ratifying through the UI
must call the same
apply_*_claim()function as the CLI, carrying the same evidence, raising the same exceptions. - Keyboard-first. No mouse-only interactions for any v1 must- ship component. (Mouse is supported; mouse-only is not.)
- Accessibility. No icon-only buttons without accessible labels; no color-only state encoding (badges carry text + shape, not color alone). Pinned in ADR-0162 §"The no-go list" — restated here because it's the easiest guardrail to drift on.
- No remote network dependencies for cognition. Workbench
backend stays local-first. UI may fetch from
http://127.0.0.1:<port>only. No CDN-loaded fonts/icons in v1; bundle locally. - No engine_state writes outside the existing checkpoint path (ADR-0146/0150).
Anti-regression invariants
Inherited from the broader teaching corridor — listed so every wave PR can self-check.
wrong == 0oncore eval gsm8k_mathpreserved.- ADR-0166 — no new eval lanes introduced by UI work.
- ADR-0057 replay-equivalence — inherited unchanged.
- ADR-0167 partition — cognition and math proposal surfaces remain partitioned in the UI as on the server.
- Case 0050 hazard pin holds across every W3 ratification path.
engine_state/*never committed.- Pinned-lane SHAs should not require updates due to UI work; if one moves, the UI PR is doing something it shouldn't.
Open questions (resolve in W0)
- In-process ratification vs out-of-process dispatch. D1 recommends in-process via the existing API, but if there's a reason to keep the UI strictly read-only and dispatch ratification through the CLI (e.g. an audit-trail concern), W0 must name it.
- Single-operator only vs multi-operator-aware. The current handler architecture is single-operator (no proposal locking, no review-state contention). W0 should affirm or deny that v1 stays single-operator.
- Telemetry path. The backend
chat/telemetry.pyJSONL sink exists. W0 should pin whether the UI emits its own operator- action events (e.g. "operator-ratify", "operator-reject", "operator-defer") into the same sink, or into a separate workbench-events log. - Font / icon bundling. ADR-0162 pins typography but doesn't commit on whether to bundle Inter / a system stack. W0 should resolve so W1 doesn't introduce a runtime fetch.
- Build artifact location. Does
workbench-ui/dist/get committed, gitignored, or served from a CI step? W0 should commit. (Recommendation: gitignored; built locally; CI verifies the build is green but does not commit the artifact.)
Acceptance criteria (whole wave)
The wave is "done" when:
- A new operator can run
core workbench serve(existing CLI) and open the UI in a local browser. - Pending audit refusals from
audit_brief_11.jsonappear as a navigable queue. - An operator can ratify a LexicalClaim, FrameClaim, or CompositionClaim proposal via keyboard in under 10 seconds per row for a row whose evidence they understand.
- The ratification produces the same JSONL artifact change as
invoking the corresponding
apply_*_claim()from the CLI — byte-equal where possible, semantic-equal otherwise. core test --suite teaching -q,core test --suite runtime -q, andcore eval gsm8k_mathremain green throughout.- Case 0050 remains refused after end-to-end UI ratification of any pending proposal.
- The five operator pain points listed above are individually retired (each can be pointed at one shipped component).
Memory pointers
- feedback-batch-during-research — bundling rule for the W2/W3/W4 PRs when CI cycle time argues for it
- feedback-no-self-dispatch-of-subagents — Shay dispatches
- feedback-production-line-pattern — wave brief pack pattern
- feedback-parallel-agent-worktrees — fresh worktree per brief
- feedback-wrong-zero-hazard-case-0050 — W3 mandatory pin
- feedback-address-critiques-dont-waive — guardrail violations are reworked, not waived
- milestone-adr-0172-tier1-2026-05-27 — context for why throughput, not capability, is the bottleneck
- adr-0167-audit-as-evidence-wave — parent corridor
Next step
If approved, W0 docs PR is the immediate output: ADR-0173 (working title: Workbench Ratification Trust Boundary) authored against the five operator pain points above, the existing handlers as scope, and the keyboard contract as pinned semantics. Once W0 lands, W1 (the ADR-0162 Branch 1 scaffold) can dispatch.