Native Subagent Observability

Final spec, v3, decisions locked · 2026‑08‑01. Ship Claude Code subagent/workflow and Codex collab-agent observability on the current orchestrator using only native provider emissions. Zero migrations, zero new server state. Designed to be deleted when orchestration‑v2 lands. Supersedes both prior drafts (this URL v1–v2 and the sol synthesis), the closed #4220 / #3650, and carries the open #4779 / #4662 / #4663 / #4664 stack's best ideas forward without depending on #2829.

Decisions locked by Theo, 2026‑08‑01: transport is the client-side fold (with snapshot-aging instrumentation as the guard) · the timeline gets the rich inline workflow card (8 member rows) · if the Codex probe surfaces child threads in the sidebar, v1 filters them client-side while keeping them openable · per-agent Stop stays deferred to the final capability-gated slice.

1Outcome

Orchestrator v2 (#2829) is months from merge: 810 files, 18 conflicting (seven modify/delete against v1 files main keeps evolving), zero human approvals, a failing conventions gate, the correctness bot skipping ("241 blocking issues, diff too large"), and testers still filing regressions. Meanwhile every audit converged on one fact: the observability data already flows, and we drop it. Claude's task telemetry is parsed, persisted, and streamed today, then anonymized client-side; Codex's native collab-agent thread tree is deliberately suppressed. The gap is normalization and UI, not pipeline.

A user should answer five questions at a glance, without reading raw tool rows:

  1. Who exists? Names, roles, models, direct spawns, workflow members, reusable Codex identities.
  2. What is happening? Current tool or progress summary, workflow phase, elapsed time, recent activity.
  3. What needs me? Waiting, approval, failure, interruption, and stale-transport states are unmistakable.
  4. What did it cost? Provider-correct token totals and breakdowns, no pretend dollar figures.
  5. Where do I inspect? Panel is the fleet view, timeline gives context, transcripts arrive on demand later.

2Drop points on main

The plumbing is ~70% built, the UI 0%. Claude's task_started/task_progress/task_notification already become persisted, streamed task.* activities with taskId, titles, lastToolName, and usage (ClaudeAdapter.ts:2678‑2736ProviderRuntimeIngestion.ts:489‑572). Where the rest dies, highest leverage first:

WhereWhat dies
ClaudeAdapter.ts:2681subagent_type, workflow_name, tool_use_id, prompt stripped from task.started. No agent identity survives downstream.
ClaudeAdapter.ts:2716task_updated: explicit return. All status transitions (killed, paused, is_backgrounded, end_time, error) lost.
ClaudeAdapter.ts:2591background_tasks_changed roster: explicit return, deferring to a typed control request nothing ever issues.
ClaudeAdapter.ts:844subagent_type string-concatenated into the tool label, destroying the structured field.
ProviderRuntimeIngestion.ts:682tool.progress/tool.summary hit default: break: free per-tool elapsed seconds and parent_tool_use_id discarded before persistence.
ClaudeAdapter.ts:2082parent_tool_use_id is consulted only to skip subagent token deltas. It is ignored on content-block/item events, so subagent tool calls emit as top-level timeline items indistinguishable from the parent's: the direct cause of many agents spitting interleaved results into one chat.
CodexSessionRuntime.ts:625‑641Child-thread suppression drops 13 notification types, including per-child thread/tokenUsage/updated (the richest per-subagent usage anywhere); survivors re-tagged onto the parent turn. Keys off v1 collabAgentToolCall.receiverThreadIds, which are empty under multi‑agent‑v2, so v2 behavior is unverified (§6 probe).
CodexAdapter.ts:664thread/started keeps only the thread id, discarding parentThreadId, agentNickname, agentRole, source.subAgent.threadSpawn.{depth, agentPath}.
session-logic.ts:634Web drops task.started and never reads taskId into WorkLogEntry: no grouping key, concurrent agents interleave anonymously.
ActivityPayloadProjection.ts:158‑202Wire slimming keeps an allowlist of payload fields. Every widened field must be allowlisted and covered by a regression test proving it survives to the client (do both: the audit says fields are stripped; the test makes it impossible to regress).

SDK options staying off: forwardSubagentText (nested live text multiplies stream volume; drill-in reads provider storage on demand) and agentProgressSummaries (recurring real model calls; opt-in later, lastToolName is the fallback activity line).

3Principles

  1. Zero migrations, not zero storage. Agent lifecycle data is durably stored: widened task.* rows persist through the existing event log + activities projection, replay on restart, and stream to every client. What we refuse is new schema (any table or column takes migration slot 036, which #2829 claims, with a documented silent-skip hazard) and a second roster store v2 would have to reconcile. Additive fields inside activity payloads and runtime contracts are the sanctioned mechanism: minimal, migration-free, and living in files #2829 deletes wholesale. The activities table's existing kind column already makes task.* rows SQL-filterable; payload fields handle finer filtering client-side.
  2. Provider truth, normalized once. Claude and Codex disagree about identity, completion, usage, and resumption. Preserve those semantics inside the adapters; emit one small shared lifecycle vocabulary.
  3. The UI model sits above orchestration. Components consume AgentPanelModel only, derived through one source-precedence function. Legacy activities and future v2 projections each get a mapper; components never know which fed them.
  4. No lying UI. Unsupported providers show no agent capability. Unsupported controls are absent, not disabled. Capability comes from data (canStop, probe-verified operations), never a hand-maintained support matrix. A child-scoped control must never fall back to interrupting the parent turn.
  5. Distilled, not mirrored. Lifecycle rollups in the live stream; transcripts, scripts, and journals via on-demand RPCs from provider-owned storage. No transcript bodies in activities.
  6. Never block ingestion. Observability failures log and continue; interrupts still propagate.
  7. Perf rules stand. Status dots are static (no pulse, shimmer, spinner loops). Elapsed timers use the WorkingTimer DOM-write pattern, coarse cadence, frozen while disconnected. Token counts are plain counters, never bars or rings: burn has no denominator.
  8. Remote is first-class. Live state crosses the existing typed activity subscription; hosted web, relay, and tunnel get the identical experience. No UI reads provider files directly.
  9. Designed to be deleted. The fold is isolated legacy compatibility (client-runtime module + adapter normalizers), named as such, with a documented deletion trigger: v2 projection presence per thread, full deletion at v1 retirement.

4Architecture: fold the stream once

Claude SDK                     Codex app-server
task_* + workflow_progress        subAgentActivity + child thread stream
      │                               │
      ▼                               ▼
  ClaudeAdapter                   CodexSessionRuntime/Adapter
  carry linkage, handle           intercept child notifications (probe-verified),
  task_updated + tool_progress    synthesize task.* with timelineBypass
      │                               │
      ▼────────────────────────────▼
      self-describing task.* runtime events
      (existing family + task.updated; identity repeated on every row)
                    │
                    ▼
      EXISTING activity path: persisted, capped at 500/thread, streamed
      over thread-detail sync (web · desktop · hosted · mobile · remote)
      — no new table, row, reducer, or event-log write —
                    │
                    ▼
      packages/client-runtime: tolerant, memoized fold
      → exact v2-shaped RuntimeSubagent state
      → deriveAgentPanelModel({ nativeActivities, v2Projection })
         v2 wins when present; sources never merged
      → panel · strip · workflow card · timeline rows · mobile sheet

Why the fold won (decision record): the database already retains and streams task activities, so making rows self-describing gives reconnect, restart, and revert behavior for free; a server snapshot would re-create a small projection system (reducer, hydration, session-exit settlement, revert invalidation) exactly where we are avoiding one before v2, and every material change would append a full-roster payload to the append-only event log forever, in a codebase with replay-cost and OOM history. The snapshot's one real advantage, durability past the 500-row retention window, is mitigated by repeating identity on every row (live agents always reconstructible) and guarded by instrumentation (§7); if real threads measurably lose live rosters, the snapshot row is the documented escalation path, not the starting point.

5Contracts and invariants

Additive contract widening (packages/contracts/src/providerRuntime.ts)

type RuntimeTaskUsage = {
  totalTokens: number
  inputTokens?; cachedInputTokens?; outputTokens?; reasoningOutputTokens?
  toolUses?: number; durationMs?: number
}

// spread into task.started / task.progress / task.updated / task.completed
type TaskAgentLinkage = {
  title?; role?; model?; toolUseId?; parentAgentId?
  workflowName?; agentIndex?; phaseIndex?; phaseTitle?
  phases?: ReadonlyArray<{ index: number; title: string }>
  attempt?: number
  runHandles?: { runId?; scriptPath?; transcriptDir?; sessionUrl? }
  outputFile?: string
  agentPath?: string                 // Codex hierarchy (/root/marlow)
  timelineBypass?: boolean           // Codex-synthesized events skip timeline mapping
}

// Tool lifecycle payloads (tool.started/updated/completed/progress) gain
// optional attribution so clients can re-home subagent tool rows:
type ToolAgentAttribution = {
  agentId?: string                   // owning task/agent id when the tool ran inside a subagent
  parentToolUseId?: string           // the SDK's parent linkage, carried instead of dropped
}

// NEW event type task.updated (non-terminal patch):
{ taskId, status?, error?, endedAt?, isBackgrounded? } & TaskAgentLinkage

Identity and workflow linkage repeat on progress and terminal rows, not just start, so the fold recovers even when the start row aged out of the 500-activity window. Typed usage replaces Schema.Unknown. This is a provider-runtime contract (server-internal; activities reach clients as open-kind unknown payloads), not a new orchestration domain.

Runtime state (packages/client-runtime, derived values, not records)

type RuntimeSubagentStatus =
  | "pending" | "running" | "waiting" | "idle"
  | "completed" | "failed" | "cancelled" | "interrupted"

interface RuntimeSubagent {
  id: string                         // Claude task_id · Codex child thread id
  threadId: string
  driver: "claude" | "codex"
  parentAgentId: string | null
  kind: "subagent" | "workflow" | "workflow_agent"
  title: string
  role: { name: string; source: "provider" | "app_default" }
  model: string | null
  status: RuntimeSubagentStatus
  activationCount: number
  usage: RuntimeTaskUsage | null
  progress: string | null
  lastToolName: string | null
  approvalRequestId: string | null   // waiting-row deep-link (PR 5)
  result: string | null
  error: string | null
  canStop: boolean                   // verified provider capability (PR 5)
  workflow: { name?; phases; handles? } | null
  workflowMembership: { workflowId; agentIndex; phaseIndex: number | null; attempt } | null
  recentActivity: readonly { at: string; summary: string }[]   // ring, max 6
  startedAt: string; completedAt: string | null; updatedAt: string
}

interface RuntimeSubagentActivation {
  id; threadId; subagentId; ordinal
  status: Exclude<RuntimeSubagentStatus, "idle">   // deliberate asymmetry
  usage; startedAt; completedAt; updatedAt
}

Field names and transition semantics are #4779's, verbatim, so the v2 swap is aliases and a mapper, not remapping.

Invariants (each traces to a shipped bug or incident in the prior PRs)

6Provider mapping

Claude ships first PR 1

SDK signalEffect
task_startedUpsert identity: title=description, kind from task_type (local_workflow → workflow), role from subagent_type, workflow name, running, new activation. Stop flattening the structured fields into the label.
task_progressAccumulate activation usage, update progress/tool/recent activity. workflow_progress[] (undeclared but wire-confirmed) parsed through one adapter-local defensive normalizer: ordered phases to the coordinator, member upserts by stable slot, dedupe by index before caps, unknown states read running after startedAt and pending before. If the field vanishes upstream, the coordinator row and plain lifecycle survive. File upstream for typing.
task_updatedStatus/background/error/end-time patches (killed → cancelled, paused → idle). Currently dropped on the floor.
task_notificationTerminal state, result summary, final usage, outputFile handle. Bounded, duplicate-safe.
tool_progress"What it's doing" heartbeat + recent activity (carries task_id, elapsed seconds). Currently dropped in ingestion.
Workflow tool resultrunHandles (runId, scriptPath, transcriptDir, sanitized sessionUrl) onto the coordinator.

Codex is gated on a current wire probe PREP PR 3

Codex subagents are full child threads (auto-subscribed; parentThreadId, agentNickname, agentRole, source.subAgent.threadSpawn.{depth, agentPath}). The adapter synthesizes the same task.* activities with timelineBypass, so Codex requires zero client changes once Claude and the fold ship.

Observed child signalCandidate effect
child thread/started (subAgent source)task.started: id=thread id, title=nickname (final agentPath segment), role=agentRole else general-purpose, parent + depth + path.
subAgentActivitystarted → running · interacted → running + activation++ · interrupted → interrupted.
child turn/started / turn/completedrunning / idle (resumable, not terminal; result cleared on re-activation).
child thread/status/changedapproval/input flags → waiting; systemError → failed (was once silently dropped: agents stuck running forever).
child thread/tokenUsage/updatedCumulative usage, field-wise max-merge, full breakdown.
child item/started|completedProgress + recent activity for material tool/status changes only.
child thread/settings/updatedModel (absent from v2 parent items).
Probe before routing code. The existing suppression keys off v1 receiver ids that are empty under multi‑agent‑v2; today's v2 child-chatter behavior is unverified, and the older protocol types may be stale against the packaged binary. Capture through the app-server wrapper: one direct spawn, one nested spawn, one follow-up activation, one approval, one interruption, token updates, child-first ordering, and parent-timeline behavior for both v1 and current multi-agent models. Known constraints regardless: a child's first event can precede parent-side registration (tolerate unknown ids: a notification keyed by neither the parent thread nor a known v1 receiver is provisionally a v2 child; re-check v1 registration every notification); wrap child notifications as one synthetic adapter event and return before parent-timeline mapping; never create T3 threads or change run ownership for native children. Exit: the routing table is backed by a checked fixture or protocol test.
Sidebar contingency (decided): if the probe shows un-suppressed child threads surfacing as sidebar threads, v1 filters them client-side using #4664's three-line predicate, without its server machinery: hidden from the thread list, still openable via links, transcripts stay reachable. No server filtering, no deep-link redirects, no product-toggle debate in this project.

OpenCode participates automatically if its adapter ever emits the normalized lifecycle. ACP (Cursor/Grok): unsupported until a trustworthy native signal exists; never infer agents from tool names or transcript text. No app-owned subagents: this visualizes work the providers created.

7Recovery, bounds, and performance

Fold placement and cost

Recovery semantics

8The UX: context plus depth

Direction locked: awareness in context, depth on demand. Three coordinated surfaces over one model. Inbox-zero rules: in-flight work is presence, not alarm; waiting is the only amber emphasis.

Composer live strip PR 2

Visible only while ≥1 agent is pending/running/waiting: total agents, waiting count, active phase, total tokens. Click opens the panel; never auto-opens.

4 agents1 waitingVerify · 2 activeΣ 247k tok  ▾

Inline workflow run card PR 2 (decided: rich card)

One card per coordinator in the timeline, replacing its generic task and duplicate tool rows: phase counters, up to 8 member rows ordered by urgency (running + failed first, then most recent), overflow as "+N more → open Agents". Remote runs swap the body for a sanitized external session link. The card is a derived, capped view of the fold; the panel is the complete one. Perf guardrails: static visuals, dedicated roster context isolated from the virtualized list, and the render-count test as the acceptance gate.

Agents panel PR 2

First-class right-panel surface beside Plan/Diff/Files/Preview/Terminal. Tab auto-appears once the thread has agents (count badge when hidden), always reachable from the "+" menu with an explanatory empty state, plus a command-palette action ("Agents: Open", no default keybinding). Workflow members group by phase, then direct spawns. Rows: name, role, model, status, progress fallback (progress → lastToolName; settled rows lead error → result), elapsed, tokens, activation count. Expanded rows: the six-entry activity ring. Footer: running/waiting/settled counts + Σ tokens.

⎊ Agents · 4Diff · Files · +
Workflow · audit-auth-flow   {} script
✓ Audit
audit:entrypointsopus118.4k tok · 41 tools · 4m 02s
Found 3 candidate races · report → audit/entry.md
Verify · 2 active · 1 done
verify:session-refreshsonnet41.2k tok ↑ · 12 tools · 1m 40s
▸ Reproducing the race in sessionRefresh.test.ts
verify:token-rotationsonnet26.0k tok · blocked 41s
Waiting on approval
Fix · pending
Direct spawns
Marlowexplorercodex61.8k tok · run 2
Idle · resumable · /root/marlow
● 1 running · 1 waiting · 2 settledΣ 247k tok

Mobile PR 4

Read-only first: a static active-agent count in the thread header opens a native sheet (phone) or inspector-pane tenant (tablet) over the exact shared derivation. Same grouping, status, result, usage vocabulary. Waiting and terminal signals survive any space-constrained condensation. No script/transcript viewer, no desktop metaphors, no filesystem affordances. Stop lands later with shared confirmation UX.

Status language

StatusDotLabel
pendingzinc 40%, staticQueued
runningsky, staticRunning
waitingamber, staticWaiting · deep-links to the approval once attributed (PR 5)
idlesky 50%, staticIdle · resumable (nonterminal in state, settled visual)
completedgreenCompleted
failedredFailed + inline error text (errors are the only inline previews)
cancelled / interruptedzinc 60%Stopped

Elapsed measures the current activation, not first spawn, so a resumed agent's timer excludes idle gaps; frozen at completedAt and while disconnected.

9Timeline and work-log behavior: the quiet-timeline guarantee

Invariant: the main timeline carries the parent's narrative plus at most one row per agent or workflow run. Everything an agent does internally lives in the panel and card, not the chat. Today this is violated twice: Claude forwards subagent tool_use/tool_result blocks into the main stream (even with forwardSubagentText off) and the adapter ignores parent_tool_use_id when emitting item.* events, so subagent tool calls render as top-level rows; and task.progress ticks land as individual work-log entries. With many concurrent agents the chat becomes incoherent. The fix has three parts:

  1. Attribute: the adapter stamps ToolAgentAttribution (§5) on every tool lifecycle event whose parent_tool_use_id resolves to a tracked agent, instead of dropping the linkage. Persisted in the payload, so it also filters correctly for filtering-aware clients replaying history, and gives v2 a clean signal to migrate.
  2. Re-home: clients fold agent-attributed tool rows out of the main work log entirely; they surface as the owning agent's progress/lastToolName and recent-activity entries. Old clients keep seeing today's flat rows (no worse than the status quo).
  3. Collapse: task.* progress ticks never render individually; they group into the one lifecycle row (direct agents) or the run card (workflows), exactly as below.
SignalTimelineAgents surface
Subagent tool calls + resultsNone. Re-homed via ToolAgentAttribution; the parent chat shows only the agent's single lifecycle row.Owning row's progress line + recent-activity ring; full detail via PR 5 drill-in.
Direct Claude agentOne compact lifecycle entry: Bot icon, real name (today: hammer + "Subagent task"); progress rows collapse by taskId.Full row: role, progress, usage, result.
Workflow coordinatorThe rich inline run card (§8) instead of generic task + duplicate tool rows.Groups phases and members.
Workflow memberNo independent row while its coordinator exists; orphaned members fall back to the direct list.Under its phase.
Codex child eventsAlways bypass the parent timeline (timelineBypass); they still update fold and panel.Update the child row.
CompletionConcise terminal signal kept on web and mobile (any surface that hides rows keeps its own terminal signal).Status, result, usage, elapsed freeze.

Routine result snippets stay out of the timeline; failure text renders inline because it changes the next user action. Full nested rendering stays out of scope: MessagesTimelineRow is a flat union, and #2829's own V2LifecycleRow is the pattern to converge on at merge time.

10Reuse manifest

SourceTakeDisposition
#4220 @ a40376b2Live strip (70 lines), activity-text priority, panel footer, provider mapping research, status vocabulary.Port and retype to the panel model. Discard the agent.snapshot roster contracts and the 501-line pressure reducer entirely.
#3650 @ 0f16c5e6Inline run card + urgency ordering, two-line row layout (workflowUi.tsx: wrapping label, muted meta line; fixed a real clipping bug), WorkflowInspectionService + 349-line test (realpath containment, symlink re-containment, allowlist, size caps), transcript pager (600-line tail), Shiki script shell, thread.task.stop command path.Card and rows in PR 2. Inspection + stop lift in PR 5 only. Discard task.workflow-updated upsert ids.
#4779Status/role/kind/usage vocabulary, SubagentObservability.ts usage math + bounded ring, activation model.Port pure helpers verbatim; preserve field names. No migration 045, no activation table.
#4662Reusable-identity semantics: completion = idle, cumulative max-merge, activation counting.Encode as fold invariants. No run-ownership or child-adoption machinery.
#4663Pure derivation structure, panel/card components, right-panel wiring.Lift structure onto the source-neutral model; zero v2 imports; strip animate-pulse.
#4664The three-line child-thread predicate.Client-side sidebar filter only, and only if the probe requires it. Hiding-with-unreachable-transcripts stays out.
git f49c1a97Recovered #4220 plan docs (adversarially-reviewed provider audit + UI mocks + feature→field milestones).Reference; this spec supersedes them.

11Delivery: probe + five PRs, first two are the release

SliceScopeExit
PREPCodex wire capture, in parallel with PR 1: direct spawn, nested spawn, follow-up activation, approval, interruption, token updates, child-first ordering, parent-timeline behavior for v1 and current multi-agent models; sidebar-surfacing check for the §6 contingency.Notification/routing table backed by a checked fixture or protocol test.
PR 1Model native subagent activity. Widen task contracts + task.updated + ToolAgentAttribution, fix the ClaudeAdapter drop points (incl. resolving parent_tool_use_id to the owning agent on tool lifecycle events), ingestion cases for task.updated/tool.progress, payload-allowlist entries + wire-survival regression test, tolerant memoized fold + deriveAgentPanelModel in client-runtime, dispatch-rate counter. No visible UI, no migrations.Real Claude subagents and workflows derive correct identities, activations, status, phases, usage, and recovery.
PR 2Web + desktop experience. Agents panel, live strip, rich inline workflow card, compact direct-agent lifecycle rows + work-log dedupe, add-surface and command-palette entries, panel storage bump, empty/reconnect/stale states. Lazy panel; roster renders isolated from the virtualized timeline.Release cut line. A live Claude fleet is understandable without raw transcripts; the quiet-timeline guarantee holds (a 5-agent run adds ≤5 lifecycle rows + 1 card to the chat, zero interleaved tool rows); no repainting animation; render-count test green.
PR 3Codex child agents. Probed child tracker, synthesized lifecycle with timelineBypass, unknown-child ordering coverage, v1 + current-protocol parent-timeline regression tests, sidebar filter if the probe demands it.The existing UI lights up for a Codex fleet with zero client changes and no parent-transcript regression.
PR 4Mobile awareness. Header count + read-only sheet/inspector over shared derivation; terminal and waiting signals preserved under condensation.Local, relay, and tunnel connections show identical fleet state on iOS and Android.
PR 5Inspect and control (decided: deferred here, capability-gated). Approval deep-links via native attribution; on-demand transcript/logs RPCs from verified provider paths (Claude: outputFile/agent-<id>.jsonl via lifted containment; Codex: thread/read if the probe confirms the child thread); script opens in the existing file surface; copy-resume; task-scoped Stop where proven (canStop; optimistic "Stop requested", provider notification terminalizes; Codex Stop only if the probe proves child-scoped interrupt; never fall back to interrupting the parent). Independently removable.Inspect a child without streaming its output into the parent; Stop never hits the wrong scope; waiting click lands on the approval.

12Verification and acceptance

Fold and contract tests (one per known failure mode)

Provider fixtures

Claude: direct lifecycle · local workflow with phases and retries · tool progress + output handle. Codex: child event before registration · follow-up on an idle child · approval and system error · v1 and current parent-transcript routing. Both: session exit and reconnect.

UI acceptance

Integrated proof before each UI PR merges

13The v2 handoff

  1. When the v2 projection exists for a thread, deriveAgentPanelModel selects it and stops folding native activities for that thread. One precedence function; sources never merged (the duplicate-agents failure mode).
  2. Runtime types swap to v2 aliases: field names, statuses, activation model, and usage semantics already match.
  3. The panel, strip, card, mobile sheet, status language, and tests remain untouched: the product layer v2 would otherwise have to invent at merge time, already user-validated. #4663 becomes a diff against a shipped UI.
  4. The fold and the widened v1 contracts are isolated legacy compatibility, deleted with the v1 adapters (#2829 removes those files wholesale). Provider normalizers stay adapter-local so v2 can cherry-pick the mapping logic. Inspection RPCs and verified controls remain useful regardless of orchestration source.
  5. No migration slots consumed, no tables, no projection schema, no closed unions touched: nothing in #2829's DB surface (036–045) conflicts.

14Risks, non-goals, deferred

RiskMitigation
Claude's undeclared workflow_progress changes shapeOne defensive normalizer, optional fields, caps, fixtures, coordinator fallback; file upstream for typing.
Codex child classification catches unrelated trafficProbe before code; preserve the v1 receiver map; re-check classification per notification; registration-order tests.
Live roster ages out of the 500-row window on long threadsIdentity repeated on every row; instrumentation from PR 1; documented escalation to a server snapshot row only if data demands it.
Widened rows get noisyAdapter-side coalescing (material transitions, 5k-token buckets, summary dedupe); dispatch-rate counter.
Legacy + v2 render duplicates after mergeSingle precedence function; never combine arrays.
Inline card hurts timeline perf8-row cap, urgency ordering, static visuals, dedicated context, lazy panel, render-count test as an acceptance gate.
Fold becomes permanentIsolated files, named legacy bridge, deletion trigger documented in §13.

Non-goals: no new orchestration engine, graph, queue, or ownership model; no v1 subagent/activation tables; no ACP inference; no app-owned subagents; no server-side thread hiding; no cost-in-USD or fleet analytics; no history backfill (dropped fields exist only in rotating NDJSON logs).

Deferred: nested live text forwarding (forwardSubagentText); AI progress summaries by default (opt-in later, they cost model calls); mission-control tree + dossier zoom (post-PR 5, layout only, every field already present); #4664-style hiding with unreachable transcripts (revisit only after drill-in ships everywhere).