pi-background-tasks 1.0.7 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +7 -7
  2. package/TESTING.md +3 -3
  3. package/TEST_PLAN.md +2 -2
  4. package/docs/INDEX.md +25 -25
  5. package/docs/choose-a-workflow.md +4 -4
  6. package/docs/commands/bg-clear.md +1 -1
  7. package/docs/commands/bg-update.md +1 -1
  8. package/docs/commands/bg.md +1 -1
  9. package/docs/commands/fusion-models.md +1 -1
  10. package/docs/commands/fusion.md +5 -8
  11. package/docs/commands/jobs.md +1 -1
  12. package/docs/commands/kill.md +1 -1
  13. package/docs/commands/logs.md +1 -1
  14. package/docs/commands/task-manager.md +2 -2
  15. package/docs/concepts/completion-delivery.md +1 -0
  16. package/docs/getting-started.md +1 -1
  17. package/docs/manifest.json +59 -50
  18. package/docs/read-before-edit.md +1 -0
  19. package/docs/reference/runtime-contracts.md +48 -45
  20. package/docs/reference/shortcuts-and-dock.md +2 -2
  21. package/docs/subsystems/background-task-runtime.md +7 -1
  22. package/docs/subsystems/docs-freshness-gate.md +4 -4
  23. package/docs/subsystems/fusion.md +13 -9
  24. package/docs/subsystems/host-ui-and-telemetry.md +1 -1
  25. package/docs/tools/bg_delegate.md +1 -1
  26. package/docs/tools/bg_kill.md +1 -1
  27. package/docs/tools/bg_logs.md +1 -1
  28. package/docs/tools/bg_result.md +14 -10
  29. package/docs/tools/bg_run.md +1 -1
  30. package/docs/tools/bg_run_pi_attested.md +1 -1
  31. package/docs/tools/bg_status.md +1 -1
  32. package/docs/tools/fusion_investigate.md +6 -4
  33. package/docs/tools/fusion_reason.md +5 -5
  34. package/docs/tools/fusion_research.md +6 -2
  35. package/docs/tools/fusion_validate.md +5 -3
  36. package/package.json +1 -1
  37. package/src/core/common.ts +50 -2
  38. package/src/core/fusion/artifacts.ts +72 -20
  39. package/src/core/fusion/orchestrator.ts +154 -68
  40. package/src/core/fusion/result-package.ts +385 -0
  41. package/src/core/fusion/types.ts +9 -0
  42. package/src/core/registry.ts +187 -20
  43. package/src/delegate-extension.ts +130 -24
  44. package/src/extension.ts +17 -6
  45. package/src/fusion-extension.ts +308 -154
@@ -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/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/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
@@ -36,7 +36,7 @@ Every public tool schema is closed and has no public capability/mode switch. The
36
36
 
37
37
  ## Commands
38
38
 
39
- `/fusion <prompt>` trims the command text and runs the reason workflow. `/fusion` with no arguments opens the multiline editor when UI is available; editor cancellation or blank edited text returns without child spawn. TUI mode wraps the run in a cancellable loader. Success sends a hidden `fusion-request` custom message and a visible `fusion-result` custom message containing the merger's exact text; the parent model is not asked to rewrite the result.
39
+ `/fusion <prompt>` trims the command text and starts the reason workflow as a managed background task. `/fusion` with no arguments opens the multiline editor when UI is available; editor cancellation or blank edited text returns without child spawn. Durable preflight and task registration finish before the command returns; no loader remains open and no premature result message is appended. Terminal state uses the standard background notification, and `bg_result` verifies and retrieves the committed result.
40
40
 
41
41
  `/fusion-models` requires TUI mode. It edits five slots (`Candidate 1`, `Candidate 2`, `Candidate 3`, `Evaluator`, `Merger`), allows duplicates, supports `$current`, shows unavailable configured choices, and persists `fusion-models.json` with schema `pi-background-tasks.fusion-models.v1`. Saves are lock-protected, atomic, and revision-safe: if the file changed after load, the selector reports a config conflict instead of overwriting concurrent work.
42
42
 
@@ -51,11 +51,13 @@ Investigate, research, and validate receive clean-task canonical input: exactly
51
51
  All workflows use the same orchestrator shape:
52
52
 
53
53
  1. plan budget and write artifacts before any child exists;
54
- 2. run three candidate children in parallel;
55
- 3. anonymize candidate identities as A/B/C before evaluation;
56
- 4. run a blind no-tool evaluator;
57
- 5. run one no-tool evaluator-repair child only if the first evaluator JSON is invalid or schema-invalid;
58
- 6. run a no-tool merger.
54
+ 2. pause at a no-child-yet readiness barrier while the managed background-task receipt becomes durable;
55
+ 3. run three candidate children in parallel;
56
+ 4. anonymize candidate identities as A/B/C before evaluation;
57
+ 5. run a blind no-tool evaluator;
58
+ 6. run one no-tool evaluator-repair child only if the first evaluator JSON is invalid or schema-invalid;
59
+ 7. run a no-tool merger;
60
+ 8. durably commit `merged.md` plus manifest-bound `result.json`, then publish terminal task state.
59
61
 
60
62
  Do not describe Fusion as unconditionally exactly five model calls. A completed run may use five or six child invocations, while preflight failures use zero; candidate failures, cancellation, spawn retry, output caps, or invalid repair alter observed attempts.
61
63
 
@@ -104,13 +106,15 @@ Output contracts are checked after durable attempt recording: candidate response
104
106
 
105
107
  ## Artifacts, usage, and lifecycle
106
108
 
107
- 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`, `error.json`, and workflow-specific context/source-policy artifacts.
109
+ 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.
108
110
 
109
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.
110
112
 
111
113
  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.
112
114
 
113
- Cancellation and shutdown are loud and durable when a run store exists. The extension tracks active runs, links external abort signals, aborts on session shutdown/reload, and waits for settlement. Child processes have a 30 minute wall timeout, 20 minute idle watchdog, SIGTERM grace, SIGKILL wait, process-group kill on POSIX, bounded stdout/stderr, and cleanup-error propagation.
115
+ The four public Fusion tools return a background launch receipt after the readiness barrier. Tool-launched runs default to terminal notification plus follow-up wake; `/fusion` uses notification-only. The first successful `bg_result` retrieval durably claims and attaches complete Fusion usage exactly once; repeated retrieval returns the answer without duplicating session accounting. Running retrieval never waits.
116
+
117
+ Cancellation and shutdown are loud and durable when a run store exists. The extension tracks active runs, managed tasks own their abort controllers, `bg_kill` and session shutdown abort them, and terminal task publication waits for workflow settlement. Child processes have a 30 minute wall timeout, 20 minute idle watchdog, SIGTERM grace, SIGKILL wait, process-group kill on POSIX, bounded stdout/stderr, and cleanup-error propagation.
114
118
 
115
119
  ## Troubleshooting
116
120
 
@@ -40,7 +40,7 @@ A finished badge is cleared when that task's detail view is opened, or when `/bg
40
40
 
41
41
  `/tasks`, `/bg-tasks`, and `Shift+Down` open the same overlay. Non-interactive contexts receive an error notification directing users to `/jobs`, `/logs`, `bg_status`, or `bg_logs`.
42
42
 
43
- The list view supports selection, paging, stop, confirmed stop-all, history toggle, rerun, output path, and close. The detail view shows task identity, status, runtime, output path, description, task-owned model/context/tokens/tools when reported, command, error, and an output tail.
43
+ The list view supports selection, paging, stop, confirmed stop-all, history toggle, rerun, output path, and close. Rerun is shell-task-only: typed delegate and Fusion tasks fail with guidance to relaunch through their owning tool rather than executing their display command as a shell command. The detail view shows task identity, status, runtime, output path, description, task-owned model/context/tokens/tools when reported, command, error, and an output tail.
44
44
 
45
45
  Detail output semantics:
46
46
 
@@ -11,7 +11,7 @@ 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:265`
14
+ - Source: `src/delegate-extension.ts:292`
15
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.
16
16
  - Root schema: `object`; additionalProperties: `false`
17
17
 
@@ -11,7 +11,7 @@ covers_sources: []
11
11
 
12
12
  <!-- pi-docs:begin name="tool-contract-bg_kill" generator="scripts/docs/generate.mjs" -->
13
13
  - Label: **Background Kill**
14
- - Source: `src/extension.ts:897`
14
+ - Source: `src/extension.ts:908`
15
15
  - Description: Stop a running background task by ID. Fails loudly if the task is unknown or already finished.
16
16
  - Root schema: `object`
17
17
 
@@ -11,7 +11,7 @@ covers_sources: []
11
11
 
12
12
  <!-- pi-docs:begin name="tool-contract-bg_logs" generator="scripts/docs/generate.mjs" -->
13
13
  - Label: **Background Logs**
14
- - Source: `src/extension.ts:852`
14
+ - Source: `src/extension.ts:863`
15
15
  - Description: Read bounded output from a background task for deliberate inspection; this is not a waiting primitive. Output is capped at 50.0KB for model safety and points to the full output file when truncated.
16
16
  - Root schema: `object`
17
17
 
@@ -10,15 +10,15 @@ covers_sources: []
10
10
  # `bg_result`
11
11
 
12
12
  <!-- pi-docs:begin name="tool-contract-bg_result" generator="scripts/docs/generate.mjs" -->
13
- - Label: **Delegate Result**
14
- - Source: `src/delegate-extension.ts:428`
15
- - Description: Retrieve the hash-verified answer from a bg_delegate task. Never blocks: a running task returns a typed not-ready result. A completed answer is verified against its recorded SHA-256 before it is returned, and an oversized answer is never truncated.
13
+ - Label: **Background Result**
14
+ - Source: `src/delegate-extension.ts:453`
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
 
18
18
  | Field | Required | Type | Description | Constraints |
19
19
  | --- | --- | --- | --- | --- |
20
20
  | `delivery` | no | `string` | inline returns the verified answer text; artifact returns metadata plus the artifact reference. Oversized answers are never truncated. | |
21
- | `taskId` | yes | `string` | Delegate task id returned by bg_delegate. | |
21
+ | `taskId` | yes | `string` | Background delegate or Fusion task id returned by its launch tool. | |
22
22
 
23
23
  <details>
24
24
  <summary>Normalized TypeBox contract</summary>
@@ -33,7 +33,7 @@ covers_sources: []
33
33
  "type": "string"
34
34
  },
35
35
  "taskId": {
36
- "description": "Delegate task id returned by bg_delegate.",
36
+ "description": "Background delegate or Fusion task id returned by its launch tool.",
37
37
  "type": "string"
38
38
  }
39
39
  },
@@ -47,13 +47,13 @@ covers_sources: []
47
47
  </details>
48
48
  <!-- pi-docs:end name="tool-contract-bg_result" -->
49
49
 
50
- `bg_result` retrieves the result of a `bg_delegate` task. It never blocks: a running task returns a typed not-ready view, and a terminal task is verified before any answer bytes are returned.
50
+ `bg_result` retrieves the result of a `bg_delegate` or background Fusion task. It never blocks: a running task returns a typed not-ready view, and a terminal task is verified before any answer bytes are returned.
51
51
 
52
52
  ## Public arguments
53
53
 
54
54
  Required:
55
55
 
56
- - `taskId: string` — task id or unambiguous prefix resolved by the background-task registry. Must be non-empty after trimming.
56
+ - `taskId: string` — delegate or Fusion task id, or an unambiguous prefix resolved by the background-task registry. Must be non-empty after trimming.
57
57
 
58
58
  Optional:
59
59
 
@@ -74,7 +74,7 @@ This is not an error and does not wait. End the turn or do other independent wor
74
74
 
75
75
  ## Verification before return
76
76
 
77
- For terminal tasks, the parent evaluates the child artifacts. `result.json` is the child-written commit point. If it is absent, the task has no accepted answer even if the child exited `0`.
77
+ For terminal delegate tasks, the parent evaluates the child artifacts. `result.json` is the child-written commit point. If it is absent, the task has no accepted answer even if the child exited `0`.
78
78
 
79
79
  A present package is accepted only after verifying:
80
80
 
@@ -90,6 +90,10 @@ A present package is accepted only after verifying:
90
90
 
91
91
  The returned text is decoded from the same aggregate buffer that was hashed. Corruption, stale packages, missing attestations, route drift, or invalid UTF-8 produce typed failures and no answer bytes.
92
92
 
93
+ ## Fusion retrieval
94
+
95
+ A completed Fusion task is accepted only when `manifest.json` is terminal `completed`, its `result.json` and `merged.md` fixed references match, both files match manifest-bound byte lengths and SHA-256 values, run/workflow/artifact identity matches the task, result details carry the current schema, usage is complete, and merged bytes are well-formed UTF-8. The first successful retrieval attaches complete Fusion usage exactly once; later retrievals omit usage to prevent double-counting.
96
+
93
97
  ## Inline/artifact delivery and no truncation
94
98
 
95
99
  `bg_result` never truncates an answer.
@@ -102,7 +106,7 @@ Large answers remain complete in `result.json` as base64 blocks plus aggregate h
102
106
 
103
107
  ## Failure classes users see
104
108
 
105
- Common retrieval outcomes:
109
+ Common delegate retrieval outcomes:
106
110
 
107
111
  - `task_unknown` — unknown id/prefix or not a delegate task.
108
112
  - `result_unavailable` / `child_exited_without_commit` — terminal task produced no committed result package.
@@ -113,7 +117,7 @@ Common retrieval outcomes:
113
117
  - `artifact_read_failed`, `artifact_spill_failed`, `artifact_error` — artifact I/O failure.
114
118
  - `result_too_large_for_inline` — explicit inline request exceeded the inline cap.
115
119
 
116
- 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.
120
+ 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.
117
121
 
118
122
  ## Parent outcome separation
119
123
 
@@ -11,7 +11,7 @@ covers_sources: []
11
11
 
12
12
  <!-- pi-docs:begin name="tool-contract-bg_run" generator="scripts/docs/generate.mjs" -->
13
13
  - Label: **Background Run**
14
- - Source: `src/extension.ts:671`
14
+ - Source: `src/extension.ts:682`
15
15
  - Description: Start a named long-running shell command in the background and return immediately with a task ID and output path. By default, completed, failed, or killed terminal state is delivered automatically as <background-task-notification> and starts a follow-up agent turn; do not sleep or poll merely to wait. Output is written to .pi/tasks and model-visible logs are bounded to 50.0KB.
16
16
  - Root schema: `object`
17
17
 
@@ -11,7 +11,7 @@ covers_sources: []
11
11
 
12
12
  <!-- pi-docs:begin name="tool-contract-bg_run_pi_attested" generator="scripts/docs/generate.mjs" -->
13
13
  - Label: **Attested Pi Run**
14
- - Source: `src/extension.ts:756`
14
+ - Source: `src/extension.ts:767`
15
15
  - Description: Opt-in evidence-oriented direct Pi spawn. Launches exactly one `pi --mode json` child, records raw Pi events/stderr, hashes prompt/report/output, observes OAuth through ModelRegistry, and emits a strict attestation sidecar only after successful completion.
16
16
  - Root schema: `object`
17
17
 
@@ -11,7 +11,7 @@ covers_sources: []
11
11
 
12
12
  <!-- pi-docs:begin name="tool-contract-bg_status" generator="scripts/docs/generate.mjs" -->
13
13
  - Label: **Background Status**
14
- - Source: `src/extension.ts:821`
14
+ - Source: `src/extension.ts:832`
15
15
  - Description: Inspect one background task or list all running/recent background tasks. This is a point-in-time inspection tool, not a waiting primitive.
16
16
  - Root schema: `object`
17
17
 
@@ -11,8 +11,8 @@ covers_sources: []
11
11
 
12
12
  <!-- pi-docs:begin name="tool-contract-fusion_investigate" generator="scripts/docs/generate.mjs" -->
13
13
  - Label: **Fusion Investigate**
14
- - Source: `src/fusion-extension.ts:1049`
15
- - Description: Run a five-model Fusion investigation from a structured, self-contained objective/background/deliverable. Candidate children run in clean bounded read-only contexts.
14
+ - Source: `src/fusion-extension.ts:1197`
15
+ - Description: Start a five-model Fusion investigation as a tracked background task and return immediately after durable preflight. Retrieve the verified result with bg_result after notification. Candidate children run in clean bounded read-only contexts.
16
16
  - Root schema: `object`; additionalProperties: `false`
17
17
 
18
18
  | Field | Required | Type | Description | Constraints |
@@ -103,9 +103,11 @@ Investigate uses clean-task canonical input (`pi-background-tasks.fusion-input.v
103
103
 
104
104
  Candidate children run with the fixed inspect policy: `read`, `grep`, `find`, and `ls` only, with built-in tools disabled and Fusion/background/write/shell tools denied. Evaluator, evaluator-repair, and merger run with no tools.
105
105
 
106
- ## Execution model
106
+ ## Execution and delivery model
107
107
 
108
- Three inspect candidates independently re-derive repository facts, a blind evaluator compares anonymous candidate answers, and a merger synthesizes the final answer. One evaluator-repair child is run only when the first evaluator response is invalid JSON or fails the evaluation schema.
108
+ After durable no-child preflight, the tool returns a tracked background task receipt. Three inspect candidates independently re-derive repository facts, a blind evaluator compares anonymous candidate answers, and a merger synthesizes the final answer. One evaluator-repair child is run only when the first evaluator response is invalid JSON or fails the evaluation schema.
109
+
110
+ Wait for the terminal notification, then call `bg_result({taskId})` once. Retrieval verifies the committed result and never truncates. Repository reads are live, so continue only independent work and do not mutate the investigated scope while the task runs.
109
111
 
110
112
  ## Failure behavior
111
113
 
@@ -11,8 +11,8 @@ covers_sources: []
11
11
 
12
12
  <!-- pi-docs:begin name="tool-contract-fusion_reason" generator="scripts/docs/generate.mjs" -->
13
13
  - Label: **Fusion Reason**
