docs(specs): add live progress design

This commit is contained in:
pi
2026-04-12 10:01:08 +01:00
parent a49d102f33
commit a4e627084d
2 changed files with 177 additions and 2 deletions

View File

@@ -0,0 +1,175 @@
# 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.