pi-background-tasks 0.9.0 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BACKGROUND-TASKS-INSTRUCTIONS.md +63 -0
- package/PUBLISHING.md +43 -29
- package/README.md +233 -441
- package/TESTING.md +15 -9
- package/TEST_PLAN.md +43 -17
- package/docs/INDEX.md +157 -0
- package/docs/api/eventbus-v1.md +166 -0
- package/docs/assets/architecture.svg +78 -0
- package/docs/assets/footer-dock.svg +47 -0
- package/docs/assets/logo.svg +49 -0
- package/docs/attestations.json +189 -0
- package/docs/choose-a-workflow.md +98 -0
- package/docs/commands/bg-clear.md +70 -0
- package/docs/commands/bg-update.md +82 -0
- package/docs/commands/bg.md +90 -0
- package/docs/commands/fusion-models.md +70 -0
- package/docs/commands/fusion.md +69 -0
- package/docs/commands/jobs.md +74 -0
- package/docs/commands/kill.md +82 -0
- package/docs/commands/logs.md +90 -0
- package/docs/commands/task-manager.md +109 -0
- package/docs/concepts/completion-delivery.md +66 -0
- package/docs/concepts/context-projection-and-budgeting.md +79 -0
- package/docs/getting-started.md +122 -0
- package/docs/manifest.json +1825 -0
- package/docs/operations/configuration.md +110 -0
- package/docs/operations/releasing.md +67 -0
- package/docs/operations/testing.md +101 -0
- package/docs/operations/troubleshooting.md +38 -0
- package/docs/read-before-edit.md +94 -0
- package/docs/reference/runtime-contracts.md +213 -0
- package/docs/reference/shortcuts-and-dock.md +70 -0
- package/docs/subsystems/attested-pi-runs.md +141 -0
- package/docs/subsystems/background-task-runtime.md +85 -0
- package/docs/subsystems/child-launch-durability-and-safety.md +57 -0
- package/docs/subsystems/delegation.md +190 -0
- package/docs/subsystems/docs-freshness-gate.md +26 -0
- package/docs/subsystems/fusion.md +121 -0
- package/docs/subsystems/host-ui-and-telemetry.md +83 -0
- package/docs/tools/bg_delegate.md +193 -0
- package/docs/tools/bg_kill.md +114 -0
- package/docs/tools/bg_logs.md +133 -0
- package/docs/tools/bg_result.md +120 -0
- package/docs/tools/bg_run.md +168 -0
- package/docs/tools/bg_run_pi_attested.md +170 -0
- package/docs/tools/bg_status.md +111 -0
- package/docs/tools/fusion_investigate.md +116 -0
- package/docs/tools/fusion_reason.md +75 -0
- package/docs/tools/fusion_research.md +162 -0
- package/docs/tools/fusion_validate.md +206 -0
- package/logo.png +0 -0
- package/package.json +25 -7
- package/src/core/delegate/budget.ts +1 -1
- package/src/core/delegate/launch.ts +5 -0
- package/src/core/fusion/artifacts.ts +34 -4
- package/src/core/fusion/budget.ts +112 -20
- package/src/core/fusion/child-protocol.ts +82 -0
- package/src/core/fusion/clean-context.ts +91 -0
- package/src/core/fusion/config.ts +124 -35
- package/src/core/fusion/context.ts +29 -7
- package/src/core/fusion/evaluation.ts +392 -15
- package/src/core/fusion/orchestrator.ts +217 -23
- package/src/core/fusion/pi-child.ts +183 -23
- package/src/core/fusion/prompts.ts +39 -26
- package/src/core/fusion/source-policy.ts +257 -0
- package/src/core/fusion/types.ts +156 -11
- package/src/core/fusion/web-fetch.ts +104 -15
- package/src/core/fusion/workflows.ts +119 -65
- package/src/extension.ts +3 -3
- package/src/fusion-child-extension.ts +159 -120
- package/src/fusion-extension.ts +585 -240
- package/src/testing/normalize.ts +0 -22
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
doc_id: reference/shortcuts-and-dock
|
|
3
|
+
audience: user
|
|
4
|
+
mode: mixed
|
|
5
|
+
review_policy: contract
|
|
6
|
+
stability: stable
|
|
7
|
+
covers_surfaces: ['shortcut:ctrl+alt+c', 'shortcut:shift+down']
|
|
8
|
+
covers_sources: []
|
|
9
|
+
---
|
|
10
|
+
# Shortcuts and dock reference
|
|
11
|
+
|
|
12
|
+
<!-- pi-docs:begin name="shortcut-contracts" generator="scripts/docs/generate.mjs" -->
|
|
13
|
+
| Shortcut | Description | Provenance |
|
|
14
|
+
| --- | --- | --- |
|
|
15
|
+
| `ctrl+alt+c` | Clear finished background task footer notices (terminal-dependent fallback for /bg-clear) | `src/extension.ts:586` |
|
|
16
|
+
| `shift+down` | Open focused background task footer dock | `src/extension.ts:579` |
|
|
17
|
+
<!-- pi-docs:end name="shortcut-contracts" -->
|
|
18
|
+
|
|
19
|
+
## Registered shortcuts
|
|
20
|
+
|
|
21
|
+
| Shortcut | Behavior |
|
|
22
|
+
|---|---|
|
|
23
|
+
| `Shift+Down` | Open the focused background task footer dock / task manager. |
|
|
24
|
+
| `Ctrl+Alt+C` | Clear finished background task footer notices; this is an optional terminal-dependent fallback for [`/bg-clear`](../commands/bg-clear.md). |
|
|
25
|
+
|
|
26
|
+
If a terminal does not deliver `Ctrl+Alt+C`, use `/bg-clear`. It is the canonical command path.
|
|
27
|
+
|
|
28
|
+
## Footer states
|
|
29
|
+
|
|
30
|
+
The footer appears when there are running tasks, unseen finished tasks, or an update segment. Count labels are:
|
|
31
|
+
|
|
32
|
+
- `running` for active tasks;
|
|
33
|
+
- `failed` for status `failed`;
|
|
34
|
+
- `stopped` for status `killed`;
|
|
35
|
+
- `done` for status `completed`.
|
|
36
|
+
|
|
37
|
+
Examples:
|
|
38
|
+
|
|
39
|
+
```text
|
|
40
|
+
bg 1 running · Shift↓
|
|
41
|
+
bg 1 done · Shift↓ · /bg-clear
|
|
42
|
+
bg 1 running · 1 failed · 1 stopped · 1 done · Shift↓ · /bg-clear
|
|
43
|
+
bg 1 running · Shift↓ · ⬆ v999.0.0 /bg-update
|
|
44
|
+
bg 1 running · focused
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The `/bg-clear` hint is hidden while the dock is open, where the entry hint becomes `focused`. A finished task's badge is marked seen when its detail view opens; `/bg-clear` or `Ctrl+Alt+C` marks all currently unseen finished tasks seen. Merely opening the list view or closing the dock does not clear badges.
|
|
48
|
+
|
|
49
|
+
## Dock entry points
|
|
50
|
+
|
|
51
|
+
- `Shift+Down`
|
|
52
|
+
- [`/tasks`](../commands/task-manager.md)
|
|
53
|
+
- [`/bg-tasks`](../commands/task-manager.md)
|
|
54
|
+
|
|
55
|
+
All open the same task manager when an interactive UI is available.
|
|
56
|
+
|
|
57
|
+
## Dock output detail
|
|
58
|
+
|
|
59
|
+
The detail view follows a UI-only 128 KiB tail buffer, refreshes once per second while following, and shows 12 output lines. Scrolling up pauses following; reaching the bottom or pressing `r` resumes it.
|
|
60
|
+
|
|
61
|
+
## Related docs
|
|
62
|
+
|
|
63
|
+
- [`/bg-clear`](../commands/bg-clear.md)
|
|
64
|
+
- [`/tasks` and `/bg-tasks`](../commands/task-manager.md)
|
|
65
|
+
- [`/bg-update`](../commands/bg-update.md)
|
|
66
|
+
- [Host UI and telemetry](../subsystems/host-ui-and-telemetry.md)
|
|
67
|
+
|
|
68
|
+
## Source ownership/reference
|
|
69
|
+
|
|
70
|
+
Shortcut and footer implementation is owned by [host-ui-and-telemetry](../subsystems/host-ui-and-telemetry.md).
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
---
|
|
2
|
+
doc_id: subsystems/attested-pi-runs
|
|
3
|
+
audience: maintainer
|
|
4
|
+
mode: authored
|
|
5
|
+
review_policy: behavioral
|
|
6
|
+
stability: evolving
|
|
7
|
+
covers_surfaces: []
|
|
8
|
+
covers_sources: [src/core/attested-pi-run.ts]
|
|
9
|
+
---
|
|
10
|
+
# Attested Pi runs subsystem
|
|
11
|
+
|
|
12
|
+
This document is the primary behavioral owner for `src/core/attested-pi-run.ts`.
|
|
13
|
+
|
|
14
|
+
It does **not** claim ownership of shared registry, Pi-launch, common task, or durable-fs modules. Those modules spawn the prepared request, store task metadata, publish terminal state, and provide atomic write primitives used by this subsystem.
|
|
15
|
+
|
|
16
|
+
## Purpose
|
|
17
|
+
|
|
18
|
+
Attested Pi runs are opt-in local-evidence tasks for a structured child Pi invocation. They are intended for cases where an operator wants local hashes and observed route/auth/session facts, not just a background output file.
|
|
19
|
+
|
|
20
|
+
They do not replace ordinary `bg_run`, and they do not provide remote cryptographic proof.
|
|
21
|
+
|
|
22
|
+
## Structured request and argv
|
|
23
|
+
|
|
24
|
+
The request shape is:
|
|
25
|
+
|
|
26
|
+
- `name`
|
|
27
|
+
- `provider`
|
|
28
|
+
- `model`
|
|
29
|
+
- `prompt`
|
|
30
|
+
- `reportPath`
|
|
31
|
+
- optional `extraPiArgs`
|
|
32
|
+
- optional `thinking`
|
|
33
|
+
- optional `timeoutSeconds`
|
|
34
|
+
|
|
35
|
+
Validation rejects blank `name`, `provider`, `model`, `reportPath`, and empty `prompt`. `reportPath` must resolve inside task cwd and may not target `.git` or `.pi/tasks`.
|
|
36
|
+
|
|
37
|
+
The logical argv always begins:
|
|
38
|
+
|
|
39
|
+
```text
|
|
40
|
+
pi --mode json --provider <provider> --model <model>
|
|
41
|
+
```
|
|
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`).
|
|
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.
|
|
46
|
+
|
|
47
|
+
## Auth and environment boundary
|
|
48
|
+
|
|
49
|
+
The child environment removes direct/metered API configuration:
|
|
50
|
+
|
|
51
|
+
- `OPENROUTER_API_KEY`, `OPENROUTER_BASE_URL`
|
|
52
|
+
- `OPENAI_API_KEY`, `OPENAI_BASE_URL`
|
|
53
|
+
- `ANTHROPIC_API_KEY`, `ANTHROPIC_BASE_URL`
|
|
54
|
+
- `PI_API_KEY`, `PI_API_BASE_URL`, `PI_AUTH_FILE`
|
|
55
|
+
|
|
56
|
+
The producer observes auth through the model registry before spawn. The selected model must exist and `ModelRegistry.isUsingOAuth(selected)` must be available and true. Current accepted provider classes are:
|
|
57
|
+
|
|
58
|
+
- `openai-codex` → `pi-codex-oauth`, `subscription-codex`;
|
|
59
|
+
- `anthropic` → `pi-anthropic-oauth`, `subscription-anthropic`.
|
|
60
|
+
|
|
61
|
+
Any unsupported provider or non-OAuth route fails before attestation.
|
|
62
|
+
|
|
63
|
+
## Event parsing and observed route
|
|
64
|
+
|
|
65
|
+
Raw child stdout is captured as Pi JSON events. A successful stream must be newline-terminated and contain:
|
|
66
|
+
|
|
67
|
+
- exactly one `session` with id/cwd;
|
|
68
|
+
- exactly one `agent_start`;
|
|
69
|
+
- at least one assistant `message_end`;
|
|
70
|
+
- exactly one `agent_end`;
|
|
71
|
+
- the last assistant `stopReason` value reported in the stream is `stop`; messages that omit `stopReason` do not replace an earlier reported value;
|
|
72
|
+
- stable assistant provider/model across assistant messages;
|
|
73
|
+
- no assistant error.
|
|
74
|
+
|
|
75
|
+
The parsed provider/model must match the model selected from the registry. Token usage, cost total when present, tool usage, and a human transcript are derived from these events.
|
|
76
|
+
|
|
77
|
+
## Git authority
|
|
78
|
+
|
|
79
|
+
Before spawn, the subsystem records:
|
|
80
|
+
|
|
81
|
+
- git `HEAD` commit;
|
|
82
|
+
- git `HEAD^{tree}`;
|
|
83
|
+
- `git status --porcelain=v1 --untracked-files=all` cleanliness;
|
|
84
|
+
- repository root realpath;
|
|
85
|
+
- cwd realpath.
|
|
86
|
+
|
|
87
|
+
The worktree must be clean at start. On successful child completion, finish commit/tree/cleanliness are checked again. Changed commit/tree or dirty worktree prevents attestation.
|
|
88
|
+
|
|
89
|
+
## Files and hashes
|
|
90
|
+
|
|
91
|
+
Runtime paths are allocated by the background registry under `.pi/tasks/<session-id>-<pid>/`:
|
|
92
|
+
|
|
93
|
+
- `<task-id>.output`
|
|
94
|
+
- `<task-id>.json`
|
|
95
|
+
- `<task-id>.pi-events.jsonl`
|
|
96
|
+
- `<task-id>.stderr`
|
|
97
|
+
- `<task-id>.pi-telemetry-wrapper.cjs`
|
|
98
|
+
- `<task-id>.attestation.json`
|
|
99
|
+
|
|
100
|
+
The wrapper file is a note that the task is direct-spawned and no shell telemetry wrapper is used. The sidecar hashes metadata, output, events, stderr, wrapper, prompt bytes, and the required report file.
|
|
101
|
+
|
|
102
|
+
The sidecar schema is `phase2.pi_task_attestation.v1`. It includes locator, source hashes, lifecycle, invocation/auth facts, git authority, artifact hashes, and a self hash (`attestation_sha256`) over canonical sidecar content excluding that self field.
|
|
103
|
+
|
|
104
|
+
## Durability and visibility
|
|
105
|
+
|
|
106
|
+
Initial output/events/stderr/wrapper files and metadata are created before spawn. On close, stdout/stderr buffers are fsynced to the events/stderr files. Successful event parsing rewrites task output from the parsed transcript; failures write diagnostics/stderr output.
|
|
107
|
+
|
|
108
|
+
For a completed child with parsed events, the registry asks this subsystem to build the attestation, then writes `<task-id>.attestation.json` using durable atomic replacement. Only after that write returns does the registry set in-memory `task.status` to `completed` and publish terminal state. The metadata file may receive a completed snapshot earlier in this path; terminal in-memory/UI visibility is held until after sidecar durability.
|
|
109
|
+
|
|
110
|
+
## Failure and no-partial-sidecar behavior
|
|
111
|
+
|
|
112
|
+
No successful sidecar is emitted when:
|
|
113
|
+
|
|
114
|
+
- request validation fails;
|
|
115
|
+
- route/auth observation fails;
|
|
116
|
+
- worktree is dirty at start;
|
|
117
|
+
- child spawn fails;
|
|
118
|
+
- child exits non-zero, times out, or is killed;
|
|
119
|
+
- stdout events are malformed, incomplete, not newline-terminated, route-drifted, or the last reported assistant stop reason is not `stop`;
|
|
120
|
+
- the expected report file is missing/unreadable;
|
|
121
|
+
- git authority changes or finish worktree is dirty;
|
|
122
|
+
- attestation construction or durable sidecar write fails.
|
|
123
|
+
|
|
124
|
+
Raw evidence files remain for diagnosis. Sidecar final-name writes use atomic replacement, so ordinary write failures do not create a truncated final sidecar that looks complete. If sidecar construction/write fails after earlier metadata work, the task is marked failed.
|
|
125
|
+
|
|
126
|
+
## Trust boundary
|
|
127
|
+
|
|
128
|
+
The sidecar is unsigned local evidence in a same-user-writable working tree/task store. It states what this extension observed and hashed locally. It is not a signature, not tamper-proof, not a remote cryptographic proof, and not evidence against an attacker who can modify the repository or `.pi/tasks` artifacts.
|
|
129
|
+
|
|
130
|
+
## Maintainer checklist
|
|
131
|
+
|
|
132
|
+
When changing this subsystem, re-check:
|
|
133
|
+
|
|
134
|
+
- forbidden auth/mode/provider/model/thinking args;
|
|
135
|
+
- OAuth-only observation and provider class mapping;
|
|
136
|
+
- stripped metered environment keys;
|
|
137
|
+
- JSON event strictness and route consistency;
|
|
138
|
+
- git clean/commit/tree checks at start and finish;
|
|
139
|
+
- prompt/report/events/stderr/output/wrapper/metadata hash coverage;
|
|
140
|
+
- sidecar write ordering relative to terminal visibility;
|
|
141
|
+
- no sidecar on failure paths.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
---
|
|
2
|
+
doc_id: subsystems/background-task-runtime
|
|
3
|
+
audience: maintainer
|
|
4
|
+
mode: authored
|
|
5
|
+
review_policy: behavioral
|
|
6
|
+
stability: stable
|
|
7
|
+
covers_surfaces: []
|
|
8
|
+
covers_sources: [src/core/common.ts, src/core/registry.ts, src/core/windows-taskkill.ts]
|
|
9
|
+
---
|
|
10
|
+
# Background task runtime
|
|
11
|
+
|
|
12
|
+
The runtime owns task identity, shell invocation, process lifecycle, bounded logs, metadata, telemetry ingestion, completion publication, and platform termination.
|
|
13
|
+
|
|
14
|
+
## Core contracts
|
|
15
|
+
|
|
16
|
+
- Task statuses are exactly `running`, `completed`, `failed`, and `killed`.
|
|
17
|
+
- Terminal statuses are exactly `completed`, `failed`, and `killed`.
|
|
18
|
+
- Runtime directory: `.pi/tasks/<session-id>-<pid>/` under the project cwd.
|
|
19
|
+
- Per task: `<task-id>.output` and `<task-id>.json`; some agent modes may add wrapper or attestation files.
|
|
20
|
+
- In-memory recent retention prunes oldest finished tasks over the limit while preserving running tasks.
|
|
21
|
+
- `resolveTask` accepts exact ids or unambiguous prefixes and fails loudly for empty, unknown, or ambiguous ids.
|
|
22
|
+
|
|
23
|
+
## Starting ordinary tasks
|
|
24
|
+
|
|
25
|
+
`startTask` trims surrounding command whitespace and rejects an empty command. It derives task name from explicit `name`, then `description`, then command. Names are compacted and truncated; callers should still provide concise names.
|
|
26
|
+
|
|
27
|
+
Shell commands are spawned in the task cwd using `stdio: ['ignore','pipe','pipe']`, `windowsHide:true`, the extension environment, and detached process groups on non-Windows. Shell commands are **not sandboxed**.
|
|
28
|
+
|
|
29
|
+
Default delivery at registry level is `notifyOnCompletion:true` and `triggerOnCompletion:false`; surface tools may override that. [`bg_run`](../tools/bg_run.md) explicitly defaults both to true.
|
|
30
|
+
|
|
31
|
+
## Shell policy
|
|
32
|
+
|
|
33
|
+
POSIX uses `$SHELL` when set, otherwise `/bin/sh`, with `-c <command>`.
|
|
34
|
+
|
|
35
|
+
Windows defaults to `cmd.exe` or `ComSpec`, with args `['/d','/s','/c','"<command>"']` and `windowsVerbatimArguments:true`. `PI_BG_SHELL=cmd|bash` can select a shell; `PI_BG_SHELL_PATH` is accepted only with `PI_BG_SHELL` and must be an absolute `.exe`/`.com` path. `PI_BG_SHELL=bash` without a path searches PATH for `bash.exe` or `bash.com`; unresolved or invalid shell settings fail before creating a task.
|
|
36
|
+
|
|
37
|
+
## Logs and output caps
|
|
38
|
+
|
|
39
|
+
All child stdout/stderr is written to the output file unless it is recognized control telemetry from a wrapped Pi agent. The runtime enforces `PI_BG_MAX_OUTPUT_BYTES` or the default 20 MiB output cap; exceeding it appends an error notice, kills the task, and finalizes as `failed`.
|
|
40
|
+
|
|
41
|
+
Model-visible log reads use bounded file reads capped by `MAX_LOG_BYTES` (currently up to 50 KiB). Truncated reads preserve the full output path in the notice.
|
|
42
|
+
|
|
43
|
+
## Telemetry
|
|
44
|
+
|
|
45
|
+
Telemetry is task-owned. It is parsed from task output/control lines when the task reports it; it is never copied from the parent session. Optional telemetry includes context usage, token usage, tool usage, and model. Malformed optional telemetry is ignored without clearing prior task state; unknown wrapped-agent JSON is written to the transcript rather than silently dropped.
|
|
46
|
+
|
|
47
|
+
`isAgent` explicitly controls telemetry wrapping. If `isAgent:false`, a `pi -p` command is treated as an ordinary command. If `isAgent:true` and the POSIX command contains an interceptable `pi -p`, `pi --print`, or `pi --mode json` invocation, the runtime writes a wrapper and converts Pi JSON events into task-owned metrics and human transcript lines. Path-qualified `pi` commands are not intercepted. On Windows cmd, telemetry wrapping is unavailable and the task records `win32-cmd-cannot-safely-intercept-pi-argv`.
|
|
48
|
+
|
|
49
|
+
## Finalization and completion
|
|
50
|
+
|
|
51
|
+
A child closing with code `0` becomes `completed` unless killed/timeout/cap state overrides it. Nonzero exit becomes `failed` with `Exited with code ...`. User or shutdown kills become `killed`; timeout and output cap become `failed`.
|
|
52
|
+
|
|
53
|
+
During finalization, the runtime flushes wrapped-agent output, ends and waits for the output stream to finish/close, writes terminal metadata through the durable metadata path, updates waiters, initiates terminal EventBus publication, sends the completion notification when enabled and not shutting down, persists notification state, then prunes old finished tasks. Actual EventBus emission may wait behind the run-response publication gate and therefore may occur after the completion notification; it still occurs only after stream close and terminal metadata. The registry calls a historically named `closeAndFsyncOutputStream()` helper, but its current implementation ends and observes the stream rather than issuing `fsync` for ordinary `.output`; durable terminal truth refers to the metadata-backed status, not a stronger crash-durability guarantee for every output byte.
|
|
54
|
+
|
|
55
|
+
## Stopping tasks
|
|
56
|
+
|
|
57
|
+
Only `running` tasks can be stopped.
|
|
58
|
+
|
|
59
|
+
POSIX stop path:
|
|
60
|
+
|
|
61
|
+
1. send `SIGTERM` to the detached process group (`-pid`),
|
|
62
|
+
2. if that fails, call the child handle's `kill`,
|
|
63
|
+
3. after the grace window, send one `SIGKILL` escalation.
|
|
64
|
+
|
|
65
|
+
Windows stop path:
|
|
66
|
+
|
|
67
|
+
1. run `%SystemRoot%\System32\taskkill.exe /PID <pid> /T`, or `%WINDIR%` fallback,
|
|
68
|
+
2. after the grace window, abort the soft helper and run `/F`,
|
|
69
|
+
3. treat taskkill exit 128 as an already-exited race,
|
|
70
|
+
4. surface force failures loudly with `Descendant processes may have leaked`.
|
|
71
|
+
|
|
72
|
+
Windows never falls back to root-only `child.kill` for tree termination. The taskkill helper uses structured argv, `shell:false`, bounded stdout/stderr capture, external abort, and a helper timeout.
|
|
73
|
+
|
|
74
|
+
## Related docs
|
|
75
|
+
|
|
76
|
+
- [`bg_run`](../tools/bg_run.md)
|
|
77
|
+
- [`bg_status`](../tools/bg_status.md)
|
|
78
|
+
- [`bg_logs`](../tools/bg_logs.md)
|
|
79
|
+
- [`bg_kill`](../tools/bg_kill.md)
|
|
80
|
+
- [Completion delivery](../concepts/completion-delivery.md)
|
|
81
|
+
- [Host UI and telemetry](host-ui-and-telemetry.md)
|
|
82
|
+
|
|
83
|
+
## Source ownership/reference
|
|
84
|
+
|
|
85
|
+
Primary source ownership for this document is `src/core/common.ts`, `src/core/registry.ts`, and `src/core/windows-taskkill.ts`.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
doc_id: subsystems/child-launch-durability-and-safety
|
|
3
|
+
audience: maintainer
|
|
4
|
+
mode: authored
|
|
5
|
+
review_policy: behavioral
|
|
6
|
+
stability: evolving
|
|
7
|
+
covers_surfaces: []
|
|
8
|
+
covers_sources: [src/core/durable-fs.ts, src/core/pi-launch.ts]
|
|
9
|
+
---
|
|
10
|
+
# Child launch, durability, and safety
|
|
11
|
+
|
|
12
|
+
Primary sources: `src/core/pi-launch.ts` and `src/core/durable-fs.ts`.
|
|
13
|
+
|
|
14
|
+
## Pi launch resolution
|
|
15
|
+
|
|
16
|
+
On non-Windows platforms, `resolvePiLaunch()` returns `{ executable: 'pi', argvPrefix: [], kind: 'path' }`.
|
|
17
|
+
|
|
18
|
+
On Windows, the package does not trust shell PATH shims. It resolves `@earendil-works/pi-coding-agent/package.json`, reads `bin.pi`, realpaths the package root and bin target, verifies the target stays inside the package root, and accepts only a regular file with one of these forms:
|
|
19
|
+
|
|
20
|
+
- `.js`, `.cjs`, `.mjs`: launch with `process.execPath` and the target as `argvPrefix[0]`;
|
|
21
|
+
- `.exe`, `.com`: launch the target directly.
|
|
22
|
+
|
|
23
|
+
Resolution failures throw `PiLaunchResolutionError` with code `pi_executable_resolution_failed`; no substitute route or shell fallback is selected.
|
|
24
|
+
|
|
25
|
+
## Windows argv and command-line length
|
|
26
|
+
|
|
27
|
+
`assertWindowsCommandLineWithinLimit()` renders the exact Windows command line with Windows quoting rules, measures UTF-16 length plus the terminating NUL, and throws `PiCommandLineLimitError` (`pi_command_line_too_long`) if it exceeds 32,767 characters. The check is used before child launches that construct `pi` argv.
|
|
28
|
+
|
|
29
|
+
Delegate seed bytes are delivered over stdin, not argv, so large seeds do not rely on command-line quoting or shell length limits.
|
|
30
|
+
|
|
31
|
+
## Durable write invariant
|
|
32
|
+
|
|
33
|
+
`durable-fs.ts` provides two public operations:
|
|
34
|
+
|
|
35
|
+
- `writeFileDurable(path, data)`: open the target once with `w`, write, `sync()`, close.
|
|
36
|
+
- `replaceFileDurable(path, data)`: create a task-owned temp file with exclusive `wx` at `0o600`, write, `sync()`, close, rename over the target, then directory-sync on non-Windows.
|
|
37
|
+
|
|
38
|
+
Invariant: a pathname is never reopened merely to fsync it. Sync failures are fatal and surfaced as `DurableFileError`; cleanup failures are retained in the error object instead of hiding the primary failure.
|
|
39
|
+
|
|
40
|
+
Temp ownership matters: if exclusive temp creation collides, the caller does not delete the other writer's file. A successful rename is the commit point; if a post-rename directory sync fails, the error marks `renameCompleted: true` because the replacement may already be visible.
|
|
41
|
+
|
|
42
|
+
## POSIX directory sync limitation
|
|
43
|
+
|
|
44
|
+
After atomic replace, POSIX-like platforms open and sync the parent directory to durably record the rename. Windows skips directory sync because Node/Windows directory fsync is not portable in the same way. This is an explicit platform limitation, not a silent success claim; file contents are still written and synced before rename.
|
|
45
|
+
|
|
46
|
+
## Process trust boundaries
|
|
47
|
+
|
|
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.
|
|
52
|
+
|
|
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
|
+
|
|
55
|
+
## Terminal integrity
|
|
56
|
+
|
|
57
|
+
Task terminal status is not published until output streams are ended and observed finished/closed and terminal metadata is written. Ordinary task `.output` streams are not explicitly fsynced; attested event/stderr buffers and atomic metadata/artifact paths use the durable helpers described above. If stream close or terminal metadata fails, the task is marked failed; terminal truth is not guessed from the process exit alone.
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
---
|
|
2
|
+
doc_id: subsystems/delegation
|
|
3
|
+
audience: maintainer
|
|
4
|
+
mode: authored
|
|
5
|
+
review_policy: behavioral
|
|
6
|
+
stability: evolving
|
|
7
|
+
covers_surfaces: []
|
|
8
|
+
covers_sources: [extensions/delegate-child.ts, src/core/delegate/artifacts.ts, src/core/delegate/budget.ts, src/core/delegate/hook-contract-evidence.json, src/core/delegate/hook-contract.ts, src/core/delegate/launch.ts, src/core/delegate/result-package.ts, src/core/delegate/runner.ts, src/core/delegate/seed.ts, src/core/delegate/types.ts, src/delegate-child-extension.ts, src/delegate-extension.ts]
|
|
9
|
+
---
|
|
10
|
+
# Delegation subsystem
|
|
11
|
+
|
|
12
|
+
This document is the primary behavioral owner for delegation runtime code:
|
|
13
|
+
|
|
14
|
+
- `src/delegate-extension.ts`
|
|
15
|
+
- `src/delegate-child-extension.ts`
|
|
16
|
+
- `extensions/delegate-child.ts`
|
|
17
|
+
- every current file under `src/core/delegate/**`, including `hook-contract-evidence.json`
|
|
18
|
+
|
|
19
|
+
It does **not** claim ownership of shared `common`, `registry`, `pi-launch`, or `durable-fs`; delegation consumes those integration points.
|
|
20
|
+
|
|
21
|
+
## Behavioral contract
|
|
22
|
+
|
|
23
|
+
Delegation provides one background child Pi agent, one directive, one pinned route, and read-only inspection tools. The parent gets a launch receipt immediately and later retrieves a verified answer through `bg_result`.
|
|
24
|
+
|
|
25
|
+
The design deliberately separates:
|
|
26
|
+
|
|
27
|
+
- launch admission (no side effects on refusal),
|
|
28
|
+
- child isolation and runtime guards,
|
|
29
|
+
- child answer commit (`result.json`),
|
|
30
|
+
- parent adjudication (`outcome.json`),
|
|
31
|
+
- user retrieval (`bg_result`).
|
|
32
|
+
|
|
33
|
+
## Seed and context policy
|
|
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.
|
|
36
|
+
|
|
37
|
+
Projection behavior:
|
|
38
|
+
|
|
39
|
+
| Source content | Delegate seed behavior |
|
|
40
|
+
|---|---|
|
|
41
|
+
| user text | included verbatim |
|
|
42
|
+
| assistant text | included verbatim |
|
|
43
|
+
| user images | marker text only |
|
|
44
|
+
| assistant thinking | omitted; ledger row with bytes/hash/count |
|
|
45
|
+
| tool-call arguments | omitted; ledger row with bytes/hash/tool name/call id |
|
|
46
|
+
| tool-result text | omitted; ledger row with bytes/hash/tool name/call id |
|
|
47
|
+
| tool-result images | ledger-only omission with bytes/hash/mime |
|
|
48
|
+
| unknown blocks | projection failure; no child |
|
|
49
|
+
|
|
50
|
+
The assistant message containing the active `bg_delegate` call is excluded as a whole. Therefore sibling tool calls in the same assistant message are not visible to any child launched by that batch.
|
|
51
|
+
|
|
52
|
+
`directive.text` is stored exactly, hashed, and marked `authority: "explicit_text"`. The child prompt and system prompt state that the directive is authoritative and projected history is untrusted supporting context. Omitted parent tool output cannot be recovered by the child; the child is instructed to say so rather than guess.
|
|
53
|
+
|
|
54
|
+
## Launch and isolation
|
|
55
|
+
|
|
56
|
+
Public admission first loads hook evidence and resolves the requested/current route; launch preparation then resolves the package-owned child guard extension. Inside `preflightDelegateLaunch()`, the hook-contract gate runs before capability/tool policy, limit checks, seed construction, and launch budget admission. All of these checks complete before child process, child session directory, or artifact root creation. Route, guard-extension, hook-contract, or later admission refusal therefore leaves zero child processes and zero delegate artifacts; do not rely on one absolute error-precedence order across those pre-preflight resolutions.
|
|
57
|
+
|
|
58
|
+
The child launch:
|
|
59
|
+
|
|
60
|
+
- direct Pi spawn through the registry, not a shell;
|
|
61
|
+
- prompt bytes over stdin, not a positional/shell argument;
|
|
62
|
+
- separate random `--session-id`;
|
|
63
|
+
- task-owned `--session-dir` under the artifact directory;
|
|
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.
|
|
67
|
+
|
|
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.
|
|
69
|
+
|
|
70
|
+
## Route and budget
|
|
71
|
+
|
|
72
|
+
Routes are pinned once:
|
|
73
|
+
|
|
74
|
+
- omitted route → parent current model;
|
|
75
|
+
- explicit route → exact registry entry;
|
|
76
|
+
- unavailable/unknown-capacity routes fail;
|
|
77
|
+
- no substitution, fallback, or retry on a different route.
|
|
78
|
+
|
|
79
|
+
Budgets use a delegate-specific conservative estimator. Constants currently documented by source/tests:
|
|
80
|
+
|
|
81
|
+
- reserved output: `16,384` tokens;
|
|
82
|
+
- framing reserve: `8,192` tokens;
|
|
83
|
+
- safety reserve: `4,096` tokens;
|
|
84
|
+
- minimum usable input: `8,192` tokens;
|
|
85
|
+
- default turns/tools/timeout: `24` / `120` / `1200s`;
|
|
86
|
+
- 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;
|
|
89
|
+
- inline answer cap: `48 KiB`.
|
|
90
|
+
|
|
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.
|
|
92
|
+
|
|
93
|
+
## Child guard and commit discipline
|
|
94
|
+
|
|
95
|
+
The child verifies seed hash, task id, and launch nonce at extension load before the first model call. It then enforces:
|
|
96
|
+
|
|
97
|
+
- context budget before every provider call;
|
|
98
|
+
- per-result spill receipts before tool output enters the transcript;
|
|
99
|
+
- aggregate tool-output cap;
|
|
100
|
+
- turn and tool-call limits;
|
|
101
|
+
- route attestation for assistant messages;
|
|
102
|
+
- complete usage records only (missing/partial usage is `unavailable`, never zero);
|
|
103
|
+
- accepted final stop reason `stop` only, so provider `length` stops become `child_model_output_limit` rather than partial success;
|
|
104
|
+
- non-empty, non-whitespace answer text;
|
|
105
|
+
- well-formed UTF-8 answer blocks.
|
|
106
|
+
|
|
107
|
+
A terminal latch prevents later success commit after any degraded/refused condition. This avoids a hash-valid result built on silently modified context.
|
|
108
|
+
|
|
109
|
+
`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
|
+
|
|
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.
|
|
112
|
+
|
|
113
|
+
## Spill artifacts and `delegate_read_artifact`
|
|
114
|
+
|
|
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.
|
|
116
|
+
|
|
117
|
+
`delegate_read_artifact` requires:
|
|
118
|
+
|
|
119
|
+
- `artifact: string` relative to the delegate artifact root;
|
|
120
|
+
- `offset: non-negative safe integer`;
|
|
121
|
+
- `length: positive safe integer`.
|
|
122
|
+
|
|
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.
|
|
124
|
+
|
|
125
|
+
## Retrieval contract
|
|
126
|
+
|
|
127
|
+
`bg_result` verifies committed packages before returning bytes. It checks identity, seed hash, route and route attestations, schema, usage shape, strict base64, per-block hashes, aggregate hash, byte lengths, and UTF-8 round trip. Running tasks return a not-ready view (`state:"running"`, `delivery:"none"`) without blocking.
|
|
128
|
+
|
|
129
|
+
Default delivery inlines answers up to `48 KiB`; larger answers return artifact metadata. Explicit oversized inline requests fail with `result_too_large_for_inline`. Answers are never truncated.
|
|
130
|
+
|
|
131
|
+
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
|
+
|
|
133
|
+
## User-oriented failure taxonomy
|
|
134
|
+
|
|
135
|
+
Admission / no child:
|
|
136
|
+
|
|
137
|
+
- `delegate_hook_contract_unsupported`
|
|
138
|
+
- `delegate_isolation_unsupported`
|
|
139
|
+
- `route_unresolved`
|
|
140
|
+
- `route_capacity_unknown`
|
|
141
|
+
- `seed_projection_failed`
|
|
142
|
+
- `seed_budget_exceeded`
|
|
143
|
+
- `seed_persist_failed`
|
|
144
|
+
- `invalid_arguments`
|
|
145
|
+
|
|
146
|
+
Launch / execution:
|
|
147
|
+
|
|
148
|
+
- `child_spawn_failed`
|
|
149
|
+
- `child_startup_failed`
|
|
150
|
+
- `child_timeout`
|
|
151
|
+
- `child_cancelled`
|
|
152
|
+
- `child_turn_limit`
|
|
153
|
+
- `child_tool_call_limit`
|
|
154
|
+
- `child_exited_without_commit`
|
|
155
|
+
|
|
156
|
+
Budget / limits:
|
|
157
|
+
|
|
158
|
+
- `provider_context_budget_exhausted`
|
|
159
|
+
- `aggregate_tool_output_cap`
|
|
160
|
+
- `child_model_output_limit`
|
|
161
|
+
- `child_capture_limit`
|
|
162
|
+
|
|
163
|
+
Integrity / artifacts:
|
|
164
|
+
|
|
165
|
+
- `child_result_invalid`
|
|
166
|
+
- `child_result_encoding_invalid`
|
|
167
|
+
- `route_attestation_missing`
|
|
168
|
+
- `route_mismatch`
|
|
169
|
+
- `seed_hash_mismatch`
|
|
170
|
+
- `answer_hash_mismatch`
|
|
171
|
+
- `artifact_spill_failed`
|
|
172
|
+
- `artifact_read_failed`
|
|
173
|
+
- `artifact_error`
|
|
174
|
+
|
|
175
|
+
Retrieval:
|
|
176
|
+
|
|
177
|
+
- `result_not_ready`
|
|
178
|
+
- `result_unavailable`
|
|
179
|
+
- `result_too_large_for_inline`
|
|
180
|
+
- `task_unknown`
|
|
181
|
+
|
|
182
|
+
Each `DelegateError` renders code, message, child-created flag, artifact location when known, preserved evidence, and remediation.
|
|
183
|
+
|
|
184
|
+
## Hook-contract compatibility gate
|
|
185
|
+
|
|
186
|
+
The child guard relies on Pi hook behavior proven by `tests/scripted-provider/pi-hook-contract.test.ts`; shipped evidence is byte-identical to `src/core/delegate/hook-contract-evidence.json`.
|
|
187
|
+
|
|
188
|
+
Required guarantees include context hook ordering, returned context messages reaching the provider, abort blocking the provider request by handing an aborted signal, abort terminating the run, context throw isolation, tool-result replacement before transcript entry, replacement identity preservation, and extension load order.
|
|
189
|
+
|
|
190
|
+
Pi 0.83 evidence explicitly shows two guarantees are false and therefore not required: context throws do not block provider calls, and abort does not skip the provider call site. The guard is built fail-closed anyway: it aborts and returns a suppressed message set, so the original oversized content is not dispatched even if a provider ignored the aborted signal. Missing/malformed/unsupported evidence fails with `delegate_hook_contract_unsupported`; the guard is not weakened at runtime.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
doc_id: subsystems/docs-freshness-gate
|
|
3
|
+
audience: maintainer
|
|
4
|
+
mode: mixed
|
|
5
|
+
review_policy: contract
|
|
6
|
+
stability: stable
|
|
7
|
+
covers_surfaces: []
|
|
8
|
+
covers_sources: []
|
|
9
|
+
---
|
|
10
|
+
# Docs freshness gate
|
|
11
|
+
|
|
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.
|
|
13
|
+
|
|
14
|
+
<!-- pi-docs:begin name="docs-freshness-gate" generator="scripts/docs/generate.mjs" -->
|
|
15
|
+
- Canonical package version: `1.0.3`
|
|
16
|
+
- Governed markdown docs: 40
|
|
17
|
+
- Public surfaces extracted: 30
|
|
18
|
+
- Governed production sources: 44
|
|
19
|
+
- Tool contracts extracted: 11
|
|
20
|
+
- Schema IDs extracted: 37
|
|
21
|
+
- Environment variable references extracted: 46
|
|
22
|
+
- Behavioral attestation receipts not passing: 0
|
|
23
|
+
- Receipt store: `docs/attestations.json`
|
|
24
|
+
|
|
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.
|
|
26
|
+
<!-- pi-docs:end name="docs-freshness-gate" -->
|