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.
Files changed (45) hide show
  1. package/BACKGROUND-TASKS-INSTRUCTIONS.md +1 -1
  2. package/PUBLISHING.md +2 -0
  3. package/README.md +16 -8
  4. package/TESTING.md +23 -14
  5. package/TEST_PLAN.md +13 -12
  6. package/THIRD_PARTY_NOTICES.md +30 -0
  7. package/docs/INDEX.md +8 -4
  8. package/docs/choose-a-workflow.md +5 -2
  9. package/docs/commands/claude-cache.md +50 -0
  10. package/docs/concepts/context-projection-and-budgeting.md +4 -2
  11. package/docs/getting-started.md +3 -0
  12. package/docs/manifest.json +86 -22
  13. package/docs/operations/configuration.md +15 -1
  14. package/docs/operations/releasing.md +6 -3
  15. package/docs/operations/troubleshooting.md +3 -1
  16. package/docs/read-before-edit.md +4 -1
  17. package/docs/reference/runtime-contracts.md +53 -53
  18. package/docs/subsystems/anthropic-attribution.md +63 -0
  19. package/docs/subsystems/attested-pi-runs.md +2 -2
  20. package/docs/subsystems/child-launch-durability-and-safety.md +3 -3
  21. package/docs/subsystems/delegation.md +34 -17
  22. package/docs/subsystems/docs-freshness-gate.md +6 -6
  23. package/docs/subsystems/fusion.md +2 -2
  24. package/docs/tools/bg_delegate.md +33 -16
  25. package/docs/tools/bg_result.md +2 -2
  26. package/docs/tools/bg_run.md +5 -0
  27. package/extensions/anthropic-attribution.ts +1 -0
  28. package/package.json +4 -2
  29. package/src/core/anthropic-attribution-path.ts +26 -0
  30. package/src/core/{fusion/anthropic-attribution.ts → anthropic-attribution.ts} +61 -8
  31. package/src/core/attested-pi-run.ts +10 -1
  32. package/src/core/common.ts +2 -1
  33. package/src/core/context/token-budget.ts +16 -3
  34. package/src/core/delegate/artifacts.ts +18 -12
  35. package/src/core/delegate/budget.ts +78 -33
  36. package/src/core/delegate/launch.ts +48 -16
  37. package/src/core/delegate/result-package.ts +16 -0
  38. package/src/core/delegate/runner.ts +45 -2
  39. package/src/core/delegate/seed.ts +12 -0
  40. package/src/core/delegate/types.ts +22 -3
  41. package/src/core/fusion/config.ts +1 -1
  42. package/src/core/fusion/pi-child.ts +7 -124
  43. package/src/core/registry.ts +4 -1
  44. package/src/delegate-child-extension.ts +377 -71
  45. 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
- Then optional `--thinking <thinking>`, then literal `extraPiArgs`, then 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`).
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.v1`. 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.
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
- - only package-owned child guard extension explicitly loaded;
66
- - ambient extension/skill/template/theme/context discovery disabled.
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
- The only v1 capability is `inspect`. Allowed 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. The boundary is argv/tool-registry enforced.
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
- Budgets use a delegate-specific conservative estimator. Constants currently documented by source/tests:
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 value carried in the seed: `4 MiB`; current child code does not separately enforce this before packaging;
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 actual child prompt carrying the seed. Runtime guard measures retained input before every model call. An over-budget runtime call latches `provider_context_budget_exhausted`, aborts the run, and suppresses outgoing content.
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 budget before every provider call;
98
- - per-result spill receipts before tool output enters the transcript;
99
- - aggregate tool-output cap;
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
- - complete usage records only (missing/partial usage is `unavailable`, never zero);
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
- - well-formed UTF-8 answer blocks.
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 currently captured in the background task output file; although delegate artifact constants name `child.stdout.txt` and `child.stderr.txt`, current registry finalization does not populate those files in the delegate artifact directory.
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 exactly that UTF-8-decoded range. Path escape and short reads fail loudly.
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.1.4`
16
- - Governed markdown docs: 40
17
- - Public surfaces extracted: 30
18
- - Governed production sources: 48
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: 44
20
+ - Schema IDs extracted: 46
21
21
  - Environment variable references extracted: 49
22
- - Behavioral attestation receipts not passing: 4
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/anthropic-attribution.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]
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-owned Claude Code attribution provider, `@ravshansbox/pi-anthropic-sps`, and the runtime governor. The repo-local `spawn-anthropic-attribution` entrypoint re-exports that same package-owned implementation, so normal agent spawns and Fusion cannot drift into different OAuth/cache request shapes. 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. The sanitizer then removes only known rejected prompt lines while preserving attribution and cache controls.
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:307`
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
- - ambient discovery is disabled with `--no-extensions`, `--no-skills`, `--no-prompt-templates`, `--no-themes`, `--no-context-files`;
164
- - only the package-owned delegate child extension is loaded explicitly.
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
- There is no shell, edit/write, network tool, recursive delegation, Fusion, or ambient project resource loading in the child tool set.
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 and package-owned child guard 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.
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
- - runtime context is measured before each model call;
178
- - per-tool-result transcript cap: `64 KiB`;
179
- - aggregate tool-output cap: `64 MiB`;
180
- - answer capture cap value in the seed: `4 MiB`; current child code carries this limit but does not separately enforce it before packaging;
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
- 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/`, and later `result.json` / `outcome.json` when produced. Child stdout/stderr are captured through the background task output path; the delegate artifact constants include child stream filenames, but current launch/finalize code does not mirror streams into those delegate files.
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.
@@ -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:468`
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 known, 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.
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
 
@@ -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.1.4",
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
+ }