Skip to content

feat: live "dispatch deck" — footer status for in-flight subagents #117

Description

@randomm

Problem

When PM dispatches one or more subagents (via dispatch_specialist / dispatch_parallel, or dispatch_lens_review), the user sees nothing in the parent UI while children run. Long children mean minutes of silent spinner — see screenshot context in [this session's design discussion].

We already compute the data we'd need to show: extension/src/progress.ts:17-40 defines RunningState (role, turns, lastToolName, lastText, usage, elapsed) and spawn.ts updates it on every message_end event via ingestEvent(). After the async pivot (#19/#20), the onProgress callback that used to push this into Pi's onUpdate tool-block is no longer wired — the state is computed and discarded.

Pi's extension API exposes the right primitive: ctx.ui.setStatus(id, text) — a persistent footer status entry, addressable by id, theme-aware. Reference: extension/node_modules/@earendil-works/pi-coding-agent/examples/extensions/status-line.ts:15-31.

Design

A single "dispatch deck" status entry, rendered as multi-line text we own end-to-end. One key ("ensemble:deck") — we control the layout, so ordering and overflow are ours.

Per-row format

⏳ developer · 2m14s · bash (#7) — Running test suite in worktree-A
  • Icon: running, done, failed
  • Role label (with optional tag for lens-review children, e.g. code-review[security])
  • Elapsed (e.g. 47s, 2m14s)
  • Last tool name + tool-use count
  • Truncated lastText (≤60 chars, newlines stripped)

Ordering: insertion order, stable

First-dispatched-first-displayed. Once a row exists, it stays at that index until removed. Backed by Map<jobId, RunningState> iterated in insertion order (JS Maps preserve this natively).

Linger: 0s on completion

On child completion (success OR failure), the row drops immediately. Persistent record lives in lifecycle scrollback entries (separate issue) and in PM's reaction text.

Overflow: 4 rows + summary

⏳ ensemble dispatch · 5 running · 1 done · 1m04s elapsed
 ↳ developer · 1m04s · bash
 ↳ explore   · 1m04s · grep
 ↳ ops       · 47s   · gh
 ↳ adversarial · 23s · diff
 (+1 more — use dispatch_status for full list)

Picks the 4 most-recently-active.

Sources

All three feed the same deck via existing onProgress(state) emissions:

  • dispatch_specialist (single async) — extension/src/spawn.ts:328,398
  • dispatch_parallel (multi async) — same onProgress path
  • dispatch_lens_review (6 sync lens children) — extension/src/lens-review.ts:247

Opt-out

PI_ENSEMBLE_QUIET_STATUS=1 disables the deck entirely.

Acceptance criteria

  • New module extension/src/dispatch-deck.ts (~120 LoC); exports update(jobId, state), clear(jobId); captures ExtensionContext via session_start listener
  • spawn.ts and lens-review.ts both feed the deck via their existing onProgress callbacks (no new event plumbing — just route to the new module)
  • Row ordering is stable across updates (insertion order)
  • Multi-line render fits Pi's footer (no Pi-side wrapping; we truncate per row)
  • Overflow at >4 children renders summary header + 4 detail rows + "+N more"
  • Row drops at 0s on child completion (no linger)
  • PI_ENSEMBLE_QUIET_STATUS=1 opt-out honoured
  • Offline smoke test (smoke-tests/test-dispatch-deck.ts) covers: render single child, render parallel batch, ordering stability under interleaved updates, overflow rendering, opt-out env var
  • Visually verified via live smoke (extend test-progress-live.ts or new test-dispatch-deck-live.ts) — footer renders during real parallel dispatch
  • All existing offline smoke tests still pass
  • tsc + biome clean

Out of scope (other issues)

  • Lifecycle scrollback entries (separate issue) — durable record of dispatch/complete/fail events
  • dispatch_peek tool (research: PM peek-inside-running-subagent (no transcript bloat) #21) — PM-callable query, builds on the same state map
  • Drop inline onUpdate from dispatch_lens_review (separate issue) — once the deck is the single live surface

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions