oira666_pi-subagent 0.4.0 → 0.4.2

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.
Files changed (88) hide show
  1. package/README.md +54 -640
  2. package/agents/code-architect.md +1 -1
  3. package/agents/code-reviwer.md +1 -1
  4. package/agents/code-writer.md +1 -1
  5. package/agents/team-lead.md +1 -1
  6. package/agents.ts +163 -181
  7. package/budget.ts +91 -142
  8. package/config.ts +8 -7
  9. package/create-subagent/SKILL.md +83 -0
  10. package/detail.ts +4 -673
  11. package/docs/configuration.md +117 -0
  12. package/docs/sessions.md +73 -0
  13. package/docs/usage.md +56 -0
  14. package/extension/broadcast.ts +231 -0
  15. package/extension/contracts.ts +15 -0
  16. package/extension/details.ts +17 -0
  17. package/extension/events.ts +103 -0
  18. package/extension/execution.ts +157 -0
  19. package/extension/expand-command.ts +138 -0
  20. package/extension/launch-tool.ts +295 -0
  21. package/extension/models.ts +87 -0
  22. package/extension/policy.ts +287 -0
  23. package/extension/progress.ts +82 -0
  24. package/extension/prompt-hook.ts +73 -0
  25. package/extension/prompts.ts +20 -0
  26. package/extension/provider.ts +171 -0
  27. package/extension/resume-offer.ts +102 -0
  28. package/extension/resume-tool.ts +283 -0
  29. package/extension/runtime.ts +131 -0
  30. package/extension/schemas.ts +115 -0
  31. package/extension/session-start.ts +209 -0
  32. package/extension/state.ts +70 -0
  33. package/extension/tool-registration.ts +26 -0
  34. package/extension/usage.ts +202 -0
  35. package/index.ts +30 -2714
  36. package/names.ts +12 -435
  37. package/overlay.ts +4 -574
  38. package/package.json +29 -14
  39. package/render.ts +188 -285
  40. package/resume.ts +50 -43
  41. package/runner/arguments.ts +263 -0
  42. package/runner/attempt-state.ts +14 -0
  43. package/runner/attempt.ts +343 -0
  44. package/runner/constants.ts +37 -0
  45. package/runner/events.ts +172 -0
  46. package/runner/files.ts +28 -0
  47. package/runner/launch.ts +148 -0
  48. package/runner/options.ts +72 -0
  49. package/runner/parallel.ts +183 -0
  50. package/runner/protocol.ts +131 -0
  51. package/runner/result.ts +47 -0
  52. package/runner/single.ts +204 -0
  53. package/runner.ts +8 -1509
  54. package/shared.ts +38 -38
  55. package/storage/budget-ledger.ts +135 -0
  56. package/storage/budget-state.ts +34 -0
  57. package/storage/name-records.ts +42 -0
  58. package/storage/names-identity.ts +143 -0
  59. package/storage/names-lock.ts +75 -0
  60. package/storage/names-registry.ts +93 -0
  61. package/storage/session-fork.ts +73 -0
  62. package/storage/values.ts +8 -0
  63. package/tree.ts +5 -704
  64. package/types/contracts.ts +165 -0
  65. package/types/details.ts +166 -0
  66. package/types/messages.ts +104 -0
  67. package/types/outcomes.ts +53 -0
  68. package/types/records.ts +8 -0
  69. package/types/transcript.ts +45 -0
  70. package/types/usage.ts +138 -0
  71. package/types.ts +6 -610
  72. package/ui/detail-lines.ts +242 -0
  73. package/ui/detail-model.ts +44 -0
  74. package/ui/detail-selectors.ts +60 -0
  75. package/ui/detail-transcript.ts +237 -0
  76. package/ui/frame.ts +21 -0
  77. package/ui/overlay-input.ts +54 -0
  78. package/ui/pager-view.ts +237 -0
  79. package/ui/pager.ts +208 -0
  80. package/ui/picker.ts +109 -0
  81. package/ui/session.ts +40 -0
  82. package/ui/tree-build.ts +302 -0
  83. package/ui/tree-format.ts +126 -0
  84. package/ui/tree-lines.ts +54 -0
  85. package/ui/tree-live.ts +98 -0
  86. package/ui/tree-model.ts +22 -0
  87. package/ui/value.ts +11 -0
  88. package/shims.d.ts +0 -148
package/README.md CHANGED
@@ -1,703 +1,117 @@
1
- # Pi Subagent
1
+ # Pi subagent
2
2
 
3
- Delegate tasks to specialized subagents.
3
+ Delegate tasks to agents running in separate Pi processes. Each worker has its own context and saved session. The parent receives its final text and usage statistics, not its reasoning or tool transcript.
4
+
5
+ Requires Pi 0.85.1 or newer, using the `@earendil-works` SDK packages.
4
6
 
5
7
  ## Install
6
8
 
7
9
  ```bash
8
10
  pi install npm:oira666_pi-subagent
9
- ```
10
-
11
- Or via git:
12
-
13
- ```bash
11
+ # Or install from Git:
14
12
  pi install git:github.com/gee666/pi-subagent.git
15
13
  ```
16
14
 
17
- ## Remove
15
+ To remove it:
18
16
 
19
17
  ```bash
20
18
  pi remove npm:oira666_pi-subagent
21
19
  ```
22
20
 
