pi-background-tasks 2.0.0 → 2.1.2

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.
@@ -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/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/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/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]
9
9
  ---
10
10
 
11
11
  # Fusion subsystem
@@ -88,27 +88,29 @@ 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, so the package-owned compact metadata extension is always supplied. Anthropic children additionally receive the `@ravshansbox/pi-anthropic-sps` sanitizer extension because discovery is disabled and Claude routes need Pi system-prompt sanitization. That dependency only sanitizes rejected system-prompt lines and preserves existing cache fields; it does not choose Fusion's cache policy.
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.
92
92
 
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, and a closed cache-policy observation. It also governs every final `before_provider_request` payload after earlier extensions have transformed it. Claude's sanitizer therefore loads before the package governor. For Anthropic routes, the package normalizes only cache breakpoints already selected by Pi's adapter: default retention is `long` (`ttl: "1h"`), while inherited `PI_CACHE_RETENTION=short|none|long` explicitly selects the policy. Long retention falls back to short when model compatibility rejects it. Existing no-marker payloads remain unmarked so Pi's call-level `cacheRetention="none"` compaction requests are never overridden. Malformed controls, invalid policy values, or more than four breakpoints abort before transport. Non-Anthropic payloads remain byte-equivalent apart from the governor's existing JSON normalization.
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
 
95
- After cache normalization, the governor serializes and hashes the exact payload, applies the shared conservative estimator, reserves the model's declared maximum output plus 4,096 safety tokens, and aborts before transport if the payload cannot fit or if the child exceeds 128 provider requests. Pi's provider-hook behavior is characterized through the same `openai-codex-responses` transport adapter used by subscription Codex routes in a real local HTTP agent loop: transforms chain in extension load order and `ctx.abort()` prevents network transport. Cache observations use `pi-background-tasks.fusion-claude-cache-observation.v1`, state requested/effective retention, source, breakpoint count, and provider-request ordinal, and are hash-bound inside `pi-background-tasks.fusion-child-result.v3` attempt event artifacts. At terminal `agent_settled`, the extension emits exactly one `pi-background-tasks.fusion-child-settlement.v2` frame binding the complete ordered metadata stream by count and SHA-256, the final record/hash, and any recovered retry-marker ordinals. The parent validates closed cache evidence and increasing request ordinals, reconstructs stdout against the final metadata, requires final stop reason `stop`, verifies model identity, and preserves usage/cost exactly. Non-final `toolUse` records remain normal. A non-final `error` is accepted only when it is a zero-content, empty-hash, zero-usage retry marker, a later final `stop` exists, and the terminal settlement hash/accounts for that exact ordinal. `length`, `aborted`, `pending`, final `error`, error records carrying text or usage, missing/duplicate/tampered settlement, and settlement before terminal idleness all fail loudly.
95
+ After cache normalization, the governor serializes and hashes the exact payload, applies the shared conservative estimator, reserves the model's declared maximum output plus 4,096 safety tokens, and aborts before transport if the payload cannot fit or if the child exceeds 128 provider requests. Pi's provider-hook behavior is characterized through the same `openai-codex-responses` transport adapter used by subscription Codex routes in a real local HTTP agent loop: transforms chain in extension load order and `ctx.abort()` prevents network transport. Cache observations use `pi-background-tasks.fusion-claude-cache-observation.v1`, state requested/effective retention, source, breakpoint count, and provider-request ordinal, and are hash-bound inside `pi-background-tasks.fusion-child-result.v4` attempt event artifacts. At terminal `agent_settled`, the extension emits exactly one `pi-background-tasks.fusion-child-settlement.v3` frame binding the complete ordered metadata stream by count and SHA-256, the final record/hash, recovered retry-marker ordinals, and any one recovered oversized-original ordinal. The parent validates closed cache/output-contract evidence and increasing request ordinals, reconstructs stdout against the final metadata, requires final stop reason `stop`, verifies model identity, and preserves usage/cost exactly. Non-final `toolUse` records remain normal. A non-final `error` is accepted only when it is a zero-content, empty-hash, zero-usage retry marker, a later final `stop` exists, and the terminal settlement hash/accounts for that exact ordinal. Exactly one non-final `stop` is accepted only as a hash-bound oversized candidate original immediately followed by its same-session replacement. `length`, `aborted`, `pending`, final `error`, error records carrying text or usage, unbound non-final `stop`, missing/duplicate/tampered settlement, and settlement before terminal idleness all fail loudly.
96
96
 