14
- - Source: `src/fusion-extension.ts:1032`
15
- - Description: Run a five-model Fusion reason workflow. Candidate children receive the reason projection and no tools; evaluator and merger also run without tools.
14
+ - Source: `src/fusion-extension.ts:1179`
15
+ - Description: Start a five-model Fusion reason workflow as a tracked background task and return immediately after durable preflight. Retrieve the verified result with bg_result after notification. Candidate children receive the reason projection and no tools; evaluator and merger also run without tools.
16
16
  - Root schema: `object`; additionalProperties: `false`
17
17
 
18
18
  | Field | Required | Type | Description | Constraints |
@@ -59,11 +59,11 @@ The schema is closed: `prompt` is required, must trim to non-blank text, and no
59
59
 
60
60
  Candidate children run with `--no-tools`. The blind evaluator, conditional evaluator-repair, and merger also run with `--no-tools` by stage policy.
61
61
 
62
- ## Execution model
62
+ ## Execution and delivery model
63
63
 
64
- A successful run starts three candidate children, then a blind evaluator, then a merger. If the evaluator output is not valid closed-schema JSON, Fusion performs one evaluator-repair attempt and revalidates. Do not assume exactly five child calls: repair, preflight refusal, cancellation, spawn retry, and failures change the observed attempt count.
64
+ The tool freezes its input, completes durable no-child preflight, registers a managed background task, and returns its task/run id immediately. The workflow then starts three candidate children, a blind evaluator, optional evaluator repair, and a merger.
65
65
 
66
- The tool result returns the merger's exact text directly, with `details` containing the Fusion result metadata and `usage` cloning the complete Pi `Usage` object including all cost fields.
66
+ Wait for the terminal notification and call `bg_result({taskId})` once. `bg_result` verifies `manifest.json`, `result.json`, and `merged.md` hashes before returning bytes; oversized output becomes an artifact reference, never truncation. Complete Fusion usage is attached to the first successful retrieval exactly once so repeated retrieval cannot double-count session cost.
67
67
 
68
68
  ## Limitations
69
69
 
@@ -11,8 +11,8 @@ covers_sources: []
11
11
 
12
12
  <!-- pi-docs:begin name="tool-contract-fusion_research" generator="scripts/docs/generate.mjs" -->
13
13
  - Label: **Fusion Research**
14
- - Source: `src/fusion-extension.ts:1066`
15
- - Description: Run a five-model Fusion research workflow over explicitly supplied public http(s) URLs. Targeted URL fetch is not web search; fetched pages and URLs are untrusted.
14
+ - Source: `src/fusion-extension.ts:1216`
15
+ - Description: Start a five-model Fusion research workflow as a tracked background task and return immediately after durable preflight. Retrieve the verified result with bg_result after notification. Targeted URL fetch is not web search; fetched pages and URLs are untrusted.
16
16
  - Root schema: `object`; additionalProperties: `false`
17
17
 
18
18
  | Field | Required | Type | Description | Constraints |
@@ -153,6 +153,10 @@ The private `fusion_web_fetch` schema is closed: `{url, extract?: 'text'|'markdo
153
153
 
154
154
  Failures use typed error codes such as `invalid_url`, `unsupported_scheme`, `blocked_address`, `dns_failure`, `redirect_limit`, `redirect_blocked`, `response_too_large`, `unsupported_content_type`, `request_timeout`, `network_error`, `extraction_failed`, and `http_error`.
155
155
 
156
+ ## Background delivery
157
+
158
+ After durable no-child preflight, the tool returns a tracked background task receipt. Wait for the terminal notification, then call `bg_result({taskId})` once; retrieval verifies the committed result and never truncates. Repository reads are live, so do not mutate relevant files while the task runs.
159
+
156
160
  ## Audit
157
161
 
158
162
  Research candidates write sealed per-attempt tool-call logs. Logs persist tool names, byte counts, SHA-256 digests, status, duration, and fetch provenance (`url`/`final_url`/status/bytes/content hash for successful fetches; only a rejected URL hash for rejected fetches). Raw arguments, raw results, and page content are not written to the audit log.
@@ -11,8 +11,8 @@ covers_sources: []
11
11
 
12
12
  <!-- pi-docs:begin name="tool-contract-fusion_validate" generator="scripts/docs/generate.mjs" -->
13
13
  - Label: **Fusion Validate**
14
- - Source: `src/fusion-extension.ts:1084`
15
- - Description: Run an advisory, read-only Fusion validation review from a structured contract. It is not a build/test/lint substitute and never modifies files.
14
+ - Source: `src/fusion-extension.ts:1236`
15
+ - Description: Start an advisory, read-only Fusion validation review as a tracked background task and return immediately after durable preflight. Retrieve the verified result with bg_result after notification. It is not a build/test/lint substitute and never modifies files.
16
16
  - Root schema: `object`; additionalProperties: `false`
17
17
 
18
18
  | Field | Required | Type | Description | Constraints |
@@ -197,7 +197,9 @@ Each candidate must return closed JSON (`pi-background-tasks.fusion-validation-c
197
197
 
