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

176 lines
5.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.