pi-background-tasks 2.1.4 → 2.4.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/BACKGROUND-TASKS-INSTRUCTIONS.md +1 -1
- package/PUBLISHING.md +2 -0
- package/README.md +16 -8
- package/TESTING.md +23 -14
- package/TEST_PLAN.md +13 -12
- package/THIRD_PARTY_NOTICES.md +30 -0
- package/docs/INDEX.md +8 -4
- package/docs/choose-a-workflow.md +5 -2
- package/docs/commands/claude-cache.md +50 -0
- package/docs/concepts/context-projection-and-budgeting.md +4 -2
- package/docs/getting-started.md +3 -0
- package/docs/manifest.json +86 -22
- package/docs/operations/configuration.md +15 -1
- package/docs/operations/releasing.md +6 -3
- package/docs/operations/troubleshooting.md +3 -1
- package/docs/read-before-edit.md +4 -1
- package/docs/reference/runtime-contracts.md +53 -53
- package/docs/subsystems/anthropic-attribution.md +63 -0
- package/docs/subsystems/attested-pi-runs.md +2 -2
- package/docs/subsystems/child-launch-durability-and-safety.md +3 -3
- package/docs/subsystems/delegation.md +34 -17
- package/docs/subsystems/docs-freshness-gate.md +6 -6
- package/docs/subsystems/fusion.md +2 -2
- package/docs/tools/bg_delegate.md +33 -16
- package/docs/tools/bg_result.md +2 -2
- package/docs/tools/bg_run.md +5 -0
- package/extensions/anthropic-attribution.ts +1 -0
- package/package.json +4 -2
- package/src/core/anthropic-attribution-path.ts +26 -0
- package/src/core/{fusion/anthropic-attribution.ts → anthropic-attribution.ts} +61 -8
- package/src/core/attested-pi-run.ts +10 -1
- package/src/core/common.ts +2 -1
- package/src/core/context/token-budget.ts +16 -3
- package/src/core/delegate/artifacts.ts +18 -12
- package/src/core/delegate/budget.ts +78 -33
- package/src/core/delegate/launch.ts +48 -16
- package/src/core/delegate/result-package.ts +16 -0
- package/src/core/delegate/runner.ts +45 -2
- package/src/core/delegate/seed.ts +12 -0
- package/src/core/delegate/types.ts +22 -3
- package/src/core/fusion/config.ts +1 -1
- package/src/core/fusion/pi-child.ts +7 -124
- package/src/core/registry.ts +4 -1
- package/src/delegate-child-extension.ts +377 -71
- package/src/delegate-extension.ts +58 -6
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
doc_id: subsystems/anthropic-attribution
|
|
3
|
+
audience: maintainer
|
|
4
|
+
mode: authored
|
|
5
|
+
review_policy: behavioral
|
|
6
|
+
stability: evolving
|
|
7
|
+
covers_surfaces: []
|
|
8
|
+
covers_sources: [extensions/anthropic-attribution.ts, src/core/anthropic-attribution-path.ts, src/core/anthropic-attribution.ts]
|
|
9
|
+
---
|
|
10
|
+
# Anthropic attribution subsystem
|
|
11
|
+
|
|
12
|
+
This subsystem owns the package-wide Anthropic subscription attribution provider, exact-match system-prompt sanitization, cache-retention command, and the package extension path shared by isolated child Pi processes.
|
|
13
|
+
|
|
14
|
+
## Global package behavior
|
|
15
|
+
|
|
16
|
+
`package.json.pi.extensions` loads `extensions/anthropic-attribution.ts` for every normal `pi-background-tasks` installation, before the background-task entrypoint. The extension is provider-gated: non-Anthropic sessions and payloads are unchanged.
|
|
17
|
+
|
|
18
|
+
For Anthropic sessions it registers the package-owned `anthropic` provider transport and applies the Claude Code subscription request contract:
|
|
19
|
+
|
|
20
|
+
- subscription OAuth token transport only; metered Anthropic credentials are refused;
|
|
21
|
+
- Claude Code session, account, device, beta, user-agent, and system-identity attribution;
|
|
22
|
+
- model-specific fixed/adaptive thinking policy;
|
|
23
|
+
- the conservative 200K subscription context policy;
|
|
24
|
+
- system, final-tool, and final-conversation cache surfaces;
|
|
25
|
+
- provider-authoritative usage and one-hour cache-write accounting when reported.
|
|
26
|
+
|
|
27
|
+
The extension reads `userID` and `oauthAccount.accountUuid` from `~/.claude.json` without writing it. Missing/malformed account data, unsupported model policy, malformed payload/cache controls, and non-OAuth transport fail loudly.
|
|
28
|
+
|
|
29
|
+
## Sanitization
|
|
30
|
+
|
|
31
|
+
The package has no runtime dependency on `@ravshansbox/pi-anthropic-sps`. Its three reviewed exact-match prompt-line rules are implemented locally in `src/core/anthropic-attribution.ts`, with the upstream MIT notice retained in `THIRD_PARTY_NOTICES.md`.
|
|
32
|
+
|
|
33
|
+
Only complete matching lines are removed. Other system text, non-text blocks, custom block fields, and valid cache controls are preserved. The rules cover both Pi documentation-list variants—with and without `environment-variables.md`—plus the cross-reference instruction line.
|
|
34
|
+
|
|
35
|
+
## Duplicate-owner protocol
|
|
36
|
+
|
|
37
|
+
A package extension and an independent project/user copy can otherwise register duplicate provider hooks and `/claude-cache` commands. The factory therefore probes `pi-anthropic-attribution:claim:v1` on Pi's shared EventBus before registration. The first successfully registered copy installs one responder; later compatible copies become inert.
|
|
38
|
+
|
|
39
|
+
Ownership is published only after all hooks and the command register. Extension loading is sequential and EventBus listener invocation is synchronous at the probe boundary, so a failed first factory cannot strand a false claim. The responder lives for the shared EventBus runtime, matching the extension registrations it protects.
|
|
40
|
+
|
|
41
|
+
## Isolated package children
|
|
42
|
+
|
|
43
|
+
Ambient discovery is insufficient for child paths that use `--no-extensions`. `resolveAnthropicAttributionExtensionPath()` is the single package path seam used by:
|
|
44
|
+
|
|
45
|
+
- Fusion Anthropic children, before the Fusion runtime governor;
|
|
46
|
+
- Anthropic delegate children, before the delegate guard;
|
|
47
|
+
- Anthropic attested Pi children.
|
|
48
|
+
|
|
49
|
+
Non-Anthropic child argv does not resolve or add this extension. Missing package extension bytes fail before child creation; no route substitution or sanitizer fallback is attempted.
|
|
50
|
+
|
|
51
|
+
Arbitrary shell commands started through `bg_run` are not rewritten. An Anthropic child `pi` launched this way must keep normal extension discovery enabled. If the command deliberately uses `--no-extensions`, it must also explicitly load this package's `extensions/anthropic-attribution.ts` with `-e`/`--extension`; otherwise attribution and sanitization are bypassed and the launch is unsupported. The package does not parse or override arbitrary shell authority.
|
|
52
|
+
|
|
53
|
+
## Cache retention
|
|
54
|
+
|
|
55
|
+
`PI_CACHE_RETENTION=none|short|long` selects process/provider policy. `/claude-cache status|short|long|default` stores a branch-local session override as a custom entry that does not enter model context. Call-level `cacheRetention` remains highest precedence, notably preserving Pi's compaction opt-out.
|
|
56
|
+
|
|
57
|
+
## Related docs
|
|
58
|
+
|
|
59
|
+
- [`/claude-cache`](../commands/claude-cache.md)
|
|
60
|
+
- [Configuration](../operations/configuration.md)
|
|
61
|
+
- [Fusion subsystem](fusion.md)
|
|
62
|
+
- [Delegation subsystem](delegation.md)
|
|
63
|
+
- [Attested Pi runs](attested-pi-runs.md)
|
|
@@ -40,9 +40,9 @@ The logical argv always begins:
|
|
|
40
40
|
pi --mode json --provider <provider> --model <model>
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
For an Anthropic request, the package then adds `--extension <package-owned-anthropic-attribution>` before optional thinking. Next come optional `--thinking <thinking>`, literal `extraPiArgs`, and the prompt as the final user prompt argument. Forbidden extra args are direct auth (`--api-key`, `--auth-file`), mode/print (`-p`, `--print`, `--mode`), and duplicate structured fields (`--provider`, `--model`, `--thinking`). Missing attribution bytes refuse an Anthropic launch before task creation.
|
|
44
44
|
|
|
45
|
-
The registry launches exactly one child through the resolved Pi executable with `shell:false`. The attestation records the stable logical argv (`['pi', ...]`), not platform-specific Windows Node/CLI shims. Attested tasks are created with generic background completion notification/wake disabled; terminal snapshots are still published through the task system.
|
|
45
|
+
The registry launches exactly one child through the resolved Pi executable with `shell:false`. The attestation records the stable logical argv (`['pi', ...]`), including any package-owned attribution extension, not platform-specific Windows Node/CLI shims. Attested tasks are created with generic background completion notification/wake disabled; terminal snapshots are still published through the task system.
|
|
46
46
|
|
|
47
47
|
## Auth and environment boundary
|
|
48
48
|
|
|
@@ -46,9 +46,9 @@ After atomic replace, POSIX-like platforms open and sync the parent directory to
|
|
|
46
46
|
## Process trust boundaries
|
|
47
47
|
|
|
48
48
|
- Background shell tasks run the operator-provided shell command in the project cwd and are not sandboxed.
|
|
49
|
-
- Delegate children are direct `pi` spawns, not shell commands. They use a task-owned session id and session dir, stripped parent session environment, disabled discovery, and an explicit child guard extension.
|
|
50
|
-
- Fusion children are direct `pi --mode text` spawns with private metadata/tool-call audit extensions and workflow-specific tool policy.
|
|
51
|
-
- Attested Pi tasks are direct `pi --mode json` spawns and produce evidence sidecars after successful parsing and durability.
|
|
49
|
+
- Delegate children are direct `pi` spawns, not shell commands. They use a task-owned session id and session dir, stripped parent session environment, disabled discovery, and an explicit child guard extension; Anthropic delegates first load the package attribution extension.
|
|
50
|
+
- Fusion children are direct `pi --mode text` spawns with private metadata/tool-call audit extensions and workflow-specific tool policy; Anthropic children first load the package attribution extension.
|
|
51
|
+
- Attested Pi tasks are direct `pi --mode json` spawns and produce evidence sidecars after successful parsing and durability; Anthropic tasks receive the package attribution extension explicitly.
|
|
52
52
|
|
|
53
53
|
Never blur parent and child authority: parent tools can start/inspect/kill tasks, but child tools must stay within their explicit argv tool set.
|
|
54
54
|
|
|
@@ -32,7 +32,7 @@ The design deliberately separates:
|
|
|
32
32
|
|
|
33
33
|
## Seed and context policy
|
|
34
34
|
|
|
35
|
-
The seed schema is `pi-background-tasks.delegate-seed.
|
|
35
|
+
The seed schema is `pi-background-tasks.delegate-seed.v2`. It wraps the frozen `visible-conversation-ledger-v2` projection under delegate policy id `delegate-inspect-v1`; it never emits Fusion input schemas or claims Fusion provenance. The exact selected `extension_mode` is hash-bound into the seed, and is also recorded in launch details, task facts, and `manifest.json`.
|
|
36
36
|
|
|
37
37
|
Projection behavior:
|
|
38
38
|
|
|
@@ -62,57 +62,72 @@ The child launch:
|
|
|
62
62
|
- separate random `--session-id`;
|
|
63
63
|
- task-owned `--session-dir` under the artifact directory;
|
|
64
64
|
- parent session/provider/model/reasoning env keys stripped;
|
|
65
|
-
-
|
|
66
|
-
-
|
|
65
|
+
- skill/template/theme/context discovery always disabled;
|
|
66
|
+
- extension discovery disabled by default in `extensionMode:"isolated"`;
|
|
67
|
+
- extension discovery deliberately enabled only by `extensionMode:"ambient"`;
|
|
68
|
+
- non-Anthropic children explicitly load the package-owned child guard in both modes;
|
|
69
|
+
- Anthropic children explicitly load package attribution/sanitization first, then the child guard, in both modes.
|
|
67
70
|
|
|
68
|
-
|
|
71
|
+
Ambient mode exists for providers registered by user/project Pi extensions. It omits only `--no-extensions`; it accepts no caller-supplied extension paths and performs no provider fallback or route substitution. Ambient discovery executes arbitrary trusted-location extension code in the child process. That code has Node process privileges and is not sandboxed by Pi's model-visible tool allowlist, so ambient mode deliberately weakens the inspect-only process-isolation guarantee. It must not be described as safe or equivalent to isolated mode.
|
|
72
|
+
|
|
73
|
+
The only v1 capability is `inspect`. Allowed model-visible tools are exactly `read`, `grep`, `find`, `ls`, and `delegate_read_artifact`; forbidden tools deny shell, writes, background task controls, recursive delegation, attested Pi launch, and Fusion. This tool boundary remains argv/tool-registry enforced in both extension modes, but it is not a sandbox for ambient extension initialization or handlers. Missing Anthropic attribution bytes are a pre-artifact `delegate_isolation_unsupported` refusal; no un-attributed child or alternate route is launched.
|
|
69
74
|
|
|
70
75
|
## Route and budget
|
|
71
76
|
|
|
72
77
|
Routes are pinned once:
|
|
73
78
|
|
|
74
79
|
- omitted route → parent current model;
|
|
80
|
+
- extension-only routes still must be visible in the parent registry and require explicit `extensionMode:"ambient"` so the fresh child can load their implementing extension;
|
|
75
81
|
- explicit route → exact registry entry;
|
|
76
82
|
- unavailable/unknown-capacity routes fail;
|
|
77
83
|
- no substitution, fallback, or retry on a different route.
|
|
78
84
|
|
|
79
|
-
|
|
85
|
+
Budgeting separates admission from package-owned runtime growth. Constants currently documented by source/tests:
|
|
80
86
|
|
|
81
87
|
- reserved output: `16,384` tokens;
|
|
82
88
|
- framing reserve: `8,192` tokens;
|
|
83
89
|
- safety reserve: `4,096` tokens;
|
|
84
90
|
- minimum usable input: `8,192` tokens;
|
|
91
|
+
- protected finalization input runway: `32,768` tokens;
|
|
92
|
+
- finalization trigger inside retained-growth runway: `8,192` tokens;
|
|
85
93
|
- default turns/tools/timeout: `24` / `120` / `1200s`;
|
|
86
94
|
- per-result transcript cap: `64 KiB`;
|
|
87
|
-
- aggregate tool-output cap: `64 MiB`;
|
|
88
|
-
- answer capture cap
|
|
95
|
+
- aggregate raw tool-output cap: `64 MiB`;
|
|
96
|
+
- answer capture cap: `4 MiB`, enforced before result packaging;
|
|
89
97
|
- inline answer cap: `48 KiB`.
|
|
90
98
|
|
|
91
|
-
Launch admission measures the child system prompt plus the
|
|
99
|
+
Launch admission measures the child system prompt plus the exact child prompt carrying the seed. Backed large prompts use the shared family calibration; prompts or routes below the calibration domain and unknown/unbacked routes use the provable `1.00 B/token` profile. `budget-plan.json` v3 also records the provable conservative counter-forecast, protected finalization reserve, and retained-growth budget.
|
|
100
|
+
|
|
101
|
+
After launch, token measurements are advisory. Fusion BUG-185 proved that a package-local estimate must not reject a live provider payload after subtracting hypothetical output. Delegate therefore does not self-report provider exhaustion from that estimate. Pi and the provider own live context handling; a genuine provider context error remains loud. Package-owned growth is controlled before transcript entry: a tool result spills whenever it exceeds the per-result cap **or** retaining it would consume protected final-answer runway. Conservative false positives therefore create explicit hash receipts rather than failed tasks. Near the end of the runway the child disables tools and injects one finalization instruction so it can answer from evidence already gathered.
|
|
92
102
|
|
|
93
103
|
## Child guard and commit discipline
|
|
94
104
|
|
|
95
105
|
The child verifies seed hash, task id, and launch nonce at extension load before the first model call. It then enforces:
|
|
96
106
|
|
|
97
|
-
- context
|
|
98
|
-
- per-result spill receipts before tool output enters the transcript;
|
|
99
|
-
-
|
|
107
|
+
- advisory retained-context measurement before every provider call, without a BUG-185-style token abort;
|
|
108
|
+
- route-runway-aware and per-result spill receipts before tool output enters the transcript, including structured preservation of image-bearing results;
|
|
109
|
+
- bounded artifact range reads returned as lossless base64 against remaining inline runway;
|
|
110
|
+
- aggregate raw tool-output cap;
|
|
111
|
+
- protected no-tool finalization when retained-growth runway becomes low;
|
|
100
112
|
- turn and tool-call limits;
|
|
101
113
|
- route attestation for assistant messages;
|
|
102
|
-
-
|
|
114
|
+
- per-turn usage accumulation across the full agent loop; if any turn is missing/partial, aggregate usage is `unavailable` rather than understated or replaced by a later record;
|
|
103
115
|
- accepted final stop reason `stop` only, so provider `length` stops become `child_model_output_limit` rather than partial success;
|
|
116
|
+
- answer capture from only the final clean-stop assistant message, never intermediate tool-use narration;
|
|
104
117
|
- non-empty, non-whitespace answer text;
|
|
105
|
-
-
|
|
118
|
+
- the declared answer capture cap, with no partial result on overflow;
|
|
119
|
+
- well-formed UTF-8 answer blocks;
|
|
120
|
+
- durable `runtime-budget.json` evidence containing context measurements, retained/spilled bytes, finalization state, first-request observed usage, and calibration-underforecast evidence.
|
|
106
121
|
|
|
107
122
|
A terminal latch prevents later success commit after any degraded/refused condition. This avoids a hash-valid result built on silently modified context.
|
|
108
123
|
|
|
109
124
|
`result.json` is the single answer data plane. It is child-written by temp file, file fsync, and rename; POSIX then fsyncs the parent directory, while Windows skips directory fsync because Node does not provide the same portable guarantee there. Final-name presence is the child commit point. No final `result.json` means no accepted answer, regardless of process exit code. `child-terminal.json` records child-side terminal failures when no success package is committed.
|
|
110
125
|
|
|
111
|
-
After adjudication, the parent makes a best-effort durable write of `outcome.json`. This is separate from `result.json` so child and parent cannot race over one state field. An `outcome.json` write failure is currently ignored and does not change the returned adjudication, so the artifact may be absent even though evaluation completed. Child stdout/stderr are
|
|
126
|
+
After adjudication, the parent makes a best-effort durable write of `outcome.json`. This is separate from `result.json` so child and parent cannot race over one state field. An `outcome.json` write failure is currently ignored and does not change the returned adjudication, so the artifact may be absent even though evaluation completed. Child stdout/stderr are captured in the background task output file. Delegate-local `child.stdout.txt` and `child.stderr.txt` are not currently populated, and terminal reporting now lists only diagnostic paths that actually exist.
|
|
112
127
|
|
|
113
128
|
## Spill artifacts and `delegate_read_artifact`
|
|
114
129
|
|
|
115
|
-
Oversized tool results are durably written in full under `spill/` and replaced with receipts. A failed spill withholds the original payload and latches a terminal failure; no uncommitted artifact is claimed by receipt.
|
|
130
|
+
Oversized tool results are durably written in full under `spill/` and replaced with receipts. A single text block is stored as exact UTF-8; malformed lone-surrogate text is rejected rather than silently converted to U+FFFD. Multi-block or image-bearing content is stored in a closed JSON envelope that preserves block boundaries, MIME types, text, and complete base64 image data. New receipts record `content_format`; historical v1 receipts without that optional field remain readable. A failed spill withholds the original payload and latches a terminal failure; no uncommitted artifact is claimed by receipt.
|
|
116
131
|
|
|
117
132
|
`delegate_read_artifact` requires:
|
|
118
133
|
|
|
@@ -120,7 +135,7 @@ Oversized tool results are durably written in full under `spill/` and replaced w
|
|
|
120
135
|
- `offset: non-negative safe integer`;
|
|
121
136
|
- `length: positive safe integer`.
|
|
122
137
|
|
|
123
|
-
It reads the whole artifact file, verifies the requested range is in bounds, and returns
|
|
138
|
+
It reads the whole artifact file, verifies the requested range is in bounds, and returns the exact bytes as base64 plus offset/length metadata. Arbitrary ranges are never decoded as UTF-8, so a range that splits a multibyte sequence remains byte-exact rather than becoming U+FFFD. Path escape and short reads fail loudly.
|
|
124
139
|
|
|
125
140
|
## Retrieval contract
|
|
126
141
|
|
|
@@ -130,6 +145,8 @@ Default delivery inlines answers up to `48 KiB`; larger answers return artifact
|
|
|
130
145
|
|
|
131
146
|
Current `autoDeliver` status: `bg_delegate` accepts and records `never | when_small | always` and includes it in launch facts/details. The registry's generic terminal notification currently does not evaluate delegate results or include answer text, so `bg_result` remains the retrieval path.
|
|
132
147
|
|
|
148
|
+
`extensionMode` accepts only `isolated | ambient`, defaults to `isolated`, and is surfaced in receipt text and durable metadata. Ambient receipts include an explicit arbitrary-code/isolation warning.
|
|
149
|
+
|
|
133
150
|
## User-oriented failure taxonomy
|
|
134
151
|
|
|
135
152
|
Admission / no child:
|
|
@@ -155,7 +172,7 @@ Launch / execution:
|
|
|
155
172
|
|
|
156
173
|
Budget / limits:
|
|
157
174
|
|
|
158
|
-
- `provider_context_budget_exhausted`
|
|
175
|
+
- `provider_context_budget_exhausted` (legacy terminal records only; current children do not infer provider exhaustion from an advisory estimate)
|
|
159
176
|
- `aggregate_tool_output_cap`
|
|
160
177
|
- `child_model_output_limit`
|
|
161
178
|
- `child_capture_limit`
|
|
@@ -12,14 +12,14 @@ covers_sources: []
|
|
|
12
12
|
This authored section defines the boundary: documentation facts are extracted from package metadata and TypeScript ASTs, then generated into docs and the manifest. Unsupported syntax fails the gate rather than falling back to regex or stale hand-maintained inventories. Public registrations must remain unconditional top-level direct calls or use the one validated local tool-wrapper shape; host/method aliases, computed access, nested or conditional registration, wrapper chaining/passing, constructor helpers, ambiguous public metadata, destructured Pi parameters, and repeated imported registrars are rejected.
|
|
13
13
|
|
|
14
14
|
<!-- pi-docs:begin name="docs-freshness-gate" generator="scripts/docs/generate.mjs" -->
|
|
15
|
-
- Canonical package version: `2.
|
|
16
|
-
- Governed markdown docs:
|
|
17
|
-
- Public surfaces extracted:
|
|
18
|
-
- Governed production sources:
|
|
15
|
+
- Canonical package version: `2.4.0`
|
|
16
|
+
- Governed markdown docs: 42
|
|
17
|
+
- Public surfaces extracted: 31
|
|
18
|
+
- Governed production sources: 50
|
|
19
19
|
- Tool contracts extracted: 11
|
|
20
|
-
- Schema IDs extracted:
|
|
20
|
+
- Schema IDs extracted: 46
|
|
21
21
|
- Environment variable references extracted: 49
|
|
22
|
-
- Behavioral attestation receipts not passing:
|
|
22
|
+
- Behavioral attestation receipts not passing: 8
|
|
23
23
|
- Receipt store: `docs/attestations.json`
|
|
24
24
|
|
|
25
25
|
`npm run docs:verify` is read-only: it renders generated files twice in memory and compares them with committed bytes. `npm run docs:generate` is the only docs writer.
|
|
@@ -5,7 +5,7 @@ mode: mixed
|
|
|
5
5
|
review_policy: behavioral
|
|
6
6
|
stability: stable
|
|
7
7
|
covers_surfaces: [renderer:fusion-result, workflow:investigate, workflow:reason, workflow:research, workflow:validate]
|
|
8
|
-
covers_sources: [extensions/fusion-child.ts, src/core/fusion/
|
|
8
|
+
covers_sources: [extensions/fusion-child.ts, src/core/fusion/artifacts.ts, src/core/fusion/budget.ts, src/core/fusion/child-protocol.ts, src/core/fusion/claude-cache.ts, src/core/fusion/clean-context.ts, src/core/fusion/config.ts, src/core/fusion/context.ts, src/core/fusion/evaluation.ts, src/core/fusion/orchestrator.ts, src/core/fusion/output-contract.ts, src/core/fusion/pi-child.ts, src/core/fusion/prompts.ts, src/core/fusion/result-package.ts, src/core/fusion/source-policy.ts, src/core/fusion/types.ts, src/core/fusion/web-fetch.ts, src/core/fusion/workflows.ts, src/fusion-child-extension.ts, src/fusion-extension.ts, src/ui/fusion-model-selector.ts]
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
# Fusion subsystem
|
|
@@ -88,7 +88,7 @@ Inspect/research candidates write sealed tool-call audit logs. The log contains
|
|
|
88
88
|
|
|
89
89
|
## Child process isolation
|
|
90
90
|
|
|
91
|
-
Fusion never calls direct completion APIs. It launches direct child `pi --mode text` processes and writes the prompt over stdin. Child argv includes `--no-session`, `--no-extensions`, `--no-skills`, `--no-prompt-templates`, `--no-themes`, and `--no-context-files`; explicit extensions still load. Non-Anthropic children receive only the package-owned compact metadata/runtime-governor extension. Anthropic children receive, in fixed order, the package-
|
|
91
|
+
Fusion never calls direct completion APIs. It launches direct child `pi --mode text` processes and writes the prompt over stdin. Child argv includes `--no-session`, `--no-extensions`, `--no-skills`, `--no-prompt-templates`, `--no-themes`, and `--no-context-files`; explicit extensions still load. Non-Anthropic children receive only the package-owned compact metadata/runtime-governor extension. Anthropic children receive, in fixed order, the package-wide attribution/sanitization extension and the runtime governor. The same attribution implementation is globally loaded for ordinary package sessions; Fusion supplies its public extension entrypoint explicitly because ambient discovery is disabled. Attribution adds the Claude Code OAuth session header, linked account/device/session metadata, model-policy beta headers, system identity, beta-resource transport, cache surfaces, and model-aware cache usage pricing. It reads `userID` and `oauthAccount.accountUuid` from `~/.claude.json` without writing the file and fails loudly when required attribution data is absent or malformed. Its internal sanitizer removes all reviewed exact-match rejected prompt lines while preserving unrelated text and cache controls; no external sanitizer package is resolved.
|
|
92
92
|
|
|
93
93
|
Child text mode writes the final full answer to stdout. The private child extension emits compact reasoning-free metadata frames to stderr for finalized assistant messages: provider/model, stop reason, text block byte counts and hashes, aggregate text hash, the complete Pi `Usage` object (including Anthropic `cacheWrite1h` and provider-reported reasoning subsets), and a closed cache-policy observation. It governs every final `before_provider_request` payload after attribution and sanitization. For Anthropic routes, the child environment defaults `PI_CACHE_RETENTION` to `long` before provider serialization, so the attribution/Pi adapter creates system, final-tool, and final-conversation breakpoints with `ttl: "1h"`; inherited `PI_CACHE_RETENTION=short|none|long` remains explicit, and call-level `cacheRetention="none"` still wins for compaction. The final governor validates and normalizes those upstream-selected breakpoints, falls back to short when model compatibility rejects long retention, preserves no-marker compaction payloads, enforces Anthropic's four-breakpoint ceiling, and appends the subscription prompt-caching-scope beta idempotently. Its `effective_retention` field describes the final payload, not provider acceptance. Provider usage is preserved verbatim: `cacheWrite1h > 0` proves a one-hour write, but zero is inconclusive on subscription OAuth. Live normal-spawn and exact Fusion-child controls each observed a unique cache read after 370 idle seconds despite `cacheWrite1h = 0`; therefore payload observations prove request intent and `cacheRead` proves reuse, while neither zero telemetry nor a six-minute hit alone proves the full one-hour lifetime. Malformed controls or policy values abort before transport. Non-Anthropic payloads and child environments remain unchanged apart from the governor's existing JSON normalization.
|
|
94
94
|
|
|
@@ -11,14 +11,15 @@ covers_sources: []
|
|
|
11
11
|
|
|
12
12
|
<!-- pi-docs:begin name="tool-contract-bg_delegate" generator="scripts/docs/generate.mjs" -->
|
|
13
13
|
- Label: **Background Delegate**
|
|
14
|
-
- Source: `src/delegate-extension.ts:
|
|
15
|
-
- Description: Launch one background Pi agent seeded with a frozen projection of the current conversation, then return a launch receipt immediately. The child has its own session, a route pinned at launch that is never substituted, and read-only tools. Retrieve its verified answer with bg_result.
|
|
14
|
+
- Source: `src/delegate-extension.ts:340`
|
|
15
|
+
- Description: Launch one background Pi agent seeded with a frozen projection of the current conversation, then return a launch receipt immediately. The child has its own session, a route pinned at launch that is never substituted, and read-only tools. Extension discovery is isolated by default; ambient mode supports extension-registered providers but executes arbitrary discovered extension code. Retrieve its verified answer with bg_result.
|
|
16
16
|
- Root schema: `object`; additionalProperties: `false`
|
|
17
17
|
|
|
18
18
|
| Field | Required | Type | Description | Constraints |
|
|
19
19
|
| --- | --- | --- | --- | --- |
|
|
20
20
|
| `autoDeliver` | no | `string` | Whether the completion notification carries the answer: never \| when_small \| always. Default never; retrieve with bg_result. | |
|
|
21
21
|
| `capability` | no | `string` | Capability profile. Only "inspect" (read/search/list, no shell, no writes, no network, no recursion) is supported. | |
|
|
22
|
+
| `extensionMode` | no | `string` | Extension discovery: isolated \| ambient. Default isolated. Ambient is for extension-registered providers and executes arbitrary discovered extension code, weakening process isolation. | |
|
|
22
23
|
| `maxToolCalls` | no | `number` | Maximum tool calls. Default 120. | |
|
|
23
24
|
| `maxTurns` | no | `number` | Maximum agent turns. Default 24. | |
|
|
24
25
|
| `name` | yes | `string` | Short human-readable task name shown in the bg footer dock. Use 2-6 words. | |
|
|
@@ -46,6 +47,10 @@ covers_sources: []
|
|
|
46
47
|
"description": "Capability profile. Only \"inspect\" (read/search/list, no shell, no writes, no network, no recursion) is supported.",
|
|
47
48
|
"type": "string"
|
|
48
49
|
},
|
|
50
|
+
"extensionMode": {
|
|
51
|
+
"description": "Extension discovery: isolated | ambient. Default isolated. Ambient is for extension-registered providers and executes arbitrary discovered extension code, weakening process isolation.",
|
|
52
|
+
"type": "string"
|
|
53
|
+
},
|
|
49
54
|
"maxToolCalls": {
|
|
50
55
|
"description": "Maximum tool calls. Default 120.",
|
|
51
56
|
"type": "number"
|
|
@@ -118,6 +123,7 @@ Optional:
|
|
|
118
123
|
|
|
119
124
|
- `route: {provider: string, model: string}` — exact route pin. If omitted, the parent session's current `ctx.model.provider` and `ctx.model.id` are used.
|
|
120
125
|
- `capability: "inspect"` — default `"inspect"`; this is the only v1 capability.
|
|
126
|
+
- `extensionMode: "isolated" | "ambient"` — default `"isolated"`. Use `"ambient"` only when the pinned provider is implemented by an auto-discovered user/project extension. Ambient mode executes arbitrary discovered extension code and weakens process isolation.
|
|
121
127
|
- `maxTurns: positive integer` — default `24`.
|
|
122
128
|
- `maxToolCalls: positive integer` — default `120`.
|
|
123
129
|
- `timeoutSeconds: positive integer` — default `1200`.
|
|
@@ -153,41 +159,52 @@ Route resolution is pin-only:
|
|
|
153
159
|
- routes with no declared context window are refused before child creation;
|
|
154
160
|
- the child records provider/model attestations for assistant messages, and a mismatch prevents a successful result commit.
|
|
155
161
|
|
|
156
|
-
## Inspect-only tool boundary
|
|
162
|
+
## Inspect-only tool boundary and extension modes
|
|
157
163
|
|
|
158
|
-
The v1 capability is enforced by child argv and Pi's tool registry, not merely by prompt text:
|
|
164
|
+
The v1 model-visible capability is enforced by child argv and Pi's tool registry, not merely by prompt text:
|
|
159
165
|
|
|
160
166
|
- enabled tools: `read`, `grep`, `find`, `ls`, `delegate_read_artifact`;
|
|
161
167
|
- `--no-builtin-tools` is used with the explicit allowlist;
|
|
162
168
|
- forbidden tools include shell/write/background/delegate/Fusion surfaces (`bash`, `edit`, `write`, `bg_run`, `bg_delegate`, `bg_result`, `bg_run_pi_attested`, Fusion tools, etc.);
|
|
163
|
-
-
|
|
164
|
-
-
|
|
169
|
+
- skills, prompt templates, themes, and context files remain disabled in both extension modes;
|
|
170
|
+
- the package-owned delegate guard is loaded explicitly in both modes; Anthropic routes first load the package attribution/sanitization extension.
|
|
171
|
+
|
|
172
|
+
`extensionMode:"isolated"` adds `--no-extensions` and is the default. Use it for built-in providers and whenever ambient provider code is unnecessary.
|
|
165
173
|
|
|
166
|
-
|
|
174
|
+
`extensionMode:"ambient"` omits only `--no-extensions`, allowing Pi to discover trusted-location user/project extensions so a fresh child can resolve an extension-registered provider. It does **not** accept extension paths from the tool call, alter the pinned route, or provide fallback/substitution.
|
|
175
|
+
|
|
176
|
+
Ambient extensions execute arbitrary code in the child process with Node privileges. The read-only tool allowlist constrains tools exposed to the model; it does not sandbox extension initialization or event handlers. Therefore ambient mode weakens the inspect-only process-isolation guarantee even though the model-visible tool registry remains inspect-only.
|
|
177
|
+
|
|
178
|
+
There is no shell, edit/write, network tool, recursive delegation, or Fusion in the child tool set. That claim applies to registered model tools, not to arbitrary code loaded by ambient extensions.
|
|
167
179
|
|
|
168
180
|
## Admission, budgets, and artifacts
|
|
169
181
|
|
|
170
|
-
Public admission resolves the route
|
|
182
|
+
Public admission resolves the route, package-owned child guard, and—for Anthropic routes—the package attribution extension before entering `preflightDelegateLaunch()`. Within that preflight, the hook contract is checked before capability/limit/seed/budget admission. Every refusal still occurs before child process, child session directory, or artifact root creation, leaving zero child processes and zero delegate artifacts; callers should not depend on a single absolute error-precedence order across route, guard-extension, and hook checks.
|
|
171
183
|
|
|
172
184
|
Budgets and limits:
|
|
173
185
|
|
|
174
186
|
- route capacity is the declared context window minus reserves: `16,384` output, `8,192` framing, `4,096` safety tokens;
|
|
175
187
|
- minimum usable input is `8,192` tokens;
|
|
176
188
|
- launch admission measures the child system prompt plus the exact child prompt bytes that carry the seed;
|
|
177
|
-
-
|
|
178
|
-
-
|
|
179
|
-
-
|
|
180
|
-
-
|
|
189
|
+
- backed large prompts use route-family calibration; the plan also records a provable `1.00 B/token` counter-forecast for every byte class, including multibyte input;
|
|
190
|
+
- `32,768` input tokens are protected for finalization, with an `8,192`-token low-runway trigger;
|
|
191
|
+
- runtime context estimates are advisory and never masquerade as provider context truth;
|
|
192
|
+
- tool results spill above `64 KiB` or earlier when retaining them would consume protected runway;
|
|
193
|
+
- artifact range reads are bounded by remaining inline runway;
|
|
194
|
+
- aggregate raw tool-output cap: `64 MiB`;
|
|
195
|
+
- answer capture cap: `4 MiB`, enforced without committing a prefix;
|
|
181
196
|
- timeout defaults to `1200s`.
|
|
182
197
|
|
|
183
|
-
|
|
198
|
+
When protected runway becomes low, the child disables tools and is instructed to answer immediately from evidence already gathered. Pi and the provider—not the package estimator—own final live context admission. A genuine provider context rejection remains a loud failure and is never retried on another route.
|
|
199
|
+
|
|
200
|
+
Artifacts are under `.pi/delegate/<session-id>-<pid>/<task-id>/` and include `seed.json`, `child-prompt.txt`, `context-omission-ledger.json`, `budget-plan.json`, `manifest.json`, `child-session/`, `spill/`, `runtime-budget.json`, and later `result.json` / `outcome.json` when produced. Child stdout/stderr are captured through the background task output path; terminal failures report that real merged output path when it exists and do not claim absent delegate-local stream files.
|
|
184
201
|
|
|
185
202
|
## Spilled tool output
|
|
186
203
|
|
|
187
|
-
Oversized child tool results are written in full to `spill/...` artifacts and replaced in the transcript by receipts carrying path, byte length, SHA-256, tool name, call id, turn sequence, and source call index. The raw oversized payload is not forwarded as a fallback and is not truncated.
|
|
204
|
+
Oversized child tool results are written in full to `spill/...` artifacts and replaced in the transcript by receipts carrying path, byte length, SHA-256, content format, tool name, call id, turn sequence, and source call index. Single text blocks retain their exact UTF-8 bytes; malformed lone-surrogate text fails loudly instead of being substituted. Multi-block and image-bearing results use a structured JSON envelope preserving text, block boundaries, MIME type, and complete base64 image data. The raw oversized payload is not forwarded as a fallback and is not truncated.
|
|
188
205
|
|
|
189
|
-
Inside the child, `delegate_read_artifact({artifact, offset, length})` reads an exact byte range. It refuses path escape, negative/non-integer offsets, non-positive lengths, and reads past EOF rather than returning a short/clamped range.
|
|
206
|
+
Inside the child, `delegate_read_artifact({artifact, offset, length})` reads an exact byte range and returns those bytes as base64. It refuses path escape, negative/non-integer offsets, non-positive lengths, and reads past EOF rather than returning a short/clamped range. Base64 prevents a range that splits a UTF-8 sequence from being silently changed to replacement characters.
|
|
190
207
|
|
|
191
208
|
## Completion
|
|
192
209
|
|
|
193
|
-
`bg_delegate` returns a receipt with task id, route, child session id, artifact dir, seed hash/size, budget source, limits, auto-deliver setting, and notification/wake settings. With default notification settings, the parent receives the generic durable `background-task-notification` after terminal state and may then call `bg_result`. Do not poll solely to wait.
|
|
210
|
+
`bg_delegate` returns a receipt with task id, route, child session id, artifact dir, seed hash/size, budget source, extension mode, limits, auto-deliver setting, and notification/wake settings. Ambient receipts include an explicit arbitrary-code/isolation warning. The mode is also hash-bound in `seed.json` and persisted in task facts and `manifest.json`. With default notification settings, the parent receives the generic durable `background-task-notification` after terminal state and may then call `bg_result`. Do not poll solely to wait.
|
package/docs/tools/bg_result.md
CHANGED
|
@@ -11,7 +11,7 @@ covers_sources: []
|
|
|
11
11
|
|
|
12
12
|
<!-- pi-docs:begin name="tool-contract-bg_result" generator="scripts/docs/generate.mjs" -->
|
|
13
13
|
- Label: **Background Result**
|
|
14
|
-
- Source: `src/delegate-extension.ts:
|
|
14
|
+
- Source: `src/delegate-extension.ts:516`
|
|
15
15
|
- Description: Retrieve a hash-verified result from a bg_delegate or background Fusion task. Never blocks: a running task returns a typed not-ready result. Oversized answers are never truncated.
|
|
16
16
|
- Root schema: `object`; additionalProperties: `false`
|
|
17
17
|
|
|
@@ -124,7 +124,7 @@ Common delegate retrieval outcomes:
|
|
|
124
124
|
- `result_too_large_for_inline` — explicit inline request exceeded the inline cap.
|
|
125
125
|
- Fusion `summary_status:"integrity_failed"` — a terminal summary or its manifest binding failed verification; no summary metadata is trusted.
|
|
126
126
|
|
|
127
|
-
Delegate errors include whether a child process was created, preserved artifact hints when
|
|
127
|
+
Delegate errors include whether a child process was created, preserved artifact hints that are checked for existence, the real merged task output path when available, and remediation text. Usage missing from the provider is reported as `unavailable`, not synthesized as zero. Fusion retrieval additionally fails on non-completed manifests, identity/schema drift, malformed usage/details, invalid UTF-8, or any manifest/result/merged hash or byte-length mismatch; failed/cancelled runs return their preserved terminal error rather than partial output.
|
|
128
128
|
|
|
129
129
|
## Parent outcome separation
|
|
130
130
|
|
package/docs/tools/bg_run.md
CHANGED
|
@@ -97,6 +97,8 @@ Legacy argument preparation can derive a missing `name` from `description` or `c
|
|
|
97
97
|
|
|
98
98
|
Use for long-running tests, builds, servers, watchers, sleeps, and child agent work. Do not use normal foreground shell tools for commands expected to outlive the current turn.
|
|
99
99
|
|
|
100
|
+
For an Anthropic child `pi`, keep normal extension discovery enabled. Do not pass `--no-extensions` unless the command also explicitly loads this package's `extensions/anthropic-attribution.ts` with `-e`/`--extension`.
|
|
101
|
+
|
|
100
102
|
## Defaults
|
|
101
103
|
|
|
102
104
|
- `notifyOnCompletion`: `true`.
|
|
@@ -154,6 +156,8 @@ Creates `.pi/tasks/<session-id>-<pid>/<task-id>.output` and `.json`. If `isAgent
|
|
|
154
156
|
|
|
155
157
|
The command runs through the platform shell and is not sandboxed. Use `isAgent:true` only to request Pi-agent telemetry wrapping; setting it does not make execution safer. Model-visible logs are bounded and point to the full output path.
|
|
156
158
|
|
|
159
|
+
`bg_run` does not parse or repair arbitrary child `pi` argv. An Anthropic command that uses `--no-extensions` without explicitly loading the attribution extension bypasses the package's attribution/sanitization contract and is unsupported.
|
|
160
|
+
|
|
157
161
|
## Related docs
|
|
158
162
|
|
|
159
163
|
- [Completion delivery](../concepts/completion-delivery.md)
|
|
@@ -162,6 +166,7 @@ The command runs through the platform shell and is not sandboxed. Use `isAgent:t
|
|
|
162
166
|
- [`bg_kill`](bg_kill.md)
|
|
163
167
|
- [`/bg`](../commands/bg.md)
|
|
164
168
|
- [Background task runtime](../subsystems/background-task-runtime.md)
|
|
169
|
+
- [Anthropic attribution](../subsystems/anthropic-attribution.md)
|
|
165
170
|
|
|
166
171
|
## Source ownership/reference
|
|
167
172
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from '../src/core/anthropic-attribution.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-background-tasks",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "Pi extension for durable background shell tasks, read-only delegated agents, local attested Pi runs, and fixed-purpose Fusion workflows through child Pi processes.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "ISC",
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
"TEST_PLAN.md",
|
|
43
43
|
"PUBLISHING.md",
|
|
44
44
|
"LICENSE",
|
|
45
|
+
"THIRD_PARTY_NOTICES.md",
|
|
45
46
|
"docs/",
|
|
46
47
|
"BACKGROUND-TASKS-INSTRUCTIONS.md",
|
|
47
48
|
"logo.png"
|
|
@@ -65,6 +66,7 @@
|
|
|
65
66
|
"lint": "cd ../.. && npm run quality:ts:lint",
|
|
66
67
|
"format:check": "cd ../.. && npm run quality:ts:format",
|
|
67
68
|
"test:compat": "tsx scripts/test-compat.ts",
|
|
69
|
+
"test:pnpm-pack": "node scripts/test-pnpm-pack-install.mjs",
|
|
68
70
|
"test:hook-contract": "tsx --test --test-concurrency=1 tests/scripted-provider/pi-hook-contract.test.ts",
|
|
69
71
|
"docs:generate": "node scripts/docs/generate.mjs",
|
|
70
72
|
"docs:verify": "node scripts/docs/verify.mjs",
|
|
@@ -79,6 +81,7 @@
|
|
|
79
81
|
"pi": {
|
|
80
82
|
"image": "https://raw.githubusercontent.com/ismailsaleekh/pi-background-tasks/main/logo.png",
|
|
81
83
|
"extensions": [
|
|
84
|
+
"./extensions/anthropic-attribution.ts",
|
|
82
85
|
"./extensions/background-tasks.ts"
|
|
83
86
|
]
|
|
84
87
|
},
|
|
@@ -101,7 +104,6 @@
|
|
|
101
104
|
"node": ">=22.19.0"
|
|
102
105
|
},
|
|
103
106
|
"dependencies": {
|
|
104
|
-
"@ravshansbox/pi-anthropic-sps": "https://codeload.github.com/ravshansbox/pi-anthropic-sps/tar.gz/17409b5615f0ec0625776bc5434f92f2c55e3fd0",
|
|
105
107
|
"turndown": "7.2.4"
|
|
106
108
|
}
|
|
107
109
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { dirname, resolve } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Resolve the package-owned global Anthropic attribution extension.
|
|
7
|
+
*
|
|
8
|
+
* Package-owned child Pi processes disable ambient extension discovery, so they
|
|
9
|
+
* must explicitly load this public extension entrypoint. Keeping path resolution
|
|
10
|
+
* in one module prevents Fusion, delegation, and attested runs from deriving
|
|
11
|
+
* different package paths.
|
|
12
|
+
*/
|
|
13
|
+
export function resolveAnthropicAttributionExtensionPath(
|
|
14
|
+
moduleUrl = import.meta.url,
|
|
15
|
+
pathExists: (path: string) => boolean = existsSync,
|
|
16
|
+
): string {
|
|
17
|
+
const modulePath = fileURLToPath(moduleUrl);
|
|
18
|
+
const extension = modulePath.endsWith('.ts')
|
|
19
|
+
? 'anthropic-attribution.ts'
|
|
20
|
+
: 'anthropic-attribution.js';
|
|
21
|
+
const candidate = resolve(dirname(modulePath), '../../extensions', extension);
|
|
22
|
+
if (!pathExists(candidate)) {
|
|
23
|
+
throw new Error(`Anthropic attribution extension is missing: ${candidate}`);
|
|
24
|
+
}
|
|
25
|
+
return candidate;
|
|
26
|
+
}
|