pi-subagents 0.50.0 → 0.52.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 (109) hide show
  1. package/CHANGELOG.md +103 -0
  2. package/agents/oracle.md +3 -1
  3. package/agents/reviewer.md +1 -0
  4. package/agents/scout.md +2 -2
  5. package/agents/worker.md +2 -1
  6. package/async-retention-discovery-worker.mjs +180 -0
  7. package/docs/agents.md +40 -2
  8. package/docs/configuration.md +30 -12
  9. package/docs/extension-api.md +42 -1
  10. package/docs/models.md +2 -0
  11. package/docs/observability.md +41 -5
  12. package/docs/tool-reference.md +38 -39
  13. package/docs/workflows.md +171 -3
  14. package/package.json +4 -2
  15. package/skills/pi-subagents/SKILL.md +6 -4
  16. package/skills/pi-subagents/references/constraints-and-recipes.md +12 -6
  17. package/skills/pi-subagents/references/execution-controls.md +29 -15
  18. package/skills/pi-subagents/references/management-authoring-rpc.md +3 -3
  19. package/skills/pi-subagents/references/prompting-and-roles.md +4 -2
  20. package/src/agents/agent-management.ts +124 -351
  21. package/src/agents/agents.ts +157 -31
  22. package/src/agents/skills.ts +1 -1
  23. package/src/api/external-job-provider.ts +185 -0
  24. package/src/api/preflight.ts +36 -8
  25. package/src/api/shared-types.ts +2 -0
  26. package/src/extension/config.ts +3 -3
  27. package/src/extension/doctor.ts +3 -6
  28. package/src/extension/fanout-child.ts +2 -2
  29. package/src/extension/index.ts +166 -88
  30. package/src/extension/public-execution.ts +31 -2
  31. package/src/extension/schemas.ts +12 -35
  32. package/src/extension/tool-description.ts +35 -24
  33. package/src/inspectors/herdr/actions.ts +2 -2
  34. package/src/inspectors/herdr/inspector-runner.ts +2 -1
  35. package/src/inspectors/herdr/project-panes.ts +2 -2
  36. package/src/intercom/native-supervisor-channel.ts +32 -10
  37. package/src/missions/lifecycle.ts +6 -1
  38. package/src/missions/store.ts +4 -9
  39. package/src/missions/workflow-state.ts +2 -2
  40. package/src/profiles/profiles.ts +3 -1
  41. package/src/runs/background/active-run-index.ts +31 -8
  42. package/src/runs/background/async-execution.ts +68 -40
  43. package/src/runs/background/async-job-tracker.ts +20 -4
  44. package/src/runs/background/async-resume.ts +47 -15
  45. package/src/runs/background/async-retention.ts +886 -0
  46. package/src/runs/background/async-status.ts +39 -53
  47. package/src/runs/background/chain-append.ts +3 -33
  48. package/src/runs/background/completion-dedupe.ts +5 -1
  49. package/src/runs/background/completion-replay.ts +22 -12
  50. package/src/runs/background/control-channel.ts +14 -68
  51. package/src/runs/background/fleet-view.ts +68 -19
  52. package/src/runs/background/index-segment.ts +59 -0
  53. package/src/runs/background/inspect-rpc.ts +443 -0
  54. package/src/runs/background/notify.ts +31 -5
  55. package/src/runs/background/result-files.ts +158 -90
  56. package/src/runs/background/result-watcher.ts +116 -20
  57. package/src/runs/background/resume-guidance.ts +8 -5
  58. package/src/runs/background/retained-children.ts +13 -3
  59. package/src/runs/background/run-id-query.ts +7 -0
  60. package/src/runs/background/run-id-resolver.ts +11 -9
  61. package/src/runs/background/run-status.ts +27 -18
  62. package/src/runs/background/scheduled-runs.ts +24 -4
  63. package/src/runs/background/stale-run-reconciler.ts +6 -3
  64. package/src/runs/background/steering.ts +11 -1
  65. package/src/runs/background/subagent-runner.ts +253 -140
  66. package/src/runs/background/subagent-wait.ts +8 -8
  67. package/src/runs/background/terminal-run-index.ts +129 -0
  68. package/src/runs/background/wait-completions.ts +21 -4
  69. package/src/runs/background/wait-subscriptions.ts +81 -2
  70. package/src/runs/foreground/async-steering-action.ts +21 -14
  71. package/src/runs/foreground/execution.ts +3 -1
  72. package/src/runs/foreground/subagent-executor.ts +666 -1579
  73. package/src/runs/foreground/workflow-detach-reconcile.ts +194 -0
  74. package/src/runs/foreground/workflow-foreground-steering.ts +6 -5
  75. package/src/runs/shared/acceptance.ts +4 -4
  76. package/src/runs/shared/chain-outputs.ts +1 -3
  77. package/src/runs/shared/external-job-bridge.ts +444 -0
  78. package/src/runs/shared/external-job-runner.ts +286 -0
  79. package/src/runs/shared/mcp-direct-tool-allowlist.ts +14 -0
  80. package/src/runs/shared/model-fallback.ts +98 -38
  81. package/src/runs/shared/orca-progress-tabs.ts +84 -22
  82. package/src/runs/shared/parallel-handoff.ts +46 -4
  83. package/src/runs/shared/parallel-utils.ts +4 -15
  84. package/src/runs/shared/permissions.ts +5 -1
  85. package/src/runs/shared/pi-args.ts +8 -1
  86. package/src/runs/shared/session-lease.ts +0 -6
  87. package/src/runs/shared/subagent-control.ts +26 -4
  88. package/src/runs/shared/subagent-prompt-runtime.ts +12 -6
  89. package/src/runs/shared/workflow-graph.ts +1 -23
  90. package/src/runs/shared/worktree.ts +14 -2
  91. package/src/shared/atomic-json.ts +22 -2
  92. package/src/shared/capacity-resilient-json.ts +102 -0
  93. package/src/shared/completion-owner.ts +14 -0
  94. package/src/shared/file-system-retry.ts +49 -1
  95. package/src/shared/fork-context.ts +42 -0
  96. package/src/shared/prompt-resources.ts +0 -40
  97. package/src/shared/settings.ts +3 -27
  98. package/src/shared/types.ts +71 -26
  99. package/src/shared/utils.ts +8 -0
  100. package/src/shared/watch-strategy.ts +10 -0
  101. package/src/slash/slash-bridge.ts +2 -1
  102. package/src/slash/slash-commands.ts +29 -3
  103. package/src/tui/fleet.ts +63 -15
  104. package/src/watchdog/change-signature.ts +1 -1
  105. package/src/watchdog/lsp-diagnostics.ts +1 -0
  106. package/src/workflows/chat-progress.ts +2 -2
  107. package/src/workflows/scripted-workflow.ts +220 -67
  108. package/src/runs/foreground/chain-clarify.ts +0 -1354
  109. package/src/runs/foreground/chain-execution.ts +0 -1581
@@ -7,7 +7,9 @@ import type { Message } from "@earendil-works/pi-ai";
7
7
  import { writeAtomicJson } from "../../shared/atomic-json.ts";
8
8
  import { writeAsyncResultFile, writePendingAsyncResultFile } from "./result-files.ts";
9
9
  import { createFileCoalescer } from "../../shared/file-coalescer.ts";
10
- import { isActiveAsyncState, updateActiveRunIndex } from "./active-run-index.ts";
10
+ import { createCapacityResilientJsonWriter } from "../../shared/capacity-resilient-json.ts";
11
+ import { isStorageCapacityError } from "../../shared/file-system-retry.ts";
12
+ import { updateActiveRunIndex } from "./active-run-index.ts";
11
13
  import { createChildTranscriptWriter, type ChildTranscriptWriter } from "../../shared/child-transcript.ts";
12
14
  import { closeSteerInbox, consumeInterruptRequest, consumeSteerRequests, deliverInterruptRequest, deliverStopRequest, deliverTimeoutRequest, enqueueStepSteer, steerAcksDir, steerCapabilityPath, stepSteerInboxDir, watchAsyncControlInbox, type SteerAck, type SteerCapability, type SteerRequest } from "./control-channel.ts";
13
15
  import { appendJsonl as appendRawJsonl, formatOutputArtifactContent, getArtifactPaths, writeArtifact, writeMetadata } from "../../shared/artifacts.ts";
@@ -17,6 +19,8 @@ import {
17
19
  type ActivityState,
18
20
  type ArtifactConfig,
19
21
  type ExternalCliRunnerStatus,
22
+ type ExternalJobRunnerStatus,
23
+ type ExternalJobStatus,
20
24
  type ExternalProcessStatus,
21
25
  type ArtifactPaths,
22
26
  type AsyncParallelGroupStatus,
@@ -63,7 +67,6 @@ import {
63
67
  import {
64
68
  type RunnerSubagentStep as SubagentStep,
65
69
  type RunnerStep,
66
- isCheckpointRunnerStep,
67
70
  isDynamicRunnerGroup,
68
71
  isParallelGroup,
69
72
  flattenSteps,
@@ -136,6 +139,7 @@ import { resolveWatchdogConfig } from "../../watchdog/settings.ts";
136
139
  import { createBoundedByteTail, createBoundedLineReader, formatProtocolOutputLimit, MAX_CHILD_STDERR_BYTES, PI_AGGREGATE_EVENT_PROJECTOR, projectChildLifecycle, type ChildLifecycleAction, type ProtocolOutputLimit } from "../shared/child-protocol.ts";
137
140
  import { acquireSessionLease, type SessionLeaseRequest } from "../shared/session-lease.ts";
138
141
  import { buildExternalCliPrompt, runExternalCli } from "../shared/external-cli-runner.ts";
142
+ import { runExternalJob } from "../shared/external-job-runner.ts";
139
143
  import { createOrcaProgressTab, type OrcaProgressTab } from "../shared/orca-progress-tabs.ts";
140
144
  import { decodeSubagentCapabilityCeiling, SUBAGENT_CAPABILITY_CEILING_ENV, type ResolvedSubagentCapabilityCeiling } from "../shared/capability-ceiling.ts";
141
145
  import {
@@ -165,6 +169,7 @@ interface SubagentRunConfig {
165
169
  sessionDir?: string;
166
170
  asyncDir: string;
167
171
  sessionId?: string | null;
172
+ completionOwnerId?: string;
168
173
  piPackageRoot?: string;
169
174
  piArgv1?: string;
170
175
  worktreeSetupHook?: string;
@@ -217,6 +222,7 @@ interface StepResult {
217
222
  savedOutputPath?: string;
218
223
  skipped?: boolean;
219
224
  interrupted?: boolean;
225
+ detached?: boolean;
220
226
  timedOut?: boolean;
221
227
  stopped?: boolean;
222
228
  processSignal?: string | null;
@@ -249,8 +255,9 @@ interface StepResult {
249
255
  watchdog?: import("../../shared/types.ts").ChildWatchdogProgress;
250
256
  writerProcesses?: PiWriterProcessInstanceExitV1[];
251
257
  writerAttemptCount?: number;
252
- runner?: ExternalCliRunnerStatus;
258
+ runner?: ExternalCliRunnerStatus | ExternalJobRunnerStatus;
253
259
  externalProcess?: ExternalProcessStatus;
260
+ externalJob?: ExternalJobStatus;
254
261
  }
255
262
 
256
263
  function persistStepArtifacts(input: {
@@ -1057,7 +1064,7 @@ async function exportSessionHtml(sessionFile: string, outputDir: string, piPacka
1057
1064
 
1058
1065
  function createShareLink(htmlPath: string): { shareUrl: string; gistUrl: string } | { error: string } {
1059
1066
  try {
1060
- const auth = spawnSync("gh", ["auth", "status"], { encoding: "utf-8" });
1067
+ const auth = spawnSync("gh", ["auth", "status"], { encoding: "utf-8", windowsHide: true });
1061
1068
  if (auth.status !== 0) {
1062
1069
  return { error: "GitHub CLI is not logged in. Run 'gh auth login' first." };
1063
1070
  }
@@ -1066,7 +1073,7 @@ function createShareLink(htmlPath: string): { shareUrl: string; gistUrl: string
1066
1073
  }
1067
1074
 
1068
1075
  try {
1069
- const result = spawnSync("gh", ["gist", "create", htmlPath], { encoding: "utf-8" });
1076
+ const result = spawnSync("gh", ["gist", "create", htmlPath], { encoding: "utf-8", windowsHide: true });
1070
1077
  if (result.status !== 0) {
1071
1078
  const err = (result.stderr || "").trim() || "Failed to create gist.";
1072
1079
  return { error: err };
@@ -1109,6 +1116,7 @@ function writeRunLog(
1109
1116
  shareUrl?: string;
1110
1117
  shareError?: string;
1111
1118
  },
1119
+ write: (filePath: string, content: string) => void = (filePath, content) => fs.writeFileSync(filePath, content, "utf-8"),
1112
1120
  ): void {
1113
1121
  const lines: string[] = [];
1114
1122
  lines.push(`# Subagent run ${input.id}`);
@@ -1138,7 +1146,7 @@ function writeRunLog(
1138
1146
  }
1139
1147
  lines.push(input.summary.trim() || "(no output)");
1140
1148
  lines.push("");
1141
- fs.writeFileSync(logPath, lines.join("\n"), "utf-8");
1149
+ write(logPath, lines.join("\n"));
1142
1150
  }
1143
1151
 
1144
1152
  /** Context for running a single step */
@@ -1183,6 +1191,7 @@ interface SingleStepContext {
1183
1191
  onChildEvent?: (event: ChildEvent) => void;
1184
1192
  onWriterProcess?: (writer: { state: "none" | "spawning" } | { state: "running"; pid: number }) => void;
1185
1193
  onExternalProcess?: (process: ExternalProcessStatus) => void;
1194
+ onExternalJob?: (status: ExternalJobStatus) => void;
1186
1195
  skipAcceptance?: () => boolean;
1187
1196
  orcaProgressTab?: OrcaProgressTab;
1188
1197
  }
@@ -1360,6 +1369,69 @@ async function runSingleStepInner(
1360
1369
  });
1361
1370
  }
1362
1371
 
1372
+ if (step.runner?.type === "external-job") {
1373
+ const runner: ExternalJobRunnerStatus = {
1374
+ type: "external-job",
1375
+ provider: step.runner.provider,
1376
+ options: step.runner.options ?? {},
1377
+ capabilities: { stop: false, steer: false, resume: false, structuredOutput: false, toolEvents: false },
1378
+ };
1379
+ const outputSnapshot = captureSingleOutputSnapshot(step.outputPath);
1380
+ const external = await runExternalJob(omitUndefinedProperties({
1381
+ provider: runner.provider,
1382
+ options: runner.options,
1383
+ cwd: step.cwd ?? ctx.cwd,
1384
+ prompt: buildExternalCliPrompt(step.systemPrompt ?? "", task),
1385
+ asyncDir: path.dirname(ctx.outputFile),
1386
+ stepIndex: ctx.flatIndex,
1387
+ runId: ctx.id,
1388
+ agent: step.agent,
1389
+ sessionId: step.parentSessionId,
1390
+ registerTimeout: ctx.registerTimeout,
1391
+ registerStop: ctx.registerStop,
1392
+ timeoutMessage: ctx.timeoutMessage,
1393
+ stopMessage: ctx.stopMessage,
1394
+ onExternalJob: ctx.onExternalJob,
1395
+ }));
1396
+ try { fs.writeFileSync(ctx.outputFile, external.output, "utf-8"); } catch { /* Observability output is best-effort. */ }
1397
+ const resolvedOutput = step.outputPath && external.exitCode === 0
1398
+ ? resolveSingleOutput(step.outputPath, external.output, outputSnapshot)
1399
+ : { fullOutput: external.output };
1400
+ const outputReference = resolvedOutput.savedPath ? formatSavedOutputReference(resolvedOutput.savedPath, resolvedOutput.fullOutput) : undefined;
1401
+ const finalizedOutput = finalizeSingleOutput(omitUndefinedProperties({
1402
+ fullOutput: resolvedOutput.fullOutput,
1403
+ outputPath: step.outputPath,
1404
+ outputMode: step.outputMode,
1405
+ exitCode: external.exitCode,
1406
+ savedPath: resolvedOutput.savedPath,
1407
+ outputReference,
1408
+ saveError: resolvedOutput.saveError,
1409
+ }));
1410
+ const artifactErrors = artifactPaths && ctx.artifactConfig?.enabled !== false
1411
+ ? persistStepArtifacts({
1412
+ artifactPaths,
1413
+ artifactConfig: ctx.artifactConfig,
1414
+ output: formatOutputArtifactContent(omitUndefinedProperties({ output: resolvedOutput.fullOutput, error: external.error, metadataPath: ctx.artifactConfig?.includeMetadata === false ? undefined : artifactPaths.metadataPath })),
1415
+ metadata: { runId: ctx.id, agent: step.agent, task: PROMPT_REDACTED, runner, externalJob: external.externalJob, exitCode: external.exitCode, error: external.error, timestamp: Date.now() },
1416
+ })
1417
+ : {};
1418
+ return omitUndefinedProperties({
1419
+ agent: step.agent,
1420
+ context: step.context,
1421
+ output: finalizedOutput.displayOutput,
1422
+ outputState: external.output.trim() ? "present" : "absent",
1423
+ exitCode: external.exitCode,
1424
+ error: external.error,
1425
+ timedOut: external.timedOut,
1426
+ stopped: external.stopped,
1427
+ artifactPaths,
1428
+ outputSaveError: artifactErrors.outputSaveError,
1429
+ metadataSaveError: artifactErrors.metadataSaveError,
1430
+ runner,
1431
+ externalJob: external.externalJob,
1432
+ });
1433
+ }
1434
+
1363
1435
  const candidates = step.modelCandidates && step.modelCandidates.length > 0
1364
1436
  ? step.modelCandidates
1365
1437
  : step.model
@@ -1891,15 +1963,25 @@ type RunnerStatusStep = NonNullable<AsyncStatus["steps"]>[number] & {
1891
1963
  description?: string;
1892
1964
  };
1893
1965
 
1894
- function externalRunnerStatus(runner: SubagentStep["runner"]): ExternalCliRunnerStatus | undefined {
1895
- if (runner?.type !== "external-cli") return undefined;
1896
- return {
1897
- type: "external-cli",
1898
- command: runner.command,
1899
- args: runner.args ?? [],
1900
- promptDelivery: runner.promptDelivery ?? "stdin",
1901
- capabilities: { stop: true, steer: false, resume: false, structuredOutput: false, toolEvents: false },
1902
- };
1966
+ function externalRunnerStatus(runner: SubagentStep["runner"]): ExternalCliRunnerStatus | ExternalJobRunnerStatus | undefined {
1967
+ if (runner?.type === "external-cli") {
1968
+ return {
1969
+ type: "external-cli",
1970
+ command: runner.command,
1971
+ args: runner.args ?? [],
1972
+ promptDelivery: runner.promptDelivery ?? "stdin",
1973
+ capabilities: { stop: true, steer: false, resume: false, structuredOutput: false, toolEvents: false },
1974
+ };
1975
+ }
1976
+ if (runner?.type === "external-job") {
1977
+ return {
1978
+ type: "external-job",
1979
+ provider: runner.provider,
1980
+ options: runner.options ?? {},
1981
+ capabilities: { stop: false, steer: false, resume: false, structuredOutput: false, toolEvents: false },
1982
+ };
1983
+ }
1984
+ return undefined;
1903
1985
  }
1904
1986
 
1905
1987
  function appendCapabilityCeilingAppliedEvent(eventsPath: string, runId: string, stepIndex: number, agent: string, result: StepResult): void {
@@ -1948,6 +2030,7 @@ function markParallelGroupSetupFailure(input: {
1948
2030
  asyncDir: string;
1949
2031
  runId: string;
1950
2032
  stepIndex: number;
2033
+ writeStatus: (status: RunnerStatusPayload) => void;
1951
2034
  }): void {
1952
2035
  for (let taskIndex = 0; taskIndex < input.group.parallel.length; taskIndex++) {
1953
2036
  const flatTaskIndex = input.groupStartFlatIndex + taskIndex;
@@ -1964,7 +2047,7 @@ function markParallelGroupSetupFailure(input: {
1964
2047
  input.statusPayload.currentStep = input.groupStartFlatIndex;
1965
2048
  input.statusPayload.lastUpdate = input.failedAt;
1966
2049
  input.statusPayload.outputFile = path.join(input.asyncDir, `output-${input.groupStartFlatIndex}.log`);
1967
- writeAtomicJson(input.statusPath, input.statusPayload);
2050
+ input.writeStatus(input.statusPayload);
1968
2051
  appendJsonl(input.eventsPath, JSON.stringify({
1969
2052
  type: "subagent.parallel.completed",
1970
2053
  ts: input.failedAt,
@@ -1984,6 +2067,7 @@ function markParallelGroupRunning(input: {
1984
2067
  asyncDir: string;
1985
2068
  runId: string;
1986
2069
  stepIndex: number;
2070
+ writeStatus: (status: RunnerStatusPayload) => void;
1987
2071
  }): void {
1988
2072
  for (let taskIndex = 0; taskIndex < input.group.parallel.length; taskIndex++) {
1989
2073
  const flatTaskIndex = input.groupStartFlatIndex + taskIndex;
@@ -2001,7 +2085,7 @@ function markParallelGroupRunning(input: {
2001
2085
  input.statusPayload.lastActivityAt = input.groupStartTime;
2002
2086
  input.statusPayload.lastUpdate = input.groupStartTime;
2003
2087
  input.statusPayload.outputFile = path.join(input.asyncDir, `output-${input.groupStartFlatIndex}.log`);
2004
- writeAtomicJson(input.statusPath, input.statusPayload);
2088
+ input.writeStatus(input.statusPayload);
2005
2089
  appendJsonl(input.eventsPath, JSON.stringify({
2006
2090
  type: "subagent.parallel.started",
2007
2091
  ts: input.groupStartTime,
@@ -2151,9 +2235,6 @@ async function runSubagent(
2151
2235
  let stopped = false;
2152
2236
  let turnBudgetExceeded = false;
2153
2237
  let usageBudgetExceeded = false;
2154
- let checkpointRejected = false;
2155
- let pendingCheckpointDecision: "approved" | "rejected" | undefined;
2156
- let wakeCheckpointDecision: (() => void) | undefined;
2157
2238
  const timeoutMessage = config.timeoutMs !== undefined ? `Subagent timed out after ${config.timeoutMs}ms.` : undefined;
2158
2239
  const stopMessage = "Subagent stopped by user.";
2159
2240
  const timeoutAbortController = new AbortController();
@@ -2168,19 +2249,6 @@ async function runSubagent(
2168
2249
  let flatStepCount = 0;
2169
2250
  for (let stepIndex = 0; stepIndex < steps.length; stepIndex++) {
2170
2251
  const step = steps[stepIndex]!;
2171
- if (isCheckpointRunnerStep(step)) {
2172
- initialStatusSteps.push(omitUndefinedProperties({
2173
- agent: `checkpoint:${step.checkpoint}`,
2174
- phase: step.phase,
2175
- label: step.label ?? step.checkpoint,
2176
- status: "pending",
2177
- checkpoint: { name: step.checkpoint, ...(step.message ? { message: step.message } : {}), status: "pending", stepIndex },
2178
- recentTools: [],
2179
- recentOutput: [],
2180
- }));
2181
- flatStepCount++;
2182
- continue;
2183
- }
2184
2252
  if (isParallelGroup(step)) {
2185
2253
  parallelGroups.push({ start: flatStepCount, count: step.parallel.length, stepIndex });
2186
2254
  for (const task of step.parallel) {
@@ -2272,8 +2340,9 @@ async function runSubagent(
2272
2340
  lifecycleArtifactVersion: SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
2273
2341
  runId: id,
2274
2342
  ...(config.sessionId ? { sessionId: config.sessionId } : {}),
2343
+ ...(config.completionOwnerId ? { completionOwnerId: config.completionOwnerId } : {}),
2275
2344
  mode: config.resultMode ?? (flatSteps.length > 1 ? "chain" : "single"),
2276
- ...(config.nestedRoute ? { isNested: true } : {}),
2345
+ ...(config.nestedSelf ? { isNested: true } : {}),
2277
2346
  state: "running",
2278
2347
  steering: createSteeringStatus(),
2279
2348
  lastActivityAt: overallStartTime,
@@ -2303,9 +2372,37 @@ async function runSubagent(
2303
2372
  outputFile: path.join(asyncDir, "output-0.log"),
2304
2373
  });
2305
2374
 
2306
- fs.mkdirSync(asyncDir, { recursive: true });
2307
- writeAtomicJson(statusPath, statusPayload);
2308
- updateActiveRunIndex(asyncDir, statusPayload.state, statusPayload.toolCallId);
2375
+ let lastIndexedStatusState: AsyncStatus["state"] | undefined;
2376
+ const indexPersistence = createCapacityResilientJsonWriter({
2377
+ keepAlive: true,
2378
+ onSuccess: (_filePath, payload) => {
2379
+ lastIndexedStatusState = (payload as { state: AsyncStatus["state"] }).state;
2380
+ },
2381
+ onError: (error, filePath) => console.error(`Failed to update async run index '${filePath}':`, error),
2382
+ });
2383
+ const queueActiveRunIndex = (): void => {
2384
+ const state = statusPayload.state;
2385
+ if (state === lastIndexedStatusState && indexPersistence.pendingCount() === 0) return;
2386
+ indexPersistence.write(asyncDir, { state, toolCallId: statusPayload.toolCallId }, (_filePath, payload) => {
2387
+ const indexPayload = payload as { state: AsyncStatus["state"]; toolCallId?: string };
2388
+ updateActiveRunIndex(asyncDir, indexPayload.state, indexPayload.toolCallId, { retryCapacityErrors: true });
2389
+ });
2390
+ };
2391
+ const runPersistence = createCapacityResilientJsonWriter({
2392
+ keepAlive: true,
2393
+ onSuccess: (filePath) => {
2394
+ if (filePath === statusPath) queueActiveRunIndex();
2395
+ },
2396
+ onError: (error, filePath) => console.error(`Failed to persist async run state '${filePath}':`, error),
2397
+ });
2398
+ try {
2399
+ fs.mkdirSync(asyncDir, { recursive: true });
2400
+ } catch (error) {
2401
+ if (!isStorageCapacityError(error)) throw error;
2402
+ console.error(`Failed to prepare async run storage '${asyncDir}' while storage is full:`, error);
2403
+ }
2404
+ runPersistence.write(statusPath, statusPayload);
2405
+
2309
2406
  let pendingParallelUsageCost: CostSummary = { inputTokens: 0, outputTokens: 0, costUsd: 0 };
2310
2407
  const currentUsageTotals = (): CostSummary => {
2311
2408
  const cost = results.reduce<CostSummary>((sum, result) => ({
@@ -2360,7 +2457,6 @@ async function runSubagent(
2360
2457
  node.status = normalize(step.status);
2361
2458
  setOptionalProperty(node, "error", step.error);
2362
2459
  setOptionalProperty(node, "acceptanceStatus", step.acceptance?.status);
2363
- setOptionalProperty(node, "checkpoint", step.checkpoint);
2364
2460
  }
2365
2461
  if (statusPayload.currentStep === node.flatIndex) graph.currentNodeId = node.id;
2366
2462
  }
@@ -2379,17 +2475,15 @@ async function runSubagent(
2379
2475
  statusPayload.workflowGraph = graph;
2380
2476
  };
2381
2477
  let finalResultCommitted = false;
2382
- let lastIndexedActiveState = isActiveAsyncState(statusPayload.state);
2383
2478
  const statusResultState = (): AsyncStatus["state"] | undefined => {
2384
2479
  if (statusPayload.state === "running" || statusPayload.state === "queued") return undefined;
2385
- if (statusPayload.state === "paused" && statusPayload.checkpoint?.status === "pending") return undefined;
2386
2480
  return statusPayload.state;
2387
2481
  };
2388
2482
  const statusResultSummary = (state: AsyncStatus["state"]): string => {
2389
2483
  if (statusPayload.error) return statusPayload.error;
2390
2484
  if (state === "paused") return "Paused after interrupt. Waiting for explicit next action.";
2391
2485
  if (state === "stopped") return stopMessage;
2392
- if (state === "rejected") return statusPayload.checkpoint?.name ? `Checkpoint '${statusPayload.checkpoint.name}' rejected.` : "Subagent rejected.";
2486
+ if (state === "rejected") return "Subagent rejected.";
2393
2487
  return state === "complete" ? "Subagent completed." : "Subagent failed.";
2394
2488
  };
2395
2489
  const statusResultSuccess = (state: AsyncStatus["state"], step: RunnerStatusStep): boolean | undefined => {
@@ -2404,7 +2498,7 @@ async function runSubagent(
2404
2498
  if (!state || finalResultCommitted || !config.sessionId) return;
2405
2499
  const now = statusPayload.endedAt ?? statusPayload.lastUpdate ?? Date.now();
2406
2500
  const summary = statusResultSummary(state);
2407
- writePendingAsyncResultFile(resultPath, omitUndefinedProperties({
2501
+ runPersistence.write(resultPath, omitUndefinedProperties({
2408
2502
  lifecycleArtifactVersion: SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
2409
2503
  id,
2410
2504
  runId: id,
@@ -2415,7 +2509,6 @@ async function runSubagent(
2415
2509
  summary,
2416
2510
  error: state === "failed" || state === "stopped" || state === "rejected" ? summary : undefined,
2417
2511
  stopped: state === "stopped" ? true : undefined,
2418
- checkpoint: statusPayload.checkpoint,
2419
2512
  results: statusPayload.steps.map((step) => omitUndefinedProperties({
2420
2513
  agent: step.agent,
2421
2514
  output: step.status === "complete" || step.status === "completed" ? "" : step.error ?? summary,
@@ -2432,18 +2525,14 @@ async function runSubagent(
2432
2525
  asyncDir,
2433
2526
  cwd,
2434
2527
  sessionId: config.sessionId,
2528
+ completionOwnerId: config.completionOwnerId,
2435
2529
  sessionFile: statusPayload.sessionFile ?? latestSessionFile,
2436
- }));
2530
+ }), (filePath, payload) => writePendingAsyncResultFile(filePath, payload as Record<string, unknown>));
2437
2531
  };
2438
2532
  const writeStatusPayloadNow = (): void => {
2439
2533
  refreshWorkflowGraph();
2440
2534
  writeRecoverableStatusResult();
2441
- writeAtomicJson(statusPath, statusPayload);
2442
- const activeState = isActiveAsyncState(statusPayload.state);
2443
- if (activeState !== lastIndexedActiveState) {
2444
- updateActiveRunIndex(asyncDir, statusPayload.state, statusPayload.toolCallId);
2445
- }
2446
- lastIndexedActiveState = activeState;
2535
+ runPersistence.write(statusPath, statusPayload);
2447
2536
  emitNestedSelfEvent(statusPayload.state === "running" || statusPayload.state === "queued" ? "subagent.nested.updated" : "subagent.nested.completed");
2448
2537
  };
2449
2538
  const statusWriteCoalescer = createFileCoalescer(writeStatusPayloadNow, 100);
@@ -2459,6 +2548,11 @@ async function runSubagent(
2459
2548
  statusPayload.lastUpdate = Date.now();
2460
2549
  writeStatusPayload();
2461
2550
  };
2551
+ const updateExternalJob = (index: number, externalJob: ExternalJobStatus): void => {
2552
+ requiredStatusStep(statusPayload, index).externalJob = externalJob;
2553
+ statusPayload.lastUpdate = Date.now();
2554
+ writeStatusPayload();
2555
+ };
2462
2556
  const registerStepInterrupt = (flatIndex: number, interrupt: (() => void) | undefined): void => {
2463
2557
  if (!interrupt) {
2464
2558
  activeChildInterrupts.delete(flatIndex);
@@ -2754,8 +2848,16 @@ async function runSubagent(
2754
2848
  .sort((left, right) => left.startedAt - right.startedAt)[0];
2755
2849
  const supervisorAttentionSteps = new Map<number, ActivityState | undefined>();
2756
2850
  const mutatingFailureWindowMs = 5 * 60_000;
2757
- const appendControlEvent = (event: ReturnType<typeof buildControlEvent>) => {
2851
+ const appendControlEvent = (rawEvent: ReturnType<typeof buildControlEvent>) => {
2758
2852
  if (!controlConfig.enabled) return;
2853
+ const contextStep = statusPayload.steps[rawEvent.index ?? statusPayload.currentStep ?? 0];
2854
+ const event = {
2855
+ ...rawEvent,
2856
+ ...(contextStep?.workflowKey ?? statusPayload.workflowKey ? { workflowKey: contextStep?.workflowKey ?? statusPayload.workflowKey } : {}),
2857
+ ...(contextStep?.phase ? { phase: contextStep.phase } : {}),
2858
+ ...(contextStep?.label ? { label: contextStep.label } : {}),
2859
+ ...(contextStep?.description ? { taskPreview: contextStep.description } : {}),
2860
+ };
2759
2861
  const childIntercomTarget = config.childIntercomTargets?.[event.index ?? statusPayload.currentStep];
2760
2862
  const channels = event.type === "active_long_running"
2761
2863
  ? controlConfig.notifyChannels.filter((channel) => channel !== "intercom")
@@ -3378,10 +3480,6 @@ async function runSubagent(
3378
3480
  onInterrupt: interruptRunner,
3379
3481
  onTimeout: timeoutRunner,
3380
3482
  onStop: stopRunner,
3381
- onCheckpointDecision: (decision) => {
3382
- pendingCheckpointDecision = decision;
3383
- wakeCheckpointDecision?.();
3384
- },
3385
3483
  onSteer: (request) => {
3386
3484
  const targetStep = request.targetIndex !== undefined ? statusPayload.steps[request.targetIndex] : undefined;
3387
3485
  if (targetStep?.status === "pending") {
@@ -3432,20 +3530,6 @@ async function runSubagent(
3432
3530
 
3433
3531
  let flatIndex = 0;
3434
3532
  let stepCursor = 0;
3435
- const waitForCheckpointDecision = async (): Promise<"approved" | "rejected" | undefined> => {
3436
- while (!pendingCheckpointDecision && !interrupted && !timedOut && !stopped) {
3437
- await new Promise<void>((resolve) => {
3438
- wakeCheckpointDecision = resolve;
3439
- const timer = setTimeout(resolve, POLL_INTERVAL_MS);
3440
- timer.unref?.();
3441
- });
3442
- wakeCheckpointDecision = undefined;
3443
- }
3444
- const decision = pendingCheckpointDecision;
3445
- pendingCheckpointDecision = undefined;
3446
- return decision;
3447
- };
3448
-
3449
3533
  while (true) {
3450
3534
  if (interrupted || timedOut || stopped || turnBudgetExceeded) break;
3451
3535
  consumePendingAppendRequests();
@@ -3463,64 +3547,6 @@ async function runSubagent(
3463
3547
  const stepIndex = stepCursor++;
3464
3548
  const step = steps[stepIndex]!;
3465
3549
 
3466
- if (isCheckpointRunnerStep(step)) {
3467
- const now = Date.now();
3468
- const statusStep = statusPayload.steps[flatIndex];
3469
- const checkpoint = { name: step.checkpoint, ...(step.message ? { message: step.message } : {}), status: "pending" as const, stepIndex };
3470
- statusPayload.state = "paused";
3471
- statusPayload.currentStep = flatIndex;
3472
- statusPayload.checkpoint = checkpoint;
3473
- delete statusPayload.activityState;
3474
- statusPayload.lastUpdate = now;
3475
- if (statusStep) {
3476
- statusStep.status = "paused";
3477
- statusStep.startedAt = now;
3478
- statusStep.checkpoint = checkpoint;
3479
- }
3480
- writeStatusPayload();
3481
- appendJsonl(eventsPath, JSON.stringify({ type: "subagent.checkpoint.paused", ts: now, runId: id, stepIndex, checkpoint }));
3482
- const decision = await waitForCheckpointDecision();
3483
- if (decision === "approved") {
3484
- const approvedAt = Date.now();
3485
- const approved = { ...checkpoint, status: "approved" as const, approvedAt };
3486
- statusPayload.state = "running";
3487
- statusPayload.checkpoint = approved;
3488
- if (statusStep) {
3489
- statusStep.status = "complete";
3490
- statusStep.endedAt = approvedAt;
3491
- statusStep.durationMs = approvedAt - now;
3492
- statusStep.exitCode = 0;
3493
- statusStep.checkpoint = approved;
3494
- }
3495
- statusPayload.lastUpdate = approvedAt;
3496
- writeStatusPayload();
3497
- appendJsonl(eventsPath, JSON.stringify({ type: "subagent.checkpoint.approved", ts: approvedAt, runId: id, stepIndex, checkpoint: approved }));
3498
- flatIndex++;
3499
- continue;
3500
- }
3501
- if (decision === "rejected") {
3502
- const rejectedAt = Date.now();
3503
- const rejected = { ...checkpoint, status: "rejected" as const, rejectedAt };
3504
- checkpointRejected = true;
3505
- statusPayload.state = "rejected";
3506
- statusPayload.error = `Checkpoint '${step.checkpoint}' rejected.`;
3507
- statusPayload.checkpoint = rejected;
3508
- if (statusStep) {
3509
- statusStep.status = "rejected";
3510
- statusStep.error = statusPayload.error;
3511
- statusStep.endedAt = rejectedAt;
3512
- statusStep.durationMs = rejectedAt - now;
3513
- statusStep.exitCode = 1;
3514
- statusStep.checkpoint = rejected;
3515
- }
3516
- statusPayload.lastUpdate = rejectedAt;
3517
- writeStatusPayload();
3518
- appendJsonl(eventsPath, JSON.stringify({ type: "subagent.checkpoint.rejected", ts: rejectedAt, runId: id, stepIndex, checkpoint: rejected }));
3519
- break;
3520
- }
3521
- break;
3522
- }
3523
-
3524
3550
  if (isDynamicRunnerGroup(step)) {
3525
3551
  const groupStartFlatIndex = flatIndex;
3526
3552
  let materialized: ReturnType<typeof materializeDynamicParallelStep>;
@@ -3814,6 +3840,7 @@ async function runSubagent(
3814
3840
  onChildEvent: (event) => updateStepFromChildEvent(fi, event),
3815
3841
  onWriterProcess,
3816
3842
  onExternalProcess: (process) => updateExternalProcess(fi, process),
3843
+ onExternalJob: (externalJob) => updateExternalJob(fi, externalJob),
3817
3844
  skipAcceptance: () => timedOut || stopped,
3818
3845
  }), config.deadlineAt);
3819
3846
  const taskEndTime = Date.now();
@@ -4039,6 +4066,7 @@ async function runSubagent(
4039
4066
  asyncDir,
4040
4067
  runId: id,
4041
4068
  stepIndex,
4069
+ writeStatus: () => writeStatusPayload(),
4042
4070
  });
4043
4071
  flatIndex += group.parallel.length;
4044
4072
  break;
@@ -4066,6 +4094,7 @@ async function runSubagent(
4066
4094
  asyncDir,
4067
4095
  runId: id,
4068
4096
  stepIndex,
4097
+ writeStatus: () => writeStatusPayload(),
4069
4098
  });
4070
4099
  flatIndex += group.parallel.length;
4071
4100
  break;
@@ -4097,6 +4126,7 @@ async function runSubagent(
4097
4126
  asyncDir,
4098
4127
  runId: id,
4099
4128
  stepIndex,
4129
+ writeStatus: () => writeStatusPayload(),
4100
4130
  });
4101
4131
  const parallelResults = await mapConcurrent(
4102
4132
  group.parallel,
@@ -4198,6 +4228,7 @@ async function runSubagent(
4198
4228
  onChildEvent: (event) => updateStepFromChildEvent(fi, event),
4199
4229
  onWriterProcess,
4200
4230
  onExternalProcess: (process) => updateExternalProcess(fi, process),
4231
+ onExternalJob: (externalJob) => updateExternalJob(fi, externalJob),
4201
4232
  skipAcceptance: () => timedOut || stopped,
4202
4233
  }), config.deadlineAt);
4203
4234
  if (task.sessionFile) {
@@ -4435,6 +4466,32 @@ async function runSubagent(
4435
4466
  }
4436
4467
  } else {
4437
4468
  const seqStep = step as SubagentStep;
4469
+ let singleWorktreeSetup: WorktreeSetup | undefined;
4470
+ if (seqStep.worktree) {
4471
+ try {
4472
+ singleWorktreeSetup = createWorktrees(cwd, `${id}-s${stepIndex}`, 1, omitUndefinedProperties({
4473
+ agents: [seqStep.agent],
4474
+ setupHook: config.worktreeSetupHook
4475
+ ? omitUndefinedProperties({ hookPath: config.worktreeSetupHook, timeoutMs: config.worktreeSetupHookTimeoutMs })
4476
+ : undefined,
4477
+ baseDir: config.worktreeBaseDir,
4478
+ }));
4479
+ writePendingParallelHandoff({
4480
+ manifestPath: parallelHandoffPath(asyncDir),
4481
+ runId: id,
4482
+ mode: "single",
4483
+ source: "async",
4484
+ cwd,
4485
+ stepIndex,
4486
+ flatStartIndex: flatIndex,
4487
+ setup: singleWorktreeSetup,
4488
+ });
4489
+ } catch (error) {
4490
+ if (singleWorktreeSetup) cleanupWorktrees(singleWorktreeSetup);
4491
+ throw error;
4492
+ }
4493
+ }
4494
+ const singleCwd = singleWorktreeSetup?.worktrees[0]?.agentCwd ?? cwd;
4438
4495
  const stepStartTime = Date.now();
4439
4496
  statusPayload.currentStep = flatIndex;
4440
4497
  requiredStatusStep(statusPayload, flatIndex).status = "running";
@@ -4458,8 +4515,11 @@ async function runSubagent(
4458
4515
  }));
4459
4516
 
4460
4517
  flushPendingStepSteers(flatIndex);
4461
- const singleResult = await runSingleStepWithTimeout(seqStep, compactOptional<SingleStepContext>({
4462
- previousOutput, placeholder, cwd, sessionEnabled,
4518
+ const executionStep = singleWorktreeSetup ? { ...seqStep, cwd: singleCwd } : seqStep;
4519
+ let singleResult: Awaited<ReturnType<typeof runSingleStepWithTimeout>>;
4520
+ try {
4521
+ singleResult = await runSingleStepWithTimeout(executionStep, compactOptional<SingleStepContext>({
4522
+ previousOutput, placeholder, cwd: singleCwd, sessionEnabled,
4463
4523
  outputs: statusPayload.mode === "single" ? undefined : outputs,
4464
4524
  sessionDir: config.sessionDir,
4465
4525
  artifactsDir, artifactConfig, id,
@@ -4489,8 +4549,13 @@ async function runSubagent(
4489
4549
  onChildEvent: (event) => updateStepFromChildEvent(flatIndex, event),
4490
4550
  onWriterProcess,
4491
4551
  onExternalProcess: (process) => updateExternalProcess(flatIndex, process),
4552
+ onExternalJob: (externalJob) => updateExternalJob(flatIndex, externalJob),
4492
4553
  skipAcceptance: () => timedOut || stopped,
4493
- }), config.deadlineAt);
4554
+ }), config.deadlineAt);
4555
+ } catch (error) {
4556
+ if (singleWorktreeSetup) cleanupWorktrees(singleWorktreeSetup);
4557
+ throw error;
4558
+ }
4494
4559
  if (seqStep.sessionFile) {
4495
4560
  latestSessionFile = seqStep.sessionFile;
4496
4561
  }
@@ -4539,6 +4604,7 @@ async function runSubagent(
4539
4604
  toolBudgetBlocked: singleResult.toolBudgetBlocked,
4540
4605
  runner: singleResult.runner,
4541
4606
  externalProcess: singleResult.externalProcess,
4607
+ externalJob: singleResult.externalJob,
4542
4608
  }));
4543
4609
  if (seqStep.outputName) {
4544
4610
  outputs[seqStep.outputName] = outputEntryFromAsyncResult({
@@ -4629,6 +4695,48 @@ async function runSubagent(
4629
4695
  durationMs: stepEndTime - stepStartTime,
4630
4696
  tokens: stepTokens,
4631
4697
  }));
4698
+ if (singleWorktreeSetup && !singleResult.detached) {
4699
+ const diffs = diffWorktrees(singleWorktreeSetup, [seqStep.agent], path.join(asyncDir, "worktree-diffs", `step-${stepIndex}`));
4700
+ const diffSummary = formatWorktreeDiffSummary(diffs);
4701
+ const manifestPath = parallelHandoffPath(asyncDir);
4702
+ const handoff = {
4703
+ manifestPath,
4704
+ runId: id,
4705
+ mode: "single" as const,
4706
+ source: "async" as const,
4707
+ cwd,
4708
+ stepIndex,
4709
+ flatStartIndex: flatIndex,
4710
+ setup: singleWorktreeSetup,
4711
+ diffs,
4712
+ results: [{
4713
+ agent: singleResult.agent,
4714
+ status: singleResult.stopped ? "stopped" as const : singleResult.interrupted ? "paused" as const : singleResult.exitCode === 0 ? "completed" as const : "failed" as const,
4715
+ summary: singleResult.output || singleResult.error || "(no output)",
4716
+ ...(singleResult.artifactPaths?.outputPath ? { outputPath: singleResult.artifactPaths.outputPath } : {}),
4717
+ ...(singleResult.structuredOutput !== undefined ? { structuredOutput: singleResult.structuredOutput } : {}),
4718
+ ...(singleResult.structuredOutputPath ? { structuredOutputPath: singleResult.structuredOutputPath } : {}),
4719
+ ...(singleResult.sessionFile ? { sessionPath: singleResult.sessionFile } : {}),
4720
+ }],
4721
+ };
4722
+ try {
4723
+ writeParallelHandoffGroup(handoff);
4724
+ const cleanup = cleanupWorktrees(singleWorktreeSetup, {
4725
+ kind: "preserve",
4726
+ capturedDiffs: diffs,
4727
+ handoffManifestPath: manifestPath,
4728
+ ...(config.parentWorkflowRunId && singleResult.sessionFile && fs.existsSync(singleResult.sessionFile) && !singleResult.stopped
4729
+ ? { cleanupBlocker: "retained child resume requires managed worktree cwd" }
4730
+ : {}),
4731
+ });
4732
+ statusPayload.parallelHandoff = writeParallelHandoffGroup({ ...handoff, cleanup });
4733
+ previousOutput = [previousOutput, diffSummary, formatParallelHandoffReference(statusPayload.parallelHandoff)].filter(Boolean).join("\n\n");
4734
+ } catch (error) {
4735
+ previousOutput = [previousOutput, diffSummary, formatParallelHandoffError(error)].filter(Boolean).join("\n\n");
4736
+ }
4737
+ writeStatusPayload();
4738
+ }
4739
+
4632
4740
  if (singleResult.completionGuardTriggered) {
4633
4741
  const event = buildControlEvent(omitUndefinedProperties({
4634
4742
  from: requiredStatusStep(statusPayload, flatIndex).activityState,
@@ -4730,8 +4838,8 @@ async function runSubagent(
4730
4838
  stopped: result.stopped,
4731
4839
  turnBudgetExceeded: result.turnBudgetExceeded,
4732
4840
  })));
4733
- statusPayload.state = checkpointRejected ? "rejected" : stopped || signalTerminated ? "stopped" : timedOut || turnBudgetExceeded || usageBudgetExceeded || statusPayload.error ? "failed" : interrupted ? "paused" : results.every((r) => r.success) ? "complete" : "failed";
4734
- closeSteerInbox(asyncDir, statusPayload.state);
4841
+ statusPayload.state = stopped || signalTerminated ? "stopped" : timedOut || turnBudgetExceeded || usageBudgetExceeded || statusPayload.error ? "failed" : interrupted ? "paused" : results.every((r) => r.success) ? "complete" : "failed";
4842
+ closeSteerInbox(asyncDir, statusPayload.state, (filePath, payload) => runPersistence.write(filePath, payload));
4735
4843
  disposeControlInbox();
4736
4844
  for (const request of consumeSteerRequests(asyncDir)) deliverSteerRequest(request);
4737
4845
  const effectiveSessionFile = sessionFile ?? latestSessionFile;
@@ -4782,15 +4890,16 @@ async function runSubagent(
4782
4890
  statusPayload.error = `Step failed: ${failedStep.agent}`;
4783
4891
  }
4784
4892
  }
4893
+ writeStatusPayload();
4785
4894
  try {
4786
- writeAsyncResultFile(resultPath, {
4895
+ runPersistence.write(resultPath, {
4787
4896
  lifecycleArtifactVersion: SUBAGENT_LIFECYCLE_ARTIFACT_VERSION,
4788
4897
  id,
4789
4898
  agent: agentName,
4790
4899
  mode: resultMode,
4791
- success: !checkpointRejected && !stopped && !timedOut && !turnBudgetExceeded && !usageBudgetExceeded && !interrupted && !signalTerminated && results.every((r) => r.success),
4792
- state: checkpointRejected ? "rejected" : stopped || signalTerminated ? "stopped" : timedOut || turnBudgetExceeded || usageBudgetExceeded ? "failed" : interrupted ? "paused" : results.every((r) => r.success) ? "complete" : "failed",
4793
- summary: checkpointRejected ? (statusPayload.error ?? "Checkpoint rejected.") : stopped ? stopMessage : signalTerminated ? (statusPayload.error ?? "Subagent process terminated by signal.") : timedOut ? (timeoutMessage ?? "Subagent timed out.") : turnBudgetExceeded ? (statusPayload.error ?? "Subagent exceeded turn budget.") : usageBudgetExceeded ? (statusPayload.error ?? "Usage budget exhausted.") : interrupted ? "Paused after interrupt. Waiting for explicit next action." : summary,
4900
+ success: !stopped && !timedOut && !turnBudgetExceeded && !usageBudgetExceeded && !interrupted && !signalTerminated && results.every((r) => r.success),
4901
+ state: stopped || signalTerminated ? "stopped" : timedOut || turnBudgetExceeded || usageBudgetExceeded ? "failed" : interrupted ? "paused" : results.every((r) => r.success) ? "complete" : "failed",
4902
+ summary: stopped ? stopMessage : signalTerminated ? (statusPayload.error ?? "Subagent process terminated by signal.") : timedOut ? (timeoutMessage ?? "Subagent timed out.") : turnBudgetExceeded ? (statusPayload.error ?? "Subagent exceeded turn budget.") : usageBudgetExceeded ? (statusPayload.error ?? "Usage budget exhausted.") : interrupted ? "Paused after interrupt. Waiting for explicit next action." : summary,
4794
4903
  ...(config.timeoutMs !== undefined ? { timeoutMs: config.timeoutMs } : {}),
4795
4904
  ...(config.deadlineAt !== undefined ? { deadlineAt: config.deadlineAt } : {}),
4796
4905
  ...(statusPayload.turnBudget ? { turnBudget: statusPayload.turnBudget } : {}),
@@ -4799,7 +4908,6 @@ async function runSubagent(
4799
4908
  ...(statusPayload.toolBudget ? { toolBudget: statusPayload.toolBudget } : {}),
4800
4909
  ...(statusPayload.toolBudgetBlocked ? { toolBudgetBlocked: true } : {}),
4801
4910
  ...(statusPayload.usageBudget ? { usageBudget: statusPayload.usageBudget } : {}),
4802
- ...(statusPayload.checkpoint ? { checkpoint: statusPayload.checkpoint } : {}),
4803
4911
  ...(stopped ? { stopped: true, error: stopMessage } : timedOut ? { timedOut: true, error: timeoutMessage ?? "Subagent timed out." } : turnBudgetExceeded ? { error: statusPayload.error ?? "Subagent exceeded turn budget." } : usageBudgetExceeded ? { error: statusPayload.error ?? "Usage budget exhausted." } : {}),
4804
4912
  results: results.map((r) => omitUndefinedProperties({
4805
4913
  agent: r.agent,
@@ -4837,6 +4945,7 @@ async function runSubagent(
4837
4945
  runtimeAcknowledgedExtensions: r.runtimeAcknowledgedExtensions,
4838
4946
  runner: r.runner,
4839
4947
  externalProcess: r.externalProcess,
4948
+ externalJob: r.externalJob,
4840
4949
  execution: r.execution,
4841
4950
  review: r.review,
4842
4951
  effects: r.effects,
@@ -4855,13 +4964,12 @@ async function runSubagent(
4855
4964
  capabilityAudit: statusPayload.capabilityAudit,
4856
4965
  ...(config.parentWorkflowRunId ? { parentWorkflowRunId: config.parentWorkflowRunId } : {}),
4857
4966
  ...(config.workflowKey ? { workflowKey: config.workflowKey } : {}),
4858
- exitCode: checkpointRejected || stopped || timedOut || turnBudgetExceeded || usageBudgetExceeded ? 1 : interrupted || results.every((r) => r.success) ? 0 : 1,
4967
+ exitCode: stopped || timedOut || turnBudgetExceeded || usageBudgetExceeded ? 1 : interrupted || results.every((r) => r.success) ? 0 : 1,
4859
4968
  timestamp: runEndedAt,
4860
4969
  durationMs: runEndedAt - overallStartTime,
4861
4970
  totalTokens: statusPayload.totalTokens,
4862
4971
  totalCost: finalTotalCost,
4863
4972
  usageBudget: statusPayload.usageBudget,
4864
- checkpoint: statusPayload.checkpoint,
4865
4973
  truncated,
4866
4974
  artifactsDir,
4867
4975
  cwd,
@@ -4870,6 +4978,7 @@ async function runSubagent(
4870
4978
  launchResolvedExtensions: config.launchResolvedExtensions,
4871
4979
  runtimeAcknowledgedExtensions: singleRuntimeAcknowledgedExtensions,
4872
4980
  sessionId: config.sessionId,
4981
+ completionOwnerId: config.completionOwnerId,
4873
4982
  sessionFile: effectiveSessionFile,
4874
4983
  intercomTarget: config.controlIntercomTarget,
4875
4984
  shareUrl,
@@ -4877,7 +4986,7 @@ async function runSubagent(
4877
4986
  shareError,
4878
4987
  ...(taskIndex !== undefined && { taskIndex }),
4879
4988
  ...(totalTasks !== undefined && { totalTasks }),
4880
- });
4989
+ }, (filePath, payload) => { writeAsyncResultFile(filePath, payload as Record<string, unknown>); });
4881
4990
  finalResultCommitted = true;
4882
4991
  } catch (err) {
4883
4992
  const message = `Failed to write result file ${resultPath}: ${err instanceof Error ? err.message : String(err)}`;
@@ -4918,7 +5027,11 @@ async function runSubagent(
4918
5027
  sessionFile: effectiveSessionFile,
4919
5028
  shareUrl,
4920
5029
  shareError,
5030
+ }), (filePath, content) => runPersistence.write(filePath, { content }, (_path, payload) => {
5031
+ fs.writeFileSync(_path, (payload as { content: string }).content, "utf-8");
4921
5032
  }));
5033
+ if (runPersistence.pendingCount() === 0) runPersistence.dispose();
5034
+ if (indexPersistence.pendingCount() === 0) indexPersistence.dispose();
4922
5035
  if (config.runnerProcessInstanceId) {
4923
5036
  const writers: Record<string, PiWriterProcessInstanceExitV1[]> = {};
4924
5037
  const expectedWriters: Record<string, number> = {};