198
198
  After the no-tool merger child runs, the host renders the final validation report from validated accounting so included findings are preserved, duplicate groups are merged deterministically, excluded findings are listed only as exclusions, and candidate labels/source ids are sanitized from rationale text.
199
199
 
200
- ## Advisory limitation
200
+ ## Background delivery and advisory limitation
201
+
202
+ After durable no-child preflight, the tool returns a tracked background task receipt. Wait for the terminal notification, then call `bg_result({taskId})` once; retrieval verifies the committed report and never truncates. The repository is read live, so do not mutate the reviewed scope while the task runs.
201
203
 
202
204
  `fusion_validate` never modifies files, never runs builds/tests/linters/security scanners, and does not gate anything. It is an advisory read-only review. Supply real verification evidence when available, and state known limitations/exclusions explicitly.
203
205
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-background-tasks",
3
- "version": "1.0.7",
3
+ "version": "2.0.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",
@@ -4,6 +4,7 @@ import { extname, isAbsolute, join, win32 } from 'node:path';
4
4
  import { DEFAULT_MAX_BYTES } from '@earendil-works/pi-coding-agent';
5
5
  import type { BackgroundTaskChildProcess } from './registry.js';
6
6
  import type { DelegateBudgetRouteSource } from './delegate/types.js';
7
+ import type { FusionResultDetails, FusionUsage, FusionWorkflowId } from './fusion/types.js';
7
8
 
8
9
  export const TASK_STATUS_VALUES = ['running', 'completed', 'failed', 'killed'] as const;
9
10
  export const TERMINAL_TASK_STATUS_VALUES = ['completed', 'failed', 'killed'] as const;
@@ -62,6 +63,7 @@ export interface BgTaskSnapshot {
62
63
  telemetryUnavailableReason?: string | undefined;
63
64
  attestationPath?: string | undefined;
64
65
  delegate?: DelegateTaskFacts | undefined;
66
+ fusion?: FusionTaskFacts | undefined;
65
67
  }
66
68
 
