docs(plan): Command Center dashboard + SDLC gap-fill plan

This commit is contained in:
gsxdsm
2026-06-15 19:05:15 -07:00
parent 799e590e3e
commit 8051e89e74

View File

@@ -0,0 +1,787 @@
---
title: "feat: Command Center dashboard + software-delivery-loop gap-fill"
type: feat
status: active
date: 2026-06-15
depth: deep
origin: none (solo plan; external research from a competitor product, see Sources)
---
# feat: Command Center dashboard + software-delivery-loop gap-fill
## Summary
Build a **Command Center** for the Fusion dashboard — a combined **historical analytics**
surface (tokens, tools, activity, productivity, ecosystem, per-agent/per-node breakdowns
over selectable date ranges, with CSV + OpenTelemetry export) **and a live Mission-Control
panel** (concurrent sessions, active nodes, what each agent is doing right now, SDLC funnel
throughput). Then close the gaps between Fusion and an end-to-end software-delivery system —
the **Signal → Triage → Plan → Execute → Validate → Ship → Monitor** loop — by adding the
stages Fusion does not yet cover (external signal ingestion, monitoring/incident response,
persistent knowledge layer) and the cross-cutting capabilities the loop implies (a **Fusion Model Router** that auto-selects the
cheapest-capable model per task, auto-triage of inbound issues **and PRs**, auto-resolution of
PR review comments, and surfacing external signals as dashboard metrics).
This plan is intentionally large because the user asked for full implementation coverage of
all gaps. It is organized into three phases so it can land incrementally: **Phase A** (metrics
foundation) and **Phase B** (the Command Center itself) deliver the branch's headline feature;
**Phase C** (SDLC gap-fill) is sequenced after, and several of its units are large enough that
the plan flags them as candidates to spin into their own brainstorm before execution.
---
## Problem Frame
Fusion is the model- and surface-agnostic orchestration layer for a developer driving many
agent sessions across nodes and surfaces (see `STRATEGY.md`). Two problems:
1. **There is no observability surface.** A developer juggling 10+ agents across machines has
no single place to answer "how much am I spending, on which models, across which nodes,
what's running right now, and what did all this work actually ship?" Fusion captures the
raw data (per-task token columns, agent runs, activity log, commit associations, PRs, CLI
sessions) but exposes only fragmentary panels (`ReliabilityView`, `AgentTokenStatsPanel`).
Fusion's own `STRATEGY.md` key metrics (concurrent sessions, active nodes, ecosystem
breadth, task completion rate, LOC shipped) are precisely what such a view should make
observable.
2. **Fusion covers the middle of the SDLC loop but not the ends.** The end-to-end delivery
loop is self-reinforcing: *Signal → Triage → Plan → Execute → Validate → Ship → Monitor*.
Fusion is strong on Plan/Execute/Validate/Ship and has partial Triage (GitHub issue
ingestion). It has **no Signal ingestion beyond GitHub, no Monitor stage, and no persistent
knowledge layer** — the parts that make the loop close and compound.
This plan addresses both: the Command Center (Phases A–B) and the missing stages (Phase C).
---
## Requirements
Traceability is to Fusion's `STRATEGY.md` key metrics (KM) and the external feature set the
user asked us to match (external research, no formal requirements doc).
- **R1 — Historical analytics.** Surface token consumption (by model, provider, node, agent,
time), tool usage and autonomy ratio, activity (sessions/messages/active-nodes over time),
productivity (files, commits, PRs, LOC), and ecosystem breadth (unique models + plugins).
(KM: all five.)
- **R2 — Date-range filtering.** All analytics support a selectable range (presets + custom),
mirroring `agent-token-usage.ts`'s windowed aggregation extended to arbitrary ranges.
- **R3 — Live Mission Control.** A real-time panel: concurrent agent sessions, active nodes,
per-agent current activity, and an SDLC funnel (triage→todo→in-progress→in-review→done)
with live throughput. (KM: concurrent agent sessions, active nodes, task completion rate.)
- **R4 — Export.** CSV export of any analytics table, and OpenTelemetry (OTLP) export of the
metrics, for shipping to Datadog/Grafana/etc.
- **R5 — Analytics API.** Programmatic endpoints (activity, tokens, tools, productivity) so an
agent can pull metrics.
- **R6 — Cost.** Derive USD cost from token counts × a model pricing map (Fusion stores tokens
but not cost today).
- **R7 — Signal ingestion.** Ingest external signals beyond GitHub (error trackers / alerting:
Sentry, Datadog, PagerDuty, generic webhook) into triageable tasks.
- **R8 — Triage stage.** Auto-classify and decompose incoming signals/issues into board tasks.
- **R9 — Monitor stage.** Track deployments and production incidents, compute MTTR, and feed
Monitor signals back into the funnel (closing the loop).
- **R10 — Knowledge layer.** A persistent, incrementally-refreshed knowledge index downstream
agents can query.
- **R13 — Fusion Model Router.** Automatic per-task / per-request model selection across
providers (route routine steps to fast/cheap models, reserve stronger models for hard
reasoning), with fallback, prompt-cache awareness, and respect for existing model controls —
a direct expression of Fusion's model-agnostic thesis.
- **R14 — Auto-triage of incoming issues *and* pull requests.** Triage applies to inbound PRs
(external contributions, dependabot, etc.), not just issues/signals — classify, label, and
route or open a follow-up task.
- **R15 — Auto-resolution of PR review comments.** Build on Fusion's existing **Review-response
loop** so PR review threads are acted on automatically (fix + push + reply, or disagree with
reasoning) as a first-class, surfaced capability.
- **R16 — External signals in dashboard metrics.** The Command Center surfaces external signals
(errors, alerts, incidents from R7 sources) as a metric area and in Mission Control, not only
as task-creating triggers.
---
## Key Technical Decisions
### KTD1 — Mirror the built-in view pattern; no router, no plugin
Register `command-center` as a `BuiltInTaskView` (`useViewState.ts`), lazy-load `CommandCenter`
in `App.tsx`, and add the nav entry in `Header.tsx`, exactly mirroring `reliability`. The
dashboard has **no URL router** (view state is `?view=` + `localStorage`); do not introduce
one. Ship as a built-in view (optionally behind an `experimentalFeatures.commandCenter` flag
like `insights`/`memoryView`), **not** a plugin — it is core product surface.
### KTD2 — Aggregation lives in `packages/core`; the route is a thin adapter
Put all metric math in new `packages/core/src/*-analytics.ts` modules (so engine/CLI can reuse
it), mirroring `agent-token-usage.ts`. The dashboard exposes it via an `ApiRouteRegistrar`
(`register-command-center-routes.ts`) registered in `routes.ts`, mirroring
`register-usage-routes.ts`. Do **not** import the engine into the React frontend; everything
goes over HTTP/SSE.
### KTD3 — A queryable telemetry/events table is required (the data is not all queryable today)
Token counts live on `tasks` (queryable) but **tool calls live in per-task JSONL agent logs**
and messages/sessions are spread across `chat_room_messages`/`cli_sessions`. The Tools area
and autonomy ratio need a queryable source. Decision: introduce a `usage_events` table in
`packages/core/src/db.ts` (migration in the same file) fed from the **store-level event seams**
(task-execution `appendAgentLog`, heartbeat/run `appendRunLog`, and the CLI/chat
`chat_room_messages` writer), rather than parsing JSONL at query time. This is the single
highest-risk change — see Risks for the SCHEMA_VERSION trap. **Tool calls are NOT all funneled
through one writer:** task execution, heartbeat agents (`appendRunLog`, callback-mode — bypasses
the file store), and CLI/chat (`chat_room_messages`, not tool-granular today) are distinct paths,
so a dual-write at `agent-log-file-store.ts` alone would silently undercount. The design must
instrument all three OR explicitly scope `usage_events` to task-execution and document the
exclusion. *Alternative (open — see Open Questions):* a lazy-materialization / cache table
populated on first query per time-bucket — viable because R1/R2/R5 state no sub-second
requirement, and it avoids coupling the agent hot-path write to a SQLite transaction.
### KTD4 — Charting: extend the house "hand-rolled CSS bars" style, do not add a chart lib (default)
The codebase has **zero** charting dependencies and a strong convention of hand-built CSS-bar
histograms (`ReliabilityView.tsx:199-207`). Default to extending that style with a small set of
reusable primitives (bar, sparkline, stacked bar, funnel) under
`packages/dashboard/app/components/command-center/charts/`. Adding a dependency (Recharts) is a
notable departure requiring a changeset + maintainer sign-off; surfaced as a call-out, not
assumed. *Rationale:* keeps bundle lean, matches existing code, avoids a lazy-loaded chart
vendor in a view that already lazy-loads. Revisit only if a chart type (e.g. multi-series time
series) proves impractical by hand.
### KTD5 — Live data uses push + poll convergence, throttled deltas
The Mission-Control panel follows the documented live-data pattern: an SSE event triggers an
immediate refetch while a poll interval (e.g. 5s) runs as a fallback **only while work is
in-flight**; high-frequency updates are throttled server-side. Historical analytics use plain
query + SWR (no streaming machinery). (See `docs/solutions/architecture-patterns/observable-long-running-agent-turns-through-blocking-plugin-route-seam.md`.)
### KTD6 — Cost via a versioned pricing map, never persisted as truth
Cost is derived at read time from token columns × a `model-pricing.ts` map (input/output/cache
rates per `modelProvider`+`modelId`), not stored. Unknown models surface tokens with cost
marked unavailable rather than guessing. Keeps historical rows correct when prices change and
avoids a migration to backfill cost. The map carries a `pricingAsOf` date and per-entry source
link; the UI shows "prices as of <date>" and marks entries older than a threshold low-confidence,
so stale-but-present rates (which the unknown-model guard does not catch) are visible rather than
silently wrong.
### KTD7 — SDLC stages map onto existing workflow columns + new trait-tagged columns
Phase C does not invent a parallel pipeline. Signal/Triage/Monitor attach to the existing
workflow-column system (`Column`, `Trait`, `Workflow Extension` in `CONCEPTS.md`): a `signal`
intake column, a `triage` trait that auto-decomposes, and a `monitor` trait that watches
deployments. This reuses the workflow runtime rather than forking lifecycle policy.
### KTD8 — Signal ingestion reuses the GitHub ingestion seam
Sentry/Datadog/PagerDuty/webhook ingestion mirrors the existing GitHub source path
(`github-source-issue-close.ts`, `github-poll.ts`, `github-webhooks.ts`) behind a common
`SignalSource` adapter interface, so each provider is a small adapter rather than bespoke wiring.
### KTD9 — Model Router is a selection layer over existing agent/model resolution, not a new executor
The Fusion Model Router slots into the existing **effective-agent / model-pair resolution** path
(`CONCEPTS.md` Effective agent, Workflow Setting model lanes) as a routing policy that *chooses*
the `(provider, model)` before a session starts (session routing) and may re-route per request
for routine sub-steps. It does **not** add an executor kind — it picks which existing
CLI/provider runs. It respects column-agent overrides and model controls (an org/project that
restricts a model restricts the router's ability to pick it), and reuses the U3 pricing map +
U1 telemetry to make cost/latency-aware decisions and to measure its own savings. Routing rules
are declarative (task complexity signal → model tier) with a safe fallback to the configured
default pair when the router is disabled or a pick is unavailable. This is a natural fit for the
`ecosystem breadth` strategy metric and feeds the Command Center directly.
### KTD10 — PR-comment auto-resolution extends the existing Review-response loop, not a rebuild
Fusion already has a **Review-response loop** (entry point `packages/engine/src/pr-response-run.ts`). R15 makes it a
first-class, default-surfaced capability rather than new machinery: ensure it triggers on
PR-entity review threads, expose its activity in the Command Center / Mission Control, and gate
it consistently with the merge/auto-merge model. Do not re-implement the loop.
---
## High-Level Technical Design
### The software delivery loop: Fusion today vs. the gaps this plan fills
```mermaid
flowchart LR
subgraph Loop["Software delivery loop (SDLC)"]
Signal["Signal\n(R7 — GAP*)"] --> Triage["Triage\n(R8 — partial)"]
Triage --> Plan["Plan\n(have: CE, missions)"]
Plan --> Execute["Execute\n(have: CLI sessions, nodes)"]
Execute --> Validate["Validate\n(have: validator, review loop)"]
Validate --> Ship["Ship\n(have: merge, PR entity)"]
Ship --> Monitor["Monitor\n(R9 — GAP)"]
Monitor -.feeds.-> Signal
end
Knowledge["Knowledge layer (R10 — GAP)"] -.enriches every stage.-> Loop
CC["Command Center (R1–R6) — observes the whole loop"] -.reads telemetry from.-> Loop
style Signal fill:#3b82f6,color:#fff
style Monitor fill:#3b82f6,color:#fff
style Knowledge fill:#3b82f6,color:#fff
style CC fill:#1e40af,color:#fff
```
*GAP\* = GitHub-only today; other sources are the gap. Blue = net-new in this plan.*
### Command Center data flow (Phase A → B)
```mermaid
flowchart TD
subgraph Sources["Existing data (packages/core, SQLite + JSONL)"]
T["tasks (token cols, model, files, timing)"]
AR["agentRuns / agentHeartbeats / agentTaskSessions"]
AL["activityLog"]
CC0["task_commit_associations"]
PR["pull_requests"]
CS["cli_sessions / chat_room_messages"]
JL["per-task JSONL agent logs (tool calls)"]
end
JL -->|U1: writer also appends| UE[("usage_events (new table)")]
CS -->|U1| UE
Sources --> AGG["U2: *-analytics.ts aggregators in packages/core\n(date-range windows, group-by model/node/agent)"]
UE --> AGG
AGG --> PRICE["U3: model-pricing.ts → cost (KTD6)"]
PRICE --> API["U9: register-command-center-routes.ts (ApiRouteRegistrar)\n/api/command-center/{tokens,tools,activity,productivity,live}"]
API -->|HTTP + SWR| HV["U5: Command Center historical areas"]
API -->|SSE + poll (KTD5)| LV["U6b: Mission-Control live panel (frontend)"]
API --> CSV["U8: CSV export"]
API --> OTEL["U10: OTLP exporter"]
AGG --> FUNNEL["U7: SDLC funnel (activityLog transitions)"]
HV --> VIEW["U4: Command Center shell (lazy view, nav entry)"]
LV --> VIEW
FUNNEL --> VIEW
```
---
## Output Structure
New files this plan introduces (repo-relative; existing files edited are listed per unit):
```
packages/core/src/
usage-events.ts # U1 write/query the new events table
model-pricing.ts # U3 pricing map + cost derivation
token-analytics.ts # U2 extends agent-token-usage windows → ranges
tool-analytics.ts # U2 tool calls by category, autonomy ratio
activity-analytics.ts # U2 sessions/messages/active-nodes/stickiness
productivity-analytics.ts # U2 files/commits/PRs/LOC, language dist
command-center-live.ts # U6a live snapshot: sessions, nodes, funnel
otel-metrics.ts # U10 OTLP metric mapping (pure mapping; wiring is in dashboard)
model-router.ts # U17 routing policy + rule evaluation
packages/dashboard/src/routes/
register-command-center-routes.ts # U9 analytics + live + export endpoints
register-signal-routes.ts # U11 inbound signal webhooks
packages/dashboard/src/
command-center-csv.ts # U8 CSV serialization
signal-source.ts # U11 SignalSource adapter interface + registry (mirrors github-* — dashboard)
signal-sources/{sentry,datadog,pagerduty,webhook}.ts # U11 adapters
knowledge-index.ts # U14 knowledge store + refresh (mirrors insights-routes — dashboard)
monitor-routes.ts # U13 deployment/incident tracking
packages/dashboard/app/components/command-center/
CommandCenter.tsx # U4 shell + sub-view tabs
CommandCenter.css # U4
charts/{Bar,StackedBar,Sparkline,Funnel}.tsx + .css # U4 chart primitives
areas/{TokensArea,ToolsArea,ActivityArea,ProductivityArea,EcosystemArea,SignalsArea}.tsx # U5
MissionControlPanel.tsx # U6b live ops
SdlcFunnel.tsx # U7 funnel/throughput
DateRangePicker.tsx # U5/B shared range control
```
> **Package note (feasibility).** New modules that *mirror an existing precedent* must live in
> the same package as that precedent. The GitHub ingestion path, `reliability-metrics.ts`,
> `subtask-breakdown.ts`, `runtime-provider-probes.ts`, and `pr-conflict-resolver.ts` all live in
> `packages/dashboard/src`, **not** `packages/core` — so `signal-source.ts` (mirrors `github-*`),
> `knowledge-index.ts` (mirrors `insights-routes.ts`), the OTel wiring, and `monitor-routes.ts`
> belong in dashboard. Pure, reusable aggregation (`*-analytics.ts`, `model-pricing.ts`,
> `command-center-live.ts`, the OTLP *mapping*, `model-router.ts`) stays in `packages/core` per
> KTD2. If a core module needs GitHub-ingestion code, expose it through a core-level seam rather
> than importing dashboard into core.
---
## Implementation Units
### Phase A — Metrics foundation
#### U1. Queryable usage-events telemetry table
**Goal:** Create a normalized, queryable source for tool calls, messages, and session
lifecycle so the Tools/Activity areas and OTel export do not have to parse JSONL at query time.
**Requirements:** R1, R3, R5 (substrate).
**Dependencies:** none.
**Files:**
- `packages/core/src/db.ts` — add the `usage_events` table, a new `applyMigration(N, ...)` block, and bump `SCHEMA_VERSION` to N (currently 117). `applyMigration`, `SCHEMA_VERSION`, `MIGRATION_ONLY_TABLE_SCHEMAS`, and `SCHEMA_COMPAT_FINGERPRINT` all live in `db.ts`, **not** `db-migrate.ts` (which is the legacy-data import path) — see Risks.
- `packages/core/src/usage-events.ts` (new: append + range query helpers)
- a dedicated `emitUsageEvent(...)` capture call invoked from the layer where `model`/`provider`/`nodeId`/`category` are already in scope — the **executor / session-run layer** — **not** by overloading `store.appendAgentLog` / `store.appendRunLog`, whose signatures and the `AgentLogEntry` they persist carry none of those fields (widening them is a high-fanout ~20+ call-site change across `engine/src/merger.ts`, `executor.ts`, etc.). `agent-log-file-store.ts` is likewise unusable (pure-FS, no DB handle). The field-carrying mechanism (dedicated call vs signature-widening vs hot-path lookup) is recorded as an Open Question.
- `packages/core/src/__tests__/usage-events.test.ts`, and the `db.ts` migration test (extend)
**Approach:** Columns: `id`, `ts`, `kind` (`tool_call|tool_result|tool_error|user_message|session_start|session_stop`), `taskId`, `agentId`, `nodeId`, `model`, `provider`, `toolName`, `category`, `meta` (JSON). **v1 scope:** task-execution + run-log events (which can carry model/provider/node from the session context). The chat path (`ChatStore`/`chat_room_messages`, which has no model/provider at its write site) contributes **message counts only** — chat-origin rows are model/provider-null by design, documented in U2. **`nodeId`** is sourced from the run/session context (`agentRuns`/`cli_sessions`), not the `tasks` row (which has no `nodeId`); events with no node context record `nodeId` null. **Mapping:** the agent-log `type` value `tool` maps to `kind: tool_call` (there is no `tool_call` in `AgentLogType`, which is `text|tool|thinking|tool_result|tool_error`); `user_message`/`session_start`/`session_stop` originate from `cli_sessions`/`chat_room_messages`. **`meta` safety:** capped at a fixed byte size (~4 KB, rejected at write); carries only non-sensitive descriptors (error code, category, duration) — **never** tool arguments/content or credential-class fields — with a documented retention/age-out policy. Reads come from SQLite. Index `(ts)`, `(taskId)`, `(agentId)`.
**Patterns to follow:** `packages/core/src/agent-token-usage.ts` (range scans), the `applyMigration` shape in `db.ts`, and the schema-version learning doc.
**Test scenarios:**
- Happy: a `tool`-type agent-log entry inserts one `usage_events` row with `kind: tool_call` and correct `category`.
- Completeness: a heartbeat-run (`appendRunLog`) tool call and a chat tool call either appear in `usage_events` or are asserted intentionally absent per the documented scope (guards the multi-path undercount).
- Edge: a chat-session event with no `taskId` records with `taskId` null and `agentId` set.
- Migration: seed a DB **at the previous schema version**, run migrate, assert the table exists and `SCHEMA_VERSION` equals the highest migration target (fresh-DB tests cannot catch the early-return bug).
- Error: malformed event is skipped without throwing and without aborting the underlying write.
- Edge: a `meta` payload exceeding the byte cap is rejected at write; tool-argument content never lands in `meta`.
- Integration: a real task execution that calls 3 tools yields 3 `tool_call` rows queryable by range, with `model`/`provider`/`nodeId` populated from the session context.
#### U2. Core analytics aggregators (date-range windows)
**Goal:** Pure, reusable aggregation over tasks + `usage_events` producing the six measurement
areas for an arbitrary date range, grouped by model/provider/node/agent.
**Requirements:** R1, R2.
**Dependencies:** U1.
**Files:**
- `packages/core/src/token-analytics.ts`, `tool-analytics.ts`, `activity-analytics.ts`,
`productivity-analytics.ts` (new)
- `packages/core/src/__tests__/{token,tool,activity,productivity}-analytics.test.ts`
**Approach:** Each exports `aggregate({from, to, groupBy})`. Tokens: sum `tasks.tokenUsage*`
columns filtered by `tokenUsageLastUsedAt` in range. Tools: count `usage_events` by
`category`; **autonomy ratio = tool_call count / human-intervention events** — NOT raw user
messages, which trend to zero for autonomous task execution. The denominator's three components
have distinct, named sources (they are not one queryable thing): **approvals** from
`approval_request_audit_events` (filter to `created`/`approved`); **user-authored steers** from
the `SteeringComment[]` JSON on the task row, filtered to `author === "user"` (agent-authored
steers excluded — note this re-introduces a per-task JSON read, so mirror steers into
`usage_events` if range-querying proves costly); **waiting-on-input** is a task *status*, not a
counted event — drop it unless a concrete answer event is defined. A fully-autonomous session
(zero interventions) reports tool-calls-per-session instead of ∞. Activity: distinct
active nodes/agents per day, sessions from `cli_sessions`, messages from `usage_events`,
**stickiness = DAU/MAU**. Productivity: `tasks.modifiedFiles` count + language distribution,
`task_commit_associations` count, `pull_requests` count; LOC from commit diff stats if
available else flagged unavailable. Generalize `agent-token-usage.ts`'s 24h/7d/all-time windows
to `(from,to)`.
**Patterns to follow:** `packages/core/src/agent-token-usage.ts`.
**Test scenarios:**
- Happy: a known fixture of 5 tasks across 2 models returns correct per-model token totals.
- Edge: empty range returns zeroed structures, not nulls; a range boundary task (exactly at `from`) is included per documented inclusivity.
- Edge: a fully-autonomous session (zero human-intervention events) reports tool-calls-per-session, not ∞ or a divide-by-zero; validated against both an autonomous and an interactive fixture.
- Edge: the intervention denominator counts a user-authored steer and an approval but NOT an agent-authored steer.
- Productivity: LOC unavailable when commit diff stats are missing is reported as `null` + `unavailable: true`, not `0`.
#### U3. Model pricing → cost derivation
**Goal:** Derive USD cost from token counts without persisting cost.
**Requirements:** R6.
**Dependencies:** U2.
**Files:** `packages/core/src/model-pricing.ts` (new), `packages/core/src/__tests__/model-pricing.test.ts`; consumed by `token-analytics.ts`.
**Approach:** A map keyed by `provider:model` → `{inputPer1M, outputPer1M, cacheReadPer1M, cacheWritePer1M, source}` plus a top-level `pricingAsOf` date. `costFor(usage, model)` returns `{usd, unavailable, stale}`. Unknown model → `unavailable: true`, never a guessed price.
**Patterns to follow:** plain data module; colocate with token-analytics.
**Test scenarios:**
- Happy: known model + token counts yields expected USD to cent precision.
- Edge: unknown model returns `unavailable: true` with `usd: null`.
- Edge: cache tokens priced at cache rate, not input rate.
- Edge: the map carries a `pricingAsOf` date and entries older than the threshold return `stale: true`.
### Phase B — Command Center dashboard
#### U4. Command Center shell, nav registration, and chart primitives
**Goal:** Register the `command-center` view end-to-end and build the reusable CSS-bar chart
primitives the areas render with.
**Requirements:** R1 (shell), KTD1, KTD4.
**Dependencies:** none (can start parallel to A; renders real data once A lands).
**Files:**
- `packages/dashboard/app/hooks/useViewState.ts` (add `command-center` to union + array)
- `packages/dashboard/app/App.tsx` (lazy import + prefetch + render branch, mirror `reliability` at App.tsx:1818-1826)
- `packages/dashboard/app/components/Header.tsx` (nav button mirroring reliability at :1223-1234; add `command-center` to active-check at :1095; optional `experimentalFeatures.commandCenter` gate)
- `packages/dashboard/app/components/MobileNavBar.tsx` (mobile parity)
- `packages/dashboard/app/components/command-center/CommandCenter.tsx` + `.css`, `charts/{Bar,StackedBar,Sparkline,Funnel}.tsx` + `.css`, `DateRangePicker.tsx`
- i18n strings under the `app` namespace
- `packages/dashboard/app/components/command-center/__tests__/charts.test.tsx`, `CommandCenter.test.tsx`
**Approach:** Shell renders sub-view tabs (Overview / Tokens / Tools / Activity / Productivity / Ecosystem / Mission Control). Chart primitives are hand-rolled CSS-bar components. Use `--duration-*` tokens (never `--transition-*`) for any loader/pulse animation.
**Overview tab content:** one headline stat card per area (total tokens + cost, autonomy ratio, active nodes, tasks done, unique models, open signals) plus a compact live Mission-Control strip; the date-range picker applies to the cards but not the live strip. A single "no usage data yet" empty state when nothing exists.
**Tab a11y:** sub-tabs use the ARIA tabs pattern (`role=tablist/tab/tabpanel`, arrow-key roving tabindex, Enter/Space activates, Tab moves into the active panel); the DateRangePicker returns focus to its trigger on dismiss.
**Patterns to follow:** `ReliabilityView.tsx` (skeleton, loading/error/empty), `AgentsView.tsx` (sub-view toggles), the reliability nav button.
**Execution note:** Build the chart primitives test-first — they are pure and the CSS-token trap is invisible without a real-browser assertion.
**Test scenarios:**
- Happy: selecting the Command Center nav entry renders the shell with the Overview tab active; `?view=command-center` deep-links to it.
- Edge: empty data renders the documented empty state per area, not a crash.
- CSS (real browser): `getComputedStyle(barEl).animationName !== "none"` for any animated loader (guards the IACVT token trap); extend `animation-duration-tokens.css.test.ts` for new CSS.
- Edge: chart bar with a zero value renders a 0-width bar with accessible label, not NaN width.
- A11y: a keyboard user can arrow between tabs, activate with Enter/Space, and Tab into the panel without losing focus; the date-range picker returns focus to its trigger on dismiss.
#### U5. Historical analytics areas + date-range filtering
**Goal:** Render the measurement areas from the Phase A aggregators with a shared date-range
control, **including an External Signals area** (errors/alerts/incidents from R7 sources).
**Requirements:** R1, R2, R6, R16.
**Dependencies:** U2, U3, U4, U9; the Signals area depends on U11 data (degrades to empty until U11 lands).
**Files:** `packages/dashboard/app/components/command-center/areas/{TokensArea,ToolsArea,ActivityArea,ProductivityArea,EcosystemArea,SignalsArea}.tsx`, `DateRangePicker.tsx`; tests alongside.
**Approach:** Each area fetches its endpoint via the `api()` helper with the selected range,
renders stat cards + tables + CSS-bar charts. **Productivity framing (A5):** present LOC and
tool-count as *volume* proxies alongside outcome counters (tasks reaching done, PRs merged,
incidents resolved); do not frame high LOC/tool counts as inherently positive.
**Ecosystem area:** unique-active-model count + per-model session count as a bar chart, plugin
activation count, and a sparkline of distinct models/day; empty state when no third-party models
or plugins have been used. Reuses the tokens endpoint grouped by model where possible.
**External Signals area (R16):** signal volume by source/severity over the range, open vs
resolved, and MTTR (from U13) — wired so external signals are visible as dashboard metrics, not
only as task triggers. Until U11/U13 land, the area renders its empty state.
**SWR trap:** key any selection/drill-down reset effect on a derived value (e.g.
`rows.map(r => r.id).join(" ")`), never the array identity, or it resets every revalidation.
**Patterns to follow:** `AgentTokenStatsPanel.tsx` (token tables/totals), `ReliabilityView.tsx`.
**Test scenarios:**
- Happy: Tokens area shows per-model totals + cost; changing the range refetches and re-renders.
- Tools: autonomy ratio displayed; tool categories shown as a sorted bar chart.
- Edge (SWR): a revalidation that returns content-identical rows with new identity does **not** reset the user's column sort / selected row (regression: seed cache, defer fetch, interact, resolve with `JSON.parse(JSON.stringify(original))`, assert state survives).
- Edge: custom range with `from > to` is rejected client-side with a message.
- Productivity: unavailable LOC shows "—" with a tooltip, not `0`.
- Signals (R16): with U11 fixture data, the External Signals area shows volume by source/severity and open-vs-resolved; with no signal data it renders the empty state, not an error.
#### U6. Live Mission-Control panel
**Goal:** Real-time view of concurrent sessions, active nodes, per-agent current activity, and
the live SDLC funnel.
**Requirements:** R3.
**Dependencies:** U4, and U9 for the endpoint. **To break the U6↔U9 cycle, U6 splits in two:** U6a = the core `command-center-live.ts` snapshot composer (no deps); U6b = the `MissionControlPanel` frontend (deps U4, U9). U9's live branch depends on U6a, not the U6 frontend.
**Files:** `packages/dashboard/app/components/command-center/MissionControlPanel.tsx`, `packages/core/src/command-center-live.ts` (live snapshot, U6a), live branch in `register-command-center-routes.ts`; tests alongside.
**Approach:** `command-center-live.ts` composes a snapshot from `agentHeartbeats`/`agentRuns`/`cli_sessions`/`tasks` (current column counts). Frontend follows **push + poll convergence (KTD5)**: subscribe to the existing SSE bus, refetch on event, poll every ~5s **only while any session is in-flight**, stop polling when idle. Server throttles emits (~500ms) and sends deltas, not full snapshots, for high-churn fields.
**Patterns to follow:** `app/sse-bus.ts`, the observable-long-running-agent-turns learning doc, `AgentsOverviewBar.tsx`.
**Test scenarios:**
- Happy: a newly-started session appears in the live panel within one poll/SSE cycle; ending it removes it.
- Edge: with zero active sessions, polling is not running (assert no interval scheduled when idle).
- Integration: SSE event triggers an immediate refetch (push) even between poll ticks.
- Edge: a node going stale (no heartbeat past threshold) is shown as inactive, not dropped silently.
#### U7. SDLC funnel + throughput visualization
**Goal:** A funnel/Sankey-style visualization of tasks across columns with throughput (e.g.
tasks/day reaching done) and completion rate, both live and over a range.
**Requirements:** R1, R3 (KM: task completion rate).
**Dependencies:** U2, U4.
**Files:** `packages/dashboard/app/components/command-center/SdlcFunnel.tsx` + `.css`; aggregation in `activity-analytics.ts`; tests alongside.
**Approach:** Map the workflow columns (`triage→todo→in-progress→in-review→done`) to funnel
stages using `activityLog` transitions; show counts per stage and conversion between stages.
Reuse the `Funnel` chart primitive from U4.
**Patterns to follow:** the hand-rolled bar style; `activityLog` event types in `types.ts`.
**Test scenarios:**
- Happy: a fixture of tasks distributed across columns renders correct per-stage counts.
- Edge: workflow-defined custom columns (not the default enum) are mapped by trait, not by hardcoded names.
- Edge: completion rate over a range divides done-in-range by entered-in-range, documented and tested for the zero-denominator case.
#### U8. CSV export
**Goal:** Export any analytics table as CSV.
**Requirements:** R4.
**Dependencies:** U2.
**Files:** `packages/dashboard/src/command-center-csv.ts` (new), export branch in `register-command-center-routes.ts`; export buttons in the area components; tests alongside.
**Approach:** A route variant sets `Content-Type: text/csv` + `Content-Disposition: attachment`. Server-side serialization of the same aggregator output. **Honors `getScopedStore(req)` before aggregation, exactly like U9's JSON endpoints — no cross-project leak via the export path.** No precedent exists — net-new.
**Test scenarios:**
- Happy: token endpoint with `?format=csv` returns well-formed CSV with a header row and the attachment header.
- Edge: values containing commas/quotes/newlines are RFC-4180 quoted.
- Edge: empty result returns header-only CSV, not a 204.
- Security: a project-A request cannot retrieve project-B data via CSV export (mirrors the U9 scoping test).
#### U9. Analytics API endpoints
**Goal:** Programmatic endpoints backing the view and usable by agents.
**Requirements:** R5.
**Dependencies:** U2, U3, U6a (the `command-center-live.ts` snapshot composer — not the U6 frontend, which breaks the cycle).
**Files:** `packages/dashboard/src/routes/register-command-center-routes.ts` (new), registered in `packages/dashboard/src/routes.ts` near the other registrars (~:1991); tests in `packages/dashboard/src/__tests__/`.
**Approach:** `GET /api/command-center/{tokens,tools,activity,productivity}` (range + group-by params),
`GET /api/command-center/live` (snapshot), all thin adapters over Phase A aggregators. **Verify the
Vite proxy:** confirm `vite.config.ts`'s negative-lookahead `/api` proxy routes these to the
backend while leaving app source modules on Vite — `curl` both a real endpoint and a `?import`
source path. **Auth:** all routes inherit the dashboard's standard session/auth middleware via the
`ApiRouteRegistrar` (same as `register-usage-routes.ts`); machine/agent callers use the existing
credential model — **no analytics endpoint, including `/live`, is unauthenticated**, and every
endpoint (JSON, `/live`, and the CSV variant) applies `getScopedStore(req)` before aggregation.
**Patterns to follow:** `register-usage-routes.ts` (registrar shape), `ApiRoutesContext` in `routes/types.ts`.
**Test scenarios:**
- Happy: each endpoint returns the aggregator output with correct shape for a fixture DB.
- Edge: missing/invalid range params default to a documented window (e.g. last 7d), not a 500.
- Security: an unauthenticated request to each endpoint (including `/live`) returns 401.
- Security: project scoping — `getScopedStore(req)` is honored on the JSON and `/live` endpoints so cross-project data does not leak.
- Integration (proxy): real endpoint proxies to backend; a same-prefix `.ts?import` source path stays on Vite.
#### U10. OpenTelemetry (OTLP) metrics export
**Goal:** Export the metrics over OTLP so teams can ship to Datadog/Grafana/etc.
**Requirements:** R4.
**Dependencies:** U2, U3.
**Files:** `packages/core/src/otel-metrics.ts` (new), wiring in the dashboard server (opt-in via config/env), changeset; tests alongside. Adds an OTel SDK dependency (changeset + sign-off).
**Approach:** Map aggregator outputs to OTLP metric instruments (counters/gauges) on a periodic
export, endpoint + headers from config. Disabled by default. **The endpoint is validated on write
(https-only in production; warn loudly on http); auth headers (Datadog/Grafana tokens) are stored
via the same secret-storage strategy as other credentials and are never logged or included in
diagnostic output.**
**Test scenarios:**
- Happy: with an OTLP collector stub, token/cost/activity metrics are exported with expected
metric names + attributes (model, node, provider).
- Edge: disabled by default — no exporter starts without explicit config.
- Security: an `http://` endpoint emits a warning; auth header values are redacted from any log output.
- Error: collector unreachable logs and backs off; it never crashes the server or blocks requests.
### Phase C — Software-delivery-loop gap-fill
> **Scope note:** Phase C closes the Signal/Triage/Monitor/Knowledge gaps.
> Per the user's request these are specified as buildable units, but **U11 and U14 are
> each large enough to merit their own `ce-brainstorm` before execution** — they are flagged
> inline. Sequence Phase C after Phases A–B ship.
#### U11. External signal ingestion (Sentry / Datadog / PagerDuty / webhook)
**Goal:** Ingest signals beyond GitHub into triageable tasks via a common adapter seam.
**Requirements:** R7, KTD8.
**Dependencies:** none (independent of the Command Center); benefits from U13.
**Files:** `packages/dashboard/src/signal-source.ts` (adapter interface + registry — mirrors the GitHub path, lives in dashboard), `packages/dashboard/src/signal-sources/{sentry,datadog,pagerduty,webhook}.ts`, `packages/dashboard/src/routes/register-signal-routes.ts` (inbound webhooks), config/settings entries; tests alongside.
**Approach:** A `SignalSource` interface (`verify(req)`, `normalize(payload) → Signal`) mirroring
the GitHub source path. The normalized `Signal` includes a **`groupingKey`** populated from the
provider's native primitive (Sentry `issue.id`, PagerDuty `incident.id`, …) for U13's storm guard;
the generic webhook requires the caller to supply one or falls back to `source + normalized-title`.
Inbound webhooks land normalized `Signal`s that create tasks in a `signal`/`triage` column. Each
provider is a thin adapter.
**Security (mandatory, not deferred to the brainstorm):** every adapter's `verify(req)` performs
HMAC signature verification against a per-provider secret stored in encrypted settings/env (never
source-controlled); a missing or invalid secret rejects with 401 — **the generic webhook is never
an unauthenticated task-creation endpoint.** Add a replay window (reject timestamps outside ±5 min)
plus delivery-id nonce dedup; treat any URLs in payloads as SSRF-untrusted. Enforce a request body
size cap (~1 MB), per-source rate limiting, and field-length caps on normalized `Signal` fields;
`meta` JSON from external sources is stored as data and never rendered as raw HTML in the dashboard.
**Patterns to follow:** `github-source-issue-close.ts`, `github-webhooks.ts`, `github-poll.ts`.
**Execution note:** Characterize the existing GitHub ingestion path first, then factor the
shared seam — do not break GitHub ingestion while generalizing it.
**Flag:** Candidate for its own brainstorm (provider auth models, dedup, rate limits differ per provider). **Defer the `SignalSource` registry/interface extraction until a second provider exists** — for the first delivery, implement one provider (generic webhook) as a standalone module mirroring `github-webhooks.ts`, then extract the shared interface once the brainstorm settles the auth/dedup/rate-limit shape and two providers coexist.
**Test scenarios:**
- Happy: a valid Sentry webhook creates one triage task with normalized title/severity/link.
- Security: an unsigned/invalid-signature webhook (including the generic webhook with no secret) is rejected with 401 and creates no task.
- Security: a replayed valid payload (timestamp outside the window or duplicate delivery-id nonce) is rejected.
- Edge: duplicate delivery (same external id) is deduped, not double-created (mirror `github-tracking-dedup.ts`).
- Edge: an oversized payload (>1 MB) is rejected; per-source rate limit caps a flood.
- Error: a malformed payload returns 4xx and creates no task.
#### U12. Triage stage — auto-classify + decompose, for issues *and* pull requests
**Goal:** Auto-classify incoming signals/issues **and inbound pull requests** and decompose or
route them into board tasks.
**Requirements:** R8, R14, KTD7.
**Dependencies:** U11; reuses existing breakdown + GitHub PR ingestion.
**Files:** a `triage` trait/handler via the workflow-extension system, `packages/dashboard/src/subtask-breakdown.ts` reuse, PR-source wiring near `github-poll.ts`/`github-webhooks.ts`; tests alongside.
**Approach:** A triage column trait runs a classify+decompose pass (priority/area/labels), using
the existing subtask-breakdown machinery, then routes to `todo`. Express as a `Trait` with an
`onEnter` hook (see `CONCEPTS.md` Trait), not a hardcoded branch. **PRs:** inbound PRs (external
contributors, dependabot) are classified and either labeled/routed for review or used to open a
follow-up task; PR triage reuses the `pull_requests` / PR-entity model rather than minting issues.
**Patterns to follow:** `subtask-breakdown.ts`, `mission-interview.ts`, `github-webhooks.ts`, the Trait/Workflow-Extension model.
**Test scenarios:**
- Happy (issue): a signal-created task entering `triage` is classified and decomposed into N todo tasks linked back to the signal.
- Happy (PR): an inbound PR is classified (e.g. dependency-bump vs feature) and routed to review or a follow-up task, linked to its PR entity.
- Edge: a signal too small to decompose passes through as a single task, not zero.
- Edge: a PR Fusion itself opened is **not** re-triaged as inbound (no self-loop).
- Error: classifier failure parks the item in triage with a diagnostic, does not drop it.
#### U13. Monitor stage (deployments, incidents, MTTR) — closes the loop
**Goal:** Track deployments and production incidents, compute MTTR, and feed Monitor signals
back to Signal/Triage.
**Requirements:** R9, KTD7.
**Dependencies:** U11 (signals), U2 (so MTTR surfaces in the Command Center).
**Files:** `packages/dashboard/src/monitor-routes.ts`, a `deployments`/`incidents` table (db.ts + migration), a `monitor` column trait, MTTR aggregation in `activity-analytics.ts`, Command Center surfacing; tests alongside.
**Approach:** Record deploys (from CI/Ship events) and incidents (from U11 signals). MTTR =
incident-open → incident-resolved. A `monitor` trait watches post-ship and can auto-open a
fix task on a regression signal, closing the loop back to Triage. **Storm/dedup guard (required —
production signals are bursty):** grouping requires a **`groupingKey`** that each U11 adapter's
`normalize()` populates from its provider's native primitive (Sentry `issue.id`/`event.fingerprint`,
PagerDuty `incident.id`, Datadog monitor/aggregation key) — there is no Fusion error-fingerprint
concept, and the content-hash `computeContentFingerprint` (task title/description) is wrong for
bursty alerts. The **generic webhook has no native key**: require the caller to supply one, else
fall back to `source + normalized-title` with a documented coarser cooldown. With the key: a
threshold/sustained-duration gate precedes task creation; a cooldown attaches re-firing signals to
the existing fix task (reuse `findLatestByDedupeKey`); a circuit-breaker caps auto-created tasks per
window; a Fusion-opened fix task never re-triggers (no self-loop, mirroring U12). **Deploy/incident
ingestion auth:** the CI→`monitor-routes` endpoint requires a shared secret / bearer token (stored
in encrypted settings, never unauthenticated, 401 on missing/invalid), and payload URLs are
SSRF-untrusted — mirroring U11. The MTTR aggregator lives in `activity-analytics.ts`
(`packages/core`); deployment/incident recording and `monitor-routes.ts` live in
`packages/dashboard/src` (the aggregator is the core seam the route consumes).
**Patterns to follow:** `reliability-metrics.ts` (metric aggregation + endpoint), KTD7 traits.
**Test scenarios:**
- Happy: an incident opened then resolved yields a correct MTTR in the Monitor metrics.
- Integration: a post-ship error signal auto-creates a single linked fix task in triage (loop closure).
- Storm: a 100-event burst sharing one `groupingKey` yields exactly one fix task; a flapping alert yields no new task; an already-open fix task absorbs repeat signals.
- Edge: the generic webhook with no supplied grouping key falls back deterministically (source + normalized-title), not per-event.
- Security: an unauthenticated deploy/incident POST to `monitor-routes` returns 401 and records nothing.
- Edge: an unresolved incident contributes to "open incidents," not to MTTR.
- Edge: deploy with no following incident counts toward deploy frequency / change-fail rate denominator.
#### U14. Persistent knowledge index
**Goal:** A persistent, incrementally-refreshed knowledge layer downstream agents can query.
**Framing:** this is a *delta* over the existing `insights`/`memoryView` surfaces, which already
provide part of this — characterize what they lack before building. If the delta is small, extend
those surfaces rather than introducing a greenfield store; the new-table spec below applies only if
the brainstorm concludes a separate store is warranted.
**Requirements:** R10.
**Dependencies:** none; integrates with existing `insights`/`memoryView`.
**Files:** `packages/dashboard/src/knowledge-index.ts` (mirrors `insights-routes.ts` — lives in dashboard), a knowledge store table (`db.ts` + migration), refresh hook on task completion, a dashboard surface reusing the `memoryView`/`InsightsView` patterns; tests alongside.
**Approach:** Index repo + task/PR history into queryable knowledge pages, refreshed
incrementally on task completion (not full re-index). Expose a query API agents can call —
**under the same session/auth middleware and `getScopedStore(req)` scoping as U9** (the index
holds sensitive repo/commit/PR content, so it is an information-disclosure surface, not an open
endpoint).
**Patterns to follow:** `InsightsView.tsx` + `insights-routes.ts`, the `memoryView` experimental flag.
**Flag:** Candidate for its own brainstorm (indexing strategy, storage/embedding choice, refresh cost).
**Test scenarios:**
- Happy: completing a task adds/updates a knowledge page; a keyword query returns it.
- Edge: incremental refresh updates only affected pages, not the whole index (assert unaffected pages' timestamps unchanged).
- Integration: an agent query endpoint returns relevant pages for a known fixture.
- Security: an unauthenticated query returns 401; a project-A caller cannot retrieve project-B pages (mirrors U9 scoping).
#### U17. Fusion Model Router
**Goal:** Automatic per-task / per-request model selection across providers, optimizing
cost/latency while preserving frontier quality on hard work.
**Requirements:** R13, KTD9.
**Dependencies:** U1 (telemetry to measure savings), U3 (pricing); independent of the view UI.
**Files:** `packages/core/src/model-router.ts` (routing policy + rule evaluation), wiring into
the effective-agent / model-pair resolution path, a router config/settings surface, a Command
Center readout of router decisions + realized savings; tests alongside.
**Approach:** Per KTD9 — a selection layer, not an executor. **Session-level routing only for this
unit:** pick the `(provider, model)` pair at session start. *Per-request mid-session re-routing is
deferred* (it needs its own design pass on streaming continuity, context-window compatibility, and
prompt-cache invalidation — see Deferred). **Routing signal (load-bearing, must be settled before
build):** no structured `complexity`/`difficulty` field exists on tasks or steps today, and prompt
size alone is a weak proxy (short-but-hard vs long-but-boilerplate). The classifier signal must be
defined and validated against real Fusion task data, and paired with a **quality guardrail**
(escalation/retry to the strong tier on cheap-tier failure) and a **quality-regression metric** —
not only the cost-savings readout — so the router cannot report savings while silently degrading
output. **The gate is exitable two ways:** the unit does not ship until the brainstorm produces a
validated signal, OR it ships a deliberately-conservative v0 that routes only an allowlist of
mechanical traits (dependabot bumps, lint-only fixes) to the cheap tier and everything else to the
default pair. It must NOT be read as "build the full classifier now" with prompt-size as the de
facto signal. **Resolution lanes:** enumerate which lanes the router governs (execution, planning,
validation, …; `model-resolution.ts` exposes a distinct resolver per lane) and test each — it must
neither leak into ungoverned lanes nor return a forbidden pair in any governed lane. Respects
column-agent overrides and org/project/user model controls (cannot pick a restricted model). Safe
fallback to the configured default pair when disabled or a pick is unavailable. Emits its decisions
(including the counterfactual model that *would* have run) to U1 so the Command Center can show
adoption and realized cost delta versus always-premium.
**Patterns to follow:** the Effective-agent / Workflow-Setting model-lane resolution and
`model-resolution.ts` lanes; `runtime-provider-probes.ts` for provider availability.
**Execution note:** Implement the resolution-seam integration test-first — routing must never
hand back a pair the model controls forbid.
**Flag:** Candidate for its own brainstorm — the routing signal and quality guardrail are
load-bearing and unproven, making this at least as design-heavy as U11/U14. It is also the most
strategy-aligned new capability after the Command Center (it directly expresses the model-agnostic
thesis and feeds `ecosystem breadth`), so it should not be deferred or rejected alongside the
competitor-parity units — elevate it on its own merits.
**Test scenarios:**
- Happy: a routine step routes to the cheap tier; a deep-reasoning task routes to the strong tier.
- Edge: a column-agent `override` binding wins over the router (router defers).
- Security/governance: a model restricted by project policy is never selected, even if it scores best.
- Edge: router disabled → resolution is byte-identical to today's default-pair behavior (no regression).
- Integration: router decisions appear in `usage_events` and the Command Center shows realized cost savings vs premium-only.
#### U18. PR review-comment auto-resolution (surface + harden the Review-response loop)
**Goal:** Make automatic resolution of PR review comments a first-class, surfaced capability
built on the existing Review-response loop.
**Requirements:** R15, KTD10.
**Dependencies:** none (extends existing PR-entity + review-response machinery); benefits from U6b.
**Files:** wiring/config around the existing Review-response loop — the real entry point is
`packages/engine/src/pr-response-run.ts` (plus the `ce-resolve-pr-feedback` skill), **not** a
`pr-comment-resolver` module by that name — and the PR entity (`CONCEPTS.md` PR entity,
Review-response loop); Command Center / Mission-Control surfacing of in-flight resolutions; tests
alongside. Note the `packages/engine` home of the loop.
**Approach:** Per KTD10 — do not rebuild the loop. Ensure it triggers on PR-entity review
threads (human + bot), is gated consistently with the auto-merge model, and exposes its
activity (threads acted on, fixed vs disagreed) to the Command Center. Make default-on behavior
explicit and configurable.
**Patterns to follow:** the existing `ce-resolve-pr-feedback` skill seam, `pr-conflict-resolver.ts`, the Review-response loop description in `CONCEPTS.md`.
**Test scenarios:**
- Happy: a new review thread dispatches a resolver that fixes, pushes to the PR branch, and replies to the thread.
- Edge: the resolver disagrees → posts reasoning and leaves the thread open (no silent push).
- Edge: auto-resolution respects the auto-merge gate (disabled → resolves but does not merge).
- Integration: in-flight resolutions appear in Mission Control and counts roll into Command Center metrics.
---
## Scope Boundaries
**In scope:** The Command Center (combined historical analytics + live Mission Control,
including an External Signals metric area), its metrics foundation, export (CSV + OTel), the
Analytics API, and buildable units for every SDLC gap (Signal ingestion, Triage of issues
**and PRs**, Monitor, Knowledge, the **Fusion Model Router**,
and **auto-resolution of PR review comments**).
### Deferred to Follow-Up Work
- **Per-unit brainstorms for U11 and U14** before execution — each has substantial design
surface (provider auth/dedup; indexing/embedding strategy) that this
plan scopes but does not fully resolve.
- **Per-request mid-session model re-routing (U17)** — this unit ships session-level routing only;
per-request re-routing needs its own design pass on streaming continuity, context-window
compatibility, and prompt-cache invalidation.
- **Recharts (or any chart-lib) adoption** — only if KTD4's hand-rolled approach proves
impractical for a needed chart type; would be a separate changeset + sign-off.
- **Human "Users" analytics** — Fusion's notion of a human user is thin (`assigneeUserId`);
the Users/per-person area is modeled here as **per-agent**. A per-human breakdown waits until
multi-user (the `Pluggable multi-user` track in `STRATEGY.md`) lands.
### Out of scope
- Replacing or forking the workflow runtime — Phase C attaches to it via traits/extensions.
- A URL router for the dashboard — the `?view=` + `localStorage` model is preserved.
---
## Risks & Dependencies
- **SCHEMA_VERSION migration trap (high).** U1/U13/U14 add tables. `applyMigration`,
`SCHEMA_VERSION` (currently 117), `MIGRATION_ONLY_TABLE_SCHEMAS`, and `SCHEMA_COMPAT_FINGERPRINT`
all live in `packages/core/src/db.ts`, **not** `db-migrate.ts` (the legacy-data path). Every
`applyMigration(N)` **must** bump `SCHEMA_VERSION` to N in the same change, or the migrate loop early-returns and
the migration silently never runs on already-upgraded DBs (fresh DBs mask it). Also update
`MIGRATION_ONLY_TABLE_SCHEMAS`/`SCHEMA_COMPAT_FINGERPRINT`, add a **seed-at-previous-version**
migration test, and run the version-literal sweep across **plugin** workspaces too, not just
`packages/`. (`docs/solutions/database-issues/schema-version-constant-must-equal-highest-migration.md`.)
- **Vite `/api` proxy regex (medium).** New endpoints must be verified against the
negative-lookahead proxy in `vite.config.ts` so app source modules aren't proxied; `curl`
both a real endpoint and a `?import` source path. (`docs/solutions/integration-issues/vite-api-source-modules-proxied-to-backend.md`.)
- **CSS IACVT token trap (high).** Chart/loader animations must use `--duration-*` tokens, not
`--transition-*` (which are duration+easing pairs); misuse silently drops the whole
declaration. Extend `animation-duration-tokens.css.test.ts`; verify in a real browser.
(`docs/solutions/ui-bugs/css-animation-frozen-by-transition-token-shape-mismatch.md`.)
- **SWR identity-reset trap (medium).** View state keyed on revalidated array identity resets
every poll tick; key on derived semantic values. (`docs/solutions/ui-bugs/skill-autocomplete-highlight-reset-on-swr-revalidation.md`.)
- **Browser verification hazard (process).** Verify with `fn dashboard --dev` on a **free,
non-4040** port with `FUSION_CLIENT_DIR=$PWD/packages/dashboard/dist/client` after a fresh
build; never `fn daemon`/`fn serve` (engine + shared DB). If a chart renders empty, check the
served bundle hash first. (`docs/solutions/developer-experience/browser-testing-dashboard-from-worktree-safely.md`; aligns with the port-4040 kill-guard.)
- **Phase C breadth.** Three units are brainstorm-candidates; do not let Phase C block the
Command Center shipping from Phases A–B.
---
## Sources & Research
- **External analytics product** (six measurement areas — tokens, tools, activity, productivity,
users, agent readiness — plus CSV/OTel/API): factory.ai/news/factory-analytics.
- **External model router** (per-task/per-request auto model selection, ~20–25% cost reduction,
respects org/project/user model controls): docs.factory.ai/web/factory-router → grounds R13/U17.
- **External end-to-end delivery loop + mission-control framing**: factory.ai homepage
(Signal→Triage→Plan→Execute→Validate→Ship→Monitor) and that product's release notes
(mission control, sessions, knowledge wiki, computer use, missions, subagents).
- The X thread that prompted this work (x.com/factoryai/status/2066588050617249904) was
paywalled (HTTP 402); its subject was reconstructed from the public pages above.
- **Fusion grounding**: `STRATEGY.md` (key metrics), `CONCEPTS.md` (Column/Trait/Workflow
Extension, Effective agent, Task lifecycle), and repo research into `packages/dashboard` +
`packages/core` (data model, view registration, `ApiRouteRegistrar`, existing
`ReliabilityView`/`AgentTokenStatsPanel`/`agent-token-usage.ts`).
- **Institutional learnings**: the six `docs/solutions/` entries cited in Risks.
---
## Deferred / Open Questions
### From 2026-06-15 review
These are genuine forks the review surfaced that depend on your priorities — left open rather than
decided here. (The factual/feasibility/security findings from the same review were applied inline.)
- **Plan scope — ship A–B alone, or bundle Phase C?** Three reviewers flagged that Phases A–B
(the Command Center) have a clean, strategy-aligned premise, while Phase C's stages were derived
from a competitor's feature set rather than observed Fusion user pain, and bundling them means
approving the dashboard implicitly blesses the broader SDLC-platform direction. Options: (a) ship
A–B as the plan of record and split Phase C into its own strategy-grounded brainstorm; (b) keep
one plan but state explicitly that approving it is not approving Phase C's direction; (c) proceed
as one plan (current state, per your "build all gaps" instruction). *No change made — your call.*
- **Positioning: neutral orchestrator vs opinionated delivery system.** An opinionated
Signal→…→Monitor pipeline (Monitor stage, MTTR, role/lifecycle features) pulls against
`STRATEGY.md`'s "neutral by design, plugin ecosystem" thesis. Should the Monitor/Signal/Knowledge
stages be core product surface or live in the plugin ecosystem the strategy names as its
extension mechanism?
- **`usage_events` table vs lazy-materialization (KTD3 / U1).** The plan now notes both; the
architecture choice (always-on events table + multi-path instrumentation, vs a cache table
materialized on first query) is unresolved. R1/R2/R5 state no sub-second requirement, which keeps
lazy-materialization on the table.
- **`usage_events` field-carrying mechanism (U1).** `appendAgentLog`/`appendRunLog` have DB handles
but their signatures (and `AgentLogEntry`) carry none of `model/provider/nodeId/category`. The
plan proposes a dedicated `emitUsageEvent` call from the session layer; the alternatives are
widening the log signatures (~20+ call sites) or a per-write DB lookup. **Resolved (2026-06-15):
dedicated `emitUsageEvent(...)` call from the executor/session layer — do not widen the log method
signatures.**
- **OTel export (U10) timing.** CSV (U8) already satisfies R4 for the Command Center's developer
audience; OTLP targets an ops team running a collector. Build now, or defer U10 until a concrete
consumer exists (avoids adding the OTel SDK dependency for a default-disabled feature)?
- **Mission Control placement (D2).** Dedicated tab only (polling stops when inactive), a
persistent live strip across all tabs (SSE always subscribed), or embedded in Overview? Changes
the polling architecture U6 implements.
- **Date-range picker affordance (D3).** Preset labels/windows, calendar vs free-text custom range,
explicit Apply vs update-on-select, and the in-flight refetch state per area.
- **Mobile layout of dense charts (D4).** How charts/tabs reflow on narrow/landscape phones
(collapse to sparklines, hide behind a toggle, horizontal-scroll tab strip) — the mobile
breakpoint includes landscape (`max-height: 480px`).
- **AgentTokenStatsPanel consolidation (D5).** Deprecate it once the Tokens tab ships, keep it as a
linked inline summary, or keep it standalone with explicitly different scope (lifetime vs
windowed) — and document which data source each uses so the numbers don't silently diverge.