From af5db34f647fd71635e95554208634f989ec4a96 Mon Sep 17 00:00:00 2001 From: Jacob Kim Date: Sun, 9 Aug 2026 19:11:38 +0900 Subject: [PATCH 1/3] feat(fleet): add sidebar work queue and standardize attention vocabulary The sidebar listed workspaces in a ranked flat list that never said why one outranked another, so a stalled agent and a workspace visited yesterday looked alike. Group them into lanes instead, and fix the vocabulary and layer boundaries the lanes depend on. - Add `src/lib/fleet/sidebar-work-queue.ts`: a pure lane classifier and grouper (`action-required` > `in-progress` > `in-review` > `idle`), one lane per workspace, empty lanes dropped. Lanes derive from the existing attention projection and task status, so the section adds no store subscription, no persistence, and no IPC. - Render the lanes in `ProjectWorkspaceSidebar`, extracting the row into `WorkQueueRow` so the nested row-local subscriptions stay intact. - Rename the Fleet "need" vocabulary to the "attention item" wording the projection module already used, and surface it as "Action required" in the inbox, the top bar, and the sidebar. - Add `docs/architecture/agent-platform-taxonomy.md` fixing the three layers, the shared vocabulary, and the boundary statements, with a matching entry in `config/reliability-gates.json` and gate tests that repeat each statement. Verification: bun run typecheck, bun run test:isolated (378 files), check:doc-paths, check:reliability-gates, check:max-lines-ratchet, and the sidebar/fleet Playwright specs. Co-Authored-By: Claude Opus 5 --- config/reliability-gates.json | 39 ++- docs/architecture/agent-platform-taxonomy.md | 102 +++++++ docs/architecture/index.md | 11 +- docs/architecture/run-core.md | 5 + ...NeedsInbox.tsx => FleetAttentionInbox.tsx} | 76 ++--- src/components/layout/FleetView.tsx | 84 +++--- src/components/layout/FleetWorkspaceCard.tsx | 22 +- .../layout/ProjectWorkspaceSidebar.tsx | 266 ++++++++++-------- .../layout/ProjectWorkspaceSidebar.utils.ts | 86 +++--- .../layout/TopBarFleetAttention.tsx | 2 +- .../layout/settings-dialog-sections.tsx | 10 +- src/lib/fleet/attention-projection.ts | 130 ++++----- src/lib/fleet/sidebar-work-queue.ts | 147 ++++++++++ src/lib/fleet/workspace-activity.ts | 18 +- src/lib/providers/utility-inference.ts | 14 +- tests/agent-platform-boundaries.test.ts | 106 +++++++ tests/e2e/design-revamp.e2e.ts | 6 +- ...me.e2e.ts => fleet-action-required.e2e.ts} | 4 +- .../sidebar-active-workspaces-dismiss.e2e.ts | 14 +- tests/e2e/sidebar-work-queue-lanes.e2e.ts | 148 ++++++++++ ...est.tsx => fleet-attention-inbox.test.tsx} | 24 +- tests/fleet-attention-projection.test.ts | 24 +- ...eet-orphan-workspace-notifications.test.ts | 2 +- tests/fleet-sidebar-work-queue.test.ts | 180 ++++++++++++ tests/fleet-task-control-panel.test.ts | 2 +- tests/fleet-workspace-activity.test.ts | 12 +- tests/project-workspace-sidebar.test.ts | 94 +++---- 27 files changed, 1198 insertions(+), 430 deletions(-) create mode 100644 docs/architecture/agent-platform-taxonomy.md rename src/components/layout/{FleetNeedsInbox.tsx => FleetAttentionInbox.tsx} (86%) create mode 100644 src/lib/fleet/sidebar-work-queue.ts create mode 100644 tests/agent-platform-boundaries.test.ts rename tests/e2e/{fleet-needs-me.e2e.ts => fleet-action-required.e2e.ts} (99%) create mode 100644 tests/e2e/sidebar-work-queue-lanes.e2e.ts rename tests/{fleet-needs-inbox.test.tsx => fleet-attention-inbox.test.tsx} (70%) create mode 100644 tests/fleet-sidebar-work-queue.test.ts diff --git a/config/reliability-gates.json b/config/reliability-gates.json index 7fb570f..00ef93d 100644 --- a/config/reliability-gates.json +++ b/config/reliability-gates.json @@ -31,14 +31,20 @@ "id": "terminal-font-and-unicode", "title": "Terminal glyph and renderer addons are defensive", "invariant": "Unicode, link, search, and WebGL addons cannot prevent the DOM renderer from booting.", - "testFiles": ["tests/terminal-instance.test.ts"], - "assertionRefs": ["src/components/layout/useTerminalInstance.ts"] + "testFiles": [ + "tests/terminal-instance.test.ts" + ], + "assertionRefs": [ + "src/components/layout/useTerminalInstance.ts" + ] }, { "id": "lens-component-capture", "title": "Lens keeps component context with element picks", "invariant": "A Lens element selection may include a parent-to-leaf React component chain without requiring it for non-React pages.", - "testFiles": ["tests/lens-element-message.test.ts"], + "testFiles": [ + "tests/lens-element-message.test.ts" + ], "assertionRefs": [ "electron/main/browser/browser-element-picker.ts", "src/lib/lens/lens-element-message.ts" @@ -48,7 +54,9 @@ "id": "review-feedback-to-agent", "title": "Diff comments can return to the active agent", "invariant": "A review comment retains its diff line anchor and can be submitted as the next agent prompt.", - "testFiles": ["tests/review-feedback.test.ts"], + "testFiles": [ + "tests/review-feedback.test.ts" + ], "assertionRefs": [ "src/lib/review-feedback.ts", "src/components/panes/surfaces/EditorSurfacePanel.tsx" @@ -58,7 +66,9 @@ "id": "codex-checkpoint-provenance", "title": "Codex turns expose checkpoint provenance", "invariant": "A normal Codex turn emits a checkpoint boundary with the current Git ref when available.", - "testFiles": ["tests/codex-app-server-runtime.test.ts"], + "testFiles": [ + "tests/codex-app-server-runtime.test.ts" + ], "assertionRefs": [ "electron/providers/codex-app-server-runtime.ts", "docs/providers/provider-runtimes.md" @@ -99,7 +109,9 @@ "id": "provider-utility-inference", "title": "Utility inference remains provider neutral", "invariant": "Task naming, route classification, and commit suggestions choose an explicit or active provider deterministically and degrade without blocking the primary task.", - "testFiles": ["tests/utility-inference.test.ts"], + "testFiles": [ + "tests/utility-inference.test.ts" + ], "assertionRefs": [ "electron/providers/utility-inference.ts", "src/lib/providers/utility-inference.ts", @@ -135,6 +147,21 @@ "src/components/layout/TaskExecutionSummarySurface.tsx", "src/store/app-notification-builders.ts" ] + }, + { + "id": "agent-platform-boundaries", + "title": "Agent platform layers stay distinct", + "invariant": "A routine cannot target an existing task, the run ledger never imports execution runtimes, the secondary executor never writes ledger rows, utility inference stays mechanical, and the work queue assigns one lane per workspace.", + "testFiles": [ + "tests/agent-platform-boundaries.test.ts", + "tests/fleet-sidebar-work-queue.test.ts" + ], + "assertionRefs": [ + "docs/architecture/agent-platform-taxonomy.md", + "src/lib/fleet/sidebar-work-queue.ts", + "src/lib/providers/utility-inference.ts", + "src/lib/routines.ts" + ] } ] } diff --git a/docs/architecture/agent-platform-taxonomy.md b/docs/architecture/agent-platform-taxonomy.md new file mode 100644 index 0000000..a259c78 --- /dev/null +++ b/docs/architecture/agent-platform-taxonomy.md @@ -0,0 +1,102 @@ +# Agent Platform Taxonomy And Boundaries + +Stave grew several ways to make an agent do more work — Advisor, Worker, Fleet, +Routines, the run ledger — and each was added for its own reason. This file +fixes what each one is, what it is not, and which vocabulary the product uses, +so the next capability lands in the right layer instead of beside a similar one. + +Read this before adding anything that runs work, schedules work, delegates +work, or shows work. + +## Vocabulary + +Use these words in code, UI copy, and plans. Do not introduce synonyms. + +| Word | Means | +| --- | --- | +| Task | One conversation with one provider inside one workspace. The unit everything else attaches to. | +| Turn | One request/response cycle inside a task. Ends with exactly one terminal event. | +| Attention item | One thing that wants the user: a question, an approval, a failed run, a PR state. `FleetAttentionItem`. | +| Action required | The lane and inbox heading for blocking attention items. Replaces the older "Needs me". | +| Ledger | The durable runs/steps/receipts record in `src/lib/runs/`. It records; it never executes. | +| Receipt | One bounded record of how something started or ended. Never transcript text, never secrets. | +| Occurrence | One firing of a schedule. | + +Lane names for workspace state are fixed and ordered: +`action-required` > `in-progress` > `in-review` > `idle`. + +## Three Layers + +Every concept belongs to exactly one layer, classified by scope (turn / task / +fleet) and lifetime (ephemeral / durable). + +### Layer 1 — Turn runtime: help the current turn + +Ephemeral, turn-scoped, minimal product branding. These are task options. + +| Concept | Role | Does not | +| --- | --- | --- | +| Advisor | One read-only advice call before a turn, injected as context | Execute; persist | +| Worker | Same-provider delegation inside a turn | Survive a restart; cross providers | +| Utility inference | Mechanical meta calls: task name, route classification, commit message | Block the task; give advice | + +Boundary: Advisor produces *content* the user would recognize as an opinion. +Utility inference produces *metadata* the user never argues with. + +### Layer 2 — Supervision: see everything, intervene from anywhere + +Fleet-scoped, read plus control, no new execution semantics. + +| Concept | Role | +| --- | --- | +| Fleet | The cross-workspace surface: attention inbox, workspace cards, task control | +| Task control plane | Identity (`projectPath + workspaceId + taskId + turnId`) and staleness validation for remote actions | +| Task execution summary | Provenance-tagged scorecard; missing data is never rendered as zero | +| Sidebar work queue | The same lane model projected into the sidebar | +| Run ledger (run core) | Durable bookkeeping for delegated execution: runs, steps, receipts, idempotency, claims | + +The run ledger is shared machinery, not a feature. Compare Judge is its first +client; durable child tasks are planned as its second. Widen it for a new +client instead of building a second ledger beside it. + +### Layer 3 — Continuity: keep going without me + +Durable (SQLite), reconciled on restart, always carrying an explicit terminal +reason. Two axes: + +| | Ephemeral (exists) | Durable (planned) | +| --- | --- | --- | +| Time — run again | — | Routine (new task per occurrence) / Heartbeat (same task, same session) | +| Delegation — hand work off | Worker (Layer 1) | Child tasks (cross-provider, normal tasks + ledger receipts) | + +Routine is the only concept that lives outside a task: it mints tasks. +Everything else in this layer attaches to one existing task. + +## Boundary Statements + +These are the statements that keep the layers from collapsing into each other. +Each one is registered in `config/reliability-gates.json` and asserted by a test +whose name repeats it. + +1. A routine never wakes an existing task; its definition cannot target one. +2. A heartbeat never creates a task; it only adds a turn to one that exists. +3. A worker never survives a restart; a child task always does. +4. The ledger records and never executes; executors execute and never write + ledger rows except through coordinator transitions. +5. Advisor advises content; utility inference computes metadata. +6. The work queue assigns a workspace to exactly one lane, in fixed priority + order. + +Statements 2 and 3 are partly forward-looking: heartbeats and child tasks are +not built yet. They are recorded here so the capability lands inside the +boundary rather than beside it, and the gate test asserts the half that exists +today. + +## Adding Something New + +1. Name the layer it belongs to. If it seems to span two, it is two things. +2. Name its consumer. A module with no visible consumer does not ship — the run + ledger spent a year with exactly one client because that rule did not exist. +3. Reuse the vocabulary above. A new synonym is a new concept to everyone + reading the code later. +4. If it changes a boundary statement, change it here first, then the gate. diff --git a/docs/architecture/index.md b/docs/architecture/index.md index 07d641c..fb5c60a 100644 --- a/docs/architecture/index.md +++ b/docs/architecture/index.md @@ -10,11 +10,12 @@ This file is the fast entrypoint for codebase orientation. 4. `docs/providers/provider-runtimes.md` for provider-specific paths 5. `docs/architecture/entrypoints.md` for task-to-file routing 6. `docs/architecture/contracts.md` for multi-file contract checklists -7. `docs/architecture/run-core.md` before adding durable secondary execution consumers -8. `docs/architecture/workspace-integrity.md` before changing project/workspace/task ownership or hydration logic -9. `docs/architecture/chat-message-rendering.md` before changing assistant message UI semantics -10. `docs/architecture/agent-message-ux-catalog.md` for the provider-event-to-UX matrix and Preview baseline -11. `docs/architecture/session-cursor.md` for provider switch-back delta history and safe fresh-session fallback +7. `docs/architecture/agent-platform-taxonomy.md` before adding anything that runs, schedules, delegates, or displays work +8. `docs/architecture/run-core.md` before adding durable secondary execution consumers +9. `docs/architecture/workspace-integrity.md` before changing project/workspace/task ownership or hydration logic +10. `docs/architecture/chat-message-rendering.md` before changing assistant message UI semantics +11. `docs/architecture/agent-message-ux-catalog.md` for the provider-event-to-UX matrix and Preview baseline +12. `docs/architecture/session-cursor.md` for provider switch-back delta history and safe fresh-session fallback ## Top-Level Layout diff --git a/docs/architecture/run-core.md b/docs/architecture/run-core.md index 06e9194..33b3962 100644 --- a/docs/architecture/run-core.md +++ b/docs/architecture/run-core.md @@ -5,6 +5,11 @@ read-only background provider execution. Its first and current consumer is Compare Judge. It is not a generic workflow engine or a prerequisite for Fleet, Advisor, or Crane. +The read-only limit belongs to the executor, not to the ledger: the ledger is +shared bookkeeping and is meant to gain further clients rather than be copied. +See `docs/architecture/agent-platform-taxonomy.md` for where it sits among the +other layers. + ## Ownership - The renderer owns user intent, presentation, and consumer-specific result diff --git a/src/components/layout/FleetNeedsInbox.tsx b/src/components/layout/FleetAttentionInbox.tsx similarity index 86% rename from src/components/layout/FleetNeedsInbox.tsx rename to src/components/layout/FleetAttentionInbox.tsx index efcb155..0ec9f60 100644 --- a/src/components/layout/FleetNeedsInbox.tsx +++ b/src/components/layout/FleetAttentionInbox.tsx @@ -16,15 +16,15 @@ import { import { PrStatusIcon } from "@/components/layout/PrStatusIcon"; import { Badge, Button } from "@/components/ui"; import { - getFleetNeedTier, - type FleetNeedItem, - type FleetNeedKind, + getFleetAttentionTier, + type FleetAttentionItem, + type FleetAttentionKind, } from "@/lib/fleet/attention-projection"; import { PR_STATUS_VISUAL } from "@/lib/pr-status"; import { formatTaskUpdatedAt } from "@/lib/tasks"; import { cn } from "@/lib/utils"; -const FLEET_NEED_LABEL: Record = { +const FLEET_NEED_LABEL: Record = { "user-input": "Question", approval: "Approval", "run-failed": "Run failed", @@ -36,7 +36,7 @@ const FLEET_NEED_LABEL: Record = { "pr-ready-to-merge": "Ready to merge", }; -const FLEET_NEED_BADGE_CLASS: Record = { +const FLEET_NEED_BADGE_CLASS: Record = { "user-input": "border-warning/40 bg-warning/10 text-warning", approval: "border-warning/40 bg-warning/10 text-warning", "run-failed": "border-destructive/30 bg-destructive/10 text-destructive", @@ -51,7 +51,7 @@ const FLEET_NEED_BADGE_CLASS: Record = { "pr-ready-to-merge": "border-success/35 bg-success/10 text-success", }; -function getFleetNeedIcon(item: FleetNeedItem): ReactNode { +function getFleetNeedIcon(item: FleetAttentionItem): ReactNode { switch (item.kind) { case "user-input": return