pi-ptc-subagents 5.0.0 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,301 +1,106 @@
1
1
  # pi-ptc-subagents
2
2
 
3
- Subagents and programmable tool calling for [pi](https://pi.dev).
4
-
5
- One package, two capabilities:
6
-
7
- - **Subagent fan-out** — dispatch a task to a fresh `pi` subprocess running a named agent
8
- (from `~/.pi/agent/agents/<name>.md` or `.pi/agents/<name>.md`), in isolation. Foreground
9
- await, or background with a lifecycle you can inspect and stop.
10
- - **Programmable tool calling** — `ptc_run_code` / `ptc_workflow` run a JS/TS program in a
11
- worker; the program composes the session's tools as `tools.<name>(args)`, and only the
12
- program's return value plus its logs come back to the model.
13
-
14
- pi ships its own programmable tool calling (`codemode` — a QuickJS sandbox that cannot spawn
15
- processes). This package is the part that can spawn processes — subagents, background tasks,
16
- a real Node runtime inside programs — and it composes with `codemode` instead of replacing
17
- it. On a session where pi's `codemode` orchestrates, this package registers underneath it as
18
- the execution layer ([ADR-0025](./docs/adr/0025-extension-surface-is-a-setting.md)); the deep
19
- dive lives in [docs/usage/surface.md](./docs/usage/surface.md).
20
-
21
- **Source is open.** This repository is public and the source is here — `dist/` on npm is the
22
- compiled form of what you read below. Contributions go through pull requests: see
23
- [CONTRIBUTING.md](./CONTRIBUTING.md) for the gate your PR has to pass, and
24
- [SECURITY.md](./SECURITY.md) before reporting anything. Releases are cut from `main` by the
25
- maintainer only; if you find something you think needs a release, open an issue and say so.
3
+ Programmable tool calling and subagent dispatch for [pi](https://pi.dev).
26
4
 
27
- ## Install
5
+ pi calls tools one at a time. This package lets the model write a program that calls them
6
+ in one go — and lets that program spawn pi subagents. Two capabilities. One package.
28
7
 
29
- ```bash
30
- pi install npm:pi-ptc-subagents
31
8
  ```
9
+ full_ptc model ──▶ ptc_run_code ──▶ program ──┬──▶ tools.read · grep · edit · … (session tools)
10
+ └──▶ pi_task_ptc ──▶ pi child ──▶ report
32
11
 
33
- From a local checkout: `pnpm install && pnpm run build && pi install /abs/path/to/this/repo`.
12
+ codemode_ptc model ──▶ codemode script ───────────┬──▶ tools.read · grep · edit · … (session tools)
13
+ └──▶ pi_task_codemode ──▶ pi child ──▶ report
34
14
 
35
- pi reads the `pi.extensions` manifest field — install it and the extension is on for the next
36
- pi startup, no extra setup.
15
+ one call in, one result back — never the calls in between.
16
+ ```
37
17
 
38
- **What that command actually writes is in [docs/how-to-install.md](./docs/how-to-install.md)** — worth reading before you install, if you would rather know where the settings entry goes.
18
+ Both surfaces are detected from the session. There is no setting.
39
19
 
40
- Documentation site: **[pi-ptc-subagents on GitHub Pages](https://a1121611810.github.io/pi-ptc-subagents/)**. Every page under its Documentation section is projected at build time from the files in `docs/`, so it never says anything this README does not.
20
+ ## What you get
41
21
 
42
- ## Tools
22
+ - **One program, many tool calls.** `ptc_run_code` and `ptc_workflow` run TypeScript in a
23
+ worker. The program uses the session's tools as `tools.read(...)`, `tools.grep(...)`.
24
+ Only the result, the logs, and truncated output come back. On a codemode session, pi's
25
+ own codemode runs the script; this package supplies the task door underneath it.
26
+ - **Real subagents.** `pi_task_ptc` inside a program, `pi_task_codemode` inside a codemode
27
+ script. Every task gets its own pi process and its own report. Busy means wait, not reject.
28
+ - **Visibility.** `ptc_task_list`, `ptc_task_output`, `ptc_task_stop` appear when there is
29
+ a task to look at.
30
+ - **Search, where it belongs.** `web_ds_search` registers only inside dispatched children.
31
+ Off by default.
43
32
 
44
- - `ptc_subagent` — the top-level subagent face: dispatch a fresh `pi` subprocess for a task
45
- without writing a program. Registered only when the detected surface is `subagents`
46
- ([ADR-0025](./docs/adr/0025-extension-surface-is-a-setting.md)); on `full` the same
47
- capability is the `pi.dispatch` binding inside a program.
48
- - `ptc_run_code` — run a JS/TS program that composes tool calls; the program reaches the
49
- session's enabled built-in tools as `tools.read(...)`, `tools.write(...)`, etc.
50
- (`read`, `edit`, `write`, `grep`, `find`, `ls`), plus `tools["pi.dispatch"]`;
51
- its return value and `console.log` output are reported back. `tools.bash(...)` is an
52
- **opt-in** — see [Trust posture](#trust-posture-read-me).
53
- - `ptc_workflow` — structured variant with `meta` + plain-JSON `args`, plus the workflow
54
- helpers (`log`, `phase`, `parallel`, `pipeline`). There is no `agent()` helper on either
55
- surface.
56
- - `ptc_task_list` / `ptc_task_output` / `ptc_task_stop` — manage background dispatches
57
- (see [Background dispatch](#background-dispatch)). They stay available when PTC mode is off.
33
+ A call looks like this — the same shape on both surfaces:
58
34
 
59
- Long output follows pi's own truncation contract ([ADR-0015](./docs/adr/0015-pi-truncation-contract.md)):
60
- the text block keeps the tail (50 KB / 2000 lines), the untruncated text is written to a temp
61
- file the next program can `tools.read`, and the collapsed row shows `truncated` in its meta.
35
+ ```ts
36
+ // full_ptc, inside ptc_run_code
37
+ const r = await tools.pi_task_ptc({ task: "audit the auth code", wait: true });
62
38
 
63
- ## Dispatch (fan-out to per-call pi subprocesses)
39
+ // codemode_ptc, inside a codemode script
40
+ const r = await tools.pi_task_codemode({ task: "audit the auth code", wait: true });
64
41
 
65
- PTC programs can spawn a fresh `pi` subprocess per call via the **`pi.dispatch(...)`** binding
66
- ([ADR-0016](./docs/adr/0016-ptc-dispatch-binding.md)) — the child loads the named agent's
67
- markdown, runs that agent's tool set and system prompt in isolation, and returns a structured
68
- result. Bindings live in the one `tools` table — there is no `pi` global in the worker — so
69
- the binding is called as `tools["pi.dispatch"]({ … })` with a single object argument.
42
+ return r.report?.summary ?? r.text;
43
+ ```
70
44
 
71
- ```ts
72
- // inside a ptc_run_code program
73
- const result = await tools["pi.dispatch"]({
74
- agent: "scout",
75
- task: "find all auth code in src/",
76
- cwd: process.cwd(),
77
- });
78
-
79
- // result.status: "fulfilled" | "rejected" (the binding never throws)
80
- // result.text: final assistant text
81
- // result.usage: { input, output, cacheRead, cacheWrite, cost, turns }
82
- // result.observedProvider?, result.observedModel?: the provider/model the child actually ran
83
- // result.exitCode, result.durationMs, result.stderr?, result.errorMessage?
45
+ ## Install
46
+
47
+ ```bash
48
+ pi install npm:pi-ptc-subagents
84
49
  ```
85
50
 
86
- **Fan out in parallel** with the rest of PTC's tools — dispatch is a binding, not a
87
- model-facing lifecycle tool, so the dispatcher handles concurrency the same way it does for
88
- any other tool call:
51
+ From a local checkout:
89
52
 
90
- ```ts
91
- const [read, scoutA, scoutB] = await Promise.all([
92
- tools.read({ path: "package.json" }),
93
- tools["pi.dispatch"]({ agent: "scout", task: "review auth" }),
94
- tools["pi.dispatch"]({ agent: "scout", task: "review db" }),
95
- ]);
53
+ ```bash
54
+ pnpm install && pnpm run build && pi install /abs/path/to/this/repo
96
55
  ```
97
56
 
98
- **The child report.** A dispatched child returns more than prose: under the report contract
99
- ([ADR-0032](./docs/adr/0032-child-report.md)) it hands back a `summary`, `findings` each
100
- carrying independent evidence, `files_touched`, and token usage the host measured. On the
101
- background path that same measurement is written to the task record itself, on every terminal
102
- close for a child that started — so a child that reported nothing, or failed, still shows what it
103
- cost. The result names the channel that delivered it — `"tool"`, `"prompt-json"`, or `"none"` — and
104
- `"none"` means the child ran but did not comply, which must not read as "returned nothing".
105
- An agent opts out with one frontmatter line, `childReport: false`, and its channel reads
106
- `"opted-out"`. `ptc_subagent` renders the same report into the text the model reads, bounded
107
- at 20 findings with the withheld count stated in-band.
108
-
109
- **Bounded.** Three knobs keep fan-out from running away:
110
-
111
- - `PtcConfig.dispatchConcurrency` (default **8**) — one counter per pi session, spent by
112
- foreground dispatches, the `ptc_subagent` front, and live background children. A call over
113
- the cap resolves `{ status: "rejected", errorMessage: "dispatch concurrency limit reached" }`
114
- instead of queueing.
115
- - `PtcConfig.maxDispatchDepth` (default **3**) — recursion bound; the child sees a
116
- `<pi-ptc-context depth="N" max-depth="M">` hint so it can budget its own recursion.
117
- - `signal` — when the parent run is cancelled, every in-flight child gets `SIGTERM` then
118
- `SIGKILL` after a 5-second grace window.
119
-
120
- ### The child's model
121
-
122
- A dispatched child is a fresh `pi` subprocess and resolves its own model, first match wins:
123
-
124
- 1. the dispatch's `model` argument;
125
- 2. the named agent definition's frontmatter `model:`;
126
- 3. **the caller session's current model** — read from the pi session that spawns the child;
127
- 4. pi's configured default.
128
-
129
- Tier 3 is the behaviour change of [ADR-0044](./docs/adr/0044-a-dispatch-inherits-the-session-model.md):
130
- a session that switched provider or model mid-conversation carries that switch into the children it
131
- dispatches, instead of silently running them on pi's global default. Resolution happens once, at
132
- spawn — a model switched afterwards does not change a child that is already running, and a nested
133
- dispatch inherits the child's own session model. Pin the model at the call site or in the agent
134
- definition to opt out; there is no config key for it, and a direct library caller (or a pi that
135
- exposes no session model) falls back to pi's default exactly as before.
136
-
137
- The result says which model the child actually ran: `observedProvider` / `observedModel` on a foreground
138
- `DispatchResult`, the same optional pair on a background task record (which `ptc_task_output`
139
- renders as a line), and a `model: provider/id` line in `ptc_subagent`'s text. Absence means no
140
- observed assistant turn named the pair — no turn, a supported pi that does not report it, or an
141
- old record — never "unknown model". When the child reported its own failure, a
142
- rejected dispatch now states that error verbatim as `errorMessage` instead of the generic
143
- `dispatch produced no final text`, so the foreground failure path matches the background one. Full
144
- guide: [`docs/usage/bgdispatch.md`](./docs/usage/bgdispatch.md#which-model-the-child-runs).
145
-
146
- ### Background dispatch
147
-
148
- Foreground `pi.dispatch` blocks the program until the child exits. Pass `background: true` to
149
- spawn the child and return immediately with a `DispatchHandle`
150
- ([ADR-0022](./docs/adr/0022-background-dispatch.md)); the child outlives both the program and
151
- the turn:
57
+ To run one build without touching your settings:
152
58
 
153
- ```ts
154
- const handle = await tools["pi.dispatch"]({
155
- agent: "scout",
156
- task: "audit the auth code",
157
- background: true,
158
- label: "auth audit", // defaults to task.slice(0, 64)
159
- });
160
- // handle: { taskId: "01J…", label: "auth audit", status: "running" }
59
+ ```bash
60
+ pi -ne -e packages/entry/dist/index.js
161
61
  ```
162
62
 
163
- A detached pump drives the task's lifecycle (`running` -> `succeeded` / `failed` / `canceled`
164
- / `lost`). The model observes it with three always-on tools — they survive `/ptc off` and are
165
- registered on every surface:
63
+ ## Surfaces
166
64
 
167
- - `ptc_task_list({ status?, limit? })` — this session's tasks, newest first (default limit 100).
168
- - `ptc_task_output({ taskId, sinceBytes? })` — a task's captured output, tail-truncated to the
169
- 50 KB / 2000-line contract (ADR-0015).
170
- - `ptc_task_stop({ taskId, reason? })` — ask a running task to stop.
65
+ - **`full_ptc`** — the PTC pair is model-facing. Programs reach tasks with `pi_task_ptc`.
66
+ - **`codemode_ptc`** — pi's codemode orchestrates. Scripts reach tasks with
67
+ `pi_task_codemode`; the PTC pair is not registered at all.
171
68
 
172
- Background tasks count against the same `dispatchConcurrency` for their whole lifetime and
173
- share the `maxDispatchDepth` bound. A background task is owned by the dispatching pi process
174
- (ADR-0023): it survives programs, turns, and `/ptc off`, and ends when that session ends. Full
175
- guide: [`docs/usage/bgdispatch.md`](./docs/usage/bgdispatch.md).
69
+ Which one you get depends on whether `codemode` is loaded and callable. Nothing to configure.
176
70
 
177
- ## TUI rendering
71
+ ## PTC mode
178
72
 
179
- Both tools register custom `renderCall` / `renderResult` hooks, so a PTC run reads as a program
180
- rather than as yet another file operation ([ADR-0013](./docs/adr/0013-ptc-row-compact-summary.md)):
73
+ On a `full_ptc` session, PTC mode moves the built-in tools behind the program, so writing
74
+ one becomes the shortest path. `/ptc mode off` restores them. `/ptc off` stops new
75
+ dispatches and withdraws the orchestration tools; running tasks keep running.
181
76
 
182
- ```
183
- PTC Find AssistantMessageComponent instantiations
184
- ├─ file: "chat-viewport.ts" • 1 output line · 1.42s
185
- ├─ instantiations: Array(3)
186
- │ ├─ [0] {file: "chat-viewport.ts", line: 23}
187
- │ ├─ [1] {file: "chat-viewport.ts", line: 47}
188
- │ └─ [2] {file: "chat-viewport.ts", line: 91}
189
- └─ totalLines: 47
190
- ```
77
+ ## Configuration
191
78
 
192
- The completion value renders as a **value tree** — one row per property, nested containers
193
- behind `├─` / `└─` / `│`, capped at 4 levels deep / 6 children per container / 120 characters
194
- per row, with the withheld amount stated in-band. A scalar is one line (`→ 47`, `done`, or
195
- `failed: <reason>` in red). The text block the model reads is a separate contract with its own
196
- bounds ([ADR-0012](./docs/adr/0012-model-facing-result-text.md)): a compact value under 100
197
- characters stays on one line, every line caps at 200. Nothing is ever printed as escaped JSON.
198
-
199
- The `ptc_subagent` call row follows the same rule: it reads as a heading — agent, state flags, and
200
- a pinned model when the caller set one — plus one task preview line and a withheld count, and
201
- `ctrl+e` expands the `label` / `why` rows and the task block (the first task line plus up to
202
- `MAX_LOG_LINES_EXPANDED` continuation lines). A dispatch's long prose is never dumped as its
203
- JSON-escaped argument string.
204
-
205
- ## Images
206
-
207
- An image read _inside_ a program — `await tools.read({ path: "shot.png" })` — is attached to
208
- the PTC tool result as a real image block, so the model sees the picture instead of a marker
209
- string or a wall of base64 ([ADR-0014](./docs/adr/0014-image-hoisting.md)). Nothing is capped
210
- or deduped: every image the program's tool calls produced is attached, in call order; the
211
- collapsed row's meta shows the count (`· 1 image`).
212
-
213
- ## PTC default mode
214
-
215
- On a TUI start the session narrows its tool loadout so the built-in tools are reachable only
216
- _from inside a program_: the model calls `ptc_run_code` / `ptc_workflow` (or `ptc_subagent`)
217
- and reaches `read` / `bash` / `edit` / `write` / `grep` / `find` / `ls` through
218
- `tools.<name>(args)` inside it. Tools contributed by _other_ extensions (`web_search`, `todo`,
219
- …) stay directly callable. Turn it off for one session with `/ptc off` (also `/ptc on`, `/ptc`
220
- for status), permanently with `{ "defaultMode": false }` in `~/.pi/agent/ptc.json`.
221
-
222
- A dispatch may also carry the caller's **reason** for the slice: an optional `why` on
223
- `pi.dispatch` / `ptc_subagent`, which reaches the child as its own `<pi-ptc-why>` block after the
224
- host's policy block, and never widens the task. It is on by default. Turn it off for one session
225
- with `/ptc why off` (`/ptc why` reports the current value and whether it came from the session or
226
- the file), or permanently with `{ "whyEnabled": false }` in `~/.pi/agent/ptc.json`. A value that is
227
- not a boolean leaves the block **on** and says so at startup — a default-on switch that could turn
228
- itself off on a typo is the wrong direction. A `why` that was dropped is reported on the dispatch
229
- result (`whySuppressed`), never silently ([ADR-0043](./docs/adr/0043-a-why-switch-and-a-name.md)).
230
-
231
- Which model-facing tools this package registers is **detected, not configured** — there is no
232
- setting for it ([ADR-0034](./docs/adr/0034-surface-is-detected-not-set.md)). The short version:
233
- a pi whose `codemode` is loaded _and_ callable by the model gets the `subagents` surface
234
- (`ptc_subagent` on top; the program pair underneath at `codemode` reach); every other pi gets
235
- `full` (`ptc_run_code` / `ptc_workflow` model-facing). To use the `subagents` surface, put
236
- `codemode` in your tool list (`pi --tools read,bash,edit,write,codemode`, or `"defaultTools":
237
- ["read", "bash", "edit", "write", "+codemode"]`). To keep this package out of your sessions
238
- entirely, run `pi config` and disable its extensions there.
239
-
240
- The full detection table, startup notices, and edge cases (`--print`, `--no-extensions`,
241
- untrusted projects) live in [docs/usage/surface.md](./docs/usage/surface.md).
242
-
243
- ## Trust posture (read me)
244
-
245
- - Installing this package grants it the same machine access as any pi
246
- extension: PTC programs run as **you**, with no OS-level sandbox (ADR-0007).
247
- - Bindings mirror the session's **enabled** built-in tools (`pi.getActiveTools()`):
248
- a session started with `--tools …` / `--no-builtin-tools` can only reach those
249
- tools from inside a PTC program. pi's default session has `read`, `bash`,
250
- `edit`, `write` — of which the default surface binds `read`, `edit`, `write`
251
- (see the next bullet).
252
- - **`tools.bash(...)` is not in that default.** A PTC program runs as you, so a
253
- default shell binding hands every model-written program a shell with your own
254
- identity. It is granted explicitly, per install, in `~/.pi/agent/ptc.json`:
255
-
256
- ```json
257
- { "optInBindings": ["bash"] }
258
- ```
259
-
260
- An absent or unreadable file grants nothing, and a name in that list that is not
261
- an opt-in binding is reported at session start rather than ignored. The opt-in is
262
- separate from the session's tool set on purpose: **enabled is not granted** — pi
263
- having `bash` enabled says nothing about whether you agreed to hand it to a program.
264
-
265
- - Tool calls made from inside a PTC program execute directly and **bypass**
266
- pi's `tool_call` hooks (permission gates, path guards) — see ADR-0005. Do not
267
- rely on those guards to constrain tool use while this extension is enabled.
268
-
269
- ## Development
79
+ `~/.pi/agent/ptc.json` — all optional:
270
80
 
271
- ```bash
272
- pnpm install
273
- pnpm run typecheck # tsc --noEmit
274
- pnpm run lint # oxlint (`pnpm run lint:fix` applies fixes)
275
- pnpm run fmt # oxfmt (writes); `pnpm run fmt:check` verifies
276
- pnpm test # vp test --run --coverage (Vitest 4; coverage via @vitest/coverage-v8)
277
- pnpm run test:watch # vp test (interactive)
278
- pnpm run test:ui # vp test --ui (local browser UI; not for CI)
279
- pnpm exec vp test --run tests/render-ptc.test.ts # renderer unit tests only
280
- pnpm run build # vp pack + declaration emit
281
- pnpm run verify:dist # exercise renderCall/renderResult through the built dist (no LLM needed)
282
- PI_ROOT=<global-node-modules>/@earendil-works/pi-coding-agent \
283
- node scripts/preview-ptc-render.mjs # print the rendered rows with real theme colors
284
- ```
81
+ - `defaultMode` — enter PTC mode on `full_ptc` sessions. Default `true`.
82
+ - `whyEnabled` — carry the caller's reason into the child. Default `true`.
83
+ - `webDsSearch` / `searchMaxCalls` — web search in children; off by default, 30 calls per
84
+ child when on (`0` means unlimited).
85
+
86
+ ## Trust posture
87
+
88
+ Programs run as you. There is no OS sandbox, and a program's tool calls bypass pi's
89
+ `tool_call` hooks. `bash` is not bound by default.
285
90
 
286
- Tooling: [oxc](https://oxc.rs) — `oxlint` + `oxfmt` (official defaults) — alongside
287
- `rolldown` (also oxc-powered), `vite-plus` (bundles Vitest 4), and TypeScript 7.
91
+ ## Compatibility
288
92
 
289
- See ADR-0009 for the Vitest adoption decision (reopens ADR-0008's earlier deferment).
93
+ pi `>= 0.86.0`. Real-machine smoke tests cover 0.86.0 through 1.4, including a program that
94
+ dispatches a real child process. Details:
95
+ [docs/pi-version-matrix.md](./docs/pi-version-matrix.md).
290
96
 
291
- ## Credits
97
+ ## Documentation
292
98
 
293
- Built clean-room from the PTC behaviour of
294
- [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (MIT,
295
- Copyright (c) 2026 DeepSeek), read at tag `dsh-v0.2.0-rc.2`, and hosted by
296
- [pi](https://pi.dev) (`earendil-works/pi`, MIT). Full attribution, and what is
297
- derived from what, is in [THIRD_PARTY_NOTICES.md](./THIRD_PARTY_NOTICES.md).
99
+ - [AGENTS.md](./AGENTS.md) — how this repository is built and guarded
100
+ - [docs/adr/](./docs/adr/) — decisions, 0001–0015
101
+ - [docs/prototypes/](./docs/prototypes/) — spec and status pages
102
+ - [docs/research/](./docs/research/) — evidence notes
298
103
 
299
104
  ## License
300
105
 
301
- Apache-2.0. See [LICENSE](./LICENSE) and [THIRD_PARTY_NOTICES.md](./THIRD_PARTY_NOTICES.md).
106
+ Apache-2.0. See [LICENSE](./LICENSE).