23
- ## How It Works
24
-
25
- Each subagent runs as a **separate `pi` process** — fully isolated memory, its own model/tool loop.
26
- Processes are spawned via the operating system and communicate through JSON-line stdout.
27
- Subagent sessions are persisted separately under a `sessions-subagents` directory (a sibling of Pi's normal `sessions` directory), so they can be resumed without mixing into the main session list.
28
-
29
- - Full OS-level isolation — a crashed subagent cannot affect the parent
30
- - True parallel execution across all CPU cores
31
- - Each subprocess boots a fresh Node.js runtime
32
- - Uses the same Pi CLI entrypoint as the parent process when available
33
-
34
- Each subagent receives only the task string. The main agent in turn receives
35
- only the **final text output** from subagents (no tool calls, no reasoning).
36
-
37
- ## Tool Call Shape
38
-
39
- The delegation tool is called `subagents` (older sessions may contain the
40
- legacy name `subagent`, which is still recognized when reading history):
41
-
42
- ```json
43
- { "tasks": [{ "agent": "code-writer", "task": "Implement the API", "max_agents_allowed": 1 }] }
44
- ```
45
-
46
- Multiple tasks run in parallel:
21
+ ## Launch and resume
47
22
 
48
23
  ```json
49
24
  {
50
25
  "tasks": [
51
- { "agent": "code-writer", "task": "Draft the implementation", "max_agents_allowed": 1 },
52
- { "agent": "code-reviwer", "task": "Review the plan", "max_agents_allowed": 1 }
26
+ { "agent": "code-writer", "task": "Implement the API", "max_subagents_allowed": 0 },
27
+ { "agent": "code-reviwer", "task": "Review the design", "max_subagents_allowed": 0 }
53
28
  ]
54
29
  }
55
30
  ```
56
31
 
57
- Each task requires `agent`, `task`, and `max_agents_allowed`. The number includes the assigned agent and every agent below it. Use `1` for a worker that will finish directly.
58
-
59
- ## Delegation policy
60
-
61
- Before launching new agents, the default prompts require a concrete time or context saving that outweighs startup, discovery, and coordination costs.
62
-
63
- - Default to direct work rather than launching new agents. Use a small, flat set of specialists for substantial independent slices.
64
- - Choose the fewest agents the task needs. Plan for the whole task, including nested workers and later phases, and respect any limit the user sets.
65
- - Nested workers share the same budget. Each delegated task gets an explicit allowance.
66
- - Another management layer must save enough attention to pay for itself. Prefer direct workers and count all nested agents in the same budget.
67
- - Delegate before deep discovery, or pass existing findings directly or through a handoff file under the project's `tmp/` directory.
68
-
69
- The efficiency guidance is a model instruction. Agent budgets and depth restrictions are enforced at runtime, but neither limits dollar spending. Use `pi --subagent-max-depth 1` to block nested delegation.
70
-
71
- ## Agent budgets
72
-
73
- A main session starts with a budget of 50 new agents for its entire delegation tree. Set `PI_SUBAGENT_MAX_TOTAL_AGENTS` before starting a new session to change it. `0` blocks new launches but still permits named resumes. Invalid values block launches rather than silently removing the limit.
74
-
75
- The main agent sees its remaining count only when it is below 30. Larger counts stay out of automatic prompts and budget-rejection messages so they do not suggest a target to spend. Enforcement is unchanged. Delegated workers always receive their own remaining allowance.
76
-
77
- Each task reserves exactly `max_agents_allowed` slots from its caller. A value of `10` means ten agents total, including the assigned worker. The worker's automatic prompt says it may launch at most nine more subagents, including nested launches. Two tasks with budgets of `4` and `1` reserve five slots in total.
32
+ Call `subagents` with one or more tasks. Tasks run in parallel, subject to the concurrency limit. Every task requires an agent type, task text, and a descendant allowance. Use `0` for a worker that will not delegate, or `1` to let it launch one subagent. The caller reserves one slot for the worker plus its descendant allowance.
78
33
 
79
- - Choose budgets from the planned work, not the available maximum. Use `1` for a direct worker; `0` is invalid because the assigned worker needs a slot.
80
- - Every nested call must specify allowances too. If a batch would exceed its caller's remaining slots, the extension rejects the whole batch before launching or naming any workers. The error explains the requested and remaining amounts.
81
- - Siblings cannot borrow each other's slots. The extension commits reservations atomically across processes, so concurrent calls cannot spend the same slots.
82
- - Unused allowances stay reserved for later resumes. Finished, failed, or canceled branches do not return slots to their parent. Interrupted calls reuse their original reservations when recovered.
83
- - Named resumes do not consume new slots. The resumed worker, including any private session forks, keeps its current budget unless `max_agents_allowed` overrides it. Past launches and assigned slots still count.
84
- - The extension adds the remaining allowance and explains inclusive branch sizes in the worker's prompt automatically. No hand-written budget instructions are needed.
34
+ Workers with zero descendant allowance receive neither active delegation tools nor added delegation guidance. Raising `max_subagents_allowed` on resume restores the tools, subject to the depth limit. This uses Pi's documented `getActiveTools()` and `setActiveTools()` APIs during `session_start`. Workers that spent a positive allowance keep their tools so they can resume existing children.
85
35
 
86
- Budgets survive reloads, restarts, compaction, and session forks. The cap covers the main session's entire tree, not each tool call or user message. Changing the environment does not enlarge an existing tree; start a new main session for a fresh budget. Resuming older workers without recorded budgets gives them no new descendant allowance.
87
-
88
- Recorded calls and budgets from the older, exclusive argument remain resumable. The extension converts them without changing their reserved slots or remaining allowance.
89
-
90
- Budget state is stored alongside subagent sessions using immutable files and atomic hard links. Missing or corrupt saved state blocks new launches instead of resetting the allowance. The session filesystem must support hard links. This is an agent-count limit, not a spending limit or a security sandbox.
91
-
92
- ## Tool Prompt Overrides
93
-
94
- The complete LLM-facing description of each extension tool can be replaced in
95
- `pi-subagents.json`. Supported locations, from lowest to highest priority:
96
-
97
- 1. `~/.pi/pi-subagents.json`
98
- 2. `$PI_CODING_AGENT_DIR/pi-subagents.json` (normally `~/.pi/agent/pi-subagents.json`)
99
- 3. The nearest trusted project `.pi/pi-subagents.json`, walking up from the current directory
100
-
101
- Project values override global values per tool. Missing prompts keep their
102
- built-in defaults. Overrides replace the written delegation guidance, not the required budget argument or runtime enforcement. Keep estimation guidance in custom descriptions. Use a JSON object for `tool-prompts`:
36
+ Workers receive durable human names. Call `resume_subagents` to continue one with its previous context:
103
37
 
104
38
  ```json
105
- {
106
- "tool-prompts": {
107
- "subagents": "Your complete replacement prompt for the subagents tool.",
108
- "resume_subagents": "Your complete replacement prompt for the resume tool."
109
- }
110
- }
39
+ { "resumes": [{ "subagent": "John", "task": "Now update the tests." }] }
111
40
  ```
112
41
 
113
- ## Bundled Agents
114
-
115
- Four built-in agents ship with the extension and remain available alongside custom agents by default:
42
+ `agent` selects a definition; `subagent` identifies an existing worker. Resumes do not consume new slots. See [sessions and budgets](docs/sessions.md) for budget overrides, forks, and crash recovery.
116
43
 
117
- - `code-writer` implementation and refactoring
118
- - `code-reviwer` — code review and risk finding
119
- - `code-architect` — technical design and approach selection
120
- - `team-lead`: rare coordination of one large subproject. Its bundled `first-layer: only` setting limits launches to the main agent.
44
+ Delegate only when parallel work or context isolation saves enough effort to cover worker startup and coordination. Pass existing findings with the task so workers do not repeat your research.
121
45
 
122
- ## Defining Agents
46
+ ## Agent definitions
123
47
 
124
- Create Markdown files with YAML frontmatter:
48
+ Bundled agents:
125
49
 
126
- - **User agents:** `~/.pi/agent/agents/*.md`
127
- - **Env agents:** `$PI_CODING_AGENT_DIR/agents/*.md` *(when `PI_CODING_AGENT_DIR` is set)*
128
- - **Project agents:** `.pi/agents/*.md` *(may prompt for confirmation — see `PI_SUBAGENT_CONFIRM_PROJECT_AGENTS`)*
50
+ - `code-writer`: implementation and refactoring.
51
+ - `code-reviwer`: code review. The spelling is retained for compatibility.
52
+ - `code-architect`: technical design.
53
+ - `team-lead`: coordination of a large subproject, launchable only by the main agent.
129
54
 
130
- Agent discovery priority (highest wins on name collision): project > env/user > built-in.
131
- Built-in agents remain available alongside custom agents unless
132
- `PI_SUBAGENT_HIDE_BUILTIN_AGENTS=true`. A custom definition with the same name
133
- as a built-in agent overrides that built-in definition, including its delegation instructions. Update custom copies separately to adopt the bundled policy.
55
+ Create Markdown files in `~/.pi/agent/agents/`, `$PI_CODING_AGENT_DIR/agents/`, or the project's `.pi/agents/` directory:
134
56
 
135
57
  ```markdown
136
58
  ---
137
59
  name: writer
138
- description: Expert technical writer
60
+ description: Writes technical documentation
139
61
  thinking: low
140
62
  first-layer: enabled
141
63
  last-layer: disabled
142
64
  tools: read,write
143
65
  ---
144
66
 
145
- You are an expert technical writer focused on clarity and conciseness.
67
+ Write clear, concise technical documentation.
146
68
  ```
147
69
 
148
- ### Frontmatter Fields
70
+ The body is appended to Pi's system prompt. Project definitions override user/environment definitions, which override bundled definitions of the same name. Custom definitions replace the bundled instructions too.
149
71
 
150
- | Field | Required | Default | Description |
151
- | ------------- | -------- | -------------------- | -------------------------------------------------------- |
152
- | `name` | Yes | — | Agent identifier used in tool calls |
153
- | `description` | Yes | — | What the agent does (shown to the main agent) |
154
- | `model` | No | Current parent model | Legacy fallback only when live parent model context is unavailable |
155
- | `thinking` | No | Pi default | `off`, `minimal`, `low`, `medium`, `high`, `xhigh` |
156
- | `tools` | No | `read,bash,edit,write` | Comma-separated built-in tools |
157
- | `first-layer` | No | `enabled` | Rule for depth 1, launched by the main agent |
158
- | `second-layer` | No | `enabled` | Rule for depth 2 |
159
- | `last-layer` | No | `enabled` | Rule for the configured maximum depth |
160
- | `nth-layer(1,2,-1)` | No | `enabled` | Rule for a comma-separated list of layer numbers |
72
+ See [configuration](docs/configuration.md) for frontmatter, layer restrictions, prompt overrides, and environment settings. The optional [create-subagent skill](create-subagent/SKILL.md) teaches Pi to write definitions.
161
73
 
162
- Layer rules accept `enabled`, `disabled`, or `only`:
74
+ ## Interactive controls
163
75
 
164
- - `enabled` leaves the selected layers available. It does not restrict other layers or override another rule.
165
- - `disabled` blocks the selected layers.
166
- - `only` restricts the agent to the selected layers. Multiple `only` rules combine their selections. A matching `disabled` rule still wins, regardless of order.
76
+ Collapsed results show each child's name, task, status, and most recent activity anywhere in its subtree. `Ctrl+O` expands the newest call from memory without reading historical transcripts.
167
77
 
168
- Layer numbers start at 1. Negative numbers count back from the configured maximum depth, so `-1` means last and `-2` means next to last. Repeated numbers are harmless. Numbers outside the configured depth range match nothing. Zero, fractions, malformed selectors, and invalid values produce warnings and are ignored.
169
-
170
- ```yaml
171
- # Only the main agent can launch this agent.
172
- first-layer: only
173
- ```
78
+ `/subagent-expand <name>` opens a saved worker transcript. With no name, it opens a searchable picker. Name completion is fuzzy.
174
79
 
175
- ```yaml
176
- # Allow these layers, then exclude the second layer.
177
- nth-layer(1,2,5,-1,-5): only
178
- second-layer: disabled
179
- ```
180
-
181
- With maximum depth 8, the second example allows layers 1, 4, 5, and 8. With maximum depth 1, `first-layer: only` still allows layer 1. Adding `last-layer: disabled` would block it.
182
-
183
- Available tools: `read`, `bash`, `edit`, `write`.
184
-
185
- The Markdown body becomes the agent's system prompt (appended to Pi's default, not replacing it).
186
-
187
- ## Delegation Guards
80
+ | Key | Action |
81
+ | --- | --- |
82
+ | Left / Right | Previous / next turn |
83
+ | T | Current turn's tool list |
84
+ | Up / Down, Enter | Select and open a tool or nested child |
85
+ | C | Children across all turns |
86
+ | Esc | Return to the parent view |
87
+ | /, n, N | Search, next match, previous match |
88
+ | q | Close |
188
89
 