97
97
  Fusion child environments strip session/model/provider variables plus metered credential/base-url variables for OpenRouter, OpenAI, Anthropic, Azure OpenAI, and generic Pi API credentials before launch. Frontier model routes are admitted only when the registry reports subscription OAuth for trusted `anthropic` or `openai-codex` endpoints. There is no fallback, model substitution, endpoint override, or metered API-key route.
98
98
 
99
99
  ## Budgets and output contracts
100
100
 
101
- Budget planning is per route and per stage. Every configured candidate, evaluator, and merger route must have a usable context window. The affine estimator from the shared token-budget layer accounts for byte classes plus a 512-token intercept; backed model-family calibrations are used only where applicable, unknown/unbacked providers are reported in artifacts/result details, and multibyte/dense ASCII diagnostics are preserved. Post-run calibration compares that one-request forecast only with the first provider request; cumulative agent-loop and cache usage is retained as total usage but is never misclassified as a prompt under-forecast.
101
+ Budget planning is per route and per stage. Every configured candidate, evaluator, and merger route must have a usable context window. Anthropic routes are conservatively capped to the attribution provider's 200K subscription request policy even when Pi's catalog advertises a larger window; Fusion never budgets against a 1M mode that its attributed transport does not request. The affine estimator from the shared token-budget layer accounts for byte classes plus a 512-token intercept; backed model-family calibrations are used only where applicable, unknown/unbacked providers are reported in artifacts/result details, and multibyte/dense ASCII diagnostics are preserved. Post-run calibration compares that one-request forecast only with the first provider request; cumulative agent-loop and cache usage is retained as total usage but is never misclassified as a prompt under-forecast.
102
102
 
103
103
  `budget-plan.json` uses `pi-background-tasks.fusion-budget-plan.v4` and records route capacities, stage forecasts for candidate/evaluation/evaluation-repair/merge, conditional repair reservation, warnings, blockers, empty-request counterfactuals, and remediation. Each route reserves the larger of Fusion's 32,768-token output contract reserve and the resolved model's declared maximum output; a model advertising a 128,000-token maximum therefore receives the full 128,000-token reserve. Fatal preflight blockers launch zero children. High utilization or worst-case reservation pressure is a warning when input still fits. Exact rendered prompt checks happen again immediately before candidate, evaluation, repair, and merge launches.
104
104
 
105
- Output contracts are checked after durable attempt recording: candidate responses up to 48 KiB JSON-rendered bytes, evaluator up to 64 KiB, merger/final report up to 64 KiB, diagnostics contract 8 KiB, child stdout cap 32 MiB, child stderr cap 4 MiB. Oversized child output fails loudly and preserves evidence; Fusion never clips or silently forwards truncated content.
105
+ Every candidate system prompt discloses the exact 49,152 JSON-rendered UTF-8 byte hard maximum and requires explicit limitations when the requested scope cannot fit. Output contracts are checked after durable attempt recording: candidate responses up to 48 KiB JSON-rendered bytes, evaluator up to 64 KiB, merger/final report up to 64 KiB, diagnostics contract 8 KiB, child stdout cap 32 MiB, child stderr cap 4 MiB.
106
+
107
+ When a candidate's first complete `stop` response exceeds 48 KiB, the private child extension durably preserves that full original, removes all active tools, and queues one `followUp` user message before `agent_settled`. The same live Pi process, route, model, and in-memory conversation may only compress/restructure its immediately previous answer; it may not investigate again. Pi text mode then emits only the replacement to stdout. A conforming replacement proceeds normally. A second oversized response hard-fails as `child_output_cap`, preserves both the original artifact and replacement partial response, and never queues another continuation. Cancellation during compression preserves any hash-verified original already written. Fusion never clips, silently forwards, or repairs output in a new child session.
106
108
 
107
109
  ## Artifacts, usage, and lifecycle
108
110
 
