Files
pi-subagents/docs/superpowers/specs/2026-04-12-subagent-live-progress-design.md

5.6 KiB
Raw Blame History

Subagent Live Progress Rendering Design

Date: 2026-04-12
Package: pi-subagents

Goal

Make a running subagent show what the child agent is actually doing instead of collapsing every live update to raw event names like tool_call and tool_result.

Current state

Today src/tool.ts handles every child event with the same parent-facing update:

  • Running subagent: ${event.type}

That loses the useful semantics already present in normalized events:

  • assistant turns may already contain human-readable status text
  • tool calls carry actionable args like file paths and commands
  • tool results can tell whether a step finished or failed

src/wrapper/render.mjs already renders transcript lines, but its tool fallback is still JSON-shaped and the parent live UI does not reuse it. As a result, the live progress display is much less informative than the underlying event stream.

Chosen approach

Use a shared progress formatter.

Behavior order:

  1. prefer child assistant_text when it contains non-empty text
  2. otherwise render humanized tool activity from normalized tool events
  3. use concise completion/failure wording for tool results based on remembered tool context

Use the same formatter for:

  • parent live progress updates in src/tool.ts
  • transcript event rendering in src/wrapper/render.mjs

Keep the wrapper event protocol, runner behavior, artifacts, and result.json semantics unchanged.

Scope

Modify

  • src/tool.ts
  • src/wrapper/render.mjs
  • new shared formatter module for normalized progress events
  • src/tool.test.ts
  • src/wrapper/render.test.ts

Do not modify

  • index.ts
  • src/process-runner.ts
  • src/tmux-runner.ts
  • src/wrapper/cli.mjs event/result lifecycle
  • src/wrapper/normalize.mjs
  • artifact layout or result.json fields

Design

1. Progress priority

When a normalized event is assistant_text and text is non-empty:

  • show that text to the parent as the live status
  • render that same text in the transcript as today
  • do not replace it with a generic tool label

This matches the product goal best because the child assistant can express intent directly, for example:

  • Im inspecting the auth flow now.
  • I found the schema; next Im checking validation.

Blank assistant text should not generate a noisy no-op status line.

2. Tool activity fallback

When there is no usable assistant text, render normalized tool events into action text.

Examples of desired fallback wording:

  • read -> Reading src/auth.ts
  • grep -> Searching code for auth
  • find -> Scanning for *.test.ts
  • ls -> Listing src/
  • edit -> Editing src/tool.ts
  • write -> Writing README.md
  • bash -> keep the shortened shell command, because the command already states the action
  • unknown tool -> Running <toolName>

The formatter should use available arguments conservatively:

  • prefer exact paths, patterns, and commands already present in the event
  • do not invent missing detail
  • if args are absent, fall back to a generic tool name message instead of guessing

3. Tool result wording

tool_result should not surface as bare tool_result.

Instead, it should render a concise completion/failure message tied to the most recent relevant tool context when available, for example:

  • Finished reading src/auth.ts
  • Search finished
  • Edit failed: src/tool.ts

If no matching prior tool context is available, use a conservative generic result line such as:

  • read finished
  • grep failed

This keeps progress understandable without requiring protocol changes.

4. Shared rendering boundary

The new shared formatter module owns conversion from normalized child events to user-facing progress text.

Responsibilities:

  • map known tools to humanized action strings
  • preserve assistant text when present
  • derive concise result wording from recent tool context
  • keep unknown tools conservative

src/tool.ts should use this formatter for parent onUpdate messages.

src/wrapper/render.mjs should use the same formatter for transcript event lines so live UI and transcript do not drift.

The existing transcript header stays unchanged and generic.

5. Failure and noise handling

Keep behavior minimal and predictable:

  • blank assistant text -> emit nothing
  • unknown tool -> generic Running <toolName> / <toolName> finished / <toolName> failed
  • no tool context for a result -> generic result wording
  • artifact/result writing behavior stays unchanged
  • no new event types, no wrapper protocol expansion

Testing strategy

Follow TDD.

First failing tests

  1. src/tool.test.ts

    • assistant text is surfaced directly when present
    • fallback tool updates are humanized
    • parent progress no longer shows raw tool_call / tool_result
  2. src/wrapper/render.test.ts

    • known tools render as action text instead of JSON-shaped fallback lines
    • result lines use completion/failure wording
    • bash still renders as shortened command text

Verification after implementation

Run at least:

  • targeted test files for touched units
  • full package suite: npm test

Non-goals

  • changing child wrapper completion behavior
  • changing normalized event schema
  • adding runner-specific progress logic
  • redesigning artifact files or transcript headers
  • inventing semantic summaries beyond the data already present in events

Expected outcome

After the change, a running subagent will show meaningful status like assistant intent text or readable tool activity instead of raw event-type labels. The parent live UI and saved transcript will use the same progress language while keeping runner behavior, wrapper semantics, and result artifacts stable.