189
- Depth and cycle guards restrict nesting but do not cap total launches or spending. Layer availability is evaluated for the child being launched: depth 1 is the first layer, and `PI_SUBAGENT_MAX_DEPTH` is the last layer. The bundled `team-lead` agent sets `first-layer: only`, so only the main agent can launch it. Custom agent definitions can choose other layer rules. When cycle prevention is enabled, agents already in the current delegation stack are omitted from the child model's available-agent list. The runner still checks every task as a safety boundary: in a mixed parallel call, cyclic tasks fail while legal siblings still run.
90
+ While workers run, steering input can be broadcast to selected names, including nested paths such as `John > Maria`. Only inputs marked as streaming `steer` open the routing prompt. Idle prompts and queued follow-ups remain with the parent.
190
91
 
191
- A nested delegation failure is returned to its calling agent as a recoverable tool error. If that agent subsequently retries, completes the work itself, and produces a successful final answer, the earlier tool error does not incorrectly turn the completed agent—and all of its ancestors—into failures.
92
+ The `WITH SUBS` footer includes recursive worker usage. Resuming or privately forking a named worker adds usage without increasing the unique-worker count. See [usage accounting](docs/usage.md) for programmatic results.
192
93
 
193
- | Config | Default | Description |
194
- | ------------------------------ | ------- | ------------------------------------------------ |
195
- | `--subagent-max-depth` / `PI_SUBAGENT_MAX_DEPTH` | `3` | Max delegation depth (0 disables delegation) |
196
- | `--subagent-prevent-cycles` / `PI_SUBAGENT_PREVENT_CYCLES` | `true` | Block same agent in delegation chain |
94
+ ## Development
197
95
 
198
96
  ```bash
199
- pi --subagent-max-depth 2 # one nested level
200
- pi --subagent-max-depth 0 # disable delegation entirely
201
- pi --no-subagent-prevent-cycles # allow cycles (not recommended)
202
- ```
203
-
204
- ## Parallel Limits
205
-
206
- | Env Var | Default | Description |
207
- | -------------------------------- | ------- | ---------------------------------------- |
208
- | `PI_SUBAGENT_MAX_TOTAL_AGENTS` | `50` | Total new-agent budget for a new main session's tree |
209
- | `PI_SUBAGENT_MAX_PARALLEL_TASKS` | `30` | Max tasks per single call |
210
- | `PI_SUBAGENT_MAX_CONCURRENCY` | `8` | Max subagents running simultaneously |
211
-
212
- ## Child Process Environment
213
-
214
- Children inherit all provider, authentication, proxy, home, temp, and Pi
215
- environment variables. The runner also repairs the executable search path for
216
- elevated Windows PowerShell and pnpm installations: it normalizes duplicate
217
- `Path`/`PATH` keys and adds the Node directory, `PNPM_HOME`, npm's user bin,
218
- `%LOCALAPPDATA%\\pnpm`, and `%SystemRoot%\\System32`. This also applies to every
219
- nested child, so tools and sub-subagents use the same working environment.
220
-
221
- Pi itself is relaunched with the current runtime and entrypoint
222
- (`process.execPath process.argv[1]`). The extension does not inspect npm/pnpm
223
- shims or assume package names, `node_modules` locations, or Pi `dist` layouts.
224
- This also supports Bun and other Node-compatible runtimes with the same process
225
- semantics. Embedded hosts without a script entrypoint can use
226
- `PI_SUBAGENT_PI_COMMAND` and `PI_SUBAGENT_PI_ARGS_PREFIX` explicitly.
227
-
228
- ## Subagent Liveness Timeouts
229
-
230
- A delegated process cannot block its parents forever. The runner applies a
231
- startup timeout before the first model turn and an agent-inactivity timeout
232
- after startup. The inactivity watchdog is paused while any tool call is in
233
- progress, so tool executions can run for unlimited time; a fresh full idle
234
- window starts after the last concurrent tool finishes. Agent/turn events and
235
- changed nested-agent state reset the idle timer; repeated unchanged progress
236
- heartbeats do not. On timeout or cancellation, the runner terminates the child
237
- process tree and bounds cleanup; even if a wedged OS process never reports
238
- `close`, the tool returns an error result so every waiting parent can settle.
239
-
240
- RPC completion is based on Pi's `agent_settled` event—not `agent_end`.
241
- `agent_end` is only a low-level run boundary and may be followed by Pi's normal
242
- provider retry, overflow compaction, or queued continuation. Rejected prompt
243
- commands, signal exits, and processes that exit before `agent_settled` are
244
- reported immediately as failures.
245
-
246
- | Env Var | Default | Description |
247
- | --- | --- | --- |
248
- | `PI_SUBAGENT_STARTUP_TIMEOUT` | `120000` | Milliseconds allowed to reach the first model turn; `0` disables |
249
- | `PI_SUBAGENT_STARTUP_RETRIES` | `2` | Fresh retries after a startup timeout |
250
- | `PI_SUBAGENT_IDLE_TIMEOUT` | `1200000` | Milliseconds without agent activity after startup, excluding time spent in ongoing tool calls; `0` disables |
251
-
252
- ## Timestamps & Status Footer
253
-
254
- Subagent tool calls and live activity lines render a dim `hh:mm:ss` timestamp.
255
- The collapsed view shows every direct child as `Name (agent-type)`, two prompt
256
- lines, its current status, and `last action`. That timestamp is the newest
257
- activity anywhere in the child's recursive subtree, so active grandchildren
258
- keep their ancestor visibly alive. Press `Ctrl+O` for an instant in-memory
259
- view: the newest subagent tool call is shown verbosely, while older calls stay
260
- as lightweight trees. Ctrl+O never reads historical child session transcripts.
261
-
262
- Use `/subagent-expand <name>` (for example `/subagent-expand Olga`) to open a
263
- centered, turn-oriented popup. It starts on the latest turn and shows only that
264
- turn's task/resume prompt, final response, compact tool summary, and collapsed
265
- named children. Use Left/Right to change turns and `T` to open that turn's tool
266
- list; delegation tool rows include a minimal tree of their named children.
267
- Select a tool with Up/Down and press Enter to inspect its full arguments and
268
- result. Every overview also shows its distinct direct children across all turns;
269
- press `C`, select a child, and press Enter to open that child's same expanded
270
- view. `Esc` returns to the parent. `/` opens visible search, `n`/`N` moves
271
- through matches, and `q` closes from anywhere. The command offers fuzzy name
272
- completion, and running `/subagent-expand` with no argument opens a searchable
273
- picker (type to filter hundreds of names by name, agent type, or task). It is
274
- available only in the interactive TUI.
275
-
276
- In the tool list, delegation rows expand into their named children, and those
277
- child rows are selectable: press Enter on one to open that subagent's own view.
278
-
279
- The count in `WITH SUBS: (N)` is the number of unique subagents, including nested workers. Resuming a subagent or continuing its private session fork does not increase this count. Resume costs and tokens still contribute to the usage totals. Older compact history uses the name registry to recover identities that are no longer stored in the chat.
280
-
281
- The `WITH SUBS` status line aggregates `ctx.sessionManager.getEntries()`, which
282
- is the approach Pi documents for extension-side token stats. It applies Pi's own
283
- rules (`AgentSession.getSessionStats`): every billed entry counts, including
284
- off-branch retries, history compacted away, branch summaries, and tool-reported
285
- usage. Delegated cost is added once, from the durable subagent usage summary, so
286
- the combined line can never be lower than Pi's parent-only cost.
287
-
288
- In the interactive TUI the extension publishes the combined `total` usage line
289
- (parent + all subagents, recursively) via Pi's normal `ctx.ui.setStatus()`
290
- status line. Pi renders all extension statuses on the same footer status line.
291
-
292
- ## Steering Running Subagents
293
-
294
- While a `subagents` tool call is running, mid-stream steering input can be broadcast to one or more child agents. Targets are selected by human name; nested targets use paths such as `John > Maria > Elena`. The extension uses Pi's `InputEvent.streamingBehavior` metadata when available, so idle prompts and queued follow-ups continue to the parent normally; only true `steer` inputs open the broadcast routing prompt.
295
-
296
- ## Subagent Session Resume
297
-
298
- > Requires Pi **0.81.0 or newer**. Crash recovery uses Pi's public full Provider SDK and session-replacement lifecycle.
299
-
300
- Subagent subprocesses save sessions in `sessions-subagents`. When a main Pi session is resumed and its latest branch contains an unfinished `subagents` tool call (aborted, errored, or closed by Pi's synthetic unfinished-tool error), the extension can resume that delegation from the saved subagent sessions.
301
-
302
- The same detection also runs after navigating the session tree in the TUI (Esc navigation): if you jump back to a point whose branch ends in an unfinished `subagents` call, the extension offers to resume those subagents from their saved sessions.
303
-
304
- - TUI mode asks: **Resume subagents?**
305
- - Non-UI modes (`pi -p`, JSON/RPC) resume automatically.
306
- - Already-finished subagents are reused as completed; unfinished ones continue from their own saved sessions.
307
- - Durable child refs retain final output, own usage, model, and tool counts, so completed siblings survive a JSON/session restart without becoming `(no output)` or losing accounting.
308
- - Nested subagents use the same mechanism recursively.
309
- - Provider fallback goes through the selected model's effective Pi provider, so custom providers, custom APIs, auth-derived endpoints, headers, and provider-scoped environment are preserved.
310
- - Pending resume state and delayed callbacks are discarded on `/resume`, `/new`, `/fork`, and `/reload`, preventing stale work from an old runtime from leaking into the replacement session.
311
-
312
- | Env Var | Default | Description |
313
- | --- | --- | --- |
314
- | `PI_SUBAGENT_RESUME_PROMPT` | `true` | Set to `false` to suppress the TUI yes/no prompt and auto-resume. |
315
- | `PI_SUBAGENT_DISABLE_RESUME` | `false` | Set to `true` to disable automatic subagent resume detection entirely. |
316
-
317
- Note: crash-resume covers `subagents` calls only. An interrupted `resume_subagents` call is not replayed automatically — the model can simply issue it again, since names stay valid (see below).
318
-
319
- ## Resumable Subagents by Name (`resume_subagents`)
320
-
321
- Every subagent run is assigned a random, durable human first name from a bundled
322
- list of 1000 culturally diverse names — for example `John`, `Octavian`, or `Vishnu`.
323
- Names are never reused anywhere in the same delegation tree. The name is
324
- returned together with the agent type and shown in every TUI view.
325
-
326
- The `resume_subagents` tool continues named subagents with a new task while
327
- preserving their full previous context:
328
-
329
- ```json
330
- { "resumes": [{ "subagent": "John", "task": "Now also update the tests." }] }
97
+ npm ci
98
+ npm run check
331
99
  ```
332
100
 
333
- Each resume entry may optionally set `max_agents_allowed`:
101
+ Tests use the real SDK packages installed as development dependencies. No host loader or permissive type shims are needed. Run tests outside a delegated worker environment, or unset inherited `PI_SUBAGENT_*` storage and budget variables first.
334
102
 
335
- ```json
336
- { "resumes": [{ "subagent": "John", "task": "Continue the implementation", "max_agents_allowed": 10 }] }
337
- ```
103
+ Source organization:
338
104
 
339
- This replaces the worker's lifetime cap, including the worker itself. It does not grant ten fresh launches. Omit the field to keep the current cap.
340
-
341
- - Increases reserve only the extra capacity from the original launcher's remaining budget. The full-tree cap still applies.
342
- - A decrease cannot remove slots already spent or assigned to nested workers. It also does not return reserved capacity to the parent. Raising the cap back to a previously funded value needs no extra slots.
343
- - A caller can override workers in its own delegation tree. For nested workers, increases use their immediate launcher's allowance. Increase that launcher's cap first if necessary.
344
- - Overrides in one call must share an original launcher. Split overrides for different launchers into separate calls.
345
- - Recorded reservations are required for overrides. Older workers without them can still resume without the optional field.
346
- - Budget changes persist across resumes and forks. If interrupted after funding an increase, the extra capacity stays reserved and a retry does not charge it again.
347
-
348
- Naming is deliberately unambiguous: `agent` (in `subagents`) selects an agent
349
- *type* to spawn; `subagent` (in `resume_subagents`) addresses an already-run
350
- subagent *instance* by its unique name.
351
-
352
- - All resumes in one call run **in parallel**.
353
- - The preferred shape is `{"resumes":[...]}`. For compatibility, the common single-item shorthand `{"subagent":"name","task":"..."}` is normalized automatically before validation.
354
- - Names are unique within one delegation tree (everything spawned from one
355
- top-level session) and are persisted in a registry file under the subagent
356
- session root, so they survive restarts: you can resume a subagent in a later
357
- session of the same conversation.
358
- - The registry location and the session's ownership identity are stored in the
359
- session itself (a custom metadata entry). Pi assigns resumed/branched
360
- sessions a new internal session id, but the persisted identity (plus a
361
- `parentSession` ancestor-walk fallback for sessions created before it
362
- existed) keeps the whole tree's names alive across process restarts — for
363
- the top-level session and every nested subagent alike.
364
- - **Ownership & forks**: the agent that spawned a subagent (its *owner*)
365
- resumes the original session. A parent may pass names to its own subagents
366
- (in their task text); when a child resumes a name created by an ancestor, it
367
- transparently gets a **private fork** of that subagent (a copy of its
368
- session), so the owner's copy is never polluted by the child's continuation.
369
- Each child gets exactly **one fork per name** and keeps reusing it on
370
- subsequent resumes. Fork session locations are persisted too.
371
- - Concurrent resumes of the same target are rejected (in-process and
372
- cross-process via crash-tolerant registry markers), because two processes
373
- continuing the same session file would corrupt it.
374
- - If the original agent definition file has been removed, the resume still
375
- works: the registry remembers the agent's model/tool restrictions and the
376
- session itself carries the context.
377
-
378
- | Env Var | Default | Description |
379
- | --- | --- | --- |
380
- | `DISABLE_RESUMABLE_SUBAGENTS` | `false` | Set to `true`/`on`/`1` to disable resumable subagents entirely: no names are allocated, the `resume_subagents` tool is not registered, and the system prompt omits the feature. |
381
- | `PI_SUBAGENT_NAMES_FILE` | (internal) | Path of the shared name registry, propagated to child processes so the whole delegation tree allocates unique names. |
382
- | `PI_SUBAGENT_BUDGET_DIR` | internal | Child's reserved branch ledger. Passed through the process environment and persisted in session metadata. |
383
-
384
- ## Agent Discovery
385
-
386
- | Env Var | Description |
387
- | ----------------------- | ------------------------------------------------------------ |
388
- | `PI_CODING_AGENT_DIR` | Override Pi's agent config directory. Agents are read from `$PI_CODING_AGENT_DIR/agents/*.md`, and tool prompts from `$PI_CODING_AGENT_DIR/pi-subagents.json`. |
389
- | `PI_SUBAGENT_HIDE_BUILTIN_AGENTS` | Set to `true`/`on`/`yes`/`1` to hide all bundled agents. By default they are available alongside custom agents. |
390
-
391
- ## CLI Argument Proxying
392
-
393
- Flags passed to the parent `pi` process are forwarded to subagent child
394
- processes, so they inherit the same provider, API key, and other runtime settings. At every new launch, the extension explicitly passes the parent's currently active model; changing `/model` mid-conversation therefore affects all subsequently started subagents. Flags the extension manages itself are blocked from being forwarded.
395
-
396
- **Always forwarded verbatim:**
397
-
398
- | Flag(s) | Purpose |
399
- | --- | --- |
400
- | `--provider` | AI provider |
401
- | `--api-key` | API key |
402
- | `--system-prompt` | Base system prompt override |
403
- | `--session-dir` | Session storage directory |
404
- | `--models` | Model cycling list |
405
- | `--skill`, `--no-skills`/`-ns` | Skill loading |
406
- | `--prompt-template`, `--no-prompt-templates`/`-np` | Prompt templates |
407
- | `--theme`, `--no-themes` | Themes |
408
- | `--verbose` | Verbose startup output |
409
- | Unknown/custom flags | Forwarded with heuristic value detection |
410
-
411
- **Forwarded as fallback** (agent frontmatter overrides if set):
412
-
413
- | Flag | Overridden by |
105
+ | Directory | Responsibility |
414
106
  | --- | --- |
415
- | `--model` | Replaced at launch by the parent's currently active model (`model:` is only a no-context compatibility fallback) |
416
- | `--thinking` | `thinking:` in agent frontmatter |
417
- | `--tools` / `--no-tools` | `tools:` in agent frontmatter |
418
-
419
- **Never forwarded** (managed by the extension itself):
420
- `--mode`, `-p`/`--print`, `--session`/`--no-session`, `--continue`, `--resume`,
421
- `--append-system-prompt`, `--offline`, `--extension`/`-e`, `--no-extensions`/`-ne`,
422
- `--subagent-max-depth`, `--subagent-prevent-cycles`, `--export`, `--list-models`,
423
- `--help`, `--version`.
424
-
425
- ---
426
-
427
- ## Programmatic Usage (JSON RPC)
428
-
429
- When running `pi` programmatically with `--mode rpc` (or `--mode json`), the stream contains
430
- `tool_result_end` events whenever the agent completes a `subagents` tool call. The `details` field
431
- of these events carries the full stats for that delegation — including recursive usage and tool
432
- call counts from all subagents in the tree.
433
-
434
- ### Stream event shape
435
-
436
- ```
437
- tool_result_end
438
- └── message
439
- ├── role: "toolResult"
440
- ├── toolName: "subagents"
441
- ├── toolCallId: string
442
- ├── isError: boolean
443
- ├── content: [{ type: "text", text: "<final output>" }]
444
- └── details: SubagentDetails
445
- ```
446
-
447
- ### `SubagentDetails` object
448
-
449
- ```ts
450
- interface SubagentDetails {
451
- // Execution metadata
452
- mode: "single" | "parallel"; // one task vs multiple parallel tasks
453
- delegationMode: "spawn"; // always "spawn" (kept for backward-compatible serialization)
454
- projectAgentsDir: string | null; // path to .pi/agents/ dir if used
455
-
456
- // Individual agent results (one per task)
457
- results: SingleResult[];
458
-
459
- // ── Stats summary (own + all descendants, recursively) ──────────────────
460
- aggregatedUsage: UsageStats; // token counts and cost, full tree
461
- aggregatedToolCalls: ToolCallCounts; // { toolName: callCount }, full tree
462
-
463
- // ── Per-agent breakdown ──────────────────────────────────────────────────
464
- usageTree: UsageTreeNode[]; // one root node per result
465
- }
466
-
467
- interface SingleResult {
468
- agent: string; // agent name
469
- agentSource: "user" | "project" | "builtin" | "unknown";
470
- task: string; // task string passed to this agent
471
- exitCode: number; // 0 = process success, >0 = error, -1 = still running
472
- messages: Message[]; // full conversation history of the subagent
473
- stderr: string;
474
- usage: UsageStats; // this agent's OWN token usage only
475
- toolCalls: ToolCallCounts; // this agent's OWN tool calls only
476
- model?: string;
477
- stopReason?: string; // "end_turn" | "error" | "aborted" | ...
478
- errorMessage?: string;
479
- }
480
-
481
- interface UsageStats {
482
- input: number; // input tokens
483
- output: number; // output tokens
484
- cacheRead: number; // cache read tokens
485
- cacheWrite: number; // cache write tokens
486
- cost: number; // total cost in USD
487
- contextTokens: number; // snapshot: last context window size (not summed in aggregates)
488
- turns: number; // number of assistant turns
489
- }
490
-
491
- // toolName → call count, e.g. { "bash": 5, "read": 3, "subagents": 1 }
492
- type ToolCallCounts = Record<string, number>;
493
-
494
- interface UsageTreeNode {
495
- agent: string;
496
- task: string;
497
- ownUsage: UsageStats; // only this agent's turns
498
- ownToolCalls: ToolCallCounts; // only this agent's tool calls
499
- aggregatedUsage: UsageStats; // ownUsage + all children recursively
500
- aggregatedToolCalls: ToolCallCounts; // ownToolCalls + all children recursively
501
- children: UsageTreeNode[]; // one node per nested subagent invocation
502
- }
503
- ```
504
-
505
- ### Important notes on stats
506
-
507
- - **`SingleResult.usage`** and **`SingleResult.toolCalls`** cover **only that one agent's own work** —
508
- not its children. Children run in separate processes; their tokens never appear in the parent's usage.
509
- - **`aggregatedUsage`** / **`aggregatedToolCalls`** on `SubagentDetails` (and on each `UsageTreeNode`)
510
- are the correct totals to use when you want the cost or tool call count for an entire delegation
511
- subtree.
512
- - **`contextTokens`** is a point-in-time snapshot of the context window size at the last turn of that
513
- agent. It is **not** summed in aggregated stats (it would be meaningless as a cross-process sum).
514
- - **`toolCalls`** includes **all** tool calls an agent made, including the `"subagents"` call itself.
515
- You can use the `"subagents"` count to see how many nested delegations an agent spawned.
516
-
517
- ### Annotated example JSON
518
-
519
- The scenario below: main agent delegates to `code-writer`, which does some file work and then
520
- delegates to `code-reviwer` before finishing.
521
-
522
- ```json
523
- {
524
- "type": "tool_result_end",
525
- "message": {
526
- "role": "toolResult",
527
- "toolName": "subagents",
528
- "toolCallId": "toolu_01XYZ",
529
- "isError": false,
530
- "content": [
531
- {
532
- "type": "text",
533
- "text": "Feature implemented and reviewed. Added validation logic in auth.ts and updated the test suite."
534
- }
535
- ],
536
- "details": {
537
- "mode": "single",
538
- "delegationMode": "spawn",
539
- "projectAgentsDir": null,
540
-
541
- "aggregatedUsage": {
542
- "input": 2180,
543
- "output": 615,
544
- "cacheRead": 940,
545
- "cacheWrite": 120,
546
- "cost": 0.0079,
547
- "contextTokens": 0,
548
- "turns": 3
549
- },
550
- "aggregatedToolCalls": {
551
- "read": 3,
552
- "bash": 2,
553
- "edit": 1,
554
- "subagents": 1
555
- },
556
-
557
- "usageTree": [
558
- {
559
- "agent": "code-writer",
560
- "task": "Implement the auth feature and have it reviewed",
561
- "ownUsage": {
562
- "input": 1380,
563
- "output": 365,
564
- "cacheRead": 540,
565
- "cacheWrite": 120,
566
- "cost": 0.0058,
567
- "contextTokens": 2840,
568
- "turns": 2
569
- },
570
- "ownToolCalls": {
571
- "read": 1,
572
- "bash": 1,
573
- "edit": 1,
574
- "subagents": 1
575
- },
576
- "aggregatedUsage": {
577
- "input": 2180,
578
- "output": 615,
579
- "cacheRead": 940,
580
- "cacheWrite": 120,
581
- "cost": 0.0079,
582
- "contextTokens": 0,
583
- "turns": 3
584
- },
585
- "aggregatedToolCalls": {
586
- "read": 3,
587
- "bash": 2,
588
- "edit": 1,
589
- "subagents": 1
590
- },
591
- "children": [
592
- {
593
- "agent": "code-reviwer",
594
- "task": "Review the auth implementation in auth.ts",
595
- "ownUsage": {
596
- "input": 800,
597
- "output": 250,
598
- "cacheRead": 400,
599
- "cacheWrite": 0,
600
- "cost": 0.0021,
601
- "contextTokens": 1450,
602
- "turns": 1
603
- },
604
- "ownToolCalls": {
605
- "read": 2,
606
- "bash": 1
607
- },
608
- "aggregatedUsage": {
609
- "input": 800,
610
- "output": 250,
611
- "cacheRead": 400,
612
- "cacheWrite": 0,
613
- "cost": 0.0021,
614
- "contextTokens": 0,
615
- "turns": 1
616
- },
617
- "aggregatedToolCalls": {
618
- "read": 2,
619
- "bash": 1
620
- },
621
- "children": []
622
- }
623
- ]
624
- }
625
- ],
626
-
627
- "results": [
628
- {
629
- "agent": "code-writer",
630
- "agentSource": "builtin",
631
- "task": "Implement the auth feature and have it reviewed",
632
- "exitCode": 0,
633
- "stopReason": "end_turn",
634
- "model": "claude-opus-4-5",
635
- "stderr": "",
636
- "usage": {
637
- "input": 1380,
638
- "output": 365,
639
- "cacheRead": 540,
640
- "cacheWrite": 120,
641
- "cost": 0.0058,
642
- "contextTokens": 2840,
643
- "turns": 2
644
- },
645
- "toolCalls": {
646
- "read": 1,
647
- "bash": 1,
648
- "edit": 1,
649
- "subagents": 1
650
- },
651
- "messages": [
652
- "... full conversation history of code-writer (includes the nested subagent tool_result) ..."
653
- ]
654
- }
655
- ]
656
- }
657
- }
658
- }
659
- ```
660
-
661
- ### Collecting stats across an entire session
662
-
663
- If you are consuming the JSON stream programmatically and want to track the total cost and tool
664
- usage across all subagent work in a session, listen for every `tool_result_end` event where
665
- `message.toolName === "subagents"` (or the legacy `"subagent"` in old sessions) and sum `message.details.aggregatedUsage` across them.
666
-
667
- ```js
668
- let totalCost = 0;
669
- const totalToolCalls = {};
670
-
671
- for await (const line of jsonLines) {
672
- const event = JSON.parse(line);
673
- if (
674
- event.type === "tool_result_end" &&
675
- ["subagents", "subagent", "resume_subagents"].includes(event.message?.toolName) &&
676
- event.message?.details
677
- ) {
678
- const { aggregatedUsage, aggregatedToolCalls } = event.message.details;
679
- totalCost += aggregatedUsage.cost;
680
- for (const [tool, count] of Object.entries(aggregatedToolCalls)) {
681
- totalToolCalls[tool] = (totalToolCalls[tool] ?? 0) + count;
682
- }
683
- }
684
- }
685
- ```
686
-
687
- Note: if you also track the main agent's own usage from `message_end` events, make sure **not** to
688
- double-count the subagent costs there — the main agent's own token usage (from its own `message_end`
689
- events) does not include subagent work.
690
-
691
- ---
692
-
693
- ## create-subagent Skill
694
-
695
- If you want the agent to **create new subagent definition files** for itself, install the [`create-subagent` skill](https://github.com/gee666/pi-subagent/tree/main/create-subagent). Once installed, the agent will know how to scaffold new `.md` agent files in the right location with correct frontmatter.
696
-
697
- ## Attribution
107
+ | `extension/` | Tool registration, session lifecycle, policy, recovery provider, steering |
108
+ | `runner/` | Child launch, RPC events, watchdogs, process cleanup |
109
+ | `storage/` | Budget ledger, name registry, session files and validation |
110
+ | `types/` | Runtime contracts, transcript parsing, outcomes, usage aggregation |
111
+ | `ui/` | Transcript views, trees, navigation and overlays |
698
112
 
699
- Inspired by [vaayne/agent-kit](https://github.com/vaayne/agent-kit) and [mariozechner/pi-mono](https://github.com/badlogic/pi-mono).
113
+ Root entry points preserve existing imports. `config.ts` and `agents.ts` discover configuration and definitions. Runtime and test files stay below 350 lines; the name list is static data.
700
114
 
701
- ## License
115
+ ## Attribution and license
702
116
 
703
- MIT
117
+ Inspired by [vaayne/agent-kit](https://github.com/vaayne/agent-kit) and [pi-mono](https://github.com/badlogic/pi-mono). MIT licensed.