176 lines
5.6 KiB
Markdown
176 lines
5.6 KiB
Markdown
# 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:
|
||
|
||
- `I’m inspecting the auth flow now.`
|
||
- `I found the schema; next I’m 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.
|