67
69
  export interface AttestedPiTaskFiles {
@@ -99,6 +101,25 @@ export interface DelegateTaskOutcome {
99
101
  toolCalls?: number | undefined;
100
102
  }
101
103
 
104
+ /** Fusion-specific task facts surfaced through snapshots and `bg_result`. */
105
+ export interface FusionTaskFacts {
106
+ runId: string;
107
+ workflow: FusionWorkflowId;
108
+ artifactDir: string;
109
+ artifactDirAbs: string;
110
+ state: string;
111
+ outcome?: FusionTaskOutcome | undefined;
112
+ /** Durable once-only accounting claim made by the first successful bg_result retrieval. */
113
+ usageDelivered: boolean;
114
+ }
115
+
116
+ export interface FusionTaskOutcome {
117
+ status: 'committed' | 'failed' | 'cancelled';
118
+ resultDetails?: FusionResultDetails | undefined;
119
+ usage?: FusionUsage | undefined;
120
+ error?: string | undefined;
121
+ }
122
+
102
123
  export interface BgTask extends Omit<BgTaskSnapshot, 'name'> {
103
124
  name: string;
104
125
  outputAbsPath: string;
@@ -129,6 +150,11 @@ export interface BgTask extends Omit<BgTaskSnapshot, 'name'> {
129
150
  attestationPath?: string | undefined;
130
151
  attestedPi?: AttestedPiTaskFiles | undefined;
131
152
  delegate?: DelegateTaskFacts | undefined;
153
+ fusion?: FusionTaskFacts | undefined;
154
+ /** Cancellation hook for an in-process managed task such as Fusion. */
155
+ managedCancel?: (() => void) | undefined;
156
+ managedCancelRequested?: boolean | undefined;
157
+ managedStopWaitMs?: number | undefined;
132
158
  metadataWriteChain?: Promise<void> | undefined;
133
159
  waiters: Array<() => void>;
134
160
  }
@@ -226,6 +252,22 @@ export interface StartTaskOptions {
226
252
  }
227
253
 
228
254
  /** Prepared delegate launch handed to the registry after preflight has succeeded. */
255
+ export interface StartManagedTaskOptions {
256
+ id: string;
257
+ name: string;
258
+ command: string;
259
+ description?: string | undefined;
260
+ isAgent: boolean;
261
+ completion: Promise<void>;
262
+ cancel: () => void;
263
+ notifyOnCompletion: boolean;
264
+ triggerOnCompletion: boolean;
265
+ fusion: FusionTaskFacts;
266
+ stopWaitMs?: number | undefined;
267
+ /** Prevent terminal publication until the launch receipt handoff is observable. */
268
+ terminalPublicationGate?: Promise<void> | undefined;
269
+ }
270
+
229
271
  export interface StartDelegateTaskOptions {
230
272
  name: string;
231
273
  argv: readonly string[];
@@ -687,10 +729,15 @@ export function shellInvocation(
687
729
  failShellInvocation('PI_BG_SHELL must be exactly cmd or bash');
688
730
  }
689
731
  const explicitPath =
690
- requestedPath !== undefined ? validateWindowsShellPath(requestedPath, 'PI_BG_SHELL_PATH') : undefined;
732
+ requestedPath !== undefined
733
+ ? validateWindowsShellPath(requestedPath, 'PI_BG_SHELL_PATH')
734
+ : undefined;
691
735
  if (requestedShell === 'cmd') {
692
736
  const comSpec = env['ComSpec'];
693
- return cmdShellInvocation(command, explicitPath ?? (comSpec && comSpec.length > 0 ? comSpec : 'cmd.exe'));
737
+ return cmdShellInvocation(
738
+ command,
739
+ explicitPath ?? (comSpec && comSpec.length > 0 ? comSpec : 'cmd.exe'),
740
+ );
694
741
  }
695
742
  return posixShellInvocation(command, explicitPath ?? resolveWindowsBash(env));
696
743
  }
@@ -728,6 +775,7 @@ export function snapshot(task: BgTask): BgTaskSnapshot {
728
775
  telemetryUnavailableReason: task.telemetryUnavailableReason,
729
776
  attestationPath: task.attestationPath,
730
777
  delegate: task.delegate,
778
+ fusion: task.fusion,
731
779
  };
732
780
  }
733
781
 
@@ -6,6 +6,7 @@ import { sanitizePathSegment } from '../common.js';
6
6
  import { replaceFileDurable } from '../durable-fs.js';
7
7
  import {
8
8
  EMPTY_FUSION_USAGE,
9
+ FUSION_COMMITTED_RESULT_SCHEMA_VERSION,
9
10
  FUSION_MANIFEST_SCHEMA_VERSION,
10
11
  FUSION_VALIDATE_CANDIDATE_CONTRACT_EVENT_SCHEMA_VERSION,
11
12
  FusionError,
@@ -19,6 +20,8 @@ import {
19
20
  type FusionCapability,
20
21
  type FusionContextOmissionLedgerV2,
21
22
  type FusionChildRunResult,
23
+ type FusionCommittedResultV1,
24
+ type FusionResultDetails,
22
25
  type FusionModelConfigV1,
23
26
  type FusionSource,
24
27
  type FusionStage,
@@ -57,8 +60,17 @@ interface MutableFusionArtifactManifest {
57
60
  evaluation: FusionCapability;
58
61
  merge: FusionCapability;
59
62
  };
60
- context: { kind: import('./types.js').FusionContextKind; policy_id: string; ledger_artifact?: string; source_policy_artifact?: string };
61
- tool_policy: { candidate_tools: readonly string[]; evaluation_tools: readonly []; merge_tools: readonly [] };
63
+ context: {
64
+ kind: import('./types.js').FusionContextKind;
65
+ policy_id: string;
66
+ ledger_artifact?: string;
67
+ source_policy_artifact?: string;
68
+ };
69
+ tool_policy: {
70
+ candidate_tools: readonly string[];
71
+ evaluation_tools: readonly [];
72
+ merge_tools: readonly [];
73
+ };
62
74
  usage: FusionUsage;
63
75
  attempts: FusionAttemptArtifactRecord[];
64
76
  artifacts: Record<string, FusionArtifactRef>;
@@ -206,7 +218,11 @@ function publicManifest(manifest: MutableFusionArtifactManifest): FusionArtifact
206
218
  models: manifest.models,
207
219
  capabilities: manifest.capabilities,
208
220
  context: { ...manifest.context },
209
- tool_policy: { candidate_tools: [...manifest.tool_policy.candidate_tools], evaluation_tools: [], merge_tools: [] },
221
+ tool_policy: {
222
+ candidate_tools: [...manifest.tool_policy.candidate_tools],
223
+ evaluation_tools: [],
224
+ merge_tools: [],
225
+ },
210
226
  usage: cloneFusionUsage(manifest.usage),
211
227
  attempts: [...manifest.attempts],
212
228
  artifacts: { ...manifest.artifacts },
@@ -295,8 +311,18 @@ export class FusionArtifactStore {
295
311
  evaluation: 'reason',
296
312
  merge: 'reason',
297
313
  },
298
- context: { kind: profile.contextKind, policy_id: profile.contextKind === 'session_projection' ? 'fusion-session-projection-v1' : 'fusion-clean-task-v1' },
299
- tool_policy: { candidate_tools: profile.candidateTools, evaluation_tools: [], merge_tools: [] },
314
+ context: {
315
+ kind: profile.contextKind,
316
+ policy_id:
317
+ profile.contextKind === 'session_projection'
318
+ ? 'fusion-session-projection-v1'
319
+ : 'fusion-clean-task-v1',
320
+ },
321
+ tool_policy: {
322
+ candidate_tools: profile.candidateTools,
323
+ evaluation_tools: [],
324
+ merge_tools: [],
325
+ },
300
326
  usage: cloneFusionUsage(EMPTY_FUSION_USAGE),
301
327
  attempts: [],
302
328
  artifacts: {},
@@ -339,11 +365,18 @@ export class FusionArtifactStore {
339
365
  childCreated: false,
340
366
  });
341
367
  }
342
- if (to === 'completed' && manifest.artifacts['merged.md'] === undefined) {
343
- throw new FusionError('fusion cannot complete before merged.md is durable', {
344
- code: 'state_transition_invalid',
345
- childCreated: false,
346
- });
368
+ if (
369
+ to === 'completed' &&
370
+ (manifest.artifacts['merged.md'] === undefined ||
371
+ manifest.artifacts['result.json'] === undefined)
372
+ ) {
373
+ throw new FusionError(
374
+ 'fusion cannot complete before merged.md and result.json are durable',
375
+ {
376
+ code: 'state_transition_invalid',
377
+ childCreated: false,
378
+ },
379
+ );
347
380
  }
348
381
  manifest.state = to;
349
382
  });