109
111
  Run artifacts are private local evidence under `.pi/fusion/<session-id>-<pid>/<run-id>/`. They include `manifest.json`, `canonical-input.json`, `budget-plan.json`, per-attempt prompts/events/stderr/responses, optional partial responses for failed attempts, optional tool-call logs/seals, `blind-candidates.json`, `evaluation.json`, `merged.md`, manifest-bound `result.json`, `error.json`, and workflow-specific context/source-policy artifacts. `bg_result` verifies manifest state, fixed artifact references, byte lengths, SHA-256 values, UTF-8, run/workflow identity, and result details before returning merged bytes.
110
112
 
111
- Artifact writes use durable private temp-file/fsync/rename. Manifests enforce legal state transitions and record config, resolved models, fixed capabilities, context policy, tool policy, anonymous map, attempts, artifact refs, cumulative usage, and errors. Successful, failed, and cancelled observed attempts preserve complete Pi usage/cost components; public tool results clone the same `Usage` shape.
113
+ Artifact writes use durable private temp-file/fsync/rename. Manifests enforce legal state transitions and record config, resolved models, fixed capabilities, context policy, tool policy, anonymous map, attempts, artifact refs, cumulative usage, and errors. Successful, failed, and cancelled observed attempts preserve complete Pi usage/cost components, including optional `cacheWrite1h` and `reasoning` subsets; same-session compression includes both provider turns in that one attempt's aggregate; public tool results clone the same `Usage` shape without counting either subset as additional tokens. Terminal failures enrich their stage-local cause from the durable manifest after usage persistence: candidate/evaluator/merger progress reports completed, failed, cancelled, and not-started child facts plus exact usage so far. A late evaluator or merger budget refusal never claims that no child anywhere in the run was created.
112
114
 
113
115
  For tool-enabled children, the private audit journal remains open across every low-level `agent_end`, because Pi may still retry, compact and retry, or process a queued continuation. Only terminal `agent_settled` can exclusively publish the complete hash/count/byte seal. Runtime-guard refusal latches process failure, makes that seal incomplete, and forces the result settlement to failed. The child emits one closed `pi-background-tasks.fusion-runtime-guard.v1` stderr frame containing the refusal code, route capacities, request/tool ordinals, exact payload byte count and SHA-256, conservative token estimate, and a bounded message; it never emits the payload itself. The parent validates this frame and reports typed `child_runtime_budget_exceeded` for runtime capacity/loop refusals or `child_cache_policy_invalid` for Claude cache-policy refusal, instead of accepting a later clean-looking result or reducing it to an unexplained exit code. Tool activity after finalization, duplicate settlement, pre-settlement shutdown, extension diagnostics, malformed/duplicate runtime-guard frames, and missing/failed/stale seals are fatal. This lifecycle requires Pi 0.81.1 or newer; older Pi lines do not expose the required terminal event and are not claimed as compatible.
114
116
 
@@ -125,6 +127,7 @@ Cancellation and shutdown are loud and durable when a run store exists. The exte
125
127
  - `prompt_budget_exceeded_measured`: an exact rendered prompt exceeded capacity after upstream output was known; split the workflow or choose a larger-context subscription route.
126
128
  - `child_runtime_budget_exceeded`: a later provider payload, provider-request loop, or tool-call loop crossed a child runtime guard after launch. Inspect the attempt stderr guard frame and failed tool seal; narrow the task or select a subscription route with more safe input headroom. Do not ignore intermediate provider errors or weaken the guard.
127
129
  - `child_cache_policy_invalid`: `PI_CACHE_RETENTION` or Claude cache-control evidence was malformed. Use exactly `none`, `short`, or `long`; do not remove the final-payload guard.
130
+ - `child_output_cap` after candidate compression: inspect the final candidate response, its `response.oversized.*` original, and v4/v3 child protocol evidence. The single same-session no-tool compression attempt also exceeded 49,152 JSON-rendered bytes; split the task rather than truncating or starting a repair child.
128
131
  - `evaluation schema repair failed`: both evaluator attempts failed the closed JSON contract; inspect `evaluation.attempt-*.response.txt` and errors.
129
132
  - `tool-call log invalid`: inspect the candidate `*.tool-calls.jsonl` and `*.seal.json`; missing/partial/unsealed logs, non-allowlisted tools, hash/count mismatches, and over-budget tool output fail by design.
130
133
  - Research fetch failures are typed and do not retry via other URLs or extraction modes; verify the declared URL is public, reachable, supported content, and within caps.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-background-tasks",
