@d3ara1n/pi-subagent 0.10.4 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +62 -13
- package/package.json +1 -1
- package/src/history.ts +1 -0
- package/src/index.ts +385 -340
- package/src/output.ts +10 -11
- package/src/render-async.ts +324 -0
- package/src/render.ts +49 -130
- package/src/roles.ts +8 -8
- package/src/run.test.ts +211 -0
- package/src/run.ts +352 -0
- package/src/spawn.ts +26 -26
- package/src/types.ts +55 -7
- package/src/utils.test.ts +321 -49
- package/src/utils.ts +336 -12
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Role-based subagent orchestration for [pi](https://github.com/earendil-works/pi).
|
|
4
4
|
|
|
5
|
-
Provides a `
|
|
5
|
+
Provides a `subagent_delegate` tool that lets the main model offload tasks to specialized pi child processes with configurable model roles, real-time TUI progress, and AI-generated summaries. Runs can be foreground (blocking) or background (asynchronous, collected later via `subagent_wait`/`subagent_check`).
|
|
6
6
|
|
|
7
7
|
## Design Philosophy
|
|
8
8
|
|
|
@@ -12,16 +12,16 @@ Your primary AI has the most complete context — it knows the full conversation
|
|
|
12
12
|
|
|
13
13
|
This means:
|
|
14
14
|
- **Subagents don't plan** — the main model decides what needs to be done and provides a clear task description
|
|
15
|
-
- **Subagents don't orchestrate** —
|
|
15
|
+
- **Subagents don't orchestrate the overall plan** — the main model decides what to do and examines each result to pick the next move; nested delegation (worker → explorer) only offloads self-contained exploration/research inside one task
|
|
16
16
|
- **Subagents don't inherit history** — they don't need the full conversation; just a precise task description
|
|
17
|
-
- **Multiple subagents can run in parallel** — emit multiple `
|
|
17
|
+
- **Multiple subagents can run in parallel** — emit multiple `subagent_delegate` calls in one turn; pi executes them concurrently
|
|
18
18
|
- **Subagents can nest subagents** — a `worker` can delegate exploration to `explorer` without returning to the main model
|
|
19
19
|
|
|
20
20
|
> This design currently focuses on single-task delegation rather than chain pipelines or context-forking — those patterns fit better when subagents act as advisors (planner, oracle) rather than executors.
|
|
21
21
|
|
|
22
22
|
## How it works
|
|
23
23
|
|
|
24
|
-
1. Main model calls the `
|
|
24
|
+
1. Main model calls the `subagent_delegate` tool with a role and task description
|
|
25
25
|
2. The extension resolves the role to a model via pi-model-roles
|
|
26
26
|
3. Spawns an isolated pi child process with the configured model, tools, and system prompt
|
|
27
27
|
4. **Real-time TUI progress** shows tool calls, turns, and elapsed time as the subagent runs
|
|
@@ -39,19 +39,21 @@ This means:
|
|
|
39
39
|
|
|
40
40
|
**Nested delegation**: `worker` and `researcher` can spawn their own subagents. This keeps the main model's context clean — a worker can explore unfamiliar code via an `explorer` subagent without returning intermediate results to the main model.
|
|
41
41
|
|
|
42
|
-
**Parallel execution**: To run multiple subagents concurrently, emit multiple `
|
|
42
|
+
**Parallel execution**: To run multiple subagents concurrently, emit multiple `subagent_delegate` calls in a single turn. Pi's framework executes them in parallel automatically, with each subagent getting its own TUI progress display.
|
|
43
43
|
|
|
44
44
|
## TUI Display
|
|
45
45
|
|
|
46
|
-
- **During execution**:
|
|
47
|
-
- **Collapsed result**:
|
|
48
|
-
- **Expanded result** (Ctrl+O):
|
|
46
|
+
- **During execution**: the task's first line with a ⏳ (or ⏸ queued) indicator, a live stream of thinking blocks and tool calls (latest 5 collapsed, everything expanded), and a usage line (elapsed/budget time, turns, tokens, peak context, cost, model)
|
|
47
|
+
- **Collapsed result**: the task's first line, then `✓` + the AI-generated summary (or the first line of the output), then the usage line — no activity replay
|
|
48
|
+
- **Expanded result** (Ctrl+O): reference files, context size, the full task, the complete activity stream, the final output as rendered Markdown, and usage details
|
|
49
|
+
- **Fallback trace**: when a provider error (429, quota, timeout, ...) kills a run and it is retried on the role's `fallbackRole`, a `⚠ fallback: first attempt <model> failed (<reason>)` line appears in both views — also while the retry is running (see [Fallback observability](#fallback-observability))
|
|
49
50
|
|
|
50
51
|
## Commands
|
|
51
52
|
|
|
52
53
|
| Command | Description |
|
|
53
54
|
|---------|-------------|
|
|
54
55
|
| `/subagent:doctor` | Diagnose pi invocation, model-role resolution, configuration, and role references |
|
|
56
|
+
| `/subagent:status` | List background subagent runs and their current state |
|
|
55
57
|
|
|
56
58
|
## Dependencies
|
|
57
59
|
|
|
@@ -99,7 +101,7 @@ Edit `~/.pi/agent/settings.json`:
|
|
|
99
101
|
|
|
100
102
|
All fields are optional. Defaults: `maxConcurrency: 4`, `maxDepth: 3`, `maxTurns: 0` (unlimited), `maxCost: 0` (unlimited), `history.enabled: true`, `summary.role: "utility"`, `summary.enabled: true`.
|
|
101
103
|
|
|
102
|
-
Timeouts are defined per role. Built-in defaults are `explorer: 900`, `reviewer: 3600`, `worker: 2400`, and `researcher: 2400` seconds. The timeout is active time — the clock pauses while the child is inside a nested `
|
|
104
|
+
Timeouts are defined per role. Built-in defaults are `explorer: 900`, `reviewer: 3600`, `worker: 2400`, and `researcher: 2400` seconds. The timeout is active time — the clock pauses while the child is inside a nested `subagent_delegate` call, so delegate-capable roles need no extra headroom.
|
|
103
105
|
|
|
104
106
|
All numeric limits accept `0` for unlimited: `maxConcurrency`, `maxDepth`, `maxTurns`, `maxCost`, and per-role `timeout`. Negative values are normalized to `0`; non-numeric or non-finite values fall back to their defaults. `maxConcurrency: 0` runs delegates without queuing, and `maxDepth: 0` permits unrestricted nesting.
|
|
105
107
|
|
|
@@ -138,7 +140,7 @@ Override, disable, or add subagent roles via `agentOverrides`. Built-in and cust
|
|
|
138
140
|
|
|
139
141
|
**Required fields for custom roles:** `role`, `description`, `examples`, `decisionTrigger`, `tools`, `systemPrompt`.
|
|
140
142
|
|
|
141
|
-
**Optional fields:** `subagentRoles` (roles this role can spawn via delegate), `timeout` (per-role active-time timeout in seconds; unset or `0` is unlimited, negative values normalize to `0`), `maxTurns` / `maxCost` (per-role budget overrides; unset uses the top-level `maxTurns` / `maxCost` setting, `0` is unlimited, negative values normalize to `0`), `fallbackRole` (backup pi-model-roles role on provider
|
|
143
|
+
**Optional fields:** `subagentRoles` (roles this role can spawn via delegate), `timeout` (per-role active-time timeout in seconds; unset or `0` is unlimited, negative values normalize to `0`), `maxTurns` / `maxCost` (per-role budget overrides; unset uses the top-level `maxTurns` / `maxCost` setting, `0` is unlimited, negative values normalize to `0`), `fallbackRole` (backup pi-model-roles role the whole run is retried on after a provider error; unset means no retry — see [Fallback observability](#fallback-observability)).
|
|
142
144
|
|
|
143
145
|
Invalid custom roles (missing required fields) are silently skipped with an error notification at session start.
|
|
144
146
|
|
|
@@ -162,7 +164,7 @@ Delegate tasks that would generate many tool calls or verbose output to keep you
|
|
|
162
164
|
| `worker` | `"Rename all snake_case fields to camelCase in src/models/"` | Your context stays focused on high-level intent |
|
|
163
165
|
| `researcher` | `"Find the React 19 migration guide and summarize breaking changes"` | Search results are noisy; get a clean summary |
|
|
164
166
|
|
|
165
|
-
**Parallel usage:** emit multiple `
|
|
167
|
+
**Parallel usage:** emit multiple `subagent_delegate` calls in a single turn:
|
|
166
168
|
|
|
167
169
|
```json
|
|
168
170
|
[
|
|
@@ -171,6 +173,49 @@ Delegate tasks that would generate many tool calls or verbose output to keep you
|
|
|
171
173
|
]
|
|
172
174
|
```
|
|
173
175
|
|
|
176
|
+
## Background Delegation
|
|
177
|
+
|
|
178
|
+
Foreground and background delegation share one async run engine — foreground is simply background-but-blocking. With `background: true`, `subagent_delegate` returns immediately with a run id, and two companion tools collect the outcome:
|
|
179
|
+
|
|
180
|
+
| Tool | Purpose | Returns to the model |
|
|
181
|
+
|------|---------|---------------------|
|
|
182
|
+
| `subagent_delegate(background: true)` | Start an async run | Just the id (`sub-N`) |
|
|
183
|
+
| `subagent_wait(ids?, timeout_ms?)` | Block until **all** listed runs finish (omit `ids` for all current background runs) | Statuses only, one `id (role): finished/failed` line per run — never results; errors when the timeout hits with runs unfinished |
|
|
184
|
+
| `subagent_check(id)` | One-shot snapshot of a single run | `queued` / `running` + current activity / the **full output** once finished / failure reason + partial output |
|
|
185
|
+
|
|
186
|
+
Typical flow:
|
|
187
|
+
|
|
188
|
+
```json
|
|
189
|
+
[
|
|
190
|
+
{ "role": "worker", "task": "Implement the export module", "background": true },
|
|
191
|
+
{ "role": "researcher", "task": "Find the CSV escaping spec", "background": true }
|
|
192
|
+
]
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
…continue other work, then:
|
|
196
|
+
|
|
197
|
+
```json
|
|
198
|
+
{ "ids": ["sub-1", "sub-2"] }
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
(call `subagent_wait`), and finally `subagent_check` each finished id to fetch its result. `subagent_check` accepts one id per call because results can be large.
|
|
202
|
+
|
|
203
|
+
Semantics worth knowing:
|
|
204
|
+
|
|
205
|
+
- **Background runs survive turn cancellation** and are unaffected by a cancelled `subagent_wait` — cancelling the wait never cancels the runs; call `subagent_wait` or `subagent_check` again later.
|
|
206
|
+
- **`timeout_ms` is optional.** Without it, `subagent_wait` blocks until every run finishes; each run is still bounded by its own role timeout.
|
|
207
|
+
- Background runs share the global `maxConcurrency` gate — extra runs show up as `queued` in wait/check views.
|
|
208
|
+
- **Top-level only:** nested subagents cannot delegate in the background (a subagent process exits when its task finishes, which would orphan the run).
|
|
209
|
+
- The run registry lives in the pi process: a `/reload` or restart orphans in-flight background runs (their ids stop resolving). `/subagent:status` lists every registered run and its current state.
|
|
210
|
+
|
|
211
|
+
### Background TUI display
|
|
212
|
+
|
|
213
|
+
Each tool row renders one aspect of the same decomposition the foreground row shows all at once (input · process · result · usage):
|
|
214
|
+
|
|
215
|
+
- **Background subagent_delegate row = input only.** Collapsed: `▶ sub-1 <task first line>`. Expanded: plus `@file` references, context size, and the full task text. Static — the run progresses invisibly until a subagent_wait/subagent_check row picks it up.
|
|
216
|
+
- **subagent_wait row = process + usage.** One block per watched run: status line (`⏸ queued / ⏳ running` + id + task preview; bare, icon-free once terminal), a live activity stream (collapsed keeps the latest 5 items with a leading ellipsis; expanded shows everything) and a ticking usage bar. Once a run finishes, its process stream is replaced by a **status-only** result line (`✓ finished` / `⏲ budget-exceeded with the reason` / `✗ <reason>`) — the output itself never appears in a subagent_wait row; expanded keeps the full process stream instead. A timed-out wait freezes the view.
|
|
217
|
+
- **subagent_check row = the result view.** Same block shape as subagent_wait's single-run view (no id — there is only one), but the result line shows `✓ <AI summary>` (or the budget/failure reason when the run stopped early) and the expanded view renders the **full output** — subagent_check is where the conclusion lives.
|
|
218
|
+
|
|
174
219
|
### Passing context and reference files
|
|
175
220
|
|
|
176
221
|
pi-subagent delivers context to the child as **independent channels**, never fused into the task string. This keeps the task an unambiguous directive and lets each channel be sized independently.
|
|
@@ -203,15 +248,19 @@ Each path is injected as an independent `@file` attachment the subagent reads di
|
|
|
203
248
|
|
|
204
249
|
### Budget enforcement
|
|
205
250
|
|
|
206
|
-
`maxTurns` / `maxCost` cap a run. When exceeded, the child is killed and the last completed output is returned with `stopReason: "budget_exceeded"` (
|
|
251
|
+
`maxTurns` / `maxCost` cap a run. When exceeded, the child is killed and the last completed output is returned with `stopReason: "budget_exceeded"`. Budget stops are **intentional finishes** — the output is partial but valid: the TUI marks the run with a ⏲ line stating the reason, `subagent_wait` reports `finished (budget exceeded — output is partial)`, and the tool result (and `subagent_check`) append a `--- Budget exceeded (...) ---` note so the model knows to treat the output as partial. Defaults are unlimited (`0`); set global defaults in config or per-role overrides in `agentOverrides`. Negative values are normalized to `0`.
|
|
207
252
|
|
|
208
253
|
### Oversized outputs
|
|
209
254
|
|
|
210
255
|
When a run's output exceeds the size limit (50,000 chars), pi-subagent first tries to **compress** it with the summary model (same role configured under `summary.role`) into a compact form that preserves conclusions, code, file paths, and errors. If compression fails or doesn't shrink enough, it falls back to mechanical head+tail truncation. The prepared text is what the main model receives and what the expanded TUI renders; a hint line notes which method was used. The **full raw output is always kept in the history file** for auditing.
|
|
211
256
|
|
|
257
|
+
### Fallback observability
|
|
258
|
+
|
|
259
|
+
When a provider error (429, quota, timeout, ...) kills a run and the whole task is retried on the role's `fallbackRole`, the retry no longer hides the failure. The first attempt's model, stop reason, error message, and a stderr tail are snapshotted into `fallbackFrom` and surfaced everywhere: a `⚠ fallback:` line in the TUI (collapsed and expanded, including while the retry runs), a `--- fallback: ... ---` note in the tool result the main model reads (on success and failure alike — foreground delegate results and `subagent_check` snapshots), and a `fallbackFrom` field in the history file. When the child dies before its first message (e.g. an instant 429), the reason is recovered from stderr and the model name from what the parent requested.
|
|
260
|
+
|
|
212
261
|
### Run history
|
|
213
262
|
|
|
214
|
-
Every completed delegate run is written (best-effort) to `~/.pi/subagent/history/{sessionId}/{toolCallId}.json`, recording role, task, usage, activity log,
|
|
263
|
+
Every completed delegate run is written (best-effort) to `~/.pi/subagent/history/{sessionId}/{toolCallId}.json`, recording role, task, usage, activity log, the **full raw output** (even when the main model saw a compressed/truncated version), and the `fallbackFrom` snapshot when the run was retried on the fallback role. Useful for auditing what subagents did and how much they cost. Disable with `history.enabled: false`.
|
|
215
264
|
|
|
216
265
|
## License
|
|
217
266
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@d3ara1n/pi-subagent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Role-based subagent orchestration for pi — delegates tasks to specialized pi child processes with configurable model roles",
|
|
6
6
|
"main": "src/index.ts",
|