@cr1ms0n/pi-subagent 0.9.0 → 0.11.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 CHANGED
@@ -1,672 +1,40 @@
1
- # @cr1ms0n/pi-subagent
2
-
3
- This is an independent community fork of **@parke.dev/pi-subagent 0.8.0**, originally by Luke Parke. It is not an official upstream release. The original MIT license and copyright are preserved. Upstream source: [LukasParke/pi-extensions](https://github.com/LukasParke/pi-extensions/tree/main/packages/pi-subagent).
4
-
5
- This fork replaces the upstream fixed model route with mandatory Jev routing. New subagent dispatches require a `jevRouting` block in `~/.pi/subagent.json` (a dedicated candidate-model list plus the name of the environment variable holding your TypeSafe credential), and the TUI shows the selected execution model and tools.
6
-
7
- Production-grade isolated subagents for [Pi](https://github.com/badlogic/pi-mono).
8
-
9
- Delegate research, parallel exploration, and clean-context review to child Pi
10
- processes. Named agent personas, Jev-selected execution models and individual
11
- tools, cancellable background runs with completion notifications and a live
12
- widget, mid-run steering, graceful budget wrap-ups, bounded same-model retry, a
13
- stall watchdog, session resume and context forking, worktree isolation with a
14
- diff/apply/discard loop, capability profiles, a root/subagent/combined cost
15
- ledger, and a TUI inspector.
16
-
17
- ## Install
18
-
19
- Pi packages install from **npm**, **git**, or a **local path**:
20
-
21
- ```bash
22
- # npm (scoped; surfaces on the pi.dev gallery via the pi-package keyword)
23
- # Note: unscoped "pi-subagent" is rejected by npm as too similar to "pi-sub-agent".
24
- pi install npm:@cr1ms0n/pi-subagent@0.8.1
25
-
26
- # latest npm
27
- pi install npm:@cr1ms0n/pi-subagent
28
-
29
- # npm is the supported package install path from the monorepo.
30
- # For local development, install this workspace directly:
31
- pi install /absolute/path/to/pi-extensions/packages/pi-subagent
32
-
33
- # local checkout
34
- pi install /absolute/path/to/pi-subagent
35
- ```
36
-
37
- Then start Pi normally. The package registers:
38
-
39
- - tool: `subagent`
40
- - command: `/subagents` (run inspector overlay)
41
- - command: `/subagent-cost` (parent / subagent / combined usage on demand)
42
-
43
- This fork is published manually after pack-content and offline checks — see
44
- [docs/RELEASING.md](./docs/RELEASING.md).
45
-
46
- ## Quick usage
47
-
48
- ```ts
49
- // Single foreground task (default profile: general). Omit model and
50
- // fallback_models: Jev selects the execution model from your configured list.
51
- { task: "Find all call sites of parseConfig and summarize patterns.", description: "Map parseConfig usage" }
52
-
53
- // Named agent: persona prompt from .pi/agents/reviewer.md. Jev still picks the model.
54
- { task: "Review this diff for security issues", agent: "reviewer" }
55
-
56
- // Parallel read-only explorers (default profile for tasks[]: explore).
57
- {
58
- tasks: [
59
- { task: "Map auth middleware flow", description: "Auth flow map" },
60
- { task: "List all env vars used in server/", description: "Env var inventory" }
61
- ]
62
- }
63
-
64
- // Parallel research with automatic fan-in: one read-only child folds all
65
- // outputs into a single brief, delivered first.
66
- {
67
- tasks: [
68
- { task: "Audit backend error handling", description: "Backend audit" },
69
- { task: "Audit frontend error handling", description: "Frontend audit" }
70
- ],
71
- synthesis: "Merge both audits into one prioritized findings list"
72
- }
73
-
74
- // Background run: the widget shows the selected model; expanded results include tools.
75
- { task: "Audit dependency licenses", async: true }
76
- // later
77
- { action: "status", id: "abc123" }
78
- { action: "wait", id: "abc123" } // interruptible; does not cancel
79
- { action: "cancel", id: "abc123" }
80
-
81
- // Collecting a background run also has its own tool, for reflexive mid-flow
82
- // use. Identical semantics to action:"wait" (same handler underneath).
83
- // A timeout returns a still-running notice WITHOUT cancelling or consuming
84
- // the run, so it stays collectable.
85
- subagent_wait { id: "abc123" }
86
- subagent_wait { id: "abc123", timeout_ms: 30000 }
87
-
88
- // Dry-run a spawn request: full validation, Jev selection and local preflights
89
- // (git repo, fork session, output paths). Returns the resolved per-task plan
90
- // (model, tools, budgets, isolation) and its selector usage without spawning.
91
- // Selector fees apply, and a later dispatch selects again.
92
- { action: "plan", tasks: [{ task: "Implement feature A", profile: "general", isolation: "worktree" }] }
93
-
94
- // Structured output: the child must end with a fenced json:result block
95
- // matching the schema. Invalid output gets one automatic repair round;
96
- // delivery is the clean JSON and details carry the parsed object.
97
- {
98
- task: "Audit the auth module",
99
- output_schema: {
100
- type: "object",
101
- required: ["findings", "risk"],
102
- properties: {
103
- findings: { type: "array", items: { type: "string" } },
104
- risk: { type: "string", enum: ["low", "medium", "high"] }
105
- }
106
- }
107
- }
108
-
109
- // Fork the parent conversation into the child (needs a persisted session).
110
- // The child starts from a branched copy of everything discussed so far.
111
- { task: "Implement the plan we agreed on", context: "fork", profile: "general" }
112
-
113
- // Budgets with graceful wrap-up: at the limit the child is steered to produce
114
- // a final answer and given grace turns before any hard stop.
115
- { task: "Audit deps", max_turns: 15, grace_turns: 2 }
116
-
117
- // Transient child failures retry the already selected model and tool set, up to
118
- // max_retries. There are no emergency or fallback models.
119
- { task: "Research X", max_retries: 1 }
120
-
121
- // Steer a running child mid-run instead of cancel + retry. The message is
122
- // delivered after the current assistant turn, before the next LLM call.
123
- { action: "steer", id: "abc123", message: "Skip the tests directory; focus on src/" }
124
- // Parallel runs: pass index to pick one live task.
125
- { action: "steer", id: "abc123", index: 1, message: "Wrap up now" }
126
-
127
- // Resume a child session. The new invocation selects again.
128
- { task: "Continue from your findings and propose a fix plan", resume: "<session-id>" }
129
-
130
- // Isolated writers
131
- {
132
- tasks: [
133
- { task: "Implement feature A", profile: "general", isolation: "worktree" },
134
- { task: "Implement feature B", profile: "general", isolation: "worktree" }
135
- ]
136
- }
137
-
138
- // Close the worktree loop after the run finishes:
139
- { action: "diff", id: "abc123", index: 0 } // inspect the patch
140
- { action: "apply", id: "abc123", index: 0 } // land as uncommitted changes in your checkout
141
- { action: "discard", id: "abc123", index: 1 } // drop worktree + branch
142
- ```
143
-
144
- The `/subagents` overlay mirrors the worktree loop interactively: `s` steer,
145
- `a` apply, `x` discard on the selected run.
146
-
147
- ## Side questions (`/btw`)
148
-
149
- ```
150
- /btw does this repo have a rate limiter?
151
- /btw # prompts for the question
152
- ```
153
-
154
- `/btw` runs a one-off read-only subagent for _you_, not for the model. It uses
155
- the same policy, budget, semaphore and process-lock machinery as any run, but
156
- delivers its answer as a custom session entry, which does not participate in
157
- LLM context. The main agent keeps working and never sees the question or the
158
- answer — useful for checking something mid-task without derailing the
159
- conversation or polluting the context window.
160
-
161
- ## Backends
162
-
163
- Jev routing manages Pi-backed new dispatch only. A `backend: "codex"` or
164
- `backend: "claude"` new task is refused before any selector or provider work,
165
- including a backend inherited from agent frontmatter; the extension never
166
- silently switches it to Pi. Existing Codex/Claude runs stay manageable:
167
- `status`, `wait`, `cancel`, `steer`, `diff`, `apply` and `discard` all still
168
- work. Provider diversity is not lost, because another provider's execution
169
- model stays eligible through Pi once it is in your configured candidate list.
170
-
171
- ```ts
172
- // Refused on new work. Use the Pi-backed path so Jev can route it.
173
- { task: "Summarize this module", backend: "codex", profile: "explore" }
174
- ```
175
-
176
- The low-level SDK is a different contract: `runTasks`/`runSubagent` execute the
177
- explicit `TaskSpec` you hand them, so embedding code can still select a backend
178
- directly. Everything else (worktrees, process locks, depth limits, budgets,
179
- orphan reclaim) is backend-agnostic and applies unchanged.
180
-
181
- Capabilities differ, and **unsupported combinations are refused with an
182
- explanation rather than silently ignored**: a dropped `max_cost` or unenforced
183
- read-only profile would be a safety regression, not a minor degradation.
184
-
185
- | | `pi` (default) | `codex` | `claude` |
186
- | ------------------------------- | -------------- | ------------------------------------- | ---------------------- |
187
- | `max_cost` | yes | **refused** (reports tokens, no cost) | yes (`total_cost_usd`) |
188
- | read-only profile | tool allowlist | `--sandbox read-only` (OS-level) | `--allowedTools` |
189
- | steering / graceful wrap-up | yes | **no** (no stdin channel) | **no** (one-shot) |
190
- | `resume` | yes | yes | yes |
191
- | `context:'fork'`, `fork_resume` | yes | **refused** | yes |
192
- | `thinking` | yes | no | no |
193
- | `output_schema` | yes | yes | yes |
194
-
195
- A budget breach on a backend without steering hard-stops instead of asking the
196
- child to wrap up. Codex's read-only sandbox is enforced by the OS, which is
197
- stronger than a tool allowlist.
198
-
199
- Agent frontmatter `backend:` remains a default. New extension-managed work rejects
200
- any effective backend other than Pi, including a native backend inherited from an agent.
201
- Direct SDK specs retain the backend capabilities listed above.
202
-
203
- ## Profiles
204
-
205
- | Profile | Tools | Writes |
206
- | ---------------------------- | ------------------------------------------------------- | ------------------------------------------- |
207
- | `explore` (parallel default) | locally permitted read-only tools + Pi context tools | no project-file writes |
208
- | `review` | same as explore | no project-file writes |
209
- | `general` | Jev chooses from the full available locally permitted catalog + Pi context tools | yes for selected write-capable tools; unknown custom tools count as writable |
210
-
211
- Jev chooses individual tool names, not a capability bundle. Candidates come
212
- from the full available locally permitted catalog, not from the agent file's
213
- `tools` defaults and not from the parent's currently active tools. An explicit
214
- task `tools` list is a ceiling, and explore/review keep their read-only rule
215
- regardless of what the selector returns. An empty selection never means "all
216
- tools".
217
-
218
- For the Pi backend, the context-management tools `new_context`,
219
- `get_context_remaining`, `history`, and `notes` are added locally when the
220
- parent exposes them, so they are never a selector question. They are
221
- control-plane tools: they may update continuity notes or the remote context
222
- window, but cannot modify the child checkout or run a shell command. This
223
- exception also applies when a task supplies a narrower tool list, so Pi's
224
- `contextManagement` remains usable for configured gateway models. Locally added
225
- controls are reported in the route metadata.
226
-
227
- The finalized tool set is passed to the child as Pi's `--tools` allowlist
228
- (`--no-tools` for a true empty set). Pi 0.86.0 is the verified baseline for
229
- built-in, extension and late-registered tool enforcement; a host that cannot
230
- honor that allowlist is refused rather than silently weakened, and the extension
231
- does not claim identical behavior on untested older releases. Before the real
232
- task prompt is sent, the child is also asked to confirm the selected model and
233
- the finalized tool names through a verified private startup command; if the host
234
- cannot verify that command or the child cannot confirm both, the launch aborts
235
- with a startup diagnostic instead of running with a broader tool set.
236
-
237
- Parallel write-capable tasks sharing one checkout are rejected unless each uses
238
- `isolation: "worktree"`, distinct `cwd`, or explicit `allow_shared_writes: true`.
239
-
240
- ## Configuration
241
-
242
- Defaults can be overridden in `~/.pi/subagent.json` and per-field via env vars
243
- (env wins over file):
244
-
245
- | Setting | Env var | Default |
246
- | ----------------------- | ------------------------------------- | ------------------------------------- |
247
- | `maxTasksPerRun` | `PI_SUBAGENT_MAX_TASKS` | 8 |
248
- | `maxActiveProcesses` | `PI_SUBAGENT_MAX_ACTIVE` | 4 |
249
- | `maxQueuedTasks` | `PI_SUBAGENT_MAX_QUEUED` | 32 |
250
- | `maxGlobalActive` | `PI_SUBAGENT_MAX_GLOBAL_ACTIVE` | 16 |
251
- | `defaultTimeoutMs` | `PI_SUBAGENT_TIMEOUT_MS` | 900000 |
252
- | `maxDepth` | `PI_SUBAGENT_MAX_DEPTH` | 2 |
253
- | `killGraceMs` | `PI_SUBAGENT_KILL_GRACE_MS` | 3000 |
254
- | `sessionDir` | `PI_SUBAGENT_SESSION_DIR` | `~/.pi/subagent-sessions` |
255
- | `worktreeDir` | `PI_SUBAGENT_WORKTREE_DIR` | `~/.pi/subagent-worktrees` |
256
- | `lockDir` | `PI_SUBAGENT_LOCK_DIR` | `~/.pi/subagent-locks` |
257
- | `worktreeRetentionDays` | `PI_SUBAGENT_WORKTREE_RETENTION_DAYS` | unused (lifecycle GC) |
258
- | `sessionRetentionDays` | `PI_SUBAGENT_SESSION_RETENTION_DAYS` | unused (lifecycle GC) |
259
- | `lockRetentionDays` | `PI_SUBAGENT_LOCK_RETENTION_DAYS` | 7 |
260
- | `taskDefaults` | — | none |
261
- | `jevRouting` | - | required for new dispatch (see below) |
262
- | `graceTurns` | `PI_SUBAGENT_GRACE_TURNS` | 2 |
263
- | `stallAfterMs` | `PI_SUBAGENT_STALL_AFTER_MS` | 90000 |
264
- | `stallKillAfterMs` | `PI_SUBAGENT_STALL_KILL_AFTER_MS` | 90000 |
265
- | `maxRetries` | `PI_SUBAGENT_MAX_RETRIES` | 1 |
266
- | `widget` | `PI_SUBAGENT_WIDGET` | `background` (`off` disables) |
267
- | `notifications` | `PI_SUBAGENT_NOTIFICATIONS` | `batched` (`off` disables) |
268
- | (bin) | `PI_SUBAGENT_BIN` | auto (`process.execPath` + CLI entry) |
269
-
270
- ### Jev routing
271
-
272
- New subagent dispatches are selected by Jev, TypeSafe's structured-decision API,
273
- against a dedicated candidate list you maintain in `~/.pi/subagent.json`. Fixed
274
- routing is gone: a `modelPolicy` block produces a migration error, and an
275
- explicit `model` or `fallback_models` on new work is rejected rather than
276
- bypassing selection. Management actions (`status`, `wait`, `cancel`, `steer`,
277
- `diff`, `apply`, `discard`) never call the selector and need no credential.
278
-
279
- ```json
280
- {
281
- "jevRouting": {
282
- "selectorModel": "jev-latest",
283
- "apiKeyEnv": "TYPESAFE_API_KEY",
284
- "timeoutMs": 15000,
285
- "models": [
286
- {
287
- "model": "<provider/model-id>",
288
- "description": "<your characteristics notes, Chinese allowed>",
289
- "thinking": "<optional opaque Pi thinking default>"
290
- }
291
- ]
292
- }
293
- }
294
- ```
295
-
296
- - `selectorModel` defaults to the stable alias `jev-latest`. Pin an exact
297
- version to control which selector version is requested. This does not guarantee
298
- deterministic choices; the extension records the version that actually answered.
299
- - `apiKeyEnv` defaults to `TYPESAFE_API_KEY`. Only the variable *name* lives in
300
- the file. Set the credential in your local environment; the extension reads it
301
- at request time and never writes it to config, prompts, argv, logs or results.
302
- - `timeoutMs` defaults to 15000 and must be an integer between 100 and 600000.
303
- It bounds one logical task selection, including all its HTTP requests and queue
304
- waits. Parallel workers each have a selection allowance, still capped by their
305
- absolute task `timeout_ms` deadline. Deferred synthesis has a separate allowance.
306
- - `models` holds 1 to 255 entries, each with an exact `provider/model-id` and a
307
- non-blank description. Those descriptions are what Jev matches against your
308
- task, so write them the way you would explain the model to a colleague.
309
- `thinking` is optional.
310
-
311
- Plan and background-start native usage attachments are limited to 1024 selector
312
- HTTP receipts per invocation. Larger requests fail with a request-splitting error;
313
- background work has not started at that point. Previously incurred selector tokens
314
- remain in the ledger. This bounds an atomic delivery record, not the number of tools
315
- Jev may consider within each task.
316
-
317
- The candidate list is intersected with the models the local Pi registry reports
318
- as available. A configured model Pi cannot resolve is not eligible, and an empty
319
- eligible pool fails before any request. Adding a model anywhere else in Pi does
320
- not authorize it, and legacy `modelPolicy` entries are never imported
321
- automatically. An unknown `jevRouting` field is an error, not a silent default.
322
-
323
- Jev receives only the current delegated task text, your model IDs and
324
- descriptions, candidate tool names and descriptions, and the permission/output
325
- requirements it needs to choose. It does not receive repository files,
326
- conversation history, full system prompts, persona text or tool parameter
327
- schemas. Task text and descriptions are user content and may contain sensitive
328
- material, so treat what you delegate as disclosure to TypeSafe.
329
-
330
- Every new extension-managed dispatch routes through Jev: `task`/`tasks[]`,
331
- `action:"plan"`, `/btw`, resume, fork, locally permitted nested dispatch and the
332
- optional `synthesis` child. `action:"plan"` calls Jev and runs the same local
333
- preflights, returns the resolved model/tool plan and the selector usage, and
334
- creates no child or run entry. A later dispatch selects again; there is no cached
335
- decision to reuse. If optional synthesis selection fails, the worker plan and its
336
- usage stay valid and synthesis is reported as blocked with its diagnostic.
337
-
338
- `thinking` is optional and is an opaque Pi thinking-level string. Common values
339
- include `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, and `max`, but the
340
- package does not remap or restrict model-specific values. Pi receives the value
341
- unchanged and decides whether the active model supports it. Resolution order is:
342
- explicit task `thinking` > agent frontmatter `thinking` > profile
343
- `taskDefaults.<profile>.thinking` > the selected candidate's optional `thinking`
344
- > the parent session's thinking level. Jev never chooses a thinking level.
345
-
346
- The extension re-reads `jevRouting` on each dispatch and injects the current
347
- routing guidance into the parent prompt, so configuration edits reach the next
348
- decision without a code change. Missing or invalid `jevRouting`, or a missing
349
- credential environment variable, rejects new dispatch and plan with a remedy;
350
- management stays available.
351
-
352
- The mandatory routing above is the Extension dispatch contract. The stable SDK
353
- exports (`runTasks` and `runSubagent`) are trusted low-level library APIs: they
354
- execute the explicit `TaskSpec` you pass and perform no implicit routing, config
355
- discovery or network call. Library callers own model and tool choice, and must not
356
- read these SDK calls as Jev enforcement.
357
-
358
- ### Named agent files
359
-
360
- Define reusable subagent personas as markdown files, discovered from the same
361
- conventional roots skills use (higher root wins name conflicts):
362
-
363
- | Priority | Location | Scope |
364
- | -------- | ----------------------------------------------------------------------- | --------------------------- |
365
- | 1 | `.pi/agents/<name>.md` | project (authoritative) |
366
- | 2 | `.agents/agents/<name>.md` | shared cross-tool workspace |
367
- | 3 | `$PI_CODING_AGENT_DIR/agents/<name>.md` (default `~/.pi/agent/agents/`) | global |
368
-
369
- The markdown body becomes the child's appended system prompt; frontmatter
370
- supplies defaults using the same snake_case names as the tool parameters:
371
-
372
- ```md
373
- ---
374
- description: Security-focused code reviewer
375
- # Legacy model/fallback fields are ignored; Jev routing owns model/tool choice.
376
- thinking: high
377
- profile: review
378
- max_turns: 20
379
- spawns: false # or "*", "scout", "[reviewer, scout]"
380
- ---
381
-
382
- You are a security auditor. Review code for injection flaws, auth issues,
383
- and sensitive data exposure. Report findings with file:line evidence and
384
- severity ratings.
385
-
386
- @include shared/review-checklist.md
387
- ```
388
-
389
- Agent files may also pin a structured contract with
390
- `output_schema: {"type": "object", …}` (single-line inline JSON) or
391
- `output_schema: @contract.json` (path relative to the agent file).
392
-
393
- `spawns:` controls which agents a child of this persona may spawn:
394
- `false` disables further nesting (no tool registered in that child),
395
- `"*"` (or omit) is unrestricted, and a comma/bracket list is an allowlist
396
- (agentless tasks are rejected under an allowlist). The policy is passed to
397
- the child via `PI_SUBAGENT_SPAWNS` and enforced on each subsequent spawn.
398
-
399
- Body lines that consist solely of `@include relative/path.md` expand that
400
- file one level deep (relative to the agent file, same 64KB/symlink guards
401
- as `@contract.json`). Missing or rejected includes leave the line verbatim;
402
- includes do not recurse.
403
-
404
- Invoke with `{ task: "…", agent: "reviewer" }`. The agent file supplies
405
- persona, capability, thinking and budget defaults only: model and tool selection
406
- stay with Jev routing, and a legacy `model`/`fallback_models` in frontmatter is
407
- ignored. An explicit `system_prompt` appends after the persona body.
408
- Profiles still enforce capability: `profile: review` filters candidates to read-only
409
- tools. Legacy agent `tools` defaults are ignored; an explicit task `tools` list
410
- requesting write tools under review fails closed. The agent catalog is advertised in the tool's
411
- system-prompt guidelines (session start) and in bare `status` output (live),
412
- and file changes are picked up within seconds — no restart needed.
413
-
414
- ### Per-profile task defaults
415
-
416
- `taskDefaults` in `~/.pi/subagent.json` remains available for non-model
417
- fields such as thinking, budgets, and retry counts. Its legacy `model` and
418
- `fallbackModels` fields are ignored; model and tool routing belong only to
419
- `jevRouting`. A profile `thinking` value overrides the selected candidate's
420
- optional `thinking` default. Invalid fields are dropped field-by-field.
421
-
422
- Notes on behavior:
423
-
424
- - `timeout_ms` is the absolute task deadline: local preflight, Jev selection,
425
- setup, queue time and runtime all count against it, and selection cannot reset
426
- it. Timed-out tasks report `state: "timeout"` with
427
- `timeoutPhase: "queued"|"starting"|"running"` so agents can retry capacity
428
- issues without confusing them for task failures.
429
- - Budget stops (`max_turns`, `max_cost`) trigger a **graceful wrap-up**: the
430
- child is steered to produce its final answer NOW and allowed `graceTurns`
431
- more turns before SIGTERM. Results end as `partial` with `wrappedUp: true`
432
- when the child concluded in time. `graceTurns: 0` restores immediate stops.
433
- - A **stall watchdog** flags children with no protocol activity for
434
- `stallAfterMs` (a liveness probe distinguishes quiet-but-thinking from dead),
435
- then kills after `stallKillAfterMs` more silence — feeding automatic retry
436
- instead of burning the whole timeout.
437
- - **Transient failures retry automatically** (queue timeouts, stalls, spawn
438
- errors, provider errors) up to `maxRetries` extra attempts on the already
439
- selected model and tool set. There are no emergency or fallback models, and a
440
- quality or budget failure never reselects. Usage accumulates across attempts;
441
- results record `attempts`. Task-quality failures (nonzero exit with complete
442
- protocol, cancellations, budget stops, running timeouts) never retry.
443
- - `context: "fork"` starts a single child from a real branched copy of the
444
- parent conversation (`--fork` on the parent's session file). It requires a
445
- persisted parent session, cannot combine with `resume`, and is rejected for
446
- parallel fanout (context duplication × N is a cost bug, not a feature).
447
- - **Structured output** (`output_schema`): the contract is appended to the
448
- child's system prompt; the final message must end with a fenced
449
- `json:result` block. Validation runs parent-side against a dependency-free
450
- JSON-Schema subset (type/properties/required/items/enum/const — unknown
451
- keywords are ignored, never rejected). Invalid output triggers **one
452
- steer-based repair round**; still-invalid results end `partial` with
453
- `structuredError` set and the raw text delivered — paid work is never
454
- discarded. Validated parallel results feed the `synthesis` child as clean
455
- JSON instead of prose.
456
- - **Arg repair**: double-encoded task text (literal `\n` / `\"` escapes from
457
- LLM re-encoding) is conservatively de-mangled once at validation time.
458
- Identifier fields and paths are never touched.
459
- Protocol streams truncated after useful assistant output also end as `partial`.
460
- - Aborting a `wait` returns immediately without cancelling the background run.
461
- - Child processes are launched via the same Node runtime + CLI entry as the
462
- parent when possible (`PI_SUBAGENT_BIN` overrides). Bare `pi` on PATH is only
463
- a logged last resort.
464
- - Direct resume is exclusive **across processes** via durable locks under
465
- `lockDir`. Lost runs block resume until startup orphan reconciliation kills
466
- (or confirms dead) the recorded child process group.
467
- - `maxGlobalActive` bounds concurrent children across every Pi parent process
468
- on the machine (in addition to the per-session semaphore).
469
- - Nested children at the depth ceiling do not re-register the subagent tool;
470
- only top-level parents run maintenance/orphan reclaim/worktree GC.
471
- - Preserved worktrees live under `worktreeDir` (durable, not `/tmp`) and are
472
- garbage-collected on startup by **lifecycle**, not wall-clock retention: once
473
- a run is over (not live, past a 1h concurrency race guard), the worktree's
474
- unique work is archived as one applyable patch under
475
- `<repo-container>/_patches/` and the directory is reclaimed immediately.
476
- Branches holding commits that exist on no other ref are never deleted.
477
- `diff`/`apply`/`discard` transparently fall back to the archived patch when
478
- the directory is already gone. Live runs are never swept: the current
479
- session's live worktrees plus any worktree recorded on a running run record
480
- (concurrent Pi processes) are shielded machine-wide.
481
- - Startup GC sweeps **every** repo container under `worktreeDir`, not just the
482
- current checkout's, so repos you stop visiting are still reclaimed. A
483
- container whose base repo no longer exists is kept and reported, never
484
- deleted — its worktrees' object stores lived inside the deleted repo, so
485
- unique work cannot be distinguished from a pristine checkout, let alone
486
- archived. Empty containers (no worktrees, no archived patches) are removed.
487
- - Child session transcripts are likewise distilled on lifecycle: when a run is
488
- over and nothing on the parent branch references its session, the transcript
489
- is reduced to a small `.digest.json` (task, final output, model, usage,
490
- turn/tool/error counts) and the raw `.jsonl` is deleted. Resume needs the
491
- transcript, so anything referenced or busy machine-wide is kept.
492
- - `keep_background: true` on a task keeps processes the child intentionally
493
- backgrounded (e.g. dev servers) alive after a clean exit.
494
- - `include_wip: true` (with `isolation: "worktree"`) seeds the worktree with the
495
- parent checkout's uncommitted changes so the child sees your dirty baseline.
496
- `diff`/`apply` subtract that baseline when clean, else report the combined
497
- delta with an explicit `[includes parent WIP]` warning.
498
-
499
- ## Using the runner as a library
500
-
501
- Import the stable public SDK from the package root or the explicit `/sdk`
502
- subpath — do not reach into `src/*` internals (those paths are not part of the
503
- supported contract):
504
-
505
- ```ts
506
- import {
507
- runTasks,
508
- runSubagent,
509
- ChildRunner,
510
- WorktreeManager,
511
- Semaphore,
512
- ProcessLockManager,
513
- addUsage,
514
- normalizeUsage,
515
- emptyUsage,
516
- type TaskSpec,
517
- type TaskResult,
518
- type RunState,
519
- type UsageStats,
520
- } from "@cr1ms0n/pi-subagent/sdk";
521
- ```
522
-
523
- The package root is an alias for the same SDK:
524
- `import { runTasks } from "@cr1ms0n/pi-subagent"`.
525
-
526
- The Extension dispatch path routes every new task through Jev, so callers never
527
- pass a model to it. The low-level SDK is the opposite contract: it is
528
- explicit-spec and performs no implicit routing, config discovery or network
529
- call, so embedding code supplies the model (and tool list) it resolved itself.
530
- This placeholder is illustrative only and configures nothing:
531
-
532
- ```ts
533
- const task: TaskSpec = {
534
- task: "Audit src/ for unsafe parsing",
535
- profile: "explore",
536
- model: "<provider/model-id resolved by your embedding code>",
537
- timeoutMs: 10 * 60_000,
538
- };
539
- ```
540
-
541
- Prefer `runTasks()` for multi-task / worktree orchestration (same path the
542
- extension and pi-workflows use). `runSubagent()` runs a single child process
543
- directly without the extension host, but durable coordination is **opt-in**.
544
- Pass both `locks` (a `ProcessLockManager`) and a stable `runId` if you want
545
- global concurrency slots and orphan reclaim to see the child. Without those
546
- options no durable run record is written, so a parent restart cannot reclassify
547
- the process and nested children vanish from reconcile. There is intentionally
548
- no implicit default lock manager — embedding code that needs durability must
549
- construct and share one.
550
-
551
- The Pi extension entry is unchanged: package `pi.extensions` still points at
552
- `./extensions/subagent.ts`.
553
-
554
- ## Design invariants
555
-
556
- 1. A run belongs to one parent session and cannot update another session.
557
- 2. Per-session + machine-wide process caps and nesting depth limits prevent process storms.
558
- 3. Cancellation prevents queued tasks from spawning.
559
- 4. Direct resume of a child session is exclusive **across processes** via durable locks.
560
- 5. Tool responses are capped to ~50KB/2000 lines; full output lives in
561
- artifacts and `~/.pi/subagent-sessions`.
562
- 6. Status is compact; wait is the one-shot deliverable.
563
- 7. On parent session shutdown, live children are aborted and awaited briefly.
564
- 8. On parent (re)start, orphan process groups recorded under `lockDir` are reaped
565
- before any resume is allowed for the matching child session.
566
- 9. Provider-reported usage is counted once per root message and terminal child run;
567
- selector usage is a separate category counted once per selector request ID.
568
- 10. Protocol completion prefers `agent_settled` (falls back to non-retrying `agent_end`).
569
- 11. Jev selection precedes every new extension-managed launch, and the selected
570
- tool subset is enforced by Pi's CLI allowlist; empty selection never means all tools.
571
-
572
- ## Layout
573
-
574
- ```
575
- src/
576
- index.ts # stable public SDK entry (@cr1ms0n/pi-subagent)
577
- extension.ts # Pi wiring only
578
- schema.ts # request schemas (subagent + subagent_wait)
579
- btw.ts # /btw side questions (model-hidden entries)
580
- backend.ts # backend adapter seam + capability gate
581
- backends/ # pi | codex | claude adapters (invocation + parser)
582
- policy.ts # profiles, normalization, write guards, agent resolution
583
- routing-types.ts # selector DTOs, decisions, receipts, local resource limits
584
- routing-policy.ts# strict jevRouting parser, candidate list, routing guidance
585
- jev-router.ts # injectable Jev transport, response validation, receipts
586
- dispatch-preflight.ts # bounded read-only checks before selector work
587
- startup-check.ts / child-preflight.ts # private child model/tool verification
588
- dispatch-routing.ts # prepare -> select -> finalize for one or many tasks
589
- agents.ts # named agent files (.pi/agents/, .agents/agents/, global)
590
- launch.ts # resolve child pi via execPath / PI_SUBAGENT_BIN
591
- process-lock.ts # durable session locks, global slots, orphan records
592
- worktree.ts # git worktree isolation + diff/apply/discard
593
- orchestrator.ts # multi-task execution, worktree prep, bounded same-model retry
594
- runner.ts # child process lifecycle, RPC channel, steering,
595
- # graceful budget wrap-up, stall watchdog
596
- protocol.ts # Pi RPC/JSON event parser (agent_settled-aware)
597
- semaphore.ts # per-session concurrency limit
598
- registry.ts # session-scoped run state + durable resume locks
599
- persistence.ts # parent-session event folding
600
- usage.ts # root/subagent/combined usage ledger
601
- output.ts # exact global output caps
602
- notifications.ts # batched background-run completion notifications
603
- format.ts / ui.ts# renderers, ambient widget, /subagents overlay
604
- ```
605
-
606
- ## Develop
607
-
608
- This checkout has no `scripts`, no `devDependencies`, no `tsconfig.json` and no
609
- test runner, so `npm install`, `npm run typecheck`, `npm test` and
610
- `npm run pack:check` are not defined here. The checks that do run locally are
611
- offline:
612
-
613
- ```bash
614
- npm pack --dry-run --json
615
- ```
616
-
617
- That verifies the publishable file list matches `package.json` `files` and leaks
618
- no backup, transcript, session data, research output or generated bundle. Source
619
- changes are parse/type-strip checked by running the global Pi install's bundled
620
- esbuild over every `src/` and `extensions/` file; that catches malformed
621
- TypeScript only, not type errors or peer API mismatches. Provider-free fixtures
622
- and an injected fake selector transport, run with the already installed Pi runtime
623
- rather than a repository test framework, exercise routing behavior without any
624
- model call. See [.trellis/spec/backend/quality-guidelines.md](.trellis/spec/backend/quality-guidelines.md)
625
- for the exact commands and what each check does and does not prove.
626
-
627
- ## Cost accounting
628
-
629
- `status`, `/subagent-cost`, and the `/subagents` overlay header show separate
630
- **root**, **subagent**, and **combined** totals based on provider-reported
631
- usage. On Pi builds after v0.80.10, delivered runs also report their total
632
- usage natively on the tool result
633
- ([pi#6671](https://github.com/earendil-works/pi/pull/6671)), so Pi's own
634
- footer, `/session`, and RPC totals include subagent spend — exactly once per
635
- run; older Pi hosts ignore the field. Nested usage reported by a child's tool
636
- results (e.g. grandchild subagents) folds into the run's totals and budgets.
637
- The extension footer stays terse (running/ready counts only). Delivery and
638
- replay do not double count runs. See
639
- [docs/COST-ACCOUNTING.md](./docs/COST-ACCOUNTING.md).
640
-
641
- Jev selection is billed separately from execution. TypeSafe reports tokens, not
642
- currency, so the ledger shows routing tokens as their own category, counts each
643
- selector request once by its request ID (including plan and pre-spawn failures),
644
- and marks routing cost as **unreported** rather than free. Numeric dollar totals
645
- exclude unreported routing spend, and `max_cost` caps provider-reported execution
646
- cost only; it does not cap TypeSafe charges. Route metadata (selected model,
647
- selected tools, locally added controls, selector version, confidence, outcome,
648
- latency) travels with the run alongside usage.
649
-
650
- ## Roadmap
651
-
652
- Planned work — agent spawn policies, dry-run validation, engine hardening,
653
- live transcripts — lives in [docs/ROADMAP.md](./docs/ROADMAP.md) (rationale
654
- and design sketches) and [docs/PLAN.md](./docs/PLAN.md) (execution contract:
655
- work breakdown, acceptance criteria, test plans, and release gates per phase).
656
-
657
- ## Security
658
-
659
- See [docs/SECURITY.md](./docs/SECURITY.md). Pi packages run with full system
660
- access—review source before installing third-party packages.
661
-
662
- ## Status
663
-
664
- v0.1 focuses on the correct lifecycle engine:
665
-
666
- - process + session ownership
667
- - budgets, caps, profiles
668
- - persistence + inspector
669
- - worktree isolation helpers
670
-
671
- Named agent catalogs and automatic chain workflows are intentionally deferred
672
- until the core is battle-tested.
1
+ # Pi Smart Subagents
2
+
3
+ [English](README.md) | [简体中文](README.zh-CN.md)
4
+
5
+ Run isolated child agents in [Pi](https://pi.dev/), with Jev selecting a model and tools for each task.
6
+
7
+ An independent community fork of Luke Parke's `@parke.dev/pi-subagent` from [LukasParke/pi-extensions](https://github.com/LukasParke/pi-extensions/tree/main/packages/pi-subagent). It retains the upstream engine's named agents, parallel and background tasks, worktrees and usage accounting, and adds Jev routing with child capability verification.
8
+
9
+ ---
10
+
11
+ <a id="quick-start"></a>
12
+ ### Installation
13
+
14
+ ```bash
15
+ pi install npm:@cr1ms0n/pi-subagent
16
+ ```
17
+
18
+ ---
19
+
20
+ <a id="delegation"></a>
21
+ ### Usage
22
+
23
+ Before your first task, configure your TypeSafe API key and candidate models in `~/.pi/subagent.json`. See the [configuration example](docs/REFERENCE.md#jev-routing).
24
+
25
+ Then ask Pi, for example:
26
+
27
+ > Use a read-only subagent to review this project's directory structure and summarize the main modules.
28
+
29
+ Jev chooses the model and tools. Open `/subagents` to inspect tasks and `/subagent-cost` to view usage.
30
+
31
+ See the [usage reference](docs/REFERENCE.md#quick-usage) for parallel tasks, background work, worktrees and structured results, or the [TUI guide](docs/UX.md) for keyboard controls.
32
+
33
+ ---
34
+
35
+ <a id="license"></a>
36
+ ### License
37
+
38
+ [MIT](LICENSE). Copyright (c) 2026 Luke Parke. Fork maintained by cr1ms0n (awoaCrim). Preserve the original copyright and license when redistributing this work.
39
+
40
+ Thanks to [Linux.do](https://linux.do/).