3
- "version": "2.0.0",
3
+ "version": "2.1.2",
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",
@@ -83,14 +83,14 @@
83
83
  ]
84
84
  },
85
85
  "peerDependencies": {
86
- "@earendil-works/pi-coding-agent": "^0.81.1 || ^0.82.1 || ^0.83.0",
87
- "@earendil-works/pi-tui": "^0.81.1 || ^0.82.1 || ^0.83.0",
86
+ "@earendil-works/pi-coding-agent": "^0.81.1 || ^0.82.1 || ^0.83.0 || ^0.84.0",
87
+ "@earendil-works/pi-tui": "^0.81.1 || ^0.82.1 || ^0.83.0 || ^0.84.0",
88
88
  "typebox": "*"
89
89
  },
90
90
  "devDependencies": {
91
- "@earendil-works/pi-ai": "^0.83.0",
92
- "@earendil-works/pi-coding-agent": "^0.83.0",
93
- "@earendil-works/pi-tui": "^0.83.0",
91
+ "@earendil-works/pi-ai": "^0.84.0",
92
+ "@earendil-works/pi-coding-agent": "^0.84.0",
93
+ "@earendil-works/pi-tui": "^0.84.0",
94
94
  "@types/node": "^24.0.0",
95
95
  "@types/turndown": "5.0.6",
96
96
  "tsx": "^4.19.0",
@@ -22,14 +22,15 @@ export const DELEGATE_HOOK_CONTRACT_SCHEMA_VERSION =
22
22
  * `ctx.abort()` so the request is never issued, then report a typed failure over
23
23
  * the child result channel.
24
24
  *
25
- * Empirically established by the characterisation gate on Pi 0.83:
26
- * `ctx.abort()` does NOT skip the `streamSimple` call site. Pi still invokes the
27
- * provider entry point, but hands it an already-aborted `AbortSignal`, so no
28
- * network request is issued and the run terminates with stop reason `aborted`.
29
- * Throwing from a `context` handler is NOT a barrier at all: Pi catches the
30
- * exception, reports it as an extension error, and dispatches the call anyway.
31
- * The guard therefore uses abort, never a throw, and additionally suppresses the
32
- * oversized content itself so a non-conforming provider cannot transmit it.
25
+ * Empirically established across the supported Pi lines by the characterisation
26
+ * and exact-version compatibility gates: Pi 0.81.1-0.83.0 invoke the provider
27
+ * entry point with an already-aborted `AbortSignal`, while Pi 0.84.0 propagates
28
+ * that signal through auth resolution and skips the entry point. No network
29
+ * request is issued in either mode, and the run terminates. Throwing from a
30
+ * `context` handler is NOT a barrier at all: Pi catches the exception, reports
31
+ * it as an extension error, and continues dispatch. The guard therefore uses
32
+ * abort, never a throw, and additionally suppresses the oversized content itself
33
+ * so a non-conforming provider cannot transmit it.
33
34
  *
34
35
  * `tool-result-spill-v1`: replace an oversized `tool_result` payload with an
35
36
  * explicit hash-accounted receipt before it enters the transcript.
@@ -65,11 +66,11 @@ export interface DelegateHookContractEvidence {
65
66
  * Guarantees the child guard actually depends on.
66
67
  *
67
68
  * `context_throw_blocks_provider_call` and
68
- * `context_abort_skips_stream_invocation` are deliberately absent, because
69
- * neither holds on Pi 0.83. The gate records both as observed evidence, and the
70
- * guard is built so that it does not need either: it aborts the run AND removes
71
- * the oversized content from the outgoing message set, so the request cannot be
72
- * issued and could not carry the content even if it were.
69
+ * `context_abort_skips_stream_invocation` are deliberately absent. Throws do
70
+ * not block dispatch, and skipping the stream entry point is not shared by every
71
+ * supported Pi line. The guard needs neither behavior: it aborts the run AND
72
+ * removes the oversized content from the outgoing message set, so the request
73
+ * cannot be issued and could not carry the content even if it were.
73
74
  */
74
75
  export const DELEGATE_REQUIRED_HOOK_GUARANTEES: readonly DelegateHookGuaranteeName[] = [
75
76
  'context_fires_before_every_model_call',