pi-subagents 0.50.0 → 0.51.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +62 -0
- package/agents/gpt-pro.md +17 -0
- package/async-retention-discovery-worker.mjs +180 -0
- package/docs/agents.md +35 -2
- package/docs/configuration.md +30 -12
- package/docs/extension-api.md +42 -1
- package/docs/observability.md +4 -4
- package/docs/tool-reference.md +38 -39
- package/docs/workflows.md +169 -3
- package/package.json +4 -2
- package/skills/pi-subagents/SKILL.md +5 -4
- package/skills/pi-subagents/references/constraints-and-recipes.md +6 -4
- package/skills/pi-subagents/references/execution-controls.md +18 -15
- package/skills/pi-subagents/references/management-authoring-rpc.md +3 -3
- package/skills/pi-subagents/references/prompting-and-roles.md +2 -2
- package/src/agents/agent-management.ts +100 -345
- package/src/agents/agents.ts +125 -25
- package/src/api/external-job-provider.ts +185 -0
- package/src/api/preflight.ts +31 -6
- package/src/api/shared-types.ts +2 -0
- package/src/extension/config.ts +3 -3
- package/src/extension/doctor.ts +3 -6
- package/src/extension/fanout-child.ts +2 -2
- package/src/extension/index.ts +165 -88
- package/src/extension/public-execution.ts +28 -1
- package/src/extension/schemas.ts +12 -35
- package/src/extension/tool-description.ts +35 -24
- package/src/inspectors/herdr/actions.ts +2 -2
- package/src/inspectors/herdr/inspector-runner.ts +2 -1
- package/src/inspectors/herdr/project-panes.ts +2 -2
- package/src/intercom/native-supervisor-channel.ts +30 -9
- package/src/missions/lifecycle.ts +6 -1
- package/src/missions/store.ts +4 -9
- package/src/profiles/profiles.ts +3 -1
- package/src/runs/background/active-run-index.ts +31 -8
- package/src/runs/background/async-execution.ts +49 -31
- package/src/runs/background/async-job-tracker.ts +20 -4
- package/src/runs/background/async-resume.ts +28 -13
- package/src/runs/background/async-retention.ts +888 -0
- package/src/runs/background/async-status.ts +39 -53
- package/src/runs/background/chain-append.ts +3 -33
- package/src/runs/background/control-channel.ts +14 -68
- package/src/runs/background/index-segment.ts +59 -0
- package/src/runs/background/notify.ts +3 -1
- package/src/runs/background/result-files.ts +158 -90
- package/src/runs/background/result-watcher.ts +72 -20
- package/src/runs/background/retained-children.ts +13 -3
- package/src/runs/background/run-id-query.ts +7 -0
- package/src/runs/background/run-id-resolver.ts +11 -9
- package/src/runs/background/run-status.ts +27 -18
- package/src/runs/background/scheduled-runs.ts +22 -4
- package/src/runs/background/stale-run-reconciler.ts +6 -3
- package/src/runs/background/steering.ts +11 -1
- package/src/runs/background/subagent-runner.ts +250 -138
- package/src/runs/background/subagent-wait.ts +7 -7
- package/src/runs/background/terminal-run-index.ts +129 -0
- package/src/runs/background/wait-completions.ts +21 -4
- package/src/runs/background/wait-subscriptions.ts +80 -1
- package/src/runs/foreground/async-steering-action.ts +21 -14
- package/src/runs/foreground/execution.ts +2 -0
- package/src/runs/foreground/subagent-executor.ts +460 -1536
- package/src/runs/foreground/workflow-foreground-steering.ts +6 -5
- package/src/runs/shared/chain-outputs.ts +1 -3
- package/src/runs/shared/external-job-bridge.ts +450 -0
- package/src/runs/shared/external-job-runner.ts +286 -0
- package/src/runs/shared/mcp-direct-tool-allowlist.ts +14 -0
- package/src/runs/shared/model-fallback.ts +22 -5
- package/src/runs/shared/orca-progress-tabs.ts +84 -22
- package/src/runs/shared/parallel-handoff.ts +46 -4
- package/src/runs/shared/parallel-utils.ts +4 -15
- package/src/runs/shared/permissions.ts +5 -1
- package/src/runs/shared/pi-args.ts +8 -1
- package/src/runs/shared/subagent-control.ts +26 -4
- package/src/runs/shared/subagent-prompt-runtime.ts +12 -6
- package/src/runs/shared/workflow-graph.ts +1 -23
- package/src/runs/shared/worktree.ts +12 -1
- package/src/shared/atomic-json.ts +22 -2
- package/src/shared/capacity-resilient-json.ts +102 -0
- package/src/shared/completion-owner.ts +14 -0
- package/src/shared/file-system-retry.ts +49 -1
- package/src/shared/fork-context.ts +42 -0
- package/src/shared/prompt-resources.ts +0 -40
- package/src/shared/settings.ts +3 -27
- package/src/shared/types.ts +60 -26
- package/src/shared/utils.ts +8 -0
- package/src/shared/watch-strategy.ts +10 -0
- package/src/slash/slash-commands.ts +11 -3
- package/src/tui/fleet.ts +63 -15
- package/src/workflows/chat-progress.ts +1 -1
- package/src/workflows/scripted-workflow.ts +207 -65
- package/src/runs/foreground/chain-clarify.ts +0 -1354
- package/src/runs/foreground/chain-execution.ts +0 -1581
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,68 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.51.0] - 2026-08-18
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Add stable-key `runs.steer` to `workflowScript`, with routing for foreground and async children, structured receipts, trace entries, and checks for unawaited calls (#1186).
|
|
9
|
+
- Add `runner.type: external-job`, the exported provider bridge, the Surf GPT Pro `gpt-pro` profile, and docs for external advisor data boundaries (#1189).
|
|
10
|
+
- Add `defaultSubagentContext: "fork"` for launches that do not set an explicit context (#1161).
|
|
11
|
+
- Allow `defaultSubagentContext: "fresh"` to override agent fork defaults for launches that do not set an explicit context.
|
|
12
|
+
- Add `PI_SUBAGENT_FS_RETRY_MAX_TOTAL_MS` so hosts can cap filesystem retry waits. Unset by default. Thanks to [@MarcusNeufeldt](https://github.com/MarcusNeufeldt) for #1143.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
- Document rolling `workflowScript` fanout with `runs.run`, `Promise.race`, `runs.steer`, and `Promise.all` (#1187).
|
|
16
|
+
- Document scripted chaining as the supported workflow API, with migration examples for removed top-level chain and task inputs.
|
|
17
|
+
- Clarify `workflowScript` fanout guidance: use awaited `runs.all` for ordinary parallel work, and use stored `runs.run` promises only for fully observed advanced rolling fanout (#1229, #1230).
|
|
18
|
+
- Clarify that async workflows do not have inline `live-card` projection (#1229, #1230).
|
|
19
|
+
- Describe `async:false` as a blocking parent wait, not a UI or foreground-only mode.
|
|
20
|
+
- Clarify that subagent reviews and gates should stay async unless the parent must block until completion.
|
|
21
|
+
- Document that a host's session lifetime owns completion wakes, and how to key an idle check on live run state rather than parent activity. Thanks to [@MarcusNeufeldt](https://github.com/MarcusNeufeldt) for #1144.
|
|
22
|
+
- Register the default `subagent` tool prompt as split metadata with a short description, `promptSnippet`, and `promptGuidelines`, while keeping explicit `full`, `compact`, and `custom` description modes.
|
|
23
|
+
- Keep `worktree: true` workflow children on the single-child path while preserving managed patch handoffs.
|
|
24
|
+
|
|
25
|
+
### Removed
|
|
26
|
+
- Remove unused foreground chain and parallel execution and durable chain management surfaces.
|
|
27
|
+
- Remove legacy subagent tool compatibility fields for append-step control, schedule aliases, async recovery metadata, and string mission goals.
|
|
28
|
+
- Remove chain approval checkpoint steps and the `approve-checkpoint` / `reject-checkpoint` controls.
|
|
29
|
+
- Remove `prompts.render` from `workflowScript`; pass explicit task text to `runs.run` or use `/prompt-workflow` for reusable prompt templates.
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
- Avoid Darwin reload hangs by disabling idle native filesystem watchers and using demand-gated delivery for live results, supervisor messages, controls, and steering. Thanks to [@youlikemodernart](https://github.com/youlikemodernart) for #1220.
|
|
33
|
+
- Bound async result session, run, active-run, and result-index path segments so long provider IDs do not break launches or waits with `ENAMETOOLONG`. Thanks to [@hlstwizard](https://github.com/hlstwizard) for #1131 and [@zhouatie](https://github.com/zhouatie) for #1135.
|
|
34
|
+
- Hash result-index session segments that look like Windows paths or file names, keep reading previous URI-encoded keys, and treat `EPERM` and `EACCES` as empty scans. Thanks to [@apoapostolov](https://github.com/apoapostolov) for #1211.
|
|
35
|
+
- Sanitize foreground workflow output path segments derived from provider run IDs, so Windows launches do not fail when tool-call IDs contain path-invalid characters. Thanks to [@maxime-louward-shift](https://github.com/maxime-louward-shift) for #1235.
|
|
36
|
+
- Keep async status and result persistence retrying after temporary `ENOSPC`, quota, or file-descriptor exhaustion errors. Thanks to [@ahmadaccino](https://github.com/ahmadaccino) for #1227.
|
|
37
|
+
- Route async completion notifications and cleanup only to the parent Pi process that launched the run, so concurrent windows sharing one session file cannot consume each other's results. Thanks to [@wangjianming](https://github.com/wangjianming) for #1225.
|
|
38
|
+
- Keep extension reload cleanup scoped to the replaced session runtime, so concurrent Pi sessions in one process do not remove each other's subscriptions or parent-session identity. Thanks to [@ryanbbrown](https://github.com/ryanbbrown) for #1222.
|
|
39
|
+
- Stop failing child runs when an explicit allowlist names `contact_supervisor` without the legacy `intercom` companion. A lone `intercom` entry still requires a real external provider. Thanks to [@MingTeer](https://github.com/MingTeer) for #1207.
|
|
40
|
+
- Add explicit `isolation: "none"` for schema-driven workflows without Git worktree setup, while keeping strict `isolation: "worktree"` behavior. Thanks to [@tlsneo](https://github.com/tlsneo) for #1203.
|
|
41
|
+
- Fail closed when an existing external-job `status.json` is unreadable or malformed, including an invalid `steps` shape.
|
|
42
|
+
- Skip malformed agent definitions during discovery so valid agents still list and launch, while showing configuration errors in management diagnostics (#1200).
|
|
43
|
+
- Resolve `/subagents-generate-profiles` provider probes through the shared Pi executable resolver so configured and Windows-specific Pi commands work. Thanks to [@Wumpf](https://github.com/Wumpf) for #1199.
|
|
44
|
+
- Resolve the workflowScript parser from pi-subagents instead of the caller's working directory, so workflows start in projects that do not install Acorn. Thanks to [@xz-dev](https://github.com/xz-dev) for #1214, following up #1190.
|
|
45
|
+
- Keep workflowScript child-launch tracking working on Bun-built Pi without a hard dependency on V8 promise hooks. Thanks to [@rochecompaan](https://github.com/rochecompaan) for #1158 and [@rholak](https://github.com/rholak) for the version-window diagnosis.
|
|
46
|
+
- Treat provider subscription usage-limit errors as retryable model failures so `fallbackModels` can continue to the next configured model. Thanks to [@dwizzle204](https://github.com/dwizzle204) for #1215.
|
|
47
|
+
- Skip fallback models that are unavailable in the active registry, so shared agent configs still run where their primary model is available. Thanks to [@JPFrancoia](https://github.com/JPFrancoia) for #1147.
|
|
48
|
+
- Preserve workflow async session roots for Herdr inspectors so workflow runs open with the same trusted session-root context as standalone runs. Thanks to [@hank-warren](https://github.com/hank-warren) for #1219.
|
|
49
|
+
- Keep Herdr project and inspector panes in the background by default, and move focus only when callers set `focus: true`. The FleetView inspect key still focuses the pane it opens. Thanks to [@boggylp](https://github.com/boggylp) for #1226.
|
|
50
|
+
- Show FleetView transcript fallbacks for trusted session roots instead of warning about an untrusted session file. Thanks to [@aliceisjustplaying](https://github.com/aliceisjustplaying) for #1154.
|
|
51
|
+
- Route Fleet inspector steering for live in-process workflow children through their foreground routes instead of the detached async queue. Thanks to [@ViktorBarzin](https://github.com/ViktorBarzin) for #1218 and #1216.
|
|
52
|
+
- Serialize same-worktree Orca progress-tab creation so numbered tabs appear left to right in sequence. Thanks to [@hyein-cbio](https://github.com/hyein-cbio) for #1196.
|
|
53
|
+
- Bound repeated async-state queries to active, exact-id, and recent-terminal indexes instead of scanning the full async history (#1162).
|
|
54
|
+
- Move retention directory discovery to a read-only worker so full scans do not block the extension event loop (#1188).
|
|
55
|
+
- Reclaim proven-safe async run and orphan result state after 30 days in bounded, locked cleanup passes with rename-first tombstones (#1163).
|
|
56
|
+
- Sweep expired wait subscriptions armed by another session, so stale records stop accumulating in the subscriptions directory. Thanks to [@MarcusNeufeldt](https://github.com/MarcusNeufeldt) for #1142.
|
|
57
|
+
- Restore and list schedules after their project directory is deleted, and skip orphan schedule directories without letting create reuse stale state. Thanks to [@ELA718](https://github.com/ELA718) for #1171 and [@colinb4987](https://github.com/colinb4987) for #1167.
|
|
58
|
+
- Fall back from an implicit `defaultContext: fork` to `fresh` when the parent session file or current leaf is not available yet. Explicit `context: "fork"` remains fail-fast. Thanks to [@hyein-cbio](https://github.com/hyein-cbio) for #1137.
|
|
59
|
+
- Keep retained workflow children resumable when their managed worktree cwd is preserved in the handoff manifest (#1172).
|
|
60
|
+
- Preserve workflow child task output when neither the workflow nor child configures an output file (#1136).
|
|
61
|
+
- Preserve a child's file-only report when its output path also names the workflow summary output.
|
|
62
|
+
- Keep concurrent async result promotion from deleting a newer payload or another promoter's published result. Thanks to [@albertgwo](https://github.com/albertgwo) for #1130.
|
|
63
|
+
- Keep `mcp:<server>` direct tools available when pi-mcp-adapter cache identity includes a request-header command. Thanks to [@xz-dev](https://github.com/xz-dev) for #1141.
|
|
64
|
+
- Isolate test async state from the user temp root and write each missing-mission sync diagnostic only once (#1164, #1165).
|
|
65
|
+
- Keep structured delegation integration coverage active when the test process inherits a subagent-child environment marker.
|
|
66
|
+
|
|
5
67
|
## [0.50.0] - 2026-08-15
|
|
6
68
|
|
|
7
69
|
### Added
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: gpt-pro
|
|
3
|
+
description: Surf GPT Pro advisory runner through the external-job provider bridge
|
|
4
|
+
runner:
|
|
5
|
+
type: external-job
|
|
6
|
+
provider: surf-oracle
|
|
7
|
+
async: true
|
|
8
|
+
systemPromptMode: replace
|
|
9
|
+
inheritProjectContext: false
|
|
10
|
+
inheritSkills: false
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
You are a read-only GPT Pro advisor reached through Surf Oracle.
|
|
14
|
+
|
|
15
|
+
Review the supplied task and context.
|
|
16
|
+
Return clear advice, risks, and recommended next steps.
|
|
17
|
+
Do not claim you edited files or ran local tools.
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { parentPort } from "node:worker_threads";
|
|
4
|
+
|
|
5
|
+
const ACTIVE_RUN_INDEX_DIR = ".active-runs";
|
|
6
|
+
const RESULT_TOMBSTONE_PREFIX = ".deleting-result-";
|
|
7
|
+
|
|
8
|
+
function compareRelative(left, right) {
|
|
9
|
+
if (left < right) return -1;
|
|
10
|
+
if (left > right) return 1;
|
|
11
|
+
return 0;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function insertSmallest(entries, candidate, limit) {
|
|
15
|
+
if (limit <= 0) return;
|
|
16
|
+
const index = entries.findIndex((entry) => compareRelative(candidate.relative, entry.relative) < 0);
|
|
17
|
+
if (index === -1) entries.push(candidate);
|
|
18
|
+
else entries.splice(index, 0, candidate);
|
|
19
|
+
if (entries.length > limit) entries.pop();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function streamDirWindow(dir, limit, after, relativePath, usable) {
|
|
23
|
+
if (limit <= 0) return { entries: [], rawReads: 0, exhausted: true, cursorCleared: false };
|
|
24
|
+
let handle;
|
|
25
|
+
try {
|
|
26
|
+
handle = fs.opendirSync(dir);
|
|
27
|
+
const next = [];
|
|
28
|
+
const wrapped = [];
|
|
29
|
+
let rawReads = 0;
|
|
30
|
+
while (true) {
|
|
31
|
+
const entry = handle.readSync();
|
|
32
|
+
if (!entry) break;
|
|
33
|
+
rawReads += 1;
|
|
34
|
+
const relative = relativePath(entry);
|
|
35
|
+
if (!usable(entry, relative)) continue;
|
|
36
|
+
const candidate = { relative, name: entry.name };
|
|
37
|
+
insertSmallest(wrapped, candidate, limit);
|
|
38
|
+
if (after === undefined || compareRelative(relative, after) > 0) insertSmallest(next, candidate, limit);
|
|
39
|
+
}
|
|
40
|
+
const cursorCleared = after !== undefined && next.length === 0;
|
|
41
|
+
return { entries: cursorCleared ? wrapped : next, rawReads, exhausted: true, cursorCleared };
|
|
42
|
+
} catch (error) {
|
|
43
|
+
if (error?.code === "ENOENT") return { entries: [], rawReads: 0, exhausted: true, cursorCleared: false };
|
|
44
|
+
throw error;
|
|
45
|
+
} finally {
|
|
46
|
+
handle?.closeSync();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function discover(request) {
|
|
51
|
+
const startedAt = Date.now();
|
|
52
|
+
const cursor = structuredClone(request.cursor);
|
|
53
|
+
const raw = { reads: 0, exhausted: {} };
|
|
54
|
+
const record = (source, scan) => {
|
|
55
|
+
raw.reads += scan.rawReads;
|
|
56
|
+
raw.exhausted[source] = scan.exhausted;
|
|
57
|
+
};
|
|
58
|
+
const runScan = streamDirWindow(
|
|
59
|
+
request.asyncDirRoot,
|
|
60
|
+
request.runBudget,
|
|
61
|
+
cursor.runAfter,
|
|
62
|
+
(entry) => entry.name,
|
|
63
|
+
(entry) => entry.isDirectory() && entry.name !== ACTIVE_RUN_INDEX_DIR,
|
|
64
|
+
);
|
|
65
|
+
record("runs", runScan);
|
|
66
|
+
if (runScan.cursorCleared) delete cursor.runAfter;
|
|
67
|
+
const runCandidates = runScan.entries.slice(0, request.runBudget);
|
|
68
|
+
for (const candidate of runCandidates) cursor.runAfter = candidate.relative;
|
|
69
|
+
|
|
70
|
+
const resultCandidates = [];
|
|
71
|
+
const sourceLimit = Math.max(1, Math.ceil(request.resultBudget / 4));
|
|
72
|
+
const addFiles = (dir, kind, cursorTarget, source, budget = sourceLimit) => {
|
|
73
|
+
const remaining = Math.min(budget, request.resultBudget - resultCandidates.length);
|
|
74
|
+
if (remaining <= 0) return 0;
|
|
75
|
+
const before = resultCandidates.length;
|
|
76
|
+
const after = cursorTarget.type === "pending"
|
|
77
|
+
? cursor.resultPendingAfterBySession?.[cursorTarget.session]
|
|
78
|
+
: cursor[cursorTarget.key];
|
|
79
|
+
const scan = streamDirWindow(
|
|
80
|
+
dir,
|
|
81
|
+
remaining,
|
|
82
|
+
after,
|
|
83
|
+
(entry) => path.relative(request.resultsDir, path.join(dir, entry.name)),
|
|
84
|
+
(entry) => entry.isFile() && (entry.name.startsWith(RESULT_TOMBSTONE_PREFIX) || entry.name.endsWith(".json")),
|
|
85
|
+
);
|
|
86
|
+
record(source, scan);
|
|
87
|
+
if (scan.cursorCleared) {
|
|
88
|
+
if (cursorTarget.type === "pending") delete cursor.resultPendingAfterBySession?.[cursorTarget.session];
|
|
89
|
+
else delete cursor[cursorTarget.key];
|
|
90
|
+
}
|
|
91
|
+
for (const candidate of scan.entries.slice(0, remaining)) {
|
|
92
|
+
const tombstone = candidate.name.startsWith(RESULT_TOMBSTONE_PREFIX);
|
|
93
|
+
resultCandidates.push({
|
|
94
|
+
name: candidate.name,
|
|
95
|
+
relative: candidate.relative,
|
|
96
|
+
kind: tombstone ? "tombstone" : kind,
|
|
97
|
+
cursor: cursorTarget,
|
|
98
|
+
});
|
|
99
|
+
if (cursorTarget.type === "pending") {
|
|
100
|
+
cursor.resultPendingAfterBySession ??= {};
|
|
101
|
+
cursor.resultPendingAfterBySession[cursorTarget.session] = candidate.relative;
|
|
102
|
+
} else cursor[cursorTarget.key] = candidate.relative;
|
|
103
|
+
}
|
|
104
|
+
return resultCandidates.length - before;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
addFiles(request.resultsDir, "public", { type: "result", key: "resultPublicAfter" }, "results.public");
|
|
108
|
+
const pendingRoot = path.join(request.resultsDir, "result-pending");
|
|
109
|
+
const pendingRemaining = Math.min(request.resultBudget, sourceLimit, request.resultBudget - resultCandidates.length);
|
|
110
|
+
const pendingScan = streamDirWindow(
|
|
111
|
+
pendingRoot,
|
|
112
|
+
pendingRemaining,
|
|
113
|
+
cursor.pendingSessionAfter,
|
|
114
|
+
(entry) => entry.name,
|
|
115
|
+
(entry) => entry.isDirectory(),
|
|
116
|
+
);
|
|
117
|
+
record("results.pendingSessions", pendingScan);
|
|
118
|
+
const livePendingSessions = new Set(pendingScan.entries.map((entry) => entry.relative));
|
|
119
|
+
if (cursor.resultPendingAfterBySession) {
|
|
120
|
+
let pruned = 0;
|
|
121
|
+
for (const session of Object.keys(cursor.resultPendingAfterBySession).sort()) {
|
|
122
|
+
if (pruned >= request.resultBudget) break;
|
|
123
|
+
if (!livePendingSessions.has(session) && !fs.existsSync(path.join(pendingRoot, session))) {
|
|
124
|
+
delete cursor.resultPendingAfterBySession[session];
|
|
125
|
+
pruned += 1;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (Object.keys(cursor.resultPendingAfterBySession).length === 0) delete cursor.resultPendingAfterBySession;
|
|
129
|
+
}
|
|
130
|
+
if (pendingScan.cursorCleared) delete cursor.pendingSessionAfter;
|
|
131
|
+
const pendingSessions = pendingScan.entries.slice(0, pendingRemaining);
|
|
132
|
+
let pendingFileBudget = Math.min(sourceLimit, request.resultBudget - resultCandidates.length);
|
|
133
|
+
for (const session of pendingSessions) {
|
|
134
|
+
if (pendingFileBudget <= 0) break;
|
|
135
|
+
cursor.pendingSessionAfter = session.relative;
|
|
136
|
+
pendingFileBudget -= addFiles(
|
|
137
|
+
path.join(pendingRoot, session.name),
|
|
138
|
+
"pending",
|
|
139
|
+
{ type: "pending", session: session.relative },
|
|
140
|
+
`results.pending.${session.name}`,
|
|
141
|
+
pendingFileBudget,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
addFiles(path.join(request.resultsDir, "completion-replay"), "replay", { type: "result", key: "resultReplayAfter" }, "results.replay");
|
|
145
|
+
addFiles(path.join(request.resultsDir, "output-archives"), "archive", { type: "result", key: "resultArchiveAfter" }, "results.archive");
|
|
146
|
+
|
|
147
|
+
const cursorOps = [];
|
|
148
|
+
for (const key of ["runAfter", "resultPublicAfter", "resultReplayAfter", "resultArchiveAfter", "pendingSessionAfter"]) {
|
|
149
|
+
if (cursor[key] === request.cursor[key]) continue;
|
|
150
|
+
cursorOps.push(cursor[key] === undefined ? { type: "delete", key } : { type: "set", key, value: cursor[key] });
|
|
151
|
+
}
|
|
152
|
+
const oldPending = request.cursor.resultPendingAfterBySession ?? {};
|
|
153
|
+
const nextPending = cursor.resultPendingAfterBySession ?? {};
|
|
154
|
+
for (const session of new Set([...Object.keys(oldPending), ...Object.keys(nextPending)])) {
|
|
155
|
+
if (oldPending[session] === nextPending[session]) continue;
|
|
156
|
+
cursorOps.push(nextPending[session] === undefined
|
|
157
|
+
? { type: "delete-pending", session }
|
|
158
|
+
: { type: "set-pending", session, value: nextPending[session] });
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
type: "result",
|
|
162
|
+
passId: request.passId,
|
|
163
|
+
discoveryDurationMs: Math.max(0, Date.now() - startedAt),
|
|
164
|
+
rawReads: raw.reads,
|
|
165
|
+
sourceExhausted: raw.exhausted,
|
|
166
|
+
runCandidates,
|
|
167
|
+
resultCandidates,
|
|
168
|
+
cursorOps,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (!parentPort) throw new Error("Async retention discovery requires a worker parent port.");
|
|
173
|
+
parentPort.on("message", (request) => {
|
|
174
|
+
const passId = request?.passId;
|
|
175
|
+
try {
|
|
176
|
+
parentPort.postMessage(discover(request));
|
|
177
|
+
} catch (error) {
|
|
178
|
+
parentPort.postMessage({ type: "error", passId, error: error instanceof Error ? error.message : String(error) });
|
|
179
|
+
}
|
|
180
|
+
});
|
package/docs/agents.md
CHANGED
|
@@ -41,12 +41,45 @@ Builtins load at the lowest priority, so a user or project agent with the same n
|
|
|
41
41
|
| `worker` | Implementation work, including approved oracle handoffs. It edits files, validates, and escalates unapproved decisions instead of guessing. |
|
|
42
42
|
| `reviewer` | Code review and small fixes. It checks the implementation against the task/plan, tests, edge cases, and simplicity. |
|
|
43
43
|
| `oracle` | A second opinion before acting. It challenges assumptions, catches drift, and recommends the safest next move without editing. |
|
|
44
|
+
| `gpt-pro` | Read-only Surf GPT Pro advice through the `surf-oracle` external-job provider bridge. |
|
|
44
45
|
| `delegate` | A lightweight general delegate when you want a child agent that behaves close to the parent session. |
|
|
45
46
|
|
|
46
47
|
Rule of thumb: `scout` before you understand the code, `researcher` before you trust external facts, `worker` to implement, `reviewer` to check, and `oracle` when the decision itself feels risky.
|
|
47
48
|
|
|
48
49
|
`oracle` is an advisory reviewer that critiques direction and proposes an execution prompt without editing files. `advisor` is the same bundled role under the Claude Code-compatible name.
|
|
49
50
|
|
|
51
|
+
`gpt-pro` uses `runner.type: external-job` with provider `surf-oracle`. It starts through the same `subagent({ agent: "gpt-pro" })` mental model as any other agent, but the work is owned by Surf through the external-job provider bridge. The Pi async run remains the source of truth for status, artifacts, wake/wait, mission attachment, retention, and diagnostics.
|
|
52
|
+
|
|
53
|
+
Claude Code can be configured as a read-only advisor with `runner.type: external-cli` when the Claude Code CLI is installed and you have verified the flags for your local version. pi-subagents does not ship or enforce Claude Code flags. Use a project or user agent like this only after checking your CLI help:
|
|
54
|
+
|
|
55
|
+
```yaml
|
|
56
|
+
---
|
|
57
|
+
name: claude-advisor
|
|
58
|
+
description: Read-only Claude Code advisor through the local CLI
|
|
59
|
+
runner:
|
|
60
|
+
type: external-cli
|
|
61
|
+
command: claude
|
|
62
|
+
args: ["<verified-read-only-flags>"]
|
|
63
|
+
promptDelivery: stdin
|
|
64
|
+
async: true
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
Review the task and return advice only. Do not edit files.
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Advisory runner data boundary
|
|
71
|
+
|
|
72
|
+
Native `oracle` runs inside Pi and can use its configured read tools. `claude-advisor` sends the assembled prompt to the configured local external CLI through stdin. `gpt-pro` sends the assembled prompt to the registered Surf provider. Provider options and a prompt digest are persisted in Pi run state. The prompt text is delivered through the local host bridge to the provider and is not stored in the public result payload. Do not place secrets in advisory prompts unless the target provider is approved to receive them.
|
|
73
|
+
|
|
74
|
+
### External-job state table
|
|
75
|
+
|
|
76
|
+
| Durable file | Owner | States | Release predicate | Rollback predicate | Stale-head behavior | Fail-closed cases |
|
|
77
|
+
|--------------|-------|--------|-------------------|--------------------|---------------------|-------------------|
|
|
78
|
+
| `status.json` step `runner` and `externalJob` | pi-subagents async runner | `queued`, `running`, `completed`, `failed`, `stopped`, `blocked` | Provider `result` returns terminal data and the async result is written | Provider start/status/result/reattach returns an error | If a status file already has a provider job id, recovery calls `reattach` and `result`; it refuses to start a new prompt when the provider or prompt digest differs | Missing provider, capacity conflict, malformed provider response, bridge timeout, prompt digest mismatch |
|
|
79
|
+
| `result.json` or session result payload | pi-subagents async runner | `complete`, `failed`, `stopped` | All steps reach terminal state and result publication succeeds or is recoverably indexed | Result write fails and pending result repair records the terminal state | Stale status can repair from an existing result file | Unindexed sessionless stale failure |
|
|
80
|
+
| `external-job-requests/` and `external-job-responses/` | Host-mediated provider bridge | pending request, terminal response | Host process writes a matching response and removes the request | Bridge timeout or malformed request response | Requests are operation-scoped. Recovery sends `reattach`/`result`, not `start`, when job metadata exists | Provider not registered, host bridge not loaded, malformed request, provider exception |
|
|
81
|
+
| Provider artifact path | External provider | provider-defined terminal artifact | Provider returns `artifactPath`, or Pi writes returned text to `external-job-<index>.result.md` | Provider reports failure or no result | Existing artifact path is retained in `status.json` | Missing artifact with no text output returns a terminal message instead of inventing content |
|
|
82
|
+
|
|
50
83
|
The `researcher` builtin uses `web_search`, `fetch_content`, and `get_search_content`. Those require [pi-web-access](https://github.com/nicobailon/pi-web-access):
|
|
51
84
|
|
|
52
85
|
```bash
|
|
@@ -102,7 +135,7 @@ Use these fields when an agent should see more:
|
|
|
102
135
|
| `systemPromptMode: append` | Append the agent prompt to Pi's normal base prompt. |
|
|
103
136
|
| `inheritProjectContext: true` | Keep inherited project instructions from files like `AGENTS.md` and `CLAUDE.md`. |
|
|
104
137
|
| `inheritSkills: true` | Let the child see Pi's discovered skills catalog. |
|
|
105
|
-
| `defaultContext: fork` |
|
|
138
|
+
| `defaultContext: fork` | Prefer forked session context when a launch omits `context`; if the parent has no persisted session file or current leaf yet, the implicit default falls back to `fresh` without a failed first attempt. Explicit `context: "fork"` remains strict, and explicit `context: "fresh"` still wins. |
|
|
106
139
|
|
|
107
140
|
Builtin agents opt into project instruction inheritance by default so they follow repo-specific rules out of the box. `delegate` also uses append mode because its job is orchestration inside the parent workflow.
|
|
108
141
|
|
|
@@ -171,7 +204,7 @@ Field notes:
|
|
|
171
204
|
| `systemPromptMode` | `replace` by default; `append` keeps Pi's base prompt. |
|
|
172
205
|
| `inheritProjectContext` | Keeps or strips inherited project instruction blocks. |
|
|
173
206
|
| `inheritSkills` | Keeps or strips Pi's discovered skills catalog. |
|
|
174
|
-
| `defaultContext` | Optional `fresh` or `fork` launch context
|
|
207
|
+
| `defaultContext` | Optional `fresh` or `fork` launch-context preference. An implicit `fork` falls back to `fresh` when the parent has no persisted session file or current leaf; an explicit launch `context: "fork"` remains strict. |
|
|
175
208
|
| `skills` | Selects specific skills for the child, regardless of `inheritSkills`. |
|
|
176
209
|
| `skillPath` | Invocation-private skill files or discovery directories. Relative paths resolve from the agent definition file. Local matches take precedence, while unresolved or unreadable matches fall back to normal skill discovery. This field discovers candidates only; `skills` still selects what the child receives. |
|
|
177
210
|
| `output` | Default single-agent output file. |
|
package/docs/configuration.md
CHANGED
|
@@ -24,18 +24,10 @@ By default, project settings resolve from the nearest parent directory that cont
|
|
|
24
24
|
{ "toolDescriptionMode": "compact" }
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
Controls the parent-facing `subagent` tool description registered at startup. `full`
|
|
27
|
+
Controls the parent-facing `subagent` tool description registered at startup. The default registers split prompt metadata: a short tool description plus `promptSnippet` and `promptGuidelines`. Set `"full"` to register the complete description as one tool description, or `"compact"` to keep the execution modes, async/`subagent_wait` guidance, child-safety boundary, management/action split, one-writer review guidance, and artifact/status essentials with less prompt bloat.
|
|
28
28
|
|
|
29
29
|
`custom` reads `subagent-tool-description.md` from the project config directory, then from `~/.pi/agent/subagent-tool-description.md`. Missing, empty, unreadable, or oversized custom files fall back to the full description. Custom templates may use `{{fullDescription}}`, `{{compactDescription}}`, `{{safetyGuidance}}`, `{{agentDir}}`, and `{{projectConfigDir}}`; the safety guidance is always present so custom prose cannot remove the runtime guardrails. Restart Pi after changing the mode or custom file.
|
|
30
30
|
|
|
31
|
-
## `legacyChainControls`
|
|
32
|
-
|
|
33
|
-
```json
|
|
34
|
-
{ "legacyChainControls": true }
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
Defaults to `false`. The default registered model-facing tool schema and description omit the legacy `append-step` `step` schema and legacy checkpoint controls. This does not change runtime support for existing durable legacy chains. Set this to `true` before directly managing a legacy chain with `append-step`, `approve-checkpoint`, or `reject-checkpoint`.
|
|
38
|
-
|
|
39
31
|
## `inlineToolDisplay`
|
|
40
32
|
|
|
41
33
|
```json
|
|
@@ -93,7 +85,7 @@ Pi binds `Ctrl+B` to editor cursor-left by default. The extension shortcut takes
|
|
|
93
85
|
}
|
|
94
86
|
```
|
|
95
87
|
|
|
96
|
-
Opt in to a best-effort Orca observer that creates one Orca terminal tab for each subagent child and mirrors its live tool, assistant, stdout, and stderr progress. Tab titles use a persistent worktree-local sequence (`subagent · <agent> · 1`, `... · 2`, and so on), so separate workflows and concurrent children do not reuse the same number. This does **not** replace Pi as the child runner: native Pi children keep the same process, lifecycle, status, control, artifact, and result paths. External CLI profiles also keep their existing runner and can mirror their stdout/stderr.
|
|
88
|
+
Opt in to a best-effort Orca observer that creates one Orca terminal tab for each subagent child and mirrors its live tool, assistant, stdout, and stderr progress. Tab titles use a persistent worktree-local sequence (`subagent · <agent> · 1`, `... · 2`, and so on), so separate workflows and concurrent children do not reuse the same number. For the same worktree, `orca terminal create` runs one at a time in that sequence so the UI can append tabs from left to right as `1`, then `2`, then `3`. This does **not** replace Pi as the child runner: native Pi children keep the same process, lifecycle, status, control, artifact, and result paths. External CLI profiles also keep their existing runner and can mirror their stdout/stderr.
|
|
97
89
|
|
|
98
90
|
The integration is off by default and supports macOS and Linux. It is disabled on Windows. When enabled, `pi-subagents` looks for executable `orca` on `PATH`, or uses the executable path in `PI_SUBAGENT_ORCA_BINARY`. If no executable is available, Orca is not running, the cwd is not an Orca-managed worktree, or `terminal create` fails, the authoritative subagent still runs normally. Tab creation is deliberately best-effort and never changes the child result.
|
|
99
91
|
|
|
@@ -107,6 +99,16 @@ Set `enabled` to `false` (or remove the block) as a kill switch. In that state,
|
|
|
107
99
|
|
|
108
100
|
WorkflowScript calls use background execution when the request omits `async`. Set `asyncByDefault` to `false` to restore foreground-by-default behavior for tool launches that still use the internal single-run primitive. Callers can still force foreground with `async: false` unless `forceTopLevelAsync` is enabled.
|
|
109
101
|
|
|
102
|
+
## `defaultSubagentContext`
|
|
103
|
+
|
|
104
|
+
```json
|
|
105
|
+
{ "defaultSubagentContext": "fresh" }
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Sets `fresh` or `fork` for every subagent launch that omits `context`. This global preference replaces each agent-level `defaultContext`. Explicit `context: "fresh"` or `context: "fork"` still wins.
|
|
109
|
+
|
|
110
|
+
With `"fork"`, the setting uses the existing implicit-fork behavior. A launch starts fresh when the parent session file or current leaf is not available. `"fresh"` starts fresh even when the selected agent defaults to fork. Scheduled runs continue to set fresh context explicitly. A runner or provider that does not support fork context keeps its existing rejection behavior.
|
|
111
|
+
|
|
110
112
|
## `fleetView`
|
|
111
113
|
|
|
112
114
|
```json
|
|
@@ -406,9 +408,9 @@ Controls where subagent artifact files (inputs, outputs, transcripts, metadata)
|
|
|
406
408
|
- `"session"` (default): stores artifacts under pi's session directory (`~/.pi/agent/sessions/<session>/subagent-artifacts/`), keeping the working directory clean. It falls back to the OS temp directory when no session file exists.
|
|
407
409
|
- `"temp"`: uses the OS temp directory.
|
|
408
410
|
|
|
409
|
-
This preference also controls the default
|
|
411
|
+
This preference also controls the default workflow artifact directory used by scripted chaining. `"project"` uses `<cwd>/.pi/subagents/chain-runs/`; the directory keeps its legacy name for compatibility. The default `"session"` and `"temp"` use the user-scoped temp workflow artifact directory.
|
|
410
412
|
|
|
411
|
-
The `"session"` option uses the same directory that `cleanupAllArtifactDirs` already scans for age-based cleanup, so artifacts are still cleaned up automatically. Temporary
|
|
413
|
+
The `"session"` option uses the same directory that `cleanupAllArtifactDirs` already scans for age-based cleanup, so artifacts are still cleaned up automatically. Temporary workflow artifact directories are cleaned up separately after 24 hours.
|
|
412
414
|
|
|
413
415
|
When a project-scoped launch runs from an npm package directory, pi-subagents warns if package settings can include `.pi/subagents/` in the published package. Add `.pi/subagents/` to `.npmignore` (or `.gitignore` when no `.npmignore` exists), use a `files` allowlist that does not include `.pi/subagents/`, or select `"session"` or `"temp"`.
|
|
414
416
|
|
|
@@ -437,3 +439,19 @@ Controls smart batching of async-completion notifications. When several backgrou
|
|
|
437
439
|
## `permissions`
|
|
438
440
|
|
|
439
441
|
Native child tool permission rules. See [watchdog.md](watchdog.md#native-child-tool-permissions).
|
|
442
|
+
|
|
443
|
+
## `PI_SUBAGENT_FS_RETRY_MAX_TOTAL_MS`
|
|
444
|
+
|
|
445
|
+
Caps the total time a single retried filesystem operation may sleep, in milliseconds. Environment-only; there is no config key.
|
|
446
|
+
|
|
447
|
+
Atomic status and result writes retry on `EACCES`, `EBUSY`, and `EPERM`, which on Windows are usually a scanner or a sibling process holding the destination of a rename for a moment. The retry ladder sleeps up to about 7.9s in total, and it sleeps *synchronously* — `Atomics.wait` parks the calling thread rather than spinning.
|
|
448
|
+
|
|
449
|
+
That is the right trade-off for a CLI. It is the wrong one for a long-lived process that loads `pi-subagents` in-process and runs those writers on its event loop: one contended rename stalls everything it serves for the length of the ladder, and because the thread is parked rather than busy, it presents as an unresponsive process sitting at 0% CPU. A wide fanout makes contention on a single `status.json` likely.
|
|
450
|
+
|
|
451
|
+
Set this to bound that stall. The ladder keeps its number of attempts and only the sleeps shrink, because `run-fanout-budget` and mission state locking use the ladder's length as their attempt budget:
|
|
452
|
+
|
|
453
|
+
```text
|
|
454
|
+
PI_SUBAGENT_FS_RETRY_MAX_TOTAL_MS=1000
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
Unset by default, so behaviour is unchanged unless you opt in. Opting in trades lock-wait tolerance for responsiveness: entries clamped to `0` return immediately, so contention that would previously have been waited out surfaces as an error sooner. Values that are not a non-negative integer fail instead of being coerced.
|
package/docs/extension-api.md
CHANGED
|
@@ -132,6 +132,7 @@ Boundaries:
|
|
|
132
132
|
- Raw prompts are not exposed in public contract output.
|
|
133
133
|
- It is side-effect-free for launch state: it does not create child sessions, temp prompt files, structured-output runtimes, tool-diagnostic files, or run artifacts.
|
|
134
134
|
- Some host-owned facts, such as exact fork snapshots, nested async roots, and live model registries, can only be proven by the Pi host; those appear as `host_required` diagnostics instead of silently pretending to be exact.
|
|
135
|
+
- Preflight reads the extension config, so `defaultSubagentContext: "fresh"` or `"fork"` affects omitted context in the same way as execution. Explicit `context` still wins.
|
|
135
136
|
|
|
136
137
|
## Structured delegation API
|
|
137
138
|
|
|
@@ -262,6 +263,26 @@ Semantics:
|
|
|
262
263
|
|
|
263
264
|
Child processes do not gain provider tools or extensions automatically. Add `subagent_wait` to the child agent's `tools` allowlist and load each provider through `extensions` or `subagentOnlyExtensions`. The parent's effective `waitTool` setting is serialized through foreground, async, resume, chain, parallel, and fanout launch paths; `PI_SUBAGENT_WAIT_TOOL_ENABLED` keeps precedence.
|
|
264
265
|
|
|
266
|
+
## External job provider bridge
|
|
267
|
+
|
|
268
|
+
Extensions that own long-running advisor jobs can register a process-local provider for `runner.type: external-job` agents:
|
|
269
|
+
|
|
270
|
+
```ts
|
|
271
|
+
import { registerExternalJobProvider } from "pi-subagents/external-job-provider";
|
|
272
|
+
|
|
273
|
+
const dispose = registerExternalJobProvider({
|
|
274
|
+
name: "surf-oracle",
|
|
275
|
+
start: ({ prompt, promptDigest, cwd, runId, stepIndex, agent, options }) => startSurfJob({ prompt, promptDigest, cwd, runId, stepIndex, agent, options }),
|
|
276
|
+
status: (providerJobId) => getSurfJobStatus(providerJobId),
|
|
277
|
+
result: (providerJobId) => getSurfJobResult(providerJobId),
|
|
278
|
+
reattach: (providerJobId) => reattachSurfJob(providerJobId),
|
|
279
|
+
});
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
The provider returns handles with `providerJobId`, `state`, optional `handleUrl`/`conversationUrl`, optional `failureCode`/`failureMessage`, and optional `blockingJobId` for capacity conflicts. `result` can also return `output` and/or `artifactPath`.
|
|
283
|
+
|
|
284
|
+
The async runner process does not import provider internals. It writes operation requests into its async run directory. The parent Pi process services those requests against the registered provider and writes operation responses. If the provider is not registered, the bridge fails closed with an actionable error. If a run is recovered after provider job metadata exists, the runner calls `reattach` and `result`; it does not call `start` again.
|
|
285
|
+
|
|
265
286
|
## Herdr integration
|
|
266
287
|
|
|
267
288
|
When Pi runs inside [Herdr](https://herdr.dev), pi-subagents automatically reports active async-run counts through Herdr pane metadata.
|
|
@@ -323,6 +344,26 @@ const closed = await closeProjectPane({ cwd: "/path/to/repo", requireIdle: true
|
|
|
323
344
|
|
|
324
345
|
The API returns discriminated structured results with canonical project root, binding path, pane identity, bounded Herdr runtime fields, and stable error codes. `requireIdle: true` fails closed unless Herdr explicitly reports `agent_status: "idle"`; use it when an owning extension must not close a working or blocked pane. The API deliberately reports `trust: "human-verification-required"`: it never bypasses or claims to attest Pi's project-trust prompt. `PROJECT_PANES_API_VERSION` is currently `1`.
|
|
325
346
|
|
|
347
|
+
## Host session lifetime and completion wakes
|
|
348
|
+
|
|
349
|
+
A host that embeds this extension owns whether completion wakes can be delivered at all.
|
|
350
|
+
|
|
351
|
+
Ordinary async and foreground completion wakes use `registerSubagentNotify` and `sendCompletion`. They listen for completion events and deliver through `pi.sendMessage(..., { triggerTurn })`. Session shutdown stops the result watcher and disposes this completion notifier. `createWaitSubscriptionManager` is separate: it is the explicit non-blocking `subagent_wait` subscription path, not the ordinary completion wake path.
|
|
352
|
+
|
|
353
|
+
Detached children do not stop when the session does. They are the host process's children, not the session's, so the run keeps going, completes, and notifies nobody. What is lost is the notification, not the work.
|
|
354
|
+
|
|
355
|
+
This matters because "is the parent busy?" is the wrong idle signal. A parent that launches a detached run and hands control back — which is what the async launch output tells it to do — is not prompting, streaming, compacting, or running a shell command. A host that reaps sessions on those signals alone will dispose exactly the session that was waiting to be woken.
|
|
356
|
+
|
|
357
|
+
If your host reclaims idle sessions, keep a session alive while it still has live detached work:
|
|
358
|
+
|
|
359
|
+
- Read run state from the status files under the async run directory rather than from event traffic. A long, quiet workflow sends almost nothing to the parent, so recent-activity heuristics conclude the wrong thing.
|
|
360
|
+
- Treat `queued` and `running` as live, matching `isActiveAsyncState`. `paused` is not: an interrupted run is finalized as paused.
|
|
361
|
+
- Do not treat `lastUpdate` as a heartbeat. The runner advances it in memory every second but only rewrites `status.json` when the activity classification changes, so a live run inside one long quiet tool call leaves a stale file behind. Judging liveness by file age will reap exactly the run you meant to protect.
|
|
362
|
+
- Prefer the recorded runner `pid`, which stays true through a silent tool call and goes false when the runner dies. Keep file age only as a fallback for runs that record no pid, and give it a wide window.
|
|
363
|
+
- Match `sessionId` in `status.json` against both forms. It is resolved as `getSessionFile() ?? getSessionId()`, so it is normally the parent's session *file path*, but a session that is not persisted records a bare session id instead.
|
|
364
|
+
|
|
365
|
+
The symptom when this is missed is quiet and easy to misattribute: subagents appear never to report back, which looks like a fault in this extension rather than in the host that disposed the listener.
|
|
366
|
+
|
|
326
367
|
## Runtime files
|
|
327
368
|
|
|
328
369
|
The main runtime files in this repository:
|
|
@@ -336,7 +377,7 @@ The main runtime files in this repository:
|
|
|
336
377
|
| `src/runs/background/subagent-runner.ts` | Detached async runner. |
|
|
337
378
|
| `src/runs/background/async-execution.ts` | Background launch support. |
|
|
338
379
|
| `src/runs/background/async-status.ts` | Status discovery and formatting for async runs. |
|
|
339
|
-
| `src/
|
|
380
|
+
| `src/workflows/scripted-workflow.ts` / `src/runs/foreground/subagent-executor.ts` | Scripted workflow orchestration and child launch routing. |
|
|
340
381
|
| `src/shared/settings.ts` | Chain behavior, instructions, and config helpers. |
|
|
341
382
|
| `src/runs/shared/worktree.ts` | Git worktree isolation. |
|
|
342
383
|
| `src/intercom/intercom-bridge.ts` | Runtime intercom bridge instructions and diagnostics. |
|
package/docs/observability.md
CHANGED
|
@@ -164,15 +164,15 @@ Foreground and async runners share bounded child-protocol handling:
|
|
|
164
164
|
- `agent_end.willRetry` defers completion until the child settles.
|
|
165
165
|
- Current Pi builds use `agent_settled` as the terminal watermark; older builds retain the bounded terminal-message fallback.
|
|
166
166
|
|
|
167
|
-
##
|
|
167
|
+
## Workflow and debug artifacts
|
|
168
168
|
|
|
169
|
-
Each
|
|
169
|
+
Each scripted workflow stores runtime artifacts under a workflow artifact directory. The on-disk directory is still named `chain-runs` for compatibility. With the default `artifactDir: "session"` or with `"temp"`, it is user-scoped temp storage. With `artifactDir: "project"`, the root is `<cwd>/.pi/subagents/chain-runs/`:
|
|
170
170
|
|
|
171
171
|
```text
|
|
172
172
|
<tmpdir>/pi-subagents-<scope>/chain-runs/{runId}/
|
|
173
173
|
```
|
|
174
174
|
|
|
175
|
-
A run directory may contain files such as `context.md`, `plan.md`, `progress.md`, and `parallel-{stepIndex}/.../output.md`. User-scoped temp
|
|
175
|
+
A run directory may contain files such as `context.md`, `plan.md`, `progress.md`, and `parallel-{stepIndex}/.../output.md`. User-scoped temp workflow artifact directories older than 24 hours are cleaned up on extension startup; project-local and explicit persistent roots are not age-scanned.
|
|
176
176
|
|
|
177
177
|
Debug artifacts live under `{sessionDir}/subagent-artifacts/`, `.pi/subagents/artifacts/` for project-scoped runs, or a user-scoped temp artifact directory. Single-run relative `output` files are saved under `{artifactsDir}/outputs/{runId}/` unless `singleRunOutputBaseDir` is configured. Per task you may see:
|
|
178
178
|
|
|
@@ -187,7 +187,7 @@ For npm package projects, project-scoped artifacts need a `.npmignore` rule (or
|
|
|
187
187
|
|
|
188
188
|
## Sessions
|
|
189
189
|
|
|
190
|
-
Session files are stored under a per-run session directory. With `context: "fork"`, each child starts with `--session <branched-session-file>` produced from the parent's current leaf. That is a real session fork, not an injected summary.
|
|
190
|
+
Session files are stored under a per-run session directory. With `context: "fork"`, each child starts with `--session <branched-session-file>` produced from the parent's current leaf. That is a real session fork, not an injected summary. An omitted launch `context` that resolves through `defaultContext: fork` uses the same branch when the parent session file and current leaf exist, and otherwise starts fresh.
|
|
191
191
|
|
|
192
192
|
## Completion notifications
|
|
193
193
|
|