@@ -403,8 +436,21 @@ export class FusionArtifactStore {
403
436
  await this.writeArtifact('evaluation.json', canonicalJson(value));
404
437
  }
405
438
 
406
- async writeMerged(text: string): Promise<void> {
407
- await this.writeArtifact('merged.md', text);
439
+ async writeMerged(text: string): Promise<FusionArtifactRef> {
440
+ return this.writeArtifact('merged.md', text);
441
+ }
442
+
443
+ async writeCommittedResult(
444
+ merged: FusionArtifactRef,
445
+ details: FusionResultDetails,
446
+ ): Promise<FusionArtifactRef> {
447
+ const value: FusionCommittedResultV1 = {
448
+ schema_version: FUSION_COMMITTED_RESULT_SCHEMA_VERSION,
449
+ run_id: this.runId,
450
+ merged,
451
+ details,
452
+ };
453
+ return this.writeArtifact('result.json', `${canonicalJson(value)}\n`);
408
454
  }
409
455
 
410
456
  async writeError(state: Exclude<FusionTerminalState, 'completed'>, error: string): Promise<void> {
@@ -466,20 +512,26 @@ export class FusionArtifactStore {
466
512
  violation: FusionCalibrationViolation;
467
513
  }): Promise<FusionArtifactRef> {
468
514
  const prefix = attemptPrefix(input.stage, input.slot, input.attempt);
469
- return this.writeArtifact(calibrationViolationName(prefix), `${canonicalJson(input.violation)}\n`);
515
+ return this.writeArtifact(
516
+ calibrationViolationName(prefix),
517
+ `${canonicalJson(input.violation)}\n`,
518
+ );
470
519
  }
471
520
 
472
521
  async recordValidationCandidateContractEvent(
473
522
  input: RecordValidationCandidateContractEventInput,
474
523
  ): Promise<FusionArtifactRef> {
475
524
  const name = `candidate-${String(input.slot)}.output-contract-${input.status}.json`;
476
- return this.writeArtifact(name, `${canonicalJson({
477
- schema_version: FUSION_VALIDATE_CANDIDATE_CONTRACT_EVENT_SCHEMA_VERSION,
478
- ...input.detail,
479
- candidate_id: input.candidateId,
480
- slot: input.slot,
481
- status: input.status,
482
- })}\n`);
525
+ return this.writeArtifact(
526
+ name,
527
+ `${canonicalJson({
528
+ schema_version: FUSION_VALIDATE_CANDIDATE_CONTRACT_EVENT_SCHEMA_VERSION,
529
+ ...input.detail,
530
+ candidate_id: input.candidateId,
531
+ slot: input.slot,
532
+ status: input.status,
533
+ })}\n`,
534
+ );
483
535
  }
484
536
 
485
537
  async recordFailedAttempt(input: RecordFusionFailedAttemptInput): Promise<void> {