pi-background-tasks 0.7.6 → 0.9.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/PUBLISHING.md +7 -7
- package/README.md +238 -17
- package/TESTING.md +94 -0
- package/TEST_PLAN.md +28 -4
- package/extensions/delegate-child.ts +1 -0
- package/package.json +10 -4
- package/src/core/common.ts +41 -0
- package/src/core/context/parent-snapshot.ts +142 -0
- package/src/core/context/token-budget.ts +890 -0
- package/src/core/context/visible-conversation-v2.ts +551 -0
- package/src/core/delegate/artifacts.ts +479 -0
- package/src/core/delegate/budget.ts +370 -0
- package/src/core/delegate/hook-contract-evidence.json +18 -0
- package/src/core/delegate/hook-contract.ts +153 -0
- package/src/core/delegate/launch.ts +460 -0
- package/src/core/delegate/result-package.ts +443 -0
- package/src/core/delegate/runner.ts +406 -0
- package/src/core/delegate/seed.ts +411 -0
- package/src/core/delegate/types.ts +304 -0
- package/src/core/fusion/artifacts.ts +64 -4
- package/src/core/fusion/budget.ts +464 -65
- package/src/core/fusion/context.ts +115 -511
- package/src/core/fusion/orchestrator.ts +184 -18
- package/src/core/fusion/pi-child.ts +473 -8
- package/src/core/fusion/prompts.ts +156 -4
- package/src/core/fusion/types.ts +237 -37
- package/src/core/fusion/web-fetch.ts +904 -0
- package/src/core/fusion/workflows.ts +130 -0
- package/src/core/registry.ts +174 -0
- package/src/delegate-child-extension.ts +673 -0
- package/src/delegate-extension.ts +587 -0
- package/src/extension.ts +10 -0
- package/src/fusion-child-extension.ts +279 -2
- package/src/fusion-extension.ts +183 -26
package/PUBLISHING.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Publishing pi-background-tasks
|
|
2
2
|
|
|
3
|
-
Release checklist for npm publishing and standalone git publishing. The current release candidate is 0.
|
|
3
|
+
Release checklist for npm publishing and standalone git publishing. The current release candidate is 0.9.0, which adds the `fusion_validate` validation workflow and Anthropic child sanitization; 0.7.0 introduced the Fusion public surfaces (`/fusion`, `/fusion-models`, `fusion_brainstorm`) in addition to the background-task surfaces. Do not advertise the GitHub install target until the standalone repository has the exact release commit and tag.
|
|
4
4
|
|
|
5
5
|
## Preconditions
|
|
6
6
|
|
|
@@ -37,8 +37,8 @@ npm publish --access public
|
|
|
37
37
|
Pi install smoke after publish:
|
|
38
38
|
|
|
39
39
|
```bash
|
|
40
|
-
PI_CODING_AGENT_DIR=$(mktemp -d) pi -e npm:pi-background-tasks@0.
|
|
41
|
-
pi install npm:pi-background-tasks@0.
|
|
40
|
+
PI_CODING_AGENT_DIR=$(mktemp -d) pi -e npm:pi-background-tasks@0.9.0 --offline --no-tools --no-session -p "/jobs"
|
|
41
|
+
pi install npm:pi-background-tasks@0.9.0
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
## Publish to git
|
|
@@ -51,15 +51,15 @@ git status --short --branch
|
|
|
51
51
|
git log --oneline -3
|
|
52
52
|
git remote -v
|
|
53
53
|
git push origin main
|
|
54
|
-
git tag v0.
|
|
55
|
-
git push origin v0.
|
|
54
|
+
git tag v0.9.0
|
|
55
|
+
git push origin v0.9.0
|
|
56
56
|
```
|
|
57
57
|
|
|
58
58
|
Pi install smoke after git tag, using an isolated Pi agent directory so no local checkout or user `~/.pi` state is involved:
|
|
59
59
|
|
|
60
60
|
```bash
|
|
61
|
-
PI_CODING_AGENT_DIR=$(mktemp -d) pi -e git:github.com/ismailsaleekh/pi-background-tasks@v0.
|
|
62
|
-
pi install git:github.com/ismailsaleekh/pi-background-tasks@v0.
|
|
61
|
+
PI_CODING_AGENT_DIR=$(mktemp -d) pi -e git:github.com/ismailsaleekh/pi-background-tasks@v0.9.0 --offline --no-tools --no-session -p "/jobs"
|
|
62
|
+
pi install git:github.com/ismailsaleekh/pi-background-tasks@v0.9.0
|
|
63
63
|
```
|
|
64
64
|
|
|
65
65
|
## pi.dev/packages
|
package/README.md
CHANGED
|
@@ -2,26 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
Claude-Code-like explicit background shell task manager for [Pi](https://pi.dev/).
|
|
4
4
|
|
|
5
|
-
This package adds named, tracked background shell jobs with durable output files, bounded log reads, kill/timeout safety, task-owned context-window/token/tool-use/model telemetry, explicit Pi-agent telemetry wrapping for tasks marked as agents, a focused footer-dock task manager, `/tasks` fallback UI, and completion notifications that can wake the agent when LLM-launched work finishes. It also ships Fusion: a direct child-Pi five-call synthesis workflow exposed as `/fusion`, `/fusion-models`, and the always-active `fusion_brainstorm`
|
|
5
|
+
This package adds named, tracked background shell jobs with durable output files, bounded log reads, kill/timeout safety, task-owned context-window/token/tool-use/model telemetry, explicit Pi-agent telemetry wrapping for tasks marked as agents, a focused footer-dock task manager, `/tasks` fallback UI, and completion notifications that can wake the agent when LLM-launched work finishes. It also ships Fusion: a direct child-Pi five-call synthesis workflow exposed as `/fusion`, `/fusion-models`, and the always-active `fusion_brainstorm` and `fusion_validate` tools. A terminal task status is published only after trailing wrapped-agent telemetry is consumed and final output plus terminal metadata have completed their durability writes.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
9
9
|
From npm after publish:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
pi install npm:pi-background-tasks@0.
|
|
12
|
+
pi install npm:pi-background-tasks@0.9.0
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
From git after pushing this package to its standalone repository and tagging:
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
pi install git:github.com/ismailsaleekh/pi-background-tasks@v0.
|
|
18
|
+
pi install git:github.com/ismailsaleekh/pi-background-tasks@v0.9.0
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
For project-local install:
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
|
-
pi install -l npm:pi-background-tasks@0.
|
|
24
|
+
pi install -l npm:pi-background-tasks@0.9.0
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
## Commands
|
|
@@ -91,11 +91,14 @@ The lookup runs at most once per session on `session_start`, is time-boxed, and
|
|
|
91
91
|
## LLM tools
|
|
92
92
|
|
|
93
93
|
- `bg_run` — start named long-running commands without blocking the conversation.
|
|
94
|
+
- `bg_delegate` — launch one background Pi agent seeded with a frozen projection of the current conversation, then return a launch receipt immediately. See [Delegated background agents](#delegated-background-agents).
|
|
95
|
+
- `bg_result` — retrieve a `bg_delegate` answer, hash-verified before it is returned.
|
|
94
96
|
- `bg_run_pi_attested` — opt-in structured direct-spawn Pi agent task that emits a strict local attestation sidecar after successful completion.
|
|
95
97
|
- `bg_status` — inspect one task or all recent tasks.
|
|
96
98
|
- `bg_logs` — read bounded task output.
|
|
97
99
|
- `bg_kill` — stop a running task.
|
|
98
|
-
- `
|
|
100
|
+
- `fusion_validate({prompt})` — always-active tool that runs the same five-model Fusion workflow as a validation review of work that was just completed, and returns the merged prose review. Its closed public schema has exactly one parameter, `prompt`; **it takes no `capability` argument**, and a caller-supplied `capability` is rejected loudly rather than ignored. Candidate reviewers always run with the read-only `inspect` capability, because a reasoning-only reviewer cannot read the code it is judging; the evaluator and merger remain no-tools by stage policy. Findings are classified `critical`, `high`, or `minor`, each with a file/symbol location, the evidence the reviewer actually read, and why it matters. A review with no findings must state what was verified rather than returning an unexplained pass. See [Validation workflow](#validation-workflow).
|
|
101
|
+
- `fusion_brainstorm({prompt, capability?})` — always-active tool that runs the Fusion workflow and returns the exact merged text as the tool result for the parent agent to consume, with the exact Pi `Usage` shape attached when the host supports tool-result usage: token fields plus complete `cost.input`, `cost.output`, `cost.cacheRead`, `cost.cacheWrite`, and `cost.total`. Calling it as `fusion_brainstorm({prompt})` uses the default `reason` capability: no tools and byte-identical child argv/prompt behaviour to the previous release. The optional `capability` accepts only `"reason"`, `"inspect"`, or `"research"`; `inspect` gives candidate children read-only repository inspection tools, and `research` adds the package-owned `fusion_web_fetch` tool for targeted public URL fetches. The evaluator and merger remain no-tools by stage policy. Its closed public schema has one required parameter, `prompt`, plus optional `capability`; extra keys are rejected. It has no eligibility, quota, routine, or justification gate. Tool context capture excludes the current assistant tool-call leaf when Pi is executing that `fusion_brainstorm` call, so the nested children do not see the in-progress tool call or sibling calls. Children receive the documented conversation projection described under [Conversation context policy](#conversation-context-policy): visible user/assistant text verbatim, with thinking and tool payloads replaced by explicit hash-accounted omission receipts. Because the prompt is composed by the parent agent, it is treated as authoritative and self-contained.
|
|
99
102
|
|
|
100
103
|
`bg_run` requires a concise `name` for the footer dock, the shell `command`, and required `isAgent: boolean`. Set `isAgent: true` only when the background task launches an LLM/agent process (for example `pi -p ...` or `pi --mode json ...`); set `isAgent: false` for scripts, tests, dev servers, sleeps, and ordinary shell commands. It defaults both `notifyOnCompletion` and `triggerOnCompletion` to `true`. With those defaults, `bg_run` returns immediately, the agent continues only independent useful work or ends its current turn instead of sleeping or polling, and a durable `background-task-notification` for completed, failed, or killed state automatically starts a follow-up turn. The launch receipt states the effective notification/wake behavior explicitly. `bg_status` and `bg_logs` remain available for user-requested inspection, deliberately disabled completion delivery, concrete hang diagnosis, or reading output after the terminal event; they are not waiting primitives, and the terminal notification does not need status reconfirmation. Setting `triggerOnCompletion: false` keeps the notification but prevents it from starting an agent turn. Setting `notifyOnCompletion: false` suppresses both notification and wake-up even if `triggerOnCompletion` is true.
|
|
101
104
|
|
|
@@ -104,9 +107,170 @@ Tasks marked with `isAgent: true` that launch print/json child Pi agents through
|
|
|
104
107
|
`bg_run_pi_attested` is separate from `bg_run` and never accepts a shell command. It takes structured `provider`, `model`, `prompt`, optional literal extra Pi argv, and a relative `reportPath`; launches exactly one direct `pi --mode json` child; records raw Pi JSON events, separate stderr, exact argv/cwd, prompt/report hashes, observed Pi session/provider/model, and `ModelRegistry.isUsingOAuth` credential class. It forbids direct API-key/auth-file launch arguments and emits no partial attestation: failures remain ordinary failed tasks with no sidecar.
|
|
105
108
|
|
|
106
109
|
|
|
110
|
+
## Delegated background agents
|
|
111
|
+
|
|
112
|
+
`bg_delegate` fills the gap between `bg_run` (a background agent with a **fresh,
|
|
113
|
+
empty** context) and `fusion_brainstorm` (your current context, but synchronous
|
|
114
|
+
and five-model). It is one agent, one prompt, seeded with the current session's
|
|
115
|
+
context, non-blocking. `bg_result` retrieves its answer safely. They ship
|
|
116
|
+
together: a delegate without a safe retrieval path could not return its work.
|
|
117
|
+
|
|
118
|
+
```text
|
|
119
|
+
bg_delegate({ name, prompt }) → launch receipt, immediately
|
|
120
|
+
… the parent keeps working; the terminal notification wakes it …
|
|
121
|
+
bg_result({ taskId }) → hash-verified answer
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Context seeding
|
|
125
|
+
|
|
126
|
+
The child does **not** share the parent's live session. The parent conversation
|
|
127
|
+
is projected with the same frozen `visible-conversation-ledger-v2` transform
|
|
128
|
+
Fusion uses (see [Conversation context policy](#conversation-context-policy)),
|
|
129
|
+
frozen as an immutable seed, and the child is given its **own** `--session-id`
|
|
130
|
+
and a task-owned `--session-dir`. Conceptually it has your context; physically it
|
|
131
|
+
can never open or mutate the parent session.
|
|
132
|
+
|
|
133
|
+
| Content | Disposition |
|
|
134
|
+
|---|---|
|
|
135
|
+
| User text | included verbatim, never clipped |
|
|
136
|
+
| Assistant text | included verbatim, never clipped |
|
|
137
|
+
| User image blocks | marker only, never raw bytes |
|
|
138
|
+
| Assistant thinking | excluded; recorded as a hash-accounted omission receipt |
|
|
139
|
+
| Tool-call arguments | excluded; recorded as a hash-accounted omission receipt |
|
|
140
|
+
| Tool-result payloads | excluded; recorded as a hash-accounted omission receipt |
|
|
141
|
+
| The in-flight `bg_delegate` call and its sibling calls | scope-excluded from the branch |
|
|
142
|
+
|
|
143
|
+
The assistant message carrying the in-flight call is excluded as a whole, so when
|
|
144
|
+
several `bg_delegate` calls share one assistant message **every** sibling call is
|
|
145
|
+
excluded for **every** child: two delegates launched together receive identical
|
|
146
|
+
projected history and neither can observe the other's arguments.
|
|
147
|
+
|
|
148
|
+
The seed is canonical-JSON, SHA-256'd, and persisted. The persisted bytes are the
|
|
149
|
+
exact bytes the child reads, and the child re-verifies that hash **before its
|
|
150
|
+
first model call**. Repeated construction from the same session is
|
|
151
|
+
byte-identical.
|
|
152
|
+
|
|
153
|
+
**Documented limitation:** facts that exist only inside omitted parent tool
|
|
154
|
+
output are **not** available to the child. The child is told this explicitly and
|
|
155
|
+
instructed to say so plainly rather than guess. Restate any such finding in the
|
|
156
|
+
`prompt`.
|
|
157
|
+
|
|
158
|
+
### Route pinning
|
|
159
|
+
|
|
160
|
+
The route is pinned at launch — by default the parent's current effective
|
|
161
|
+
provider/model, or an explicit `route {provider, model}`. It is **never**
|
|
162
|
+
substituted, never falls back, and is never retried on a different route. An
|
|
163
|
+
unavailable route or one with no declared context window is a typed refusal
|
|
164
|
+
before anything is created. The child additionally asserts that every assistant
|
|
165
|
+
message it produced came from the pinned route; a mismatch prevents the run from
|
|
166
|
+
committing an answer at all.
|
|
167
|
+
|
|
168
|
+
### Inspect-only capability boundary
|
|
169
|
+
|
|
170
|
+
v1 supports exactly one capability, `inspect`. The child is launched with
|
|
171
|
+
`--tools read,grep,find,ls,delegate_read_artifact`, `--no-builtin-tools`, an
|
|
172
|
+
explicit `--exclude-tools` denylist, and no ambient extensions, skills, prompt
|
|
173
|
+
templates, themes, or context files. **The boundary is enforced by argv and the
|
|
174
|
+
child's tool registry, not by prompt text.** There is no shell, no network, no
|
|
175
|
+
edit/write, no recursive delegation, and no Fusion from the child. Writable
|
|
176
|
+
profiles are deliberately out of scope.
|
|
177
|
+
|
|
178
|
+
### Budgets, spilling, and limits
|
|
179
|
+
|
|
180
|
+
Admission is checked **before** the child process, the child session, or the
|
|
181
|
+
artifact directory exists, so a refusal leaves **zero** children and **zero**
|
|
182
|
+
artifacts. Inside the child, every model call is measured before dispatch; a call
|
|
183
|
+
that would exceed the pinned route's allowance is refused and the run terminates
|
|
184
|
+
with a typed `provider_context_budget_exhausted`.
|
|
185
|
+
|
|
186
|
+
A tool result larger than the per-result transcript cap is written **in full** to
|
|
187
|
+
a hashed artifact and replaced in the transcript by an explicit receipt naming
|
|
188
|
+
the artifact, its exact byte count, its SHA-256, and how to read a bounded range.
|
|
189
|
+
The raw payload never enters the transcript and **nothing is truncated**. The
|
|
190
|
+
bounded `delegate_read_artifact` tool returns exactly the requested range or
|
|
191
|
+
fails; a request past end-of-file is refused rather than silently shortened.
|
|
192
|
+
Turn, tool-call, aggregate-output, and wall-clock limits are enforced and
|
|
193
|
+
reported.
|
|
194
|
+
|
|
195
|
+
### Retrieving the answer
|
|
196
|
+
|
|
197
|
+
The child commits exactly one self-contained result package by temp-write,
|
|
198
|
+
`fsync`, rename, directory `fsync`. **The rename is the commit point**: a package
|
|
199
|
+
present under its final name is complete, and its absence means no answer was
|
|
200
|
+
accepted — whatever the process exit code was. A child that exits `0` without
|
|
201
|
+
committing is a typed `child_exited_without_commit`, never a silent empty
|
|
202
|
+
success. A run that degraded anything latches terminal state and **cannot**
|
|
203
|
+
commit a success package, so a hash-valid answer can never be built on silently
|
|
204
|
+
mutilated context.
|
|
205
|
+
|
|
206
|
+
`bg_result` verifies the package identity, seed hash, route, every per-block
|
|
207
|
+
SHA-256, and the aggregate SHA-256 before returning a single byte, and returns
|
|
208
|
+
bytes from the buffer it verified. A running task returns a typed *not ready*
|
|
209
|
+
result and **never blocks or polls**. An answer over the inline cap degrades to
|
|
210
|
+
an artifact reference **explicitly**; requesting `delivery:"inline"` for it is a
|
|
211
|
+
typed `result_too_large_for_inline` failure naming the artifact. It is **never**
|
|
212
|
+
truncated to fit. `autoDeliver` (`never` | `when_small` | `always`) defaults to
|
|
213
|
+
`never`: completion notifications carry metadata, and the answer is fetched
|
|
214
|
+
deliberately with `bg_result`.
|
|
215
|
+
|
|
216
|
+
### Failure taxonomy
|
|
217
|
+
|
|
218
|
+
Every delegate failure is typed and states what happened, what was preserved, and
|
|
219
|
+
what the operator can do. Admission codes
|
|
220
|
+
(`delegate_hook_contract_unsupported`, `delegate_isolation_unsupported`,
|
|
221
|
+
`route_unresolved`, `route_capacity_unknown`, `seed_projection_failed`,
|
|
222
|
+
`seed_budget_exceeded`, `seed_persist_failed`, `invalid_arguments`) always report
|
|
223
|
+
`childCreated: false`. Execution and integrity codes include `child_spawn_failed`,
|
|
224
|
+
`child_timeout`, `child_cancelled`, `child_turn_limit`, `child_tool_call_limit`,
|
|
225
|
+
`child_exited_without_commit`, `provider_context_budget_exhausted`,
|
|
226
|
+
`aggregate_tool_output_cap`, `child_result_invalid`,
|
|
227
|
+
`child_result_encoding_invalid`, `route_attestation_missing`, `route_mismatch`,
|
|
228
|
+
`seed_hash_mismatch`, `answer_hash_mismatch`, `artifact_spill_failed`, and
|
|
229
|
+
`artifact_read_failed`. Retrieval states are `result_not_ready`,
|
|
230
|
+
`result_unavailable`, `result_too_large_for_inline`, and `task_unknown`.
|
|
231
|
+
|
|
232
|
+
Usage that the provider did not report is recorded as explicitly `unavailable`,
|
|
233
|
+
never as zero.
|
|
234
|
+
|
|
235
|
+
### Proven Pi hook contract
|
|
236
|
+
|
|
237
|
+
The child-side guard depends on runtime Pi behaviour, which is **proven by
|
|
238
|
+
execution** rather than read from type declarations. The
|
|
239
|
+
`npm run test:hook-contract` gate drives a real Pi agent loop and records what it
|
|
240
|
+
observed. On Pi 0.83 it establishes that `context` fires once before every model
|
|
241
|
+
call in extension load order and that returned messages reach the provider; that
|
|
242
|
+
**throwing** from a `context` handler does **not** block the call (Pi catches it
|
|
243
|
+
and dispatches anyway); that `ctx.abort()` does not skip the provider call site
|
|
244
|
+
but hands it an already-aborted signal and terminates the run; and that
|
|
245
|
+
`tool_result` fires before the result enters the transcript, chains in load order,
|
|
246
|
+
and preserves tool-call id, role, and error flag across replacement.
|
|
247
|
+
|
|
248
|
+
Because neither a throw nor an abort is a hard admission gate on its own, the
|
|
249
|
+
guard uses abort as the barrier **and** removes the oversized content from the
|
|
250
|
+
outgoing message set, so the request cannot carry it even if a provider ignored
|
|
251
|
+
the aborted signal. If a Pi build cannot provide the required guarantees,
|
|
252
|
+
`bg_delegate` refuses to spawn with a typed
|
|
253
|
+
`delegate_hook_contract_unsupported`; the guard is never weakened to fit.
|
|
254
|
+
|
|
255
|
+
Delegate artifacts are written under:
|
|
256
|
+
|
|
257
|
+
```text
|
|
258
|
+
.pi/delegate/<session-id>-<pid>/<task-id>/
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Each run contains `seed.json`, `child-prompt.txt` (the exact bytes handed to the
|
|
262
|
+
child over stdin, never a shell or positional argument),
|
|
263
|
+
`context-omission-ledger.json`, `budget-plan.json`, `manifest.json`, the
|
|
264
|
+
task-owned `child-session/`, any `spill/` artifacts, `result.json` once the child
|
|
265
|
+
commits, and `outcome.json` once the parent adjudicates the run.
|
|
266
|
+
|
|
267
|
+
`result.json` is written by the **child** and `outcome.json` by the **parent**, so
|
|
268
|
+
neither writer can claim a state it did not observe. `manifest.state` records only
|
|
269
|
+
what the parent knew at launch and is never used to decide success.
|
|
270
|
+
|
|
107
271
|
## Fusion workflow
|
|
108
272
|
|
|
109
|
-
Fusion runs direct child `pi --mode text` processes only; it never calls `pi-ai` completion APIs. Each child is launched with `--no-session`, `--no-
|
|
273
|
+
Fusion runs direct child `pi --mode text` processes only; it never calls `pi-ai` completion APIs. Each child is launched with `--no-session`, `--no-extensions`, `--no-skills`, `--no-prompt-templates`, `--no-themes`, and `--no-context-files`, plus the tool policy described below, the resolved provider/model/thinking level, and the package-owned private `extensions/fusion-child.ts` metadata extension. The prompt travels over stdin, not a shell or positional argument.
|
|
110
274
|
|
|
111
275
|
Pi text mode writes the final full answer exactly once instead of serializing cumulative reasoning/partial-message events on every token delta. The private child extension emits one compact, reasoning-free metadata record per finalized assistant message for provider/model, stop reason, the complete Pi token/cost `Usage` object, and response byte/hash validation. Fusion persists those compact records in `*.events.jsonl`; the complete answer remains in the stage response artifact. The 32 MiB child stdout cap therefore applies to one final response, not amplified JSON telemetry. Failed attempts keep the authoritative response artifact empty and, when any stdout was captured, persist it separately as an explicitly incomplete `*.response.partial.*` artifact.
|
|
112
276
|
|
|
@@ -116,15 +280,71 @@ Model configuration is global under the Pi agent directory:
|
|
|
116
280
|
fusion-models.json
|
|
117
281
|
```
|
|
118
282
|
|
|
283
|
+
### Validation workflow
|
|
284
|
+
|
|
285
|
+
`fusion_validate` is the same orchestrator, artifact store, budget engine, conversation projection, and evaluation schema as `fusion_brainstorm`, with different stage framing. A workflow profile selects the four system prompts and the capability policy; it never changes the canonical input schema, which remains `pi-background-tasks.fusion-input.v4` for both tools. Canonical input bytes and the omission ledger are provably identical across workflows for the same conversation, and that equality is asserted by the golden-bytes gate rather than assumed.
|
|
286
|
+
|
|
287
|
+
| Concern | `fusion_brainstorm` | `fusion_validate` |
|
|
288
|
+
|---|---|---|
|
|
289
|
+
| Parameters | `{prompt, capability?}` | `{prompt}` — capability rejected |
|
|
290
|
+
| Candidate capability | caller-selected, default `reason` | always `inspect` |
|
|
291
|
+
| Evaluator / merger capability | `reason` by stage policy | `reason` by stage policy |
|
|
292
|
+
| Run id prefix | `f` | `v` |
|
|
293
|
+
| Output | prose answer | prose review |
|
|
294
|
+
| Evaluation schema | `fusion-evaluation.v1` | `fusion-evaluation.v1` (identical) |
|
|
295
|
+
|
|
296
|
+
The capability is fixed rather than defaulted. A `capability` argument on `fusion_validate` is a hard error at the schema boundary, and the orchestrator independently re-asserts the workflow capability before the artifact store or any child process exists, so a contradicting request launches **zero children**.
|
|
297
|
+
|
|
298
|
+
The three reviewers are blind-compared exactly like brainstorm candidates, but the evaluator is additionally required to treat each distinct defect claim as a unit and to carry every surviving claim into `synthesis_plan.must_include`, **including claims raised by only one reviewer**. The merger is correspondingly forbidden from dropping a single-source finding or inventing one no reviewer raised, and must state the resolution and reason wherever reviewers disagreed. Without those two clauses a real defect that only one model noticed could disappear by silent majority vote, which is the failure mode a three-model review exists to prevent.
|
|
299
|
+
|
|
300
|
+
`fusion_validate` is advisory and read-only. It never modifies files, it does not gate anything, and it is not a substitute for running tests or builds. Like every Fusion entry point, facts that exist only inside omitted tool output are not visible to reviewers; `inspect` lets them re-derive repository facts themselves, but uncommitted state that exists only in the parent agent's context must be restated in the prompt.
|
|
301
|
+
|
|
302
|
+
### Anthropic child sanitization
|
|
303
|
+
|
|
304
|
+
Fusion children launch with `--no-extensions` for isolation, which disables extension *discovery* while still honouring explicit `--extension` paths. The parent session normally loads an Anthropic system-prompt sanitizer through discovery, so a Claude child would inherit nothing and fail at the provider: Pi's own system prompt contains documentation lines Anthropic rejects.
|
|
305
|
+
|
|
306
|
+
Children routed to the `anthropic` provider therefore receive a second explicit extension, [`@ravshansbox/pi-anthropic-sps`](https://github.com/ravshansbox/pi-anthropic-sps) (MIT), resolved from the package's own dependency tree. The metadata extension is always passed first so its `message_end` frame is never displaced.
|
|
307
|
+
|
|
308
|
+
Children on every other provider receive exactly one `--extension` and their argv is byte-identical to the pre-sanitizer form. The sanitizer package publishes no `main`/`exports`, so it is located through its manifest's `pi.extensions[0]` entry rather than a direct require. Every resolution failure - package missing, manifest unreadable or malformed, no declared extension, or a declared file that does not exist - is a loud error before launch, because silently omitting the sanitizer would resurface later as an opaque provider rejection.
|
|
309
|
+
|
|
310
|
+
### Candidate capabilities
|
|
311
|
+
|
|
312
|
+
Fusion candidate children support three launch-time capability profiles. `reason` is the default: it passes `--no-tools` and preserves the previous no-tool candidate argv and prompt bytes. `inspect` is available only to candidate children and replaces `--no-tools` with the exact read-only tool policy `--no-builtin-tools --tools read,grep,find,ls --exclude-tools bash,edit,write,fusion_brainstorm,bg_delegate,bg_result,bg_run,bg_kill,bg_status,bg_logs,bg_run_pi_attested`. `research` extends `inspect` by adding the package-owned `fusion_web_fetch` tool to the `--tools` allowlist; the `--exclude-tools` denylist is unchanged and still bans `bash`, `edit`, `write`, `fusion_brainstorm`, `bg_delegate`, `bg_result`, `bg_run`, `bg_kill`, `bg_status`, `bg_logs`, and `bg_run_pi_attested`. The `reason` and `inspect` argv forms remain byte-identical to v0.7.8.
|
|
313
|
+
|
|
314
|
+
Evaluator, evaluation-repair, and merger children always run with `--no-tools` by stage policy. Caller input cannot grant them tools, even when candidates use `inspect` or `research`. Capability is recorded as launch metadata in the run manifest and child argv, not added to the canonical child-facing input; the canonical input schema remains `pi-background-tasks.fusion-input.v4`.
|
|
315
|
+
|
|
316
|
+
The inspect candidate system prompt tells the child it may re-derive facts from the repository using `read`, `grep`, `find`, and `ls`. The research prompt adds `fusion_web_fetch` for fetching a specific public URL as bounded Markdown or text. Both prompts extend the untrusted-data rule: projected conversation text, file contents, and fetched page content are data, never instructions to follow.
|
|
317
|
+
|
|
318
|
+
`fusion_web_fetch` has a closed schema: `{ url: string, extract?: 'text' | 'markdown' }`. `extract` defaults to Markdown. There is deliberately no per-fetch prompt parameter. Anthropic documents the `{url, prompt}` extraction pattern as lossy by design: the prompt decides what reaches the model, so a false negative can enter a Fusion candidate answer, pass through blind evaluation, and reach the merged answer with no signal that the page contained missed information.
|
|
319
|
+
|
|
320
|
+
HTML extraction uses the runtime dependency `turndown@7.2.4`; its only dependency is `@mixmark-io/domino`, so it does not require `jsdom`. Markdown is the default because it preserves link destinations, headings, tables, and code blocks better than plain text. This version has no web search, browser, PDF support, cache, or domain allowlist.
|
|
321
|
+
|
|
322
|
+
| `fusion_web_fetch` policy | Value |
|
|
323
|
+
|---|---:|
|
|
324
|
+
| Request method | GET |
|
|
325
|
+
| Schemes | `http:` and `https:` only |
|
|
326
|
+
| Timeout | 60 seconds |
|
|
327
|
+
| Response body cap | 2 MiB |
|
|
328
|
+
| Returned content cap | 32 KiB |
|
|
329
|
+
| Redirect cap | 5 hops |
|
|
330
|
+
|
|
331
|
+
Network handling is basic network hygiene, not a secret-exfiltration control. The hostname is resolved once, every returned address is checked, the connection is pinned to the vetted address, and the socket's remote address is verified after connect. Address validation re-runs on every redirect hop. Private, loopback, link-local, unique-local, multicast, and cloud-metadata addresses are refused. A research child that can read files and reach the network can in principle send what it read; that is an accepted trade in this version, not a sandbox or security boundary.
|
|
332
|
+
|
|
333
|
+
`fusion_web_fetch` fails loudly with typed errors rather than retrying or falling back to another URL, scheme, encoding, or extraction mode. Error codes include `invalid_url`, `unsupported_scheme`, `blocked_address`, `dns_failure`, `redirect_limit`, `redirect_blocked`, `response_too_large`, `unsupported_content_type`, `request_timeout`, `network_error`, `extraction_failed`, and `http_error`.
|
|
334
|
+
|
|
335
|
+
Every child has a stale-action watchdog in addition to the 30-minute absolute timeout. `FUSION_CHILD_IDLE_TIMEOUT_MS` defaults to 900 seconds and fails the child if no stdout or stderr activity occurs during that window; any stdout or stderr activity resets the watchdog. The threshold is deliberately far above tool latency: the child metadata frame is emitted only at `message_end` and text-mode stdout carries only the final assistant message, so one slow model turn is legitimately silent on both streams and must not be killed. The absolute timeout remains a backstop for children that keep producing output but never finish.
|
|
336
|
+
|
|
337
|
+
Inspect and research candidates also write a per-attempt tool-call audit log at `candidate-<slot>.attempt-<n>.tool-calls.jsonl`. The child appends one JSON line per completed tool call with the tool name, argument/result byte counts, and SHA-256 digests only. For `fusion_web_fetch`, the record also includes `url`, `final_url`, `http_status`, `response_bytes`, and `content_sha256`. Raw arguments, raw tool results, and page content are never written because file paths, file contents, fetched content, and tool results may contain secrets. After the child exits, the parent verifies the log is complete and contiguous; a trailing partial line, ordinal gap, duplicate ordinal, or schema-version mismatch is a loud failure.
|
|
338
|
+
|
|
119
339
|
Missing config means all five slots are `$current`. Malformed config, stale explicit models, unavailable current models, and concurrent selector write conflicts fail loudly before child inference. Selector saves use an inter-process lock plus revision re-read before rename so simultaneous dialogs cannot silently overwrite each other. Candidate identities are anonymized before evaluation; provider/model metadata stays in local artifacts, not in evaluator prompts.
|
|
120
340
|
|
|
121
341
|
Progress is surfaced through `fusion` status updates, TUI cancellable loader UI for `/fusion`, and partial `fusion_brainstorm` tool updates. Session shutdown or reload tracks the whole invocation from entry, aborts live or initializing Fusion runs, and waits for cleanup.
|
|
122
342
|
|
|
123
343
|
### Conversation context policy
|
|
124
344
|
|
|
125
|
-
Fusion children receive a **versioned conversation projection**, not a raw execution transcript. The canonical input schema is `pi-background-tasks.fusion-input.
|
|
345
|
+
Fusion children receive a **versioned conversation projection**, not a raw execution transcript. The canonical input schema is `pi-background-tasks.fusion-input.v4` and every run states exactly what was included and what was omitted.
|
|
126
346
|
|
|
127
|
-
The projection transform (`visible-conversation-ledger-
|
|
347
|
+
The projection transform (`visible-conversation-ledger-v2`) is shared by both entry points:
|
|
128
348
|
|
|
129
349
|
| Content | Disposition |
|
|
130
350
|
|---|---|
|
|
@@ -137,20 +357,21 @@ The projection transform (`visible-conversation-ledger-v1`) is shared by both en
|
|
|
137
357
|
| Tool-result images | excluded; recorded as an omission receipt (never raw bytes) |
|
|
138
358
|
| Active `fusion_brainstorm` call and its sibling calls | scope-excluded from the branch |
|
|
139
359
|
|
|
140
|
-
Omissions are **explicit, deterministic, and auditable** — never silent. Each omitted event produces a ledger row with its kind, exact byte count, and SHA-256 of the omitted bytes.
|
|
360
|
+
Omissions are **explicit, deterministic, and auditable** — never silent. Each omitted event produces a ledger row with its kind, exact byte count, and SHA-256 of the omitted bytes. Fusion v4 encodes child-facing projection entries as positional tuples to remove repeated object keys while preserving every role, ordinal, span, byte total, count, and text byte:
|
|
141
361
|
|
|
142
362
|
```json
|
|
143
|
-
|
|
363
|
+
["t","u",0,0,"hello"]
|
|
364
|
+
["o",[1,9],29019,[0,5,5]]
|
|
144
365
|
```
|
|
145
366
|
|
|
146
|
-
`
|
|
367
|
+
Text tuples are `["t", role, sourceOrdinal, blockOrdinal, text]`, where `role` is `"u"` for user or `"a"` for assistant. Omission tuples are `["o", [firstSourceOrdinal, lastSourceOrdinal], bytes, [assistantThinking, toolCalls, toolResultTexts]]`. The span is inclusive, `bytes` is the total omitted non-image payload for that run, and the count tuple order is fixed. Per-event hashes, ledger indices, and per-event byte details live in `context-omission-ledger.json`, not in the prompt: a child cannot verify a hash of payload it does not hold, so forwarding one only consumed context. That ledger also carries a `projection_map` proving every ledger row is represented by exactly one receipt or ledger-only image marker, and `accounting.omission_receipt_utf8_bytes` records the exact compact tuple receipt cost. The complete ledger is persisted as `context-omission-ledger.json`, and its row shape and root hash are unchanged by the compact encoding. **No head, tail, or preview of an omitted payload is ever forwarded** (`tool_payload_preview_bytes` is `0`), because an arbitrary prefix is usually irrelevant and can leak secrets or carry tool-output prompt injection. Repeated construction is byte-identical via canonical JSON, so prompt bytes and hashes are stable.
|
|
147
368
|
|
|
148
369
|
Two entry points share the transform but differ in request authority:
|
|
149
370
|
|
|
150
371
|
| Entry point | Policy id | `request.authority` |
|
|
151
372
|
|---|---|---|
|
|
152
|
-
| `fusion_brainstorm({prompt})` | `fusion-tool-explicit-
|
|
153
|
-
| `/fusion [prompt]` | `fusion-command-conversation-
|
|
373
|
+
| `fusion_brainstorm({prompt})` | `fusion-tool-explicit-v2` | `explicit_text` — the prompt is authoritative and self-contained |
|
|
374
|
+
| `/fusion [prompt]` | `fusion-command-conversation-v2` | `directive_over_projected_conversation` |
|
|
154
375
|
|
|
155
376
|
**Documented limitation:** facts that exist only inside omitted tool output are not available to Fusion children. Restate any required finding as visible conversation text, or include it in the `fusion_brainstorm` prompt. Children are instructed to say so plainly rather than guess. No model-generated summarization is used as hidden preprocessing.
|
|
156
377
|
|
|
@@ -158,7 +379,7 @@ Two entry points share the transform but differ in request authority:
|
|
|
158
379
|
|
|
159
380
|
Every prompt-expansion stage — candidate, evaluator, evaluation repair, and merger — is size-checked **before any child process is created**, and each stage is checked against **its own configured route**, so a large-context slot cannot hide a small-context sibling and a small slot cannot veto stages it never serves.
|
|
160
381
|
|
|
161
|
-
|
|
382
|
+
Input forecasting uses the shared affine estimator `estimateInputTokens({family, segments})`: additive integer byte-class accounting plus a 512-token affine intercept. Calibrated normal-ASCII rates are used only for backed exact model IDs, measured prompts at or above 50 KiB, and prompts that pass the low-whitespace dense-ASCII gate. That gate records the measured whitespace fraction in `budget-plan.json` and falls back conservatively for out-of-distribution near-zero-whitespace payloads; it is a heuristic token-density proxy, not a bound. Multibyte UTF-8 uses the conservative 2.0 B/tok fatal rate while persisting the provable 1.00 B/tok ceiling as advisory. The calibration basis is 882 real large Fusion prompts: Anthropic observed floor 2.047 B/tok (shipped `r=1.73`) and Codex observed floor 3.400 B/tok (shipped `r=2.89`); unknown providers and unbacked model IDs use the unbacked 1.00 B/tok floor and are surfaced in artifacts and result details.
|
|
162
383
|
|
|
163
384
|
Each stage is forecast with its **real prompt builder**, rendered with empty embedded-output slots, plus the enforced output contracts for whatever that stage will embed:
|
|
164
385
|
|
|
@@ -177,11 +398,11 @@ When a workflow cannot fit, the error names the **first failing mandatory stage*
|
|
|
177
398
|
|
|
178
399
|
Remediation is derived, not guessed: Fusion re-plans the entire workflow **with the request removed**. If it still fails, the error says plainly that shortening the request cannot help and points at starting a fresh conversation or raising the route's context window. If it then fits, the request is what determines feasibility, and the error states the exact minimum byte reduction and the maximum safe request size.
|
|
179
400
|
|
|
180
|
-
|
|
401
|
+
Preflight is two-tiered: input-only forecasts are fatal (`prompt_budget_exceeded_forecast`), while worst-case downstream output reservations are warning-only and recorded in `budget-plan.json`. Exact rendered per-stage checks remain fatal (`prompt_budget_exceeded_measured`). Runs that fit still emit advisory warnings for tight utilization or reservation overage. The warning never alters behaviour.
|
|
181
402
|
|
|
182
403
|
Every configured route must also satisfy a documented minimum capacity; smaller routes are rejected at configuration time with an actionable error naming the requirement, rather than being accepted and failing later at the provider. All route capacities, per-stage forecasts, headroom, utilization, the byte composition of the blocking stage, and the blockers list are persisted as `budget-plan.json`.
|
|
183
404
|
|
|
184
|
-
If an input still exceeds the safe budget, Fusion fails with
|
|
405
|
+
If an input still exceeds the safe budget, Fusion fails with `prompt_budget_exceeded_forecast` for input-only preflight or `prompt_budget_exceeded_measured` for exact rendered prompts. The error names the stage, measured bytes, measured token upper bound, allowed tokens, the limiting configured model and its context window, estimator source, and concrete remediation. **Zero children are launched** when preflight rejects. Provider context-window failures remain loud child failures; there is no hidden local truncation and no silent fallback anywhere in this path.
|
|
185
406
|
|
|
186
407
|
## Extension EventBus API
|
|
187
408
|
|
|
@@ -225,7 +446,7 @@ Fusion writes private debugging artifacts under:
|
|
|
225
446
|
.pi/fusion/<session-id>-<pid>/<run-id>/
|
|
226
447
|
```
|
|
227
448
|
|
|
228
|
-
Each run contains `manifest.json`, `canonical-input.json`, `context-omission-ledger.json`, `budget-plan.json`, candidate/evaluation/merge prompts, raw child JSONL events, stderr, responses, `blind-candidates.json`, `evaluation.json`, `merged.md`, and `error.json` for failed/cancelled runs. Persisted stage prompts are byte-identical to the exact bytes written to that child's stdin. `context-omission-ledger.json` carries the complete source-ordered omission ledger, and `budget-plan.json` records every configured route's capacity plus the pre-candidate feasibility decision, so a rejected run is as auditable as a successful one. Artifact files are written by private temp-file/fsync/rename, and v2 manifests persist cumulative child usage plus per-attempt observed usage/model data for successful, failed, and cancelled child attempts. Every usage record preserves the complete Pi cost breakdown; the same exact shape is cloned into `fusion_brainstorm` tool results so newer Pi hosts can calculate and replay footer/session statistics safely. These artifacts are local evidence only; they are not shown in `/jobs` or the background-task dock.
|
|
449
|
+
Each run contains `manifest.json`, `canonical-input.json`, `context-omission-ledger.json`, `budget-plan.json`, candidate/evaluation/merge prompts, raw child JSONL events, stderr, responses, and, when inspect or research candidates run, tool-call logs named `candidate-<slot>.attempt-<n>.tool-calls.jsonl`, plus `blind-candidates.json`, `evaluation.json`, `merged.md`, and `error.json` for failed/cancelled runs. Persisted stage prompts are byte-identical to the exact bytes written to that child's stdin. `context-omission-ledger.json` carries the complete source-ordered omission ledger, and `budget-plan.json` records every configured route's capacity plus the pre-candidate feasibility decision, so a rejected run is as auditable as a successful one. Artifact files are written by private temp-file/fsync/rename, and v2 manifests persist cumulative child usage plus per-attempt observed usage/model data for successful, failed, and cancelled child attempts. Every usage record preserves the complete Pi cost breakdown; the same exact shape is cloned into `fusion_brainstorm` tool results so newer Pi hosts can calculate and replay footer/session statistics safely. These artifacts are local evidence only; they are not shown in `/jobs` or the background-task dock.
|
|
229
450
|
|
|
230
451
|
For attested Pi tasks only, the task id is `b` plus 32 random hex characters (128 bits) and additional flat siblings are written in the same directory:
|
|
231
452
|
|
package/TESTING.md
CHANGED
|
@@ -24,8 +24,39 @@ npm run test:sdk
|
|
|
24
24
|
npm run test:rpc
|
|
25
25
|
npm run test:component
|
|
26
26
|
npm run test:package
|
|
27
|
+
npm run test:hook-contract
|
|
27
28
|
```
|
|
28
29
|
|
|
30
|
+
`npm run test:hook-contract` is the **Pi hook characterisation gate**. It drives a
|
|
31
|
+
real Pi agent loop against a deterministic scripted provider and records what Pi's
|
|
32
|
+
`context` and `tool_result` hooks actually do, because the `bg_delegate` child-side
|
|
33
|
+
guard depends on that behaviour and it must be proven by execution rather than read
|
|
34
|
+
from type declarations.
|
|
35
|
+
|
|
36
|
+
The observed guarantees are written to
|
|
37
|
+
`tests/scripted-provider/pi-hook-contract-evidence.json` and shipped as
|
|
38
|
+
`src/core/delegate/hook-contract-evidence.json`. A package test asserts the two are
|
|
39
|
+
byte-identical, so the runtime gate and the gate that proved it cannot drift apart.
|
|
40
|
+
If the evidence file already exists, the gate **compares** against it rather than
|
|
41
|
+
rewriting it: a change in Pi's hook behaviour fails loudly and forces a deliberate
|
|
42
|
+
re-review of the child guard instead of silently regenerating.
|
|
43
|
+
|
|
44
|
+
On Pi 0.83 the gate establishes, by execution:
|
|
45
|
+
|
|
46
|
+
| Question | Observed |
|
|
47
|
+
|---|---|
|
|
48
|
+
| Does `context` fire before every model call? | yes, once per call, in extension load order |
|
|
49
|
+
| Do messages returned from `context` reach the provider? | yes |
|
|
50
|
+
| Does **throwing** in `context` prevent the provider call? | **no** — Pi catches it and dispatches anyway |
|
|
51
|
+
| Does `ctx.abort()` prevent it? | it does not skip the call site, but the call receives an already-aborted signal and the run terminates |
|
|
52
|
+
| Does `tool_result` fire before the transcript entry, and can a handler replace it? | yes, chained in load order; the replacement reaches the provider and the original does not |
|
|
53
|
+
| Do tool-call id, role, and `isError` survive replacement? | yes |
|
|
54
|
+
|
|
55
|
+
Because neither a throw nor an abort is a hard admission gate on its own, the child
|
|
56
|
+
guard uses abort as the barrier **and** removes the oversized content from the
|
|
57
|
+
outgoing message set. A Pi build that cannot provide the required guarantees causes
|
|
58
|
+
`bg_delegate` to refuse to spawn with a typed `delegate_hook_contract_unsupported`.
|
|
59
|
+
|
|
29
60
|
Full interactive gate:
|
|
30
61
|
|
|
31
62
|
```bash
|
|
@@ -58,6 +89,69 @@ Current smoke is `tsx scripts/smoke.ts`. It creates a temporary Pi agent/session
|
|
|
58
89
|
|
|
59
90
|
It performs no inference and spawns no child, so it is safe to run offline and costs nothing. It exits non-zero if any stage would exceed the budget.
|
|
60
91
|
|
|
92
|
+
### Fusion byte-immutability gates
|
|
93
|
+
|
|
94
|
+
Two unit gates protect Fusion's persisted artifact bytes, which are a frozen format:
|
|
95
|
+
|
|
96
|
+
- `tests/unit/fusion-golden-bytes.test.ts` renders an exhaustive 28-case differential
|
|
97
|
+
corpus (empty conversations, run-boundary and image-coalescing branches, unknown
|
|
98
|
+
blocks, tool-name ordering, `compactCounts` combinations, UTF-8 and lone-surrogate
|
|
99
|
+
content, every budget stage across three route sets) and compares the raw bytes
|
|
100
|
+
against `tests/fixtures/fusion-golden-bytes.json`. The golden file is never
|
|
101
|
+
auto-updated once it exists.
|
|
102
|
+
- `tests/unit/fusion-extraction-equivalence.test.ts` compares the current
|
|
103
|
+
implementation against `tests/oracle/fusion-context-pre-extraction.ts`, a verbatim
|
|
104
|
+
copy of the projection engine as it existed before the shared transform was
|
|
105
|
+
extracted. This is an **independent oracle**, so equivalence is proven rather than
|
|
106
|
+
merely self-consistent, including `Object.is` comparison of budget floats and exact
|
|
107
|
+
error-message parity.
|
|
108
|
+
|
|
109
|
+
### Delegate gates
|
|
110
|
+
|
|
111
|
+
- `tests/unit/delegate-seed.test.ts` — verbatim visible text, thinking/tool-payload
|
|
112
|
+
exclusion, marker-only images, sibling-batch exclusion, byte-identical construction
|
|
113
|
+
across repeated builds and separate processes, and receive-side seed verification.
|
|
114
|
+
- `tests/unit/delegate-budget.test.ts` — reserve arithmetic, boundary accept/reject,
|
|
115
|
+
and the total runtime governor.
|
|
116
|
+
- `tests/unit/delegate-result-package.test.ts` — hash verification, strict base64,
|
|
117
|
+
encoding refusal for lone surrogates, route-mismatch and missing-attestation
|
|
118
|
+
detection, and explicitly unavailable usage.
|
|
119
|
+
- `tests/unit/delegate-artifacts.test.ts` — spill/receipt coordinates under
|
|
120
|
+
out-of-order completion, aggregate caps, exact bounded range reads, and terminal
|
|
121
|
+
evaluation including a zero-exit child that never committed.
|
|
122
|
+
- `tests/unit/delegate-launch.test.ts` — route pinning without substitution, argv-level
|
|
123
|
+
isolation, the hook-contract gate, and the property that a refused launch creates
|
|
124
|
+
**zero** children and **zero** artifacts.
|
|
125
|
+
- `tests/scripted-provider/delegate-child-guard.test.ts` — the child guard inside a
|
|
126
|
+
real Pi agent loop: a 2 MB tool result spilled to a hashed artifact with the payload
|
|
127
|
+
kept out of the transcript, a blocked over-budget model call, exact bounded range
|
|
128
|
+
reads, route-drift refusal, and turn-limit enforcement.
|
|
129
|
+
- `tests/sdk/delegate-sdk.test.ts` — the full public loop through the shipped
|
|
130
|
+
entrypoint with a fake child `pi`: launch receipt, projected context actually
|
|
131
|
+
reaching the child, child session isolation, not-ready retrieval, corruption
|
|
132
|
+
detection, and oversized answers degrading to an artifact reference without
|
|
133
|
+
truncation.
|
|
134
|
+
- `tests/package/delegate-mutation-guard.test.ts` — fails if silent truncation, a
|
|
135
|
+
silent fallback, a route substitution, an unbounded inline answer, a dropped
|
|
136
|
+
preflight, a synthesized zero usage, a fail-open guard hook, or an undelivered
|
|
137
|
+
seed is reintroduced. Verified by actually mutating the source: disabling the
|
|
138
|
+
spill makes two behavioural tests fail.
|
|
139
|
+
|
|
140
|
+
### Live subscription evidence run
|
|
141
|
+
|
|
142
|
+
`npx tsx scripts/delegate-live-run.ts` is a release-time evidence harness. It
|
|
143
|
+
builds a genuinely large parent session (43 visible text entries plus 120 omitted
|
|
144
|
+
tool events withholding ~162 KB of tool-result payload), launches **one** real
|
|
145
|
+
child `pi` on the parent's current **subscription OAuth** route with no API-key
|
|
146
|
+
argument, and asserts that the child produced a hash-verified answer that used
|
|
147
|
+
**both** its read-only file tools and the projected conversation. It also asserts
|
|
148
|
+
the omitted payload never appears in the seed or the child prompt.
|
|
149
|
+
|
|
150
|
+
It is not part of the default gate because it performs real inference. It caught
|
|
151
|
+
two defects that no offline gate did: a child that verified its seed file but was
|
|
152
|
+
never handed a prompt, and a budget that measured the seed instead of the prompt
|
|
153
|
+
actually sent. Both are now pinned by unit and mutation-guard tests.
|
|
154
|
+
|
|
61
155
|
Smoke proves loadability only; completion requires `npm run test`, `npm run test:full`, `npm run pack:dry-run`, and the release-only compatibility gate when preparing a release.
|
|
62
156
|
|
|
63
157
|
## Required isolated environment
|