@arhen/pi-core-subagent 1.3.0 → 1.3.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 (3) hide show
  1. package/README.md +131 -10
  2. package/package.json +1 -1
  3. package/src/index.ts +10 -15
package/README.md CHANGED
@@ -14,6 +14,21 @@ One rule underneath everything else:
14
14
 
15
15
  That is [Graph Protocol](#graph-protocol), applied to the runtime rather than to the prompt.
16
16
 
17
+ ```mermaid
18
+ flowchart LR
19
+ subgraph w1["wave 1 — runs in parallel"]
20
+ api["api<br/><i>api-mapper</i>"]
21
+ db["db<br/><i>db-mapper</i>"]
22
+ end
23
+ gate{{"gate"}}
24
+ subgraph w2["wave 2"]
25
+ doc["doc<br/><i>writer</i>"]
26
+ end
27
+ api -- "route map" --> gate
28
+ db -- "schema map" --> gate
29
+ gate -- "both outputs<br/>prepended to the prompt" --> doc
30
+ ```
31
+
17
32
  ![Subagents widget](docs/subagents-widget.png)
18
33
 
19
34
  *The `subagent` tool call plus the live above-editor widget: per-agent activity, tool counts, turns, token counters and timers.*
@@ -33,6 +48,29 @@ That is [Graph Protocol](#graph-protocol), applied to the runtime rather than to
33
48
  - **No silent hangs** — watchdog aborts children that produce no events for 3 minutes.
34
49
  - **No default runtime cap** — tasks run until done, stalled (watchdog), or aborted by the user. `maxRuntimeMs` is opt-in (default 0 = unlimited).
35
50
 
51
+ ## How it runs
52
+
53
+ Children are not subprocesses. They are separate `AgentSession`s inside the same pi process — which is why spawning is instant, and why a child's transcript never lands in your context:
54
+
55
+ ```mermaid
56
+ flowchart TB
57
+ subgraph proc["one OS process — no spawn, no IPC"]
58
+ direction TB
59
+ L["<b>leader</b><br/>your session, your context"]
60
+ subgraph kids["isolated child sessions"]
61
+ direction LR
62
+ A["api-mapper"]
63
+ B["db-mapper"]
64
+ end
65
+ end
66
+ L -- "task text in" --> A
67
+ L -- "task text in" --> B
68
+ A -. "final answer only" .-> L
69
+ B -. "final answer only" .-> L
70
+ ```
71
+
72
+ The dotted arrows are the whole point: a child may burn 200k tokens reading files, and the leader receives only its final answer.
73
+
36
74
  ## Install
37
75
 
38
76
  ```sh
@@ -103,18 +141,82 @@ subagent graph 3
103
141
 
104
142
  `✎` marks a write-toolset task; `←` lists its edges. With no `needs` anywhere the wave line is omitted entirely.
105
143
 
106
- What the edge does:
144
+ ### What one edge does
145
+
146
+ An edge is not just ordering. It is a delivery:
147
+
148
+ ```mermaid
149
+ sequenceDiagram
150
+ participant S as scheduler
151
+ participant A as api
152
+ participant D as db
153
+ participant W as doc
154
+
155
+ Note over S,D: wave 1 — both start together
156
+ S->>A: "Map every route in src/api/"
157
+ S->>D: "Map the schema in src/db/"
158
+ A-->>S: route map
159
+ Note right of W: doc is queued,<br/>waiting at the gate
160
+ D-->>S: schema map
161
+ Note over S: gate opens: every need settled
162
+ S->>W: ## Output of api<br/>&lt;route map&gt;<br/><br/>## Output of db<br/>&lt;schema map&gt;<br/>---<br/>"Write ARCHITECTURE.md…"
163
+ ```
164
+
165
+ The leader never copies those outputs into the prompt — so it cannot forget to.
166
+
167
+ ### One scheduler, four shapes
168
+
169
+ Single, parallel, chain and graph are not four code paths. They are four shapes of the same wave loop:
170
+
171
+ ```mermaid
172
+ flowchart LR
173
+ subgraph one["single"]
174
+ direction TB
175
+ s1(("a"))
176
+ end
177
+ subgraph par["parallel — no needs"]
178
+ direction TB
179
+ p1(("a")) ~~~ p2(("b")) ~~~ p3(("c"))
180
+ end
181
+ subgraph ch["chain — needs: [previous]"]
182
+ direction TB
183
+ c1(("a")) --> c2(("b")) --> c3(("c"))
184
+ end
185
+ subgraph gr["graph — needs"]
186
+ direction TB
187
+ g1(("a")) --> g2(("b"))
188
+ g1 --> g3(("c"))
189
+ g2 --> g4(("d"))
190
+ g3 --> g4
191
+ end
192
+ ```
193
+
194
+ ### The loop
195
+
196
+ ```mermaid
197
+ flowchart TD
198
+ start(["subagent call"]) --> validate{"graph valid?<br/><small>unknown id · self-edge · cycle</small>"}
199
+ validate -- no --> reject["reject the call<br/><b>zero children spawned</b>"]
200
+ validate -- yes --> loop{"tasks left?"}
201
+ loop -- no --> done(["run finished"])
202
+ loop -- yes --> ready["frontier =<br/>tasks whose needs are all settled"]
203
+ ready --> spawn["run that wave in parallel<br/><small>throttled by concurrency</small>"]
204
+ spawn --> collect["record each output<br/>mark tasks settled"]
205
+ collect --> loop
206
+ ```
207
+
208
+ Two consequences worth stating plainly:
107
209
 
108
- - **Gates** `doc` starts only after both `api` and `db` finish.
109
- - **Carries** `api`'s and `db`'s outputs are prepended to `doc`'s prompt as `## Output of api` / `## Output of db`. You do not pass them yourself, and you cannot forget to.
110
- - **Skips on breakage** — if an upstream fails or is aborted, dependents are marked aborted instead of running against a prompt with a hole in it.
210
+ - **A bad graph costs nothing.** Validation happens before the first spawn, never halfway through with three children already burning tokens.
211
+ - **A broken upstream stops its branch.** If a need fails or is aborted, its dependents are marked aborted rather than run against a prompt with a hole in it:
111
212
 
112
- Rules:
213
+ ```mermaid
214
+ flowchart LR
215
+ api["api ✓"] --> doc
216
+ db["db ✗ failed"] --> doc["doc ⏹ skipped<br/><small>never spawned</small>"]
217
+ ```
113
218
 
114
- - Tasks with no unmet `needs` run together, throttled by `concurrency`.
115
- - Unknown ids, self-edges and cycles are rejected **before any child spawns**.
116
- - `chain` is exactly `needs: [previous]` — same scheduler, kept for convenience. `{previous}` still expands.
117
- - Zero `needs` anywhere = plain parallel. No ceremony added to flat fan-out.
219
+ And the rule that keeps this from becoming ceremony: **zero `needs` anywhere = plain parallel.** No waves, no gates, no graph vocabulary imposed on flat work.
118
220
 
119
221
  Background + intercom:
120
222
 
@@ -163,7 +265,16 @@ Read-only pane over the session's subagents:
163
265
 
164
266
  ## Watching a child from outside
165
267
 
166
- `subagent_status` returns each running child's session file (JSONL). Children are `AgentSession`s in this process they have no TTY but their transcript is a real file, so any external viewer can follow one:
268
+ A child has no terminal of its own but it does write a real transcript file, and that file is the seam every external viewer can use:
269
+
270
+ ```mermaid
271
+ flowchart LR
272
+ child["child session<br/><small>no TTY</small>"] -- writes --> file[("session.jsonl")]
273
+ file -- "peek · enter" --> pane["in-pi tail"]
274
+ file -- "tail -f" --> term["any terminal pane<br/><small>herdr · tmux · zellij</small>"]
275
+ ```
276
+
277
+ `subagent_status` returns that path for every running child:
167
278
 
168
279
  ```sh
169
280
  tail -f /path/from/subagent_status.jsonl
@@ -212,6 +323,16 @@ The protocol asks the coordinator to compare the delegated subgraph against the
212
323
  - Across 9,876 τ2-bench and 1,879 AppWorld trajectories, "false success" reached **75.8%** of self-assessing coding-agent failures; adding an LLM judge scored **0.54–0.65 AUROC** (0.5 = coin flip). ([arXiv:2606.09863](https://doi.org/10.48550/arxiv.2606.09863))
213
324
  - LLM judges reading agent traces can be flipped by rewriting the trace — the exact surface a self-reported graph exposes. ([arXiv:2601.14691](https://arxiv.org/html/2601.14691))
214
325
 
326
+ The shape of the problem:
327
+
328
+ ```mermaid
329
+ flowchart TD
330
+ W["worker finishes"] --> Q{"who says it's correct?"}
331
+ Q -- "the worker itself" --> S["self-report<br/><b>0 of 34 caught</b><br/><small>at 90–100 confidence</small>"]
332
+ Q -- "another model reading the trace" --> J["LLM judge<br/><b>0.54–0.65 AUROC</b><br/><small>0.5 = coin flip</small>"]
333
+ Q -- "the machine" --> D["exit code + git diff<br/><b>34 of 34 caught</b>"]
334
+ ```
335
+
215
336
  So §9 in practice is two things you already have:
216
337
 
217
338
  ```sh
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arhen/pi-core-subagent",
3
- "version": "1.3.0",
3
+ "version": "1.3.2",
4
4
  "type": "module",
5
5
  "description": "pi extension: fast in-process subagents with a dependency-graph scheduler (needs edges gate tasks and carry upstream output into dependent prompts), plus background runs, intercom and agent-to-agent mailbox. Leader defines agents inline.",
6
6
  "license": "MIT",
package/src/index.ts CHANGED
@@ -1252,12 +1252,7 @@ const TaskItem = Type.Object({
1252
1252
  cwd: Type.Optional(Type.String({ description: "Working directory for this task. Default: current project." })),
1253
1253
  tools: Type.Optional(Type.Array(Type.String(), { description: "Explicit tool allowlist (overrides the toolset)" })),
1254
1254
  maxRuntimeMs: Type.Optional(Type.Number({ description: "Per-task timeout (ms)" })),
1255
- needs: Type.Optional(
1256
- Type.Array(Type.String(), {
1257
- description:
1258
- "Task ids this task depends on (requires those tasks to declare id). It starts only after they finish, and their outputs are prepended to its prompt. Tasks with no unmet needs run together as a wave.",
1259
- }),
1260
- ),
1255
+ needs: Type.Optional(Type.Array(Type.String(), { description: "Ids of tasks this one waits for; their outputs are prepended to this prompt." })),
1261
1256
  });
1262
1257
 
1263
1258
  type SubagentParamsShape = {
@@ -1380,18 +1375,18 @@ export default function (pi: ExtensionAPI) {
1380
1375
  pi.registerTool<typeof SubagentParams, RunDetails>({
1381
1376
  name: "subagent",
1382
1377
  label: "Subagent",
1383
- description: "Define and run isolated subagents (own context, own session). You invent the agent: name, optional system prompt, toolset (read-only default, write:true for edits). Modes: single, parallel (tasks), chain ({previous}). Tasks with `needs` form a dependency graph: each wave of tasks with satisfied needs runs in parallel, and an upstream task's output is prepended to its dependents' prompts. background:true fire-and-forgets with completion notice. allowIntercom:true lets children ask you questions and message each other.\n\nExamples (copy these shapes):\nSingle: subagent({ agent: \"reviewer\", prompt: \"You review code for correctness\", task: \"Review src/auth.ts\" })\nParallel: subagent({ tasks: [{ agent: \"mapper\", task: \"Map all API routes\" }, { agent: \"critic\", task: \"Review auth for vulnerabilities\" }] })\nGraph: subagent({ tasks: [{ id: \"api\", agent: \"api-mapper\", task: \"Map API routes\" }, { id: \"db\", agent: \"db-mapper\", task: \"Map DB schema\" }, { id: \"doc\", agent: \"writer\", needs: [\"api\", \"db\"], write: true, task: \"Write ARCHITECTURE.md. Verify: test -s ARCHITECTURE.md\" }] })\nChain: subagent({ chain: [{ agent: \"planner\", task: \"Plan the change\" }, { agent: \"doer\", write: true, task: \"Execute: {previous}\" }] })\nBackground: subagent({ agent: \"auditor\", task: \"Audit deps\", background: true })",
1378
+ // ponytail: this string is billed on every request. One example the graph one
1379
+ // covers ids, needs, write and Verify; the simpler shapes are subsets of it.
1380
+ description:
1381
+ "Run isolated subagents (own context, own session). You invent each agent: name, optional system prompt, toolset (read-only default, write:true to edit). Use `agent`+`task` for one, `tasks` for many. `needs` declares dependency edges: a task waits for its needs and receives their outputs prepended to its prompt. background:true returns immediately; allowIntercom:true lets children talk to you and each other.\n\nsubagent({ tasks: [{ id: \"api\", agent: \"api-mapper\", task: \"Map API routes\" }, { id: \"db\", agent: \"db-mapper\", task: \"Map DB schema\" }, { id: \"doc\", agent: \"writer\", needs: [\"api\", \"db\"], write: true, task: \"Write ARCHITECTURE.md. Verify: test -s ARCHITECTURE.md\" }] })",
1384
1382
  promptSnippet: "Define and delegate work to specialized subagents.",
1385
1383
  promptGuidelines: [
1386
1384
  "Use subagent when independent review, testing, research, or parallel analysis improves quality.",
1387
- "Decompose parallelizable work: if the request has 2+ independent sub-tasks (separate files, separate concerns, independent research/review), spawn N agents with a SINGLE call: subagent({ tasks: [{agent, task}, ...] }). NEVER make multiple parallel subagent calls for parallel work — one call, one run, N tasks.",
1388
- "If independent sub-tasks are sequential (each builds on the previous one's output), use chain mode with {previous}.",
1389
- "When some tasks depend on others but not all do, give tasks an `id` and list `needs`. Independent tasks then still run in parallel while dependents wait, and each dependent receives its upstream outputs automatically — do not re-describe them in the prompt.",
1390
- "Give every task a way to check itself: end the task text with a runnable command, e.g. 'Verify: npx tsc --noEmit && bun test'. A subagent's own claim of success is not evidence.",
1391
- "Define each subagent yourself: an invented name, a focused system prompt (prompt:), and a toolset read-only (default) or write (write:true).",
1392
- "Prefer read-only subagents unless the task explicitly needs edits.",
1393
- "Use background:true for long-running work; you'll be notified on completion.",
1394
- "Use allowIntercom:true only when a child may need to ask you something; keep children autonomous otherwise.",
1385
+ "Put every sub-task in ONE call: subagent({ tasks: [...] }). Never make multiple parallel subagent calls — one call, one run, N tasks.",
1386
+ "Order comes from `needs`, not from separate calls: give tasks an `id`, list the ids each depends on. Tasks with no unmet needs run in parallel; dependents receive their upstream outputs automatically — do not restate them.",
1387
+ "End each task with a runnable check, e.g. 'Verify: npx tsc --noEmit && bun test'. A subagent's claim of success is not evidence.",
1388
+ "Define each agent yourself: invented name, focused system prompt, and read-only (default) or write:true. Prefer read-only.",
1389
+ "Use background:true for long work; allowIntercom:true only when a child may need to ask you something.",
1395
1390
  ],
1396
1391
  parameters: SubagentParams,
1397
1392
  executionMode: "parallel", // sibling subagent calls run concurrently, not serialized