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/CHANGELOG.md DELETED
@@ -1,1230 +0,0 @@
1
- # Changelog
2
-
3
- All notable changes to this project will be documented in this file.
4
-
5
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
- and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
-
8
- ## [5.0.0] - 2026-10-19
9
-
10
- ### Added
11
-
12
- - **A dispatch reports the model its child actually ran.** The model used to be resolved inside the
13
- child and never came back: neither `pi.dispatch`'s `DispatchResult` nor a background
14
- `TaskRecord` said which provider or model served the call, so a wrong-model delegation could
15
- only be inferred from a failure after the fact. Both now carry an optional observed-model pair —
16
- observed by the host from the child's own assistant turns (last turn wins), not taken from the
17
- request — and the model-facing surfaces state the same fact as a `model: provider/id` line on
18
- `ptc_subagent` and `ptc_task_output` and as a `model=` fact in a refusal. A transcript that
19
- carries only a model id reports the id alone; the absence of both means no observed assistant turn
20
- named the pair — no turn, a supported pi that does not report it, or an old record — not "the
21
- model is unknown". A background record gains the pair at the same terminal close that writes
22
- `usage`, so a failed or canceled close that named a model still states it. The pair is named
23
- `observedProvider` / `observedModel` (ADR-0045): the result's `model` input is the requested
24
- model, and two different facts no longer share a name. Records written before the rename are
25
- still read (the field rename itself is breaking for program-visible result shapes — see the
26
- rename entry under Changed). The rendered value is stripped of ANSI escape sequences and
27
- control characters and head-capped at 200 characters before it enters model-facing text,
28
- because the pair is untrusted child wire data. See
29
- ADR-0044, ADR-0016 §"The child's model", and ADR-0022 §3 as amended.
30
- - **The `ptc_subagent` call row states the model a generalist dispatch will inherit.** When the
31
- call names no model and no agent (a generalist child, where a definition cannot pin the tier),
32
- the request row says `model=inherit` and its expanded view names the provider/id the caller's
33
- session is on; a named agent gets no such claim, and a pinned `model` is stated as before. This
34
- is a request fact captured at the call's first render, so a later model switch does not change
35
- an already-rendered row — and it is not the observed model, which still appears only after the
36
- child ran. ADR-0044.
37
-
38
- ### Changed
39
-
40
- - **BREAKING — the observed-model fields are named `observedProvider` / `observedModel`.**
41
- `DispatchResult.provider` / `model` and `TaskRecord.provider` / `model` carried the observed
42
- model, while the input's `model` is the requested model; the shared name made two different
43
- facts look comparable. The output fields are renamed, the input keeps its name, the child wire
44
- and the model-facing `model:` text are unchanged, and a record written before the rename is
45
- normalized on read so its observation is not lost. No aliases: the old result keys are removed,
46
- which is why this is breaking for program-visible result shapes. See ADR-0045.
47
- - **BREAKING — a dispatch that names no model now runs on the caller session's current model, not
48
- pi's global default.** `pi.dispatch` and `ptc_subagent` resolve the child's model first match
49
- wins: the dispatch's `model` argument, then the agent definition's frontmatter `model:`, then
50
- the calling session's **current** model, then pi's configured default. A session that switched
51
- provider or model mid-conversation now carries that switch into its next dispatched child — the
52
- background-dispatch guide previously said the opposite, and that statement is what this changes.
53
- Resolution is a snapshot at spawn: a model switched after the child starts does not change it, a
54
- nested dispatch inherits the child's own session model, and re-running the dispatch is how a
55
- caller picks up a new model. Tiers 1–2 are unchanged, so pinning the model at the call site or in
56
- the definition is the explicit opt-out; no config key and no `model: "inherit"` sentinel is
57
- added. A direct library caller with no context model, an older pi, or a session started without a
58
- model all fall through to pi's default exactly as before — the session read is structural, so
59
- the declared 0.86.0 floor gains no new pi type dependency. See ADR-0044 for the decision and its
60
- rejected alternatives.
61
-
62
- ### Fixed
63
-
64
- - **A failed foreground dispatch no longer loses the child's own failure.** The child is a pi
65
- subprocess: a rate limit or a model error can make it exit 0 after writing
66
- `stopReason: "error"` and an `errorMessage` on its assistant turn. The foreground loop
67
- discarded that text and reported the generic `dispatch produced no final text`, while the
68
- background registry already recorded the child's reason (issue #70's rule). The close decision now
69
- returns the child's reported error verbatim, using the background registry's rule — the child's
70
- own error outranks the exit-code and truncation inference — with cancellation still first:
71
- cancelled, then exit-0-with-text (fulfilled), then the child's error, then exit-0-without-text,
72
- then truncation. It logs one warning on that newly diagnosed path only. `stderr` is no longer
73
- described as guaranteed; `errorMessage` is the structured failure channel. This is
74
- foreground/background parity: one verdict and one sentence for one failure. See ADR-0044
75
- Decision 3 and ADR-0016 §Decision 3.
76
- - **`ptc_subagent refused the call: undefined` is gone.** A rejected dispatch without an
77
- `errorMessage` used to render the literal string `undefined`, telling the model nothing the
78
- dispatcher knew. The refusal now states the cause plus bounded facts the result already carries —
79
- whether a child ran or the harness declined to start, the exit code, turns, report channel, and
80
- the observed model when there is one — tail-bounded to 800 characters so a provider's actionable
81
- status code survives the cut. See ADR-0044 Decision 3.
82
-
83
- ## [4.0.0] - 2026-10-19
84
-
85
- ### Added
86
-
87
- - **The `ptc_subagent` call row shows the dispatch, not its argument dump.** The tool had no
88
- renderer, so pi fell back to its raw argument serialisation — one enormous
89
- `ptc_subagent task="…\\n\\n…"` line of JSON-escaped prose (2026-10-19 field report). It now
90
- registers a `renderCall`: collapsed it reads as a heading (agent, foreground/background and
91
- read-only flags, and a pinned `model` when one was passed) with one task preview line and
92
- `… 还有 N 行`; `ctrl+e` expands `label` / `why` rows and the task block — the first task line
93
- plus up to the existing `MAX_LOG_LINES_EXPANDED` continuation lines, so no new cap is
94
- introduced. Follows ADR-0013's "no escaped JSON in the call row" rule.
95
- - **`whyEnabled`, a default-on switch for the dispatch `why` block.** `~/.pi/agent/ptc.json` takes a
96
- boolean `whyEnabled`; when it is `false`, a `why` passed to `pi.dispatch` / `ptc_subagent` is
97
- dropped instead of delivered, and both the foreground result and the background record say that a
98
- suppression happened. **The switch fails on:** a non-boolean value, an unparseable file, or a file
99
- that is not a JSON object all leave the feature ON — a broken value must never remove it. A broken
100
- _key_ is reported once at `session_start` with the path, the key and the type that arrived; a broken
101
- _file_ is already reported by the default-mode reader, so one typo produces one notice. The
102
- `why` argument stays on the schema either way, so a running program is never rejected for passing
103
- an argument whose value the switch drops. `DispatchResult.whySuppressed` and
104
- `TaskRecord.whySuppressed` record **that a reason was withheld, never what it was** — a background
105
- dispatch returns a handle rather than a result, so without the record it would have no way at all
106
- to say so. See ADR-0043.
107
- - **`/ptc why on|off`**, a session-scoped override for the switch above. It records one session entry
108
- and never writes `ptc.json` — the difference is "about this conversation" against "about every
109
- future one". A resumed session restores the last record; a new one starts from the file. Bare
110
- `/ptc why` reports the value _and_ which of the two is in force, and an argument that is neither
111
- `on` nor `off` warns and changes nothing. See ADR-0043.
112
- - **A succeeded background task's completion notification now states its report channel.** The
113
- `<bg-task-notification>` for a `succeeded` record gains a `<report-channel>` child —
114
- `tool`, `prompt-json`, `none` or `opted-out`, the same **total** field ADR-0032 defines —
115
- rendered between `<error-message>` and `<output-ref>`. Before this, a model that wanted to
116
- know whether a finished child had honoured the report contract had to poll `ptc_task_output`;
117
- the push it already receives now answers it, once per finished child. The element is absent from
118
- every other event because only a `succeeded` close declares a channel: a running task has not
119
- reported _yet_, and a `failed` / `lost` / `canceled` close has no report claim to state
120
- (`none` and `opted-out` are claims about a finished child, not the absence of one). See
121
- ADR-0022 §7 as amended (2026-10-19).
122
-
123
- ### Changed
124
-
125
- - **BREAKING — a dispatch's `goal` is now `why`.** `pi.dispatch` and `ptc_subagent` took an
126
- optional `goal` since 3.4.0; that argument is now `why`, and the child's block is now
127
- `<pi-ptc-why>` instead of `<pi-ptc-goal>`. A PTC program that passes `goal` must pass `why`.
128
- The block's **wording is unchanged** — it still reads "This goal is context for doing it well" —
129
- because that sentence was held constant across ADR-0042's 180 runs and renaming the tag does not
130
- re-test the contrast; rewriting the sentence would change what was held still. See ADR-0043.
131
- The rename is not cosmetic: in the agent ecosystem `goal` names an objective or a loop, a thing
132
- worked toward over time, which is not what this argument holds — the reason one slice was cut out.
133
- `background` was the other candidate and was unavailable, because the dispatch schema already
134
- carries a `background` argument meaning "detach the child". No compatibility alias is kept: the
135
- only consumer is a model reading the live schema each session, and a second name for one value
136
- would cost exactly the comprehension this change buys back.
137
- - **The background-dispatch guide now says which model a dispatched child runs, and that it is
138
- not the caller's.** The child is a fresh `pi` subprocess, so its model resolves — first match
139
- wins — as the dispatch's `model` argument, then the named agent definition's frontmatter
140
- `model:`, then pi's own global default; the calling session's current model (and therefore a
141
- provider switched to mid-session) is **not** inherited. Recorded after the 2026-10-19 field
142
- report: the parent had switched provider, and the child it dispatched afterwards ran on pi's
143
- default provider and hit a 429. No behaviour change — the resolution order is what the code
144
- already did, and the guide states it so the failure is also preventable. See the background
145
- dispatch guide, `docs/usage/bgdispatch.md`.
146
- - **The TUI task panel now shows why a background task failed.** The `task-output` panel prints
147
- the record's failure reason as an `error: …` line first in the output block — ahead of the
148
- preview or the empty-output placeholder — and the `task-list` panel appends one to each
149
- `failed` / `lost` row whose record carries an `errorMessage`, so a child that died before
150
- writing output stays legible in the panel instead of as an empty preview, without a
151
- `ptc_task_output` call. A `lost` record carries the sweep's reason
152
- (`session_ended_while_running` / `lost_on_session_restart`) and gets the row too; a
153
- `canceled` task carries no reason and does not. Both rows reuse the panel's existing
154
- `MAX_ERROR_CHARS` bound (120, first non-blank line), so no new cap or number is added, and
155
- neither annotation counts against the panel's row caps — see ADR-0022 §11. A settled
156
- failure's empty-output placeholder also stops claiming `(no output yet)`: with a reason on the
157
- record it reads `(no output)`, while a page with no recorded reason keeps the `yet` (this
158
- payload carries no status to tell a live task from an unlabelled close).
159
-
160
- ### Fixed
161
-
162
- - **A background task that failed with an empty log now says why — on the notification, in
163
- `ptc_task_output`, and in the host log.** A 2026-10-19 field report: a child was rate-limited
164
- (429) before writing anything, its output log was 0 bytes, the `TaskRecord` carried the reason
165
- in `errorMessage`, and the parent agent received only `status="failed"` — it read the empty
166
- log as an infrastructure fault and could not act on the real one. Three surfaces now carry the
167
- cause. A `<bg-task-notification>` whose record carries an `errorMessage` gains an optional
168
- `<error-message>` (head-truncated to at most 1024 code points, an ellipsis marking the cut, and
169
- XML-escaped; absent when the record has no such text), so the push that announces the failure
170
- states it. `ptc_task_output`
171
- renders the reason as an `error=…` line ahead of a failed/lost task's body and exposes the
172
- record's `errorMessage` on the codemode projection as `error_message` (and on `details` as
173
- `errorMessage`), so the pull after the push reads the same reason. The background pump logs a `logger.warn` with the
174
- task id and the failure reason, so the host operator no longer sees only an empty child log. The
175
- terminal empty-output fallback also stops saying "yet": a terminal task reads
176
- `(no output; task <id> is <status>)`, while `running` / `stopping` keep
177
- `(no output yet; …)`. See ADR-0022 §7 as amended (2026-10-19).
178
-
179
- ## [3.4.0] - 2026-10-19
180
-
181
- ### Added
182
-
183
- - **`goal` on a dispatch.** `pi.dispatch` and `ptc_subagent` take an optional `goal`: the caller's
184
- reason for this dispatch slice, not the conversation and not the user's original request. The
185
- child receives it as its own `<pi-ptc-goal>` block after the host's policy block, and is told the
186
- task remains the deliverable. Measured across five model configurations on twelve conflict
187
- fixtures: 35 discordant pairs favoured the real goal, none favoured either control, and an
188
- equal-length placebo passed 2 of 60 runs. See ADR-0042.
189
- - `docs/research/wayfinder-208-negative-results.md` records two candidate features that were built,
190
- measured, and dropped (the summary arms and the provenance fields), with the method notes that
191
- make their negative results reusable.
192
-
193
- ## [3.3.3] - 2026-10-18
194
-
195
- ### Fixed
196
-
197
- - **An agent is now reachable under the name it declares, not only under its file name.** A definition
198
- written at `beta.md` whose frontmatter said `name: gamma` was advertised by the roster as `gamma`
199
- and refused by dispatch — under `gamma` the path was rebuilt to `gamma.md`, which did not exist,
200
- and under `beta` the parsed name did not match the request, so **neither** name worked. Discovery
201
- now reads the directory and matches each definition's frontmatter `name:`, which is what pi's own
202
- subagent loader and every comparable system does. It shares one scan with the roster rather than
203
- spelling the identity rule a second time, so the 3.3.2 invariant — for one scope, the names a
204
- roster lists are the names a dispatch resolves — holds by construction rather than by two
205
- traversals happening to agree. Two files in one scope declaring one name resolve to the first in
206
- sorted file-name order, and the shadowed file is reported. Scope precedence is unchanged: user
207
- scope still wins over project scope, and `agentScope` still defaults to `"user"`. **Behaviour
208
- change:** a definition whose file name disagrees with its `name:` becomes dispatchable under the
209
- declared name, and stops being reachable under the file name.
210
-
211
- - **A malformed agent definition is now reported instead of silently dropped.** A `.md` file in an
212
- agents directory that could not be _read_ already warned, but one that could be read and not
213
- _parsed_ — a definition saved with its opening `---` lost — produced no roster entry, no mention in
214
- a refusal, and no message at all, so the user saw `registered agents: (none)` for a directory that
215
- visibly held files. A file whose first non-blank line opens a frontmatter block, or is a `name:`
216
- key, now emits a `console.warn` naming its path. Prose kept beside the agents — a `README.md`, a
217
- `notes.txt` — stays silent, and the registry listing is unchanged either way.
218
-
219
- ### Changed
220
-
221
- - **The `ptc_subagent` description and the unknown-agent refusal no longer imply that a definition's
222
- file name is its identity.** Both previously pointed at `<name>.md` as the shape to write. The
223
- refusal now states that the `name:` header is the agent's name, that naming the file after it is
224
- the convention, and that the two are independent. Model-facing text only; no tool, argument or
225
- return-value shape changed.
226
-
227
- ## [3.3.2] - 2026-10-18
228
-
229
- ### Fixed
230
-
231
- - **The agent roster and agent discovery now name the same agents.** A `.md` file whose frontmatter
232
- carried no `name:` was advertised by the roster under its file name and refused by dispatch as an
233
- unknown agent — in the same message, because the refusal renders that roster. A model told an
234
- agent exists could not dispatch it, and the self-contradicting refusal made the real cause hard to
235
- read. Identity is the frontmatter `name:` and nothing else, which is what pi's own subagent loader
236
- does: it skips a definition whose frontmatter has no string `name`, and never derives identity
237
- from the file name. The roster now applies the same predicate `discoverAgent` applies, so the
238
- invariant — for one scope, the names the roster lists are the names a dispatch resolves — holds by
239
- construction rather than by two patches that happen to agree. A file that exists but cannot be
240
- read is likewise no longer listed under its file name; it still emits a `console.warn`, so that
241
- failure remains observable. **Behaviour change:** a file relying on the file-name fallback stops
242
- appearing in the roster, in the refusal's registry listing, and in the `ptc_subagent` tool
243
- description. Add a frontmatter `name:` to keep it dispatchable.
244
-
245
- ## [3.3.1] - 2026-10-16
246
-
247
- ### Changed
248
-
249
- - **The PTC tool descriptions now state how a `codemode` script reaches the parallel binding.**
250
- Measured in the 2026-10-16 dispatch-loop spike: a script that decides to fan out enumerates its
251
- own tool table, finds no `pi.dispatch` — correctly, the binding is never a codemode tool on any
252
- surface — and nothing model-facing said the two-hop path (`codemode` → `ptc_run_code` /
253
- `ptc_workflow` → `tools["pi.dispatch"]`) existed. The script's author read that silence as a
254
- missing registration and fell back to `ptc_subagent`, which worked but left the compose-from-a-
255
- script path untaught. The binding's own calling convention was already documented in both
256
- descriptions, but those sentences address a reader _inside_ a program; the new note addresses a
257
- reader inside the script. One shared constant renders into both descriptions, so the two
258
- surfaces cannot drift. Model-facing text only: no tool, argument, or behaviour changed.
259
-
260
- ## [3.3.0] - 2026-10-16
261
-
262
- ### Added
263
-
264
- - **A dispatched child's token usage is persisted on the task record, so a child that reported
265
- nothing still accounts for what it cost (issue #199).** `TaskRecord` gains a top-level optional
266
- `usage` — the host's own six-field counter (`input`, `output`, `cacheRead`, `cacheWrite`, `cost`,
267
- `turns`), accumulated from the child's assistant `message_end` blocks — written on **every**
268
- terminal close for any child the host actually started, independent of `reportChannel` and of the
269
- outcome: a `"none"` / `"opted-out"` child is accounted for, and so is one that closed `failed` or
270
- `canceled`. The same measurement now reaches both model-facing readers of that child:
271
- `ptc_task_output` prints the host's `usage:` line for a child that has no report block to put it
272
- in, the foreground `ptc_subagent` renders it as a `usage` row in the child-report block it
273
- already emitted, and both tools' `structuredContent` gains a top-level `usage` with the same four
274
- numbers the text shows. **`report.usage` is unchanged**: still the four-field view (`input`,
275
- `output`, `cost`, `turns`), still written only when the close resolves `succeeded` with a report,
276
- and still what the report block renders — the two agree on the fields they share because both are
277
- read off the same accumulator. An absent `usage` is a claim rather than a gap: a `running` task
278
- has not closed yet, a `lost` record was never observed closing, a child that never started has
279
- nothing to account for, and a record written before the field existed carries none — while a
280
- record without one renders the block it rendered before, unchanged. ADR-0022 §3 and ADR-0032
281
- record the amendment.
282
-
283
- ## [3.2.0] - 2026-10-15
284
-
285
- ### Added
286
-
287
- - **A dispatched child can be asked to be read-only, per call.** A new optional `readOnly`
288
- argument on the dispatch call takes `"strict"` or `"lenient"` — strings only. Strict replaces
289
- the child's tool list with the read set — `read`, `grep`, `find`,
290
- `ls`, plus the child report tool when the report contract is on — so the write tools are never
291
- declared to the child and it cannot call them. Lenient leaves the tool list exactly as it would
292
- have been and adds one line to the `<pi-ptc-context>` block the child already receives.
293
- The design first carried a boolean short spelling, and it was reversed before release rather
294
- than after: measured, pi's validator coerces booleans — `readOnly: 0` would have silently
295
- become "no tier", a full-capability child for a caller who asked to be read-only — and a string
296
- enum refuses every wrong-typed value at the schema instead. No released version ever shipped
297
- the boolean form.
298
- A tier set on the call beats an agent definition's declared `tools:` for strict; for lenient
299
- there is no precedence question, because the tier does not touch the tool list at all.
300
- Nothing breaks for anyone who does not pass it: an ordinary dispatch's argv and prompt are
301
- unchanged. **This is a capability narrowing, not a security boundary** — `bash` is deliberately
302
- absent from the strict set, so a strict child cannot run tests or call `git`, and no hostile
303
- child is contained by any of it. ADR-0041 records the reasoning and the alternatives.
304
-
305
- ## [3.1.0] - 2026-10-15
306
-
307
- ### Changed
308
-
309
- - **A failing PTC binding now reports a bounded diagnostic on every supported pi version.** The
310
- binding wrapper catches a builtin tool that REJECTS and routes the failure text through
311
- `bindingFailureMessage`, the same 2000-character tail-kept cap the `isError` channel already
312
- used. Measured per pi version: `ls` / `read` / `grep` / `find` reject for a bad path on
313
- 0.86.1 through 1.2.0 — only `bash` moved onto `AgentToolResult.isError` (which arrived in
314
- 0.99.0). Before this, a rejected builtin crossed the boundary with pi's raw, head-first error
315
- text, unbounded. The per-binding contract text is unchanged: a failing call still REJECTS.
316
- - **A truncated child response is now named instead of silently rejected.** pi 1.2.0 changed print
317
- mode so a response cut off at the model's output token limit exits 1 rather than 0. The
318
- foreground `DispatchResult` and the background `TaskRecord` now both carry an explicit message for
319
- that close, and neither front labels an exit-0 close as a truncation — so the two cannot
320
- disagree about the same child.
321
-
322
- ### Added
323
-
324
- - **`detectedSurfaceMode` is re-exported from the built package.** The surface decision (the pure
325
- function behind which orchestration face this package registers) was reachable only from the
326
- source module, so no artifact-level test or probe could drive it directly. Its four input
327
- combinations are now pinned against `dist`.
328
- - **`pnpm run compat:pi`** measures this package's gate against more than one pi version (floor,
329
- pinned, ceiling), reports each step separately, and prints the version-specific legs as a
330
- third state so a skipped leg is never mistaken for a passing one. See
331
- [docs/agents/pi-compat-matrix.md](docs/agents/pi-compat-matrix.md).
332
-
333
- - **The declared peer floor is now a floor you can actually compile against.** `peerDependencies`
334
- says `>=0.86.0`, but `tsc --noEmit` over pi 0.86.0 used to report 20 errors — the package ran
335
- there and failed to typecheck, which is the worst of both. It now reports 0, and
336
- `pnpm run compat:pi 0.86.0` passes every step. The package owns the three post-0.86.0 type
337
- surfaces (`ToolExposure`, and `outputSchema` / `exposure` / `defaultActive` on `ToolDefinition`)
338
- so `src/` depends on no pi newer than the floor; those fields arrived in 0.99.0, and a pi that
339
- does not read them is unaffected by their presence.
340
-
341
- Note the two floors are different things: **0.99.0 is where those capabilities appear** —
342
- `isError`, codemode, `exposure`, `structuredContent` — and this package still falls back
343
- below it. **0.86.0 is the floor it compiles and runs on.** An earlier revision of these notes
344
- recorded 0.86.x/0.87.x as type-broken; that was measured before this work and no longer holds.
345
-
346
- ### Documentation
347
-
348
- - Measured capability and feature-surface matrices across all 14 published pi versions from
349
- 0.86.0 to 1.2.0, with the corrected finding that the capability floor is **0.99.0**, not 1.0.0.
350
-
351
- ## [3.0.0] - 2026-10-14
352
-
353
- ### Changed
354
-
355
- - **BREAKING: `tools.bash(...)` is no longer bound by default; it is an explicit opt-in.** A PTC
356
- program runs with the user's own machine identity and its binding calls bypass pi's `tool_call`
357
- guards, so a default shell binding hands every model-written program a shell with your identity —
358
- the largest single thing a program can reach, granted by nothing more than installing this package.
359
- `bash` is now an _opt-in_ binding: it stays in the known binding set, and a program only gets it
360
- when the install grants it in `~/.pi/agent/ptc.json`:
361
-
362
- ```json
363
- { "optInBindings": ["bash"] }
364
- ```
365
-
366
- **Migration:** a program that calls `tools.bash(...)` and was relying on the default now fails with
367
- `no binding named "bash" in this run; available bindings: …`. Either add the key above (restart
368
- pi to pick it up), or, for a narrower change, pass an explicit `names` list to
369
- `createBuiltinBindings` — a caller-curated list is honoured verbatim and needs no second key. The
370
- error names the bindings that _are_ available, so the fix is readable from the refusal.
371
-
372
- Two properties worth knowing: the opt-in is **separate from the session's tool set** — pi having
373
- `bash` enabled does not grant it, because "enabled" and "you agreed to give a model-written program
374
- a shell" are different questions; and the file is read with a **fail-closed** direction, so an
375
- absent, unparseable or misspelled key grants nothing and reports why at session start rather than
376
- falling back to the old default. Note this narrows relative to DSH, whose PTC preset mounts
377
- `tool-bash`/`tool-pwsh`; a program written against DSH that wants its shell back needs the key.
378
-
379
- ## [2.1.0] - 2026-10-12
380
-
381
- ### Added
382
-
383
- - **The registered agents reach the model before it dispatches, not after it fails.** An agent
384
- markdown file's `description` was parsed and then dropped: the listing carried names alone, so
385
- the only way a model learned an agent existed was to guess its name, be refused, and read the
386
- refusal — a signal it could act on only after it had already spent the call. `ptc_subagent`'s
387
- description now leads with the omission, then lists every definition under the effective scope
388
- with its description and whether it was found in user or project scope, and says that
389
- `agentScope` defaults to `"user"` and will not see the others. An empty registry says so rather
390
- than rendering as nothing. The roster is rendered from the same traversal the refusal uses, so
391
- the two cannot disagree about what is registered.
392
-
393
- ### Changed
394
-
395
- - **A dispatched child is told what it is.** Every child's context block now says that it was
396
- dispatched by another session, that it cannot see or ask that session anything, and that its final
397
- message is the deliverable. Nothing in pi's own system prompt says any of it, which left a child
398
- able to ask a question nobody would answer or to narrate its process where an answer was wanted.
399
- It applies to named definitions too, so a definition does not have to restate what the dispatch
400
- already knows. For a child with no definition the prompt file holds nothing but that block — pi's
401
- own system prompt is still underneath it, which is precisely why the block has to say what the
402
- child is. See ADR-0039.
403
-
404
- - **`agent` is no longer required by `ptc_subagent` or `pi.dispatch`.** Omitting it dispatches a
405
- generalist child — pi's own system prompt, pi's full toolset, nothing narrowed — so a fresh
406
- install can delegate without first learning the agent-markdown format and writing a file. Naming
407
- one is now how you _narrow_ a child, which is all a definition could ever do: the dispatch prompt
408
- is appended to pi's own rather than substituted for it, and no tool list is passed unless the
409
- definition declares one. See ADR-0039.
410
-
411
- ### Fixed
412
-
413
- - **A dispatch that named nothing was refused as if it were a mistake.** Both fronts answered an
414
- omitted `agent` with _"agent is required (there is no default agent)"_. A name that is _given_
415
- and unusable is still refused — a typo must not silently dispatch something else — but the
416
- refusal now also says that omitting the argument is an option. The `ptc_subagent` description and
417
- the agent roster it carries lead with the omission, so a model can choose it deliberately.
418
-
419
- ### Removed
420
-
421
- - **The startup warning that said `ptc_subagent` had nothing to dispatch.** It fired whenever no
422
- agent definition was registered, which 2.0.3 had just started reporting as a problem — and it
423
- stopped being one in the same release: with `agent` optional, a dispatch that names nothing runs
424
- a generalist child, so an install with an empty registry is a **working** install. The warning was
425
- a false alarm about a working tool on exactly the fresh install it was written for. The `ptc-mode`
426
- status marker that mirrored it for the rest of the session is gone too. See ADR-0039.
427
-
428
- ## [2.0.3] - 2026-10-12
429
-
430
- ### Fixed
431
-
432
- - **`ptc_subagent` was registered, described to the model, and still uncallable — with nothing
433
- saying so.** On a `subagents` surface the tool needs at least one agent definition under
434
- `~/.pi/agent/agents/<name>.md` or `<project>/.pi/agents/<name>.md`, and there is no default agent
435
- (ADR-0016). With an empty registry the tool was live and inert: a real 920-message session made
436
- 520 tool calls and used it zero times, the model decided subagents were right, spent twenty-plus
437
- calls hunting the install for definitions that were not there, and gave up. `session_start` now
438
- reports it — naming both directories and the file shape — and the same fact is painted on the
439
- `ptc-mode` status, so it is still readable after the notice is gone.
440
- - **Agent files saved with CRLF, a UTF-8 BOM, or a blank line before the delimiter parsed as
441
- "no such agent".** The frontmatter pattern matched exactly one shape, and a `null` return reads,
442
- from every layer above, as an unregistered name. The body is normalised after the match rather
443
- than inside it, so the system prompt handed to a child carries no stray carriage returns. A file
444
- that exists but cannot be parsed is now also distinguishable from one that is absent.
445
- - **`ptc_subagent`'s description states where definitions load from**, and that there is no default
446
- agent — it points at the refusal rather than restating it, so the actionable detail keeps one
447
- home (the renderer, wired to the same resolver a dispatch uses).
448
-
449
- ### Added
450
-
451
- - **A real-process smoke test**, with a fake `pi` on `PATH` rather than a stubbed lifecycle: the
452
- spawn, the `--append-system-prompt` materialisation, the stdout parsing and the exit code are
453
- all exercised. Both directions are asserted — an empty registry reports, and a registered agent
454
- dispatches to completion — because either alone passes against a build that is broken in the
455
- other direction.
456
- - **A `能力的可用前提` review dimension**, asking when a user learns a tool's precondition is
457
- unmet, with "never" as the answer that blocks.
458
-
459
- ## [2.0.2] - 2026-10-10
460
-
461
- ### Changed
462
-
463
- - **The README leads with what the package is instead of how it works.** The opening now
464
- names the two capabilities up front — subagent fan-out and programmable tool calling —
465
- and states the relationship with pi's own `codemode` in the first screen. The surface
466
- detection deep-dive (three-question table, startup notices, `--print` / `--no-extensions`
467
- edge cases) moves out of the README to
468
- [docs/usage/surface.md](./docs/usage/surface.md), which now owns it; the README keeps
469
- the short version and a pointer. Everything else is tightened, not deleted.
470
- - **The built-in tool enumeration no longer claims a count pi moved past.** The README used
471
- to say "all seven built-ins"; pi 1.1.0 ships eight (`powershell` joined), so the copy now
472
- enumerates the tools this package binds (`read`, `bash`, `edit`, `write`, `grep`, `find`,
473
- `ls`) without claiming that is pi's full set. Docs only; no code change — note the
474
- binding list itself is unchanged, and `powershell` is currently not reachable from PTC
475
- programs even when enabled (tracked separately).
476
-
477
- [3.3.0]: https://github.com/a1121611810/pi-ptc-subagents/compare/v3.2.0...v3.3.0
478
- [3.2.0]: https://github.com/a1121611810/pi-ptc-subagents/compare/v3.1.0...v3.2.0
479
- [3.1.0]: https://github.com/a1121611810/pi-ptc-subagents/compare/v3.0.0...v3.1.0
480
- [3.0.0]: https://github.com/a1121611810/pi-ptc-subagents/compare/v2.1.0...v3.0.0
481
- [2.1.0]: https://github.com/a1121611810/pi-ptc-subagents/compare/v2.0.3...v2.1.0
482
- [2.0.3]: https://github.com/a1121611810/pi-ptc-subagents/compare/v2.0.2...v2.0.3
483
- [2.0.2]: https://github.com/a1121611810/pi-ptc-subagents/compare/v2.0.1...v2.0.2
484
-
485
- ## [2.0.1] - 2026-10-10
486
-
487
- ### Changed
488
-
489
- - **The package introduction copy now describes the package the code ships.**
490
- The `description` field — the hero line of the
491
- [pi.dev package page](https://pi.dev/packages/pi-ptc-subagents) — named two
492
- tools (`ptc_run_code` + `ptc_workflow`) while the package registers six
493
- model-facing tools plus the `pi.dispatch` binding, and said nothing about
494
- the subagent fan-out the package name advertises. It now centres on both
495
- capabilities the package is — programmable tool calling and subagent
496
- fan-out — with no tool names hardcoded (the tool set is detected, not
497
- configured; see [ADR-0034](./docs/adr/0034-surface-is-detected-not-set.md)).
498
- The README's Tools section gains the missing `ptc_subagent` bullet. Docs
499
- only; no code change.
500
-
501
- [2.0.1]: https://github.com/a1121611810/pi-ptc-subagents/compare/v2.0.0...v2.0.1
502
-
503
- ## [2.0.0] - 2026-10-10
504
-
505
- ### Added
506
-
507
- - **`ptc_run_code` and `ptc_workflow` reach the line where pi's `codemode` orchestrates.** A
508
- session on the `subagents` surface used to hold `ptc_subagent` and the lifecycle face with no
509
- program behind them: pi's `codemode` was composing tool calls, and the one capability its
510
- QuickJS sandbox cannot provide — spawning a process — was unreachable, because `pi.dispatch`,
511
- background tasks and the frozen six-name environment all live inside the PTC worker. Both tools
512
- are now registered there at `codemode` reach, which makes them **callable from a `codemode`
513
- script without being declared to the model**: `AgentSession._isDeclarable` admits `direct` and
514
- `model-only` only, so the request pi sends still carries exactly one orchestrator and it is
515
- pi's. Verified end to end on a real pi 1.1.0 session — a `codemode` script enumerates all six
516
- `ptc_*` tools in `ALL_TOOLS` and successfully calls `await tools.ptc_run_code({ code: … })`,
517
- while the payload carries only the subagent face
518
- ([ADR-0025](./docs/adr/0025-extension-surface-is-a-setting.md) §3 as amended).
519
-
520
- **A pi older than 0.99.0 is unaffected.** `ToolExposure` does not exist there — `grep -r
521
- exposure` over `pi-coding-agent@0.86.1`'s `dist/` returns nothing, and so does
522
- `AgentSession._getCallableTools` — so the field is ignored and the tools are declared to the
523
- model, which is correct: such a pi ships no `codemode`, resolves to `full` anyway, and has
524
- nothing that could call a `codemode`-reach tool. Measured across 0.86.1 / 0.87.1 / 0.99.0 /
525
- 0.99.1 / 0.99.2 / 1.0.0 / 1.0.4 / 1.1.0.
526
-
527
- **Naming either tool in `--tools` or `defaultTools` overrides this.** A tool named there is
528
- recorded in pi's `toolsAdded`, which the prompt projection filters only against `hidden` and
529
- never against `codemode` — so it is declared to the model regardless of its exposure. That is
530
- a user's explicit choice and is left alone; it is recorded here because the exposure field does
531
- not survive it.
532
-
533
- ### Changed
534
-
535
- - **Surface detection asks pi for the loadout instead of reconstructing it.** The third question —
536
- will `codemode` be **active**, i.e. callable by the model — used to be answered by parsing
537
- `--tools` / `--exclude-tools` / `--no-tools` / `--no-builtin-tools` out of `process.argv`,
538
- merging `defaultTools` across the user-scope and project-scope settings files, and replaying pi's
539
- precedence over the result. That is deleted. At `session_start` — the first moment pi's runtime is
540
- bound — `getActiveTools()` answers directly, and pi has already applied both the command line and
541
- its project-trust decision to what it returns. Registration moves to the same event, where pi
542
- adds a tool from it and refreshes its own registry; the command-line reader, the `defaultTools`
543
- merge, the precedence replay and the differential test that held them honest all go with it
544
- ([ADR-0035](./docs/adr/0035-ask-pi-for-the-loadout.md)).
545
-
546
- **What this fixes.** A project's `defaultTools` was read off disk with no trust check, while pi
547
- drops project settings for any project it has not been told to trust. An untrusted project could
548
- therefore name `codemode` and put the session on the `subagents` surface — whose
549
- `ptc_run_code` and `ptc_workflow` are registered at `codemode` reach, so with no `codemode`
550
- running they are reachable from nowhere (#131). A trusted project that configures `codemode` that
551
- way keeps the `subagents` surface, exactly as before.
552
-
553
- **What does not change.** The MCP auto-enable evidence stays a file-side read, because pi
554
- activates `codemode` from inside its own `session_start` handler and extension order is not ours
555
- to choose — measured across pi 0.86.1 → 1.1.0, a read from ours is too early to see it, and the
556
- existing cross-check catches it when it lands. The **codemode switch** axis also stays as it is;
557
- it may also be collapsible into the live registry, but that loses the distinction between "not
558
- shipped" and "not loaded", and it is a separate decision. Behaviour is otherwise unchanged on
559
- every pi this package supports, verified from 0.86.1 upward under both trust decisions.
560
-
561
- - **BREAKING: the `surfaceMode` setting is removed, and the surface is detected only.** Which
562
- model-facing tools this package registers is now a pure function of what pi is — three probes
563
- (does this pi ship `codemode`, will pi load it, can the model call it), with `full` as the
564
- fallback for every case a probe cannot answer
565
- ([ADR-0034](./docs/adr/0034-surface-is-detected-not-set.md)).
566
-
567
- **What is gone:**
568
-
569
- - the `surfaceMode` key in `~/.pi/agent/ptc.json` (`off` / `subagents` / `full`);
570
- - the `off` value — a session that wants none of this package's tools no longer has a way to say
571
- so here;
572
- - the `/ptc surface [off|subagents|full]` subcommand. **`/ptc off` is unaffected** — it turns PTC
573
- _mode_ off, which was never the same thing as the extension surface;
574
- - the `surfaceModeConflict` notice about a pinned value disagreeing with detection.
575
-
576
- **Migrating: use pi, not this package.** Run `pi config` and disable this package's extensions
577
- there — measured on pi 1.1.0 against this package's own `dist/index.js`, a `packages` entry whose
578
- `extensions` is `[]` or `["!dist/index.js"]` is not loaded at all, while `["+dist/index.js"]` and
579
- an omitted key are. This is strictly better than any value of a key this package reads, because
580
- pi honours it **without loading the extension**: none of this package runs, rather than running
581
- and then deciding to register nothing. In a project-level `.pi/settings.json` write
582
- `["!dist/index.js"]` rather than `[]` — pi resolves the two scopes with two different functions
583
- (`package-manager.js:1850`), where an empty project delta means "no change".
584
-
585
- **Expect one warning on upgrade if you had the key set.** A `ptc.json` carrying `surfaceMode` is
586
- reported at session start and the key is **not** honoured — deliberately, including for `"off"`,
587
- which is why this package comes back after being switched off. The warning names the file, quotes
588
- the value, and points at `pi config`; deleting the key silences it.
589
-
590
- - **ADR-0025 decision 3 is amended.** `subagents` does register the programming pair now. What
591
- the decision protected — the model being offered one orchestration surface, not two — is
592
- unchanged and is now enforced by exposure rather than by an absent tool. `CONTEXT.md`'s
593
- _orchestration surface_ and _subagent surface_ entries and the README's surface list say so
594
- too.
595
-
596
- - **The `codemode` activation probe now reads every CLI flag that can remove the tool.** It reads
597
- `--exclude-tools` / `-xt` and `--no-tools` / `-nt` in addition to `--tools` / `-t`, and it applies
598
- them in pi's own order: `-t` decides the list and beats `-nt`, while `-xt` vetoes whatever list
599
- won. That last one was a real misreading — pi builds the active set as
600
- `(tools ?? configured).filter(name => !excluded.has(name))` (`sdk.js:148`), so **exclusion beats
601
- inclusion**, and `-t codemode -xt codemode` was previously read as `active`. The direction was the
602
- harmful one: under `subagents`, `ptc_run_code` / `ptc_workflow` are at `codemode` reach, so an
603
- orchestrator believed to be present but absent is an orchestrator nobody can call.
604
- `--no-builtin-tools` is deliberately still not read: it maps to `noTools: "builtin"` and
605
- `codemode` is not one of pi's built-in tools, so reading it would be a guess about a flag that
606
- cannot move it.
607
-
608
- [2.0.0]: https://github.com/a1121611810/pi-ptc-subagents/compare/v1.6.0...v2.0.0
609
-
610
- ## [1.6.0] - 2026-10-08
611
-
612
- ### Fixed
613
-
614
- - **On pi 1.0, a session with MCP servers was offered two orchestration tools at once.** pi's MCP
615
- extension activates pi's own `codemode` tool by itself — MCP tools default to `codemode`
616
- exposure so that they are reachable only from scripts — and it does that with a
617
- `pi.setActiveTools` call that no settings file records. This package's surface detection
618
- ([ADR-0025](./docs/adr/0025-extension-surface-is-a-setting.md)) asked whether `codemode` would
619
- be _active_ by mirroring pi's loadout resolution alone ([ADR-0029](./docs/adr/0029-surface-follows-codemode-activation.md)),
620
- so on exactly the sessions pi 1.0 is built around it answered _not active_, registered
621
- `ptc_run_code` + `ptc_workflow` as well, and handed the model two ways to compose tools. Detection
622
- now reads pi's MCP configuration (`<agentDir>/mcp.json`, then `<cwd>/.pi/mcp.json`) as a second
623
- evidence source and unions the two, so those sessions resolve `subagents`: `codemode` for
624
- orchestration, `ptc_subagent` and the task lifecycle for fan-out.
625
- [ADR-0033](./docs/adr/0033-mcp-auto-enable-evidence.md) records the mirror, what it deliberately
626
- does not mirror, and the one under-report path that stays open (a server registered through
627
- `pi.registerMcpServer()`, which no file records — the drift notice below is what covers it).
628
-
629
- ### Added
630
-
631
- - **A session start warning for an active `codemode` the probes did not predict.** Once per
632
- session, and only on a detected surface: if nothing in `settings.json` or a readable `mcp.json`
633
- said codemode would be active, the surface defaulted to `full`, and pi's real tool loadout
634
- contains `codemode` anyway, the session is now told it is carrying two orchestration surfaces and
635
- which setting picks one. Checked at session start and again on the first turn, because
636
- pi's MCP extension may activate codemode after this package's own `session_start` runs. It is
637
- `TUI`-only, like every other notice in this package.
638
- - **A named report for an `mcp.json` this package could not read.** A file that is absent is
639
- silent, matching pi; a file that exists and cannot be parsed or read is named at session start
640
- with its path, and when both files are broken both are named in the one line.
641
-
642
- ### Changed
643
-
644
- - **`CONTEXT.md` gains a `codemode activation` entry** defining the question surface detection asks
645
- and its two evidence classes, so "is codemode active" no longer has to be reconstructed from an
646
- ADR.
647
- - ADR-0029 carries an amendment: its under-reporting bound still holds, but the premise it rested
648
- on — that the loadout is the only way pi activates codemode — did not.
649
-
650
- ## [1.5.1] - 2026-10-08
651
-
652
- ### Fixed
653
-
654
- - **Two claims 1.5.0's changelog made about this repository were false, and are now corrected in
655
- the repository itself.** 1.5.0's tarball still carries the original wording — published tarballs
656
- are immutable — so the correction lives here, in `main`, and in
657
- [ADR-0031](./docs/adr/0031-open-source-and-publish-authority.md)'s amendment blocks:
658
- - _"The repository is public … release authority is enforced by a `refs/tags/v*` ruleset."_ The
659
- repository was still private at 1.5.0's release, and no ruleset existed. Both were done
660
- 2026-10-08, hours after the gap was measured: the flip, then an active ruleset (id 24698892)
661
- on `refs/heads/main` — no pushes, no merges, no deletions, no force-pushes except by
662
- `a1121611810` as sole bypass actor, with one approving review and green `format` / `oxlint` /
663
- `test` checks required for everyone else.
664
- - _"The next version published from here carries an npm provenance attestation."_ No published
665
- version through 1.5.0 has one, because the provenance precondition (a public source
666
- repository) only became true with the flip above. **1.5.1 is the first version whose
667
- precondition held** — see `dist.attestations` on the registry.
668
-
669
- How the false claims shipped: they were written as decisions in an earlier session
670
- (`c1dda59`), recorded as facts, and every gate in this repository checks the shape of a claim —
671
- that a `file:line` resolves, that a constant matches a rule — not whether the thing a sentence
672
- describes exists. Five review rounds and a green release gate passed them. The same class of
673
- gap is recorded in `docs/testing-constraints.md` §"What the gate does not check".
674
-
675
- ### Added
676
-
677
- - **`docs/prototypes/` — the bgdispatch design records, recovered from research branches.** The
678
- ten research branches deleted in the run-up to going public carried twelve verdict /
679
- measurement / prototype files that had never been merged to `main`: the records of _why_ the
680
- background-dispatch design is what it is. They were recovered before the branches were deleted;
681
- `AGENTS.md` treats records as load-bearing.
682
- - **`.mailmap`**, so the public commit history displays `a1121611810` instead of the 224-character
683
- padded name that authored 309 commits. No history rewrite.
684
-
685
- ### Changed
686
-
687
- - **`.gitignore` gains `.zcode/` and `.scratch/`.** `.zcode/` was previously protected only by two
688
- nested self-ignoring `.gitignore` files that are themselves untracked. `.scratch/` (local ticket
689
- drafts, content duplicated on GitHub Issues) is removed from tracking.
690
- - **Twelve research / feature branches and two `backup/undo-*` tags deleted from the remote.**
691
- They carried the developer's machine-local paths (`/Users/lilianda`) that `main` had already
692
- scrubbed; GitHub publishes every ref, not just `main`, so a public reader could have clicked
693
- into them. Content was verified present in `main` (or recovered above) before each deletion.
694
- Four routine `dependabot/*` bumps remain as open branches for the maintainer to review.
695
- - **`docs/prototypes/**` excluded from oxlint.** The recovered files are measurement scripts and
696
- were never part of the linted source; they are records, not production code.
697
- - **The npm mirror noted during the audit turned out to be local configuration, not the
698
- lockfile** — `pnpm-lock.yaml` carries no registry URLs at all.
699
-
700
- ## [1.5.0] - 2026-10-08
701
-
702
- ### Added
703
-
704
- - **`verify:dist` is now part of the release gate.** `scripts/verify-dist-render.mjs` is the only
705
- check that exercises the _built_ artifact, and it ran nowhere: not in CI, not in the publish
706
- workflow, not in `prepublishOnly`. A feature in this project's own history passed three review
707
- rounds and 696 tests and then failed this script on the release artifact. It now runs on every
708
- pull request, in `publish.yml` before the publish step, and in `prepublishOnly`
709
- ([ADR-0031](./docs/adr/0031-open-source-and-publish-authority.md) §D).
710
- - **`THIRD_PARTY_NOTICES.md`**, stating what is derived from DeepSeek Harness (MIT, Copyright (c)
711
- 2026 DeepSeek) and from `pi` (MIT), and shipped inside the npm tarball rather than only on
712
- GitHub. The MIT notice is an obligation for the source excerpts in `docs/research/`, not a
713
- courtesy.
714
- - **A dispatched child returns a _child report_ instead of prose alone.** `summary` in the child's
715
- own words, `findings` each carrying the independent thing that supports the claim,
716
- `files_touched`, and the token usage **the host measured**. The child's prose is kept alongside
717
- the report, never replaced by it ([ADR-0032](./docs/adr/0032-child-report.md)).
718
- - **Two delivery channels, and the result names which one delivered it.** A declared
719
- `ptc_child_report` tool (the reliable one) or a fenced JSON block in the child's final text (the
720
- fallback, for installs where this package does not load in the child). `reportChannel` is
721
- **always** present — `tool`, `prompt-json`, `none` or `opted-out` — because a degradation a
722
- caller cannot see is a silent failure, and "ran but did not comply" must not read as "returned
723
- nothing".
724
- - **The report contract is on by default** and an agent opts out with one frontmatter line,
725
- `childReport: false`. An opted-out agent reads as `opted-out`, not `none`: nobody was asked is a
726
- different claim from having been asked and ignored.
727
- - **`ptc_subagent` renders the report** where the model reads it, bounded at 20 findings /
728
- 20 files / 150 characters of evidence per finding, each bound stated in-band when it withholds.
729
- This is the first real reader of that tool's declared `structuredContent` — on the `subagents`
730
- surface there is no `codemode` to read it.
731
-
732
- ### Changed
733
-
734
- - `src/tools/subagent.ts` gained the OCR rule anchor it never had. It was resolving to the `**`
735
- catch-all and being reviewed against the generic floor only.
736
- - **The repository is public, and `main` is writable only by the maintainer.** Everything else is
737
- a pull request that needs CI green and one approving review. Release authority is enforced by a
738
- `refs/tags/v*` ruleset plus the npm package's "Require two-factor authentication and disallow
739
- tokens" setting, so it no longer depends on where a credential file is kept
740
- ([ADR-0031](./docs/adr/0031-open-source-and-publish-authority.md) §A–§B).
741
- ~~**Measured 2026-10-08: this did not happen.** The repository is still private and no ruleset
742
- exists — see §Unreleased above and ADR-0031's correction block. The npm 2FA setting is unverified
743
- and is not claimed here.~~
744
- - **The next version published from here carries an npm provenance attestation.** Under trusted
745
- publishing npm generates it automatically once the source repository is public, with no workflow
746
- change — so the `homepage` and `repository` fields that pointed at a private GitHub now resolve,
747
- and the missing provenance badge that ADR-0018 §7 recorded as expected is no longer expected.
748
- ~~**Measured 2026-10-08: this is false and always was.** `dist.attestations` is empty for every
749
- published version including 1.5.0; the repository was never public, so the precondition never
750
- held and ADR-0018 §7 stands unamended.~~
751
- - **`node scripts/preview-ptc-render.mjs` requires `PI_ROOT`.** It imported pi's theme from a hard-coded
752
- path on one developer's machine, so following the README from anywhere else failed inside a
753
- module loader. It now reads the install directory from the environment and, when it is missing or
754
- wrong, says so with the commands to find it.
755
- - **The DSH citations in `docs/research/` point at the public upstream repository** instead of a
756
- temporary local extraction, so a reader can follow them. The baseline is tag `dsh-v0.2.0-rc.2`
757
- — the release the research actually read. `src/runtime/limits.ts:4` named `0.1.6-alpha.2`; the
758
- constants are byte-identical across both tags, so only the version label changed and no behaviour
759
- did ([ADR-0031](./docs/adr/0031-open-source-and-publish-authority.md) §C).
760
-
761
- ## [1.4.0] - 2026-10-03
762
-
763
- ### Added
764
-
765
- - **`/ptc surface off|subagents|full` switches the extension surface without a new session.** The
766
- surface was configurable only by hand-editing `~/.pi/agent/ptc.json` and restarting, because it is
767
- read once in the extension factory and pi has no way to unregister a tool. The new subcommand
768
- writes the key and then performs pi's own `/reload`, which clears pi's extension cache and
769
- re-runs every factory — the same path the built-in takes, from inside the extension. A malformed
770
- `ptc.json` is reported and left byte-for-byte alone, `defaultMode` in the same file survives, and
771
- setting the value that is already there writes nothing and does not reload
772
- ([ADR-0030](./docs/adr/0030-surface-switch-reloads.md)).
773
-
774
- ### Fixed
775
-
776
- - **A default pi session no longer starts with no orchestration tool at all.** The detected surface
777
- followed two questions — does this pi ship a `codemode` directory, and will pi load it — and on a
778
- stock pi 1.0.0 install the answer to both is yes, so the surface resolved to `subagents`. But pi
779
- registers `codemode` with `defaultActive: false`: it joins the model's tool list only when a
780
- loadout names it, and a session that configured nothing has no such loadout. Since the
781
- `subagents` surface deliberately does not register `ptc_run_code`, the result was a session
782
- holding `ptc_subagent` and three `ptc_task_*` tools with no way to compose any of them, plus a
783
- startup warning on **every** session. The detection now asks a third question — whether `codemode`
784
- will actually be in the tool list, read from `--tools` and from `defaultTools` in the project and
785
- user settings the way pi resolves them — and `subagents` is chosen only on positive evidence that
786
- the model can call the tool. The default on an unconfigured install is now `full`, with no
787
- warning. To opt into `subagents`, add `codemode` to `defaultTools` or `--tools`
788
- ([ADR-0029](./docs/adr/0029-surface-follows-codemode-activation.md)).
789
- - **The "pi does not register codemode" startup notice no longer hard-codes the wrong surface.** With
790
- a fifth cell in the table, the detected surface is not always `subagents` in that branch, and the
791
- message named `SurfaceModeConfig.detected` — a field populated only when an explicit `surfaceMode`
792
- overrode detection, so on the very path the notice fires from it would have printed `undefined`.
793
- It reports the surface the session actually built.
794
-
795
- ## [1.3.0] - 2026-10-02
796
-
797
- ### Fixed
798
-
799
- - **A failing `bash` binding no longer resolves as a success on pi 1.0.0.** pi 1.0.0 added a
800
- non-throwing failure channel — `AgentToolResult.isError`: "Report a failure without throwing. The
801
- model sees `content` as an error result, like a thrown error" — and `bash` moved its non-zero exit
802
- onto it, so `tools.bash({ command: "exit 3" })` inside a PTC program **resolved** where on 0.86.1
803
- it threw. The binding wrapper forwarded only `{ content, details }`, so `isError` was dropped at
804
- the boundary and the failure crossed into the program as a successful resolution whose only tell
805
- was a sentence at the end of its stdout. The binding layer now translates `isError` back into the
806
- rejection ADR-0024's contract already promises, which is what keeps a failing call from being
807
- silent.
808
- - **Disabling pi's `codemode` now brings the PTC surfaces back.** The detected surface asked one
809
- question — does this pi ship a `codemode` extension directory — and since pi 0.99.0 that is no
810
- longer the same as the one that matters. A user who put `"extensions": ["-builtin:codemode"]` in
811
- their settings, or launched with `--no-extensions`, turned the orchestrator off and this package
812
- kept handing the orchestration to it: `ptc_run_code` and `ptc_workflow` stayed unregistered and
813
- the session was left with `ptc_subagent` and nothing to compose with. Nothing errored; two tools
814
- were simply missing.
815
-
816
- A second probe now reads the **switch** — whether pi will actually load the extension — from the
817
- same three places pi reads it and in the same order (command line, `<cwd>/.pi/settings.json`,
818
- `<agentDir>/settings.json`), and the detected default follows both questions:
819
-
820
- | ships `codemode`? | will load it? | surface |
821
- | ----------------- | ------------------------------------------- | ----------- |
822
- | yes | yes (default, or `+builtin:codemode`) | `subagents` |
823
- | yes | no (`-builtin:codemode`, `--no-extensions`) | `full` |
824
- | no | — | `full` |
825
-
826
- An explicit `surfaceMode` still wins; a settings file that cannot be read as a JSON object still
827
- falls back to the next source rather than half-applying. Two notices are added at session start:
828
- one naming an unreadable settings file, one reporting that a pinned `surfaceMode` disagrees with
829
- the table. [ADR-0027](./docs/adr/0027-codemode-switch-decides-surface.md)
830
-
831
- Verified on a real 1.0.0 install for the three reachable cells, asserting on the registered tool
832
- set rather than on the resolver: `-builtin:codemode` → `ptc_run_code` + `ptc_workflow`; no switch
833
- entry → `ptc_subagent`; `+builtin:codemode` → `ptc_subagent`. The fourth cell (no `codemode` on
834
- disk at all) is not reachable on a machine with 1.0 installed and is covered by unit tests only.
835
-
836
- ### Added
837
-
838
- - **The four model-facing tools declare a structured result, so pi's `codemode` can consume them.**
839
- A script calling a tool used to get back prose: `ptc_subagent` returned
840
- `"Started background task 01J…"` with the id embedded, and the `ptc_task_*` tools returned
841
- newline-joined lines to re-parse. `ptc_subagent` is the acute case — it is the only way a
842
- `subagents`-surface session can start a subagent at all, because pi's QuickJS sandbox has no file
843
- system, no network and no `child_process`.
844
-
845
- All four now declare an `outputSchema` and return a matching `structuredContent`, which codemode
846
- scripts receive instead of the text. The shape is a lean projection rather than a mirror of
847
- `details`: `ptc_task_list` mirroring `TaskRecord[]` would push ~200 KB of per-record
848
- `outputPreview` into a sandbox whose purpose is to keep intermediate data away from the model.
849
- `content` and `details` are byte-identical to before on every path, and the model still sees
850
- exactly the same text — `structuredContent` is documented as "not sent to the model".
851
- [ADR-0028](./docs/adr/0028-structured-results-for-codemode.md); usage in
852
- [Structured results for codemode](./docs/usage/structured-results.md).
853
-
854
- ### Changed
855
-
856
- - **The codemode switch now mirrors pi's own matchers instead of approximating them.** Reading the
857
- `!` bucket by comparing a pattern's literal prefix was the wrong SHAPE of fix: the bucket is a
858
- continuum (globs, character classes, extglobs, nested negations, backslash escapes, multi-segment
859
- paths), so every review round found one more member — four costful defects in four rounds, and a
860
- 412-case sweep of the prefix version still had 24 divergences. A case list cannot be finished.
861
- `resolveCodemodeSwitch` now calls the same `minimatch` pi does, through the same
862
- `matchesAnyPattern` / `normalizeExactPattern` helpers and with pi's own `baseDir` per scope, which
863
- adds one small runtime dependency (`minimatch ^10.2.6`, the version pi itself pins) and **shortens**
864
- the code. A 192-case sweep of the mirror has zero divergences in either direction, and all eight
865
- reachable cells agree with a real pi 1.0.0 end to end.
866
- - **The dev toolchain now compiles against pi 1.0.0** rather than 0.86.1 / 0.87.0, so the type check
867
- and the whole suite run against the pi this package is actually used with. `peerDependencies`
868
- already declared `>=0.86.0`; that claim had no evidence behind it until now. Two consequences of
869
- the bump are fixed in the same change: pi narrowed a tool's fifth `execute` parameter from
870
- `ExtensionContext` to `ExtensionToolContext` (which adds `tools` and `executeTool`), and the
871
- `isError` channel above.
872
-
873
- ## [1.2.1] - 2026-09-30
874
-
875
- ### Fixed
876
-
877
- - **The `ptc_subagent` tests no longer depend on a pi agent being registered.**
878
- Five tests reached for pi's `__smoke_echo` agent, which does not resolve on every pi build,
879
- so the suite was green on one platform and red on the release runner. The fixture is now
880
- written by the test into `<cwd>/.pi/agents`, the path the agent resolver actually reads at
881
- project scope, and every dispatch asks for project scope explicitly rather than inheriting
882
- the `user` default, which points at the real `~/.pi`. Verified with an empty `HOME`: the whole
883
- suite is green with no ambient agent state at all.
884
-
885
- No product code changed in this release.
886
-
887
- ## [1.2.0] - 2026-09-30
888
-
889
- ### Added
890
-
891
- - **The surface default is detected from the pi that loaded us (ADR-0026).** With no
892
- `surfaceMode` set, a pi that ships its own `codemode` resolves to `subagents` and a pi that
893
- does not resolves to `full`. Setting the key always wins, and a probe that cannot answer
894
- falls back to `full` rather than guessing.
895
-
896
- This is the one behaviour change that is not invisible on upgrade: on pi 0.99.1 or newer, a
897
- user who has never set `surfaceMode` moves from `full` to `subagents`. `codemode` ships
898
- inactive (`defaultActive: false`), so such a session has a subagent front and no active
899
- orchestrator until codemode is added to the tool list -- the startup warning says so. Set
900
- `{ "surfaceMode": "full" }` to keep today's tools.
901
-
902
- Detection is a filesystem probe over `process.argv[1]`, because pi's own tool listing is
903
- unavailable at factory time: `getAllTools()` is a `notInitialized` stub until `bindCore`
904
- runs, and a throwing factory makes the extension fail to load rather than return an empty
905
- list.
906
-
907
- A detection the user cannot see is the one failure this design has, so the outcome is
908
- reported: at session start, through the TUI notification channel, a session with no
909
- `surfaceMode` set is told how the probe came out whenever it could not answer, and which
910
- surface the default therefore is. The expected case — a pi that ships `codemode`, detected as
911
- `subagents` — stays silent. A second notice covers the case the probe structurally cannot
912
- see: it walks the filesystem, so under `--no-extensions` or `--exclude-tools codemode` it
913
- answers `present` for a tool the session does not have. `session_start` cross-checks the
914
- probe against pi's own `getAllTools()` and reports a disagreement in either direction.
915
-
916
- Both notices are `ui.notify`, which is TUI-only — and that gap is **new here**, not inherited:
917
- measured, three `--print` runs that each emit one produced 0 bytes on stdout and 0 on
918
- stderr. The README is the only channel on which a `--print` user learns why they got the
919
- surface they got. What is established is that the outcome is issued through the documented
920
- TUI channel; no test observes either notice end to end through a real pi TUI.
921
-
922
- - **The model-facing surface is now a setting, and a subagent can be started
923
- without writing a program (ADR-0025).** A new `surfaceMode` key in
924
- `~/.pi/agent/ptc.json` decides what this extension registers: `full`
925
- (today's behaviour, and the detected default on a pi without `codemode`)
926
- registers `ptc_run_code`, `ptc_workflow` and the `ptc_task_*` trio;
927
- `subagents` registers a new top-level `ptc_subagent`
928
- plus the `ptc_task_*` trio and leaves orchestration to pi's own `codemode`;
929
- `off` registers nothing at all, so the session is stock pi. The new tool takes
930
- the same arguments as the `pi.dispatch` binding and calls the same dispatcher,
931
- so depth, concurrency and the background task lifecycle behave identically and
932
- a task spawned through it is listable with `ptc_task_list`.
933
-
934
- Motivation, measured rather than assumed: pi 0.99.1 ships its own programmable
935
- tool calling as the built-in `codemode`, which is stronger than `ptc_run_code` at
936
- isolation and tool discovery but cannot spawn a process at all. With both
937
- installed the model is taught two programming models per request
938
- (`docs/research/codemode-vs-ptc-capability-20260930.md`).
939
-
940
- - **The PTC tool descriptions now declare what a binding call resolves to
941
- (ADR-0024).** `ptc_run_code` and `ptc_workflow` carry one shared binding
942
- contract: a binding result is `{ content, details }`, `content` is an array of
943
- content blocks (the text of a text-file result is `result.content[0].text`),
944
- `details` is an object or `null`, and there is no `files` / `output` /
945
- `matches` / `entries` field to read — `bash`, `grep`, `find` and `ls` return one text block
946
- of newline-separated rows that the program splits itself. A `builtin binding`
947
- that fails rejects with `ToolCallError`; `pi.dispatch` resolves to a
948
- `DispatchResult` with `text` and `status` and no `content`.
949
-
950
- Model-facing only: the wire, the worker and every existing program are
951
- unchanged. Field report before: a pty-driven run of the real TUI with PTC mode
952
- on produced 31 program crashes across 16 runs, the largest error class being the
953
- model treating a binding result as a string or as an object with a `files`
954
- field — neither is ever true, and nothing said so
955
- (`docs/research/ptc-binding-contract-measurement-20260930.md`).
956
-
957
- Re-measured after the change, same harness and tasks: **0 of those three crash
958
- classes in 16 runs**, the correct `result.content[0].text` access in 16 of 16,
959
- and the context median for a PTC run down from 36,669 to 8,360 with median
960
- turns from 8 to 2. Exact-match rate is not claimed to have improved — it moved
961
- 10/16 to 11/16 while the control arm moved 15/16 to 13/16, which is this
962
- harness's noise floor
963
- (`docs/research/ptc-binding-contract-re-measurement-20260930.md`).
964
-
965
- ### Changed
966
-
967
- - **The dispatch concurrency cap is one counter per session, not one per run.** The acquire
968
- moved out of the dispatcher and into `dispatch()`, so a single `DispatchSlotCounter` now
969
- serves every front: concurrent programs in the same session, the `ptc_subagent` tool, and
970
- background children. The value is still `PtcConfig.dispatchConcurrency` (default 8) and the
971
- refusal shape is unchanged; what changed is whose calls the cap counts.
972
-
973
- This reduces effective concurrency in two measurable ways, and neither is a rounding
974
- difference — 24 concurrent foreground calls against a real fake-`pi` spawn:
975
-
976
- - two concurrent programs that could each have 8 in flight now share 8 (16 spawned -> 8);
977
- - a program sharing a session with 8 live background children can now be refused **every**
978
- foreground slot (8 foreground spawned -> 0). The refusal is a hard reject with no queue, so
979
- an over-cap call is not parked behind a long-running child.
980
-
981
- A single program on its own is unchanged (8 of 24, before and after), which is why the
982
- change passed three review rounds. ADR-0016 §2 and ADR-0022 §9 are amended; the depth cap is
983
- not.
984
-
985
- The knob is now live rather than dead: the live control is
986
- `createBackgroundTaskRuntime({ concurrency })`, which sizes the session counter from
987
- `PtcConfig.dispatchConcurrency`. Passing `dispatchConcurrency` through
988
- `runPtcProgram({ config })` no longer sizes the cap a pi session uses (measured: 2
989
- configured, 8 dispatched).
990
-
991
- ### Fixed
992
-
993
- - **A background task that produced no answer is no longer reported as a
994
- success (#70, field report).** A child that hits a rate limit or a model
995
- error still exits 0: pi writes the reason onto the assistant `message_end`
996
- as `stopReason: "error"`, retries, and closes clean. The background pump
997
- read the exit code alone, so such a task was recorded `succeeded` and the
998
- model was told it had worked — while `ptc_task_output` answered "(no output
999
- yet; task X is succeeded)". `succeeded` now requires the child to exit 0
1000
- **and** to have produced assistant text, which is the rule
1001
- `decideCloseOutcome` already applied to the foreground path; one failure now
1002
- has one verdict and one sentence in both paths. A failing `resolve-exit`
1003
- writes `errorMessage`, and the child's own `stopReason: "error"` text is
1004
- carried onto the record so the model is told _why_, not only _that_
1005
- (ADR-0022 §2, amended). A non-zero exit is still left unlabelled and a model
1006
- stop still resolves `canceled` — neither is relabelled by this change.
1007
-
1008
- ## [1.1.1] - 2026-09-30
1009
-
1010
- ### Fixed
1011
-
1012
- - **The expanded `phases` block now says how many phases it withheld.**
1013
- When a run declared more than eight phases, the block appended a bare
1014
- ` …` to the roll-up line — it told the reader that something was hidden
1015
- but not how much. ADR-0013 §3 requires a block to report _what_ it
1016
- withheld, and the other four blocks (`code`, `log`, `out`, `warn`) all
1017
- report a count. The tail is now `…+N more phases`, inline, because the
1018
- phases block is a single roll-up line rather than a list of rows.
1019
-
1020
- ### Changed
1021
-
1022
- - **Docs**: the README's `TUI rendering` section now documents the
1023
- model-facing text block's own bounds — 100 characters for the inline form,
1024
- 200 per line (ADR-0012) — next to the on-screen tree's 4 / 6 / 120, so the
1025
- two contracts are no longer conflated and lowering one to match the other
1026
- does not read as a typo fix.
1027
-
1028
- ## [1.1.0] - 2026-09-29
1029
-
1030
- ### Fixed
1031
-
1032
- - **Background dispatch children no longer reap their parent's tasks (field
1033
- report, 2026-09-29).** Background children share the session's task storage
1034
- via `--session-dir`; a child's startup reconcile and exit sweep used to be
1035
- directory-wide, so a dispatch child — or any `pi` process started in the same
1036
- directory — flipped every running task to `lost`. Task records now carry an
1037
- owner identity (`ownerPid` / `ownerBootMs`) and reaping is owner-scoped
1038
- (ADR-0023); `lost_on_session_restart` now means the owner process died
1039
- before the task finished, or the record predates ownership (pre-upgrade
1040
- ownerless rows, which any process binding the directory reaps).
1041
- - **`pi.dispatch` refusals are actionable.** A missing `agent` argument is
1042
- reported distinctly (`agent is required (there is no default agent)`) and
1043
- both the missing- and unknown-agent results list the agents registered under
1044
- the effective scope plus the two lookup paths and the agent-file shape. The
1045
- binding now runs its arguments through pi's own `validateToolArguments`
1046
- (the same validation the built-in bindings use) and a validation failure
1047
- comes back as a `rejected` DispatchResult instead of reaching the
1048
- subprocess layer.
1049
-
1050
- ### Changed
1051
-
1052
- - **PTC program prompts name the dispatch binding precisely.** The
1053
- `ptc_run_code` / `ptc_workflow` descriptions and the `/ptc` briefing state
1054
- that `pi.dispatch` is always bound under its literal dot name (call it as
1055
- `tools["pi.dispatch"](...)`; `tools.pi.dispatch` does not exist), and each
1056
- run exposes its actual bound names on the new `ptcBindings` global so
1057
- programs never guess the binding surface.
1058
- - **Docs**: `ptc_run_code`'s `timeoutMs` description and `docs/usage/bgdispatch.md`
1059
- now state that a run timeout terminates in-flight foreground dispatches, and
1060
- the guide gains prerequisites (no default agent) and an output-compactness
1061
- convention (page via `ptc_task_output` instead of truncating).
1062
-
1063
- ## [1.0.0] - 2026-09-24
1064
-
1065
- ### Added
1066
-
1067
- - **Background dispatch: PTC programs can fan out to long-lived children.**
1068
- `pi.dispatch(...)` gains an opt-in `{ background: true }` path: instead of
1069
- awaiting the child, the binding returns a `DispatchHandle`
1070
- (`{ taskId, label, status: "running" }`) immediately, and a detached pump
1071
- drives a session-level `TaskRecord` through
1072
- `running / stopping / succeeded / failed / canceled / lost`, past the end of
1073
- the program and of the turn. The handle is a frozen spawn-time projection;
1074
- live state comes from three model-facing tools that stay on when PTC mode is
1075
- off (`/ptc off` only blocks new spawns):
1076
- - **`ptc_task_list`** — list this session's tasks (`status?`, `limit?`,
1077
- default 100, newest first);
1078
- - **`ptc_task_output`** — read a task's captured output (`taskId`,
1079
- `sinceBytes?`), tail-truncated to pi's 50 KB / 2000-line contract
1080
- (ADR-0015) with the full text written to a temp file;
1081
- - **`ptc_task_stop`** — ask a running task to stop (`taskId`, `reason?`,
1082
- default `"model stop"`), moving it `running -> stopping -> canceled`.
1083
-
1084
- Lifecycle changes arrive as user-role `<bg-task-notification>` events under a
1085
- `<bg-task-notifications>` batch, cursor-delivered per subscriber with a
1086
- 2048-byte inline-preview ceiling. Background tasks count against the existing
1087
- `dispatchConcurrency` (8) for their whole lifetime and share the
1088
- `maxDispatchDepth` (3) recursion bound. Existing foreground `pi.dispatch`
1089
- keeps its `DispatchResult` shape unchanged. ADR-0022.
1090
-
1091
- ## [0.1.3] - 2026-09-23
1092
-
1093
- ### Added
1094
-
1095
- - **PTC rows show what the program is doing while it runs.** Two partial-state
1096
- visuals on the `ptc_run_code` / `ptc_workflow` row, both absent until now:
1097
- - a **shimmer** on the call row — same text, one character at a time bright,
1098
- the highlight sweeping at 150ms — so a running row is distinguishable from a
1099
- settled one in a column (ADR-0020);
1100
- - a **sub-call tree** under it: one row per binding call the program made,
1101
- live from the moment the call is made, with its own five-state status
1102
- (`running` / `ok` / `error` / `cancelled` / `rejected`) and duration,
1103
- visible without expanding the row and capped at 32 with a `+N more` tail
1104
- (ADR-0021). A failed run shows the failure text but no sub-call tree: the
1105
- tool throws (pi's convention), and pi builds that error result with an empty
1106
- `details`, so the tracked calls are dropped with it.
1107
-
1108
- ### Changed
1109
-
1110
- - **Build pipeline: minify + source-map exclusion.** `pnpm run build`
1111
- (`vp pack`) now produces minified `dist/*.js` (rolldown's built-in oxc
1112
- minifier; no new dependency). The npm tarball excludes `dist/**/*.map`
1113
- via the `package.json#files` whitelist — sourcemaps stay on disk for
1114
- local stack traces, but no longer ship. Tarball shrinks from 168.7 kB
1115
- packed / 548.6 kB unpacked (v0.1.2 baseline) to 40.5 kB / 113.6 kB
1116
- (−76% / −79%); `dist/*.js` total shrinks from 161,303 B to 53,959 B
1117
- (−66.5%). All 39 public exports retain their original names. ADR-0019.
1118
-
1119
- ## [0.1.2] - 2026-09-23
1120
-
1121
- ### Fixed
1122
-
1123
- - **`pi.dispatch` is registered in production again.** The injection check compared the
1124
- binding-name array by reference against `DEFAULT_BINDING_NAMES`; production resolves
1125
- names through a `.filter()` that always returns a fresh array, so the comparison never
1126
- held and shipped sessions had no `pi.dispatch` at all. Injection now keys off a new
1127
- `includeDispatch` option (defaulting to "the caller passed no explicit `names`"), and
1128
- both shipped tools pass it explicitly. ADR-0016.
1129
- - **The dispatch concurrency cap honours `dispatchConcurrency` (default 8) and rejects
1130
- immediately instead of queueing.** The dispatcher used to read `maxParallelSubCalls`
1131
- (default 10) and FIFO-queue the overflow; per ADR-0016 §2 the N+1th concurrent
1132
- `pi.dispatch` now resolves at once with `{ status: "rejected", errorMessage:
1133
- "dispatch concurrency limit reached" }`. The cap applies to `pi.dispatch` only and
1134
- has its own counter; builtin binding fan-out keeps DSH's `maxParallelSubCalls` (10)
1135
- FIFO-queueing semantics (ADR-0004), so in-flight builtin calls never consume
1136
- dispatch slots.
1137
- - **The depth-limit rejection message is verbatim again.** The program receives
1138
- exactly `dispatch depth limit reached`, matching what the child's
1139
- `<pi-ptc-context>` hint promises — the diagnostic suffix is gone.
1140
- - **`maxDispatchDepth` bounds recursion again.** Every run reported depth 0 and
1141
- children never inherited it, so the depth check could never fire. `dispatch()` now
1142
- stamps `PI_PTC_DEPTH` on the child subprocess's environment, the extension
1143
- entrypoint reads it back, and `runPtcProgram()` accepts a `depth` baseline that
1144
- reaches the binding context. ADR-0016 Recursive section.
1145
-
1146
- ## [0.1.1] - 2026-09-23
1147
-
1148
- PTC runs inside one agent turn no longer pay a cold start each. Nothing changes in
1149
- what a program may call or return; the difference is latency and cancellation
1150
- behaviour.
1151
-
1152
- ### Changed
1153
-
1154
- - **Per-turn worker pool.** One warm `worker_threads` Worker per surface
1155
- (`run_code`, `workflow`) is kept for the duration of an agent turn and reused
1156
- across its PTC runs — ~0 ms against ~58 ms for a cold spawn (median of 5). The
1157
- pool is created lazily by the first PTC run of the turn, the two surfaces do not
1158
- share one, and the extension's `turn_end` hook retires it. Idle workers are
1159
- `unref()`-ed, so a warm pool never keeps `pi` alive. `runPtcProgram()` gains an
1160
- optional `pool` field; without it the original cold-start path runs unchanged.
1161
- ADR-0017.
1162
- - **The worker entry is a real `dist/worker.js`.** The `data:` URL built from
1163
- `Function.prototype.toString()` is retired, so V8's code cache and Node's module
1164
- cache survive warm reuse. TypeScript the _model_ submits at run time is still
1165
- type-stripped inside the worker — that is `compileProgram`'s path, not the
1166
- bootstrap's.
1167
-
1168
- ### Fixed
1169
-
1170
- - **Cancellation settles within a bound in every ordering.** A still-armed deadline
1171
- is the ceiling when a cancel arrives first; a worker that never answers a timeout
1172
- settles at `timeoutMs + graceMs`. A superseded run's frames can no longer settle
1173
- its successor, and an idle worker answers a cancel instead of making the host wait
1174
- out its grace window. ADR-0017 §10.
1175
- - **`serializedBytes` no longer bills containers as zero bytes.** `[null × 100k]`
1176
- was counted as nothing, so a frame could slip past `maxMessageBytes`; the helper
1177
- now matches `JSON.stringify` byte-for-byte and sums `ArrayBuffer` / typed-array
1178
- leaves.
1179
-
1180
- ## [0.1.0] - 2026-09-22
1181
-
1182
- First npm release. Ports DSH PTC mode — Programmable Tool Calling, formerly
1183
- DSH "Code Mode" — to `pi`. Behaviour tracks
1184
- [`deepseek-ai/deepseek-harness`](https://github.com/deepseek-ai/deepseek-harness).
1185
- Not a new design. Re-alignment.
1186
-
1187
- Install with `pi install npm:pi-ptc-subagents`. PTC tools register on next
1188
- `pi` startup. No postinstall hook. No setup.
1189
-
1190
- ### Added
1191
-
1192
- - **`ptc_run_code`**. Model submits a JS/TS program. Multiple tools execute
1193
- in one shot. Return value and collected logs return to context. Worker
1194
- protocol, dispatcher, and surfaces back it.
1195
- - **`ptc_workflow`**. Narration over `ptc_run_code`. Uses the in-program
1196
- `log()` helper.
1197
- - **`pi.dispatch` binding**. PTC programs fan out to a fresh per-call `pi`
1198
- subprocess. DSH parity.
1199
- - **Builtin bindings**. `read`, `bash`, `edit`, `write`, `grep`, `find`,
1200
- `ls`. Callable from a PTC program as `tools.<name>(args)`. DSH parity.
1201
- - **Hoisted images**. Successful bindings lift image blocks onto the PTC
1202
- tool result. The model sees the picture. The program never carries it
1203
- through the return value. Parity with `dsh-tools`'s `exec.deferContext`.
1204
- - **Value-tree rendering**. Container completion values render as a tree.
1205
- One row per property or array index. Bounded by depth and width caps.
1206
- Withheld amount is shown in-band. Scalar values render as one-line hints.
1207
- - **Truncation contract**. Model-facing text honors `pi`'s truncation cap.
1208
- Container values render as compact PTC rows.
1209
- - **Skills restored**. The skills section `pi` withholds is restored. PTC
1210
- programs call them as bindings. DSH parity.
1211
- - **Build verification scripts**. `verify:dist`, `json-to-tui`,
1212
- `preview-ptc-render`. Run before publishing.
1213
-
1214
- ### Changed
1215
-
1216
- - Build pipeline switched to `vite-plus` (`vp pack`). Type declarations
1217
- bundle via `rolldown-plugin-dts`.
1218
- - Tooling moved to pnpm 12.5.1. `vitest` coverage pinned. Lockfile
1219
- determinism set via `minimumReleaseAge: 0`.
1220
- - Lint and format moved to `oxlint` (type-aware) and `oxfmt`.
1221
-
1222
- ### Fixed
1223
-
1224
- - `pi.dispatch` now spawns `pi` directly. The host script used to swallow
1225
- the child's exit.
1226
- - `maxPendingCalls` stays in the worker. The caller no longer trips on
1227
- arrivals.
1228
- - Bindings mirror the session's active tools. A session that disables a
1229
- tool also disables it inside PTC programs.
1230
- - pnpm overrides dropped. No-op under pnpm 12.5.1.