@agwab/pi-workflow 0.2.1 → 0.3.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 (70) hide show
  1. package/dist/compiler.js +6 -8
  2. package/dist/dynamic-decision.d.ts +0 -1
  3. package/dist/dynamic-decision.js +0 -7
  4. package/dist/dynamic-profiles.d.ts +0 -1
  5. package/dist/dynamic-profiles.js +0 -3
  6. package/dist/engine-run-graph.d.ts +1 -0
  7. package/dist/engine-run-graph.js +142 -2
  8. package/dist/engine.d.ts +5 -0
  9. package/dist/engine.js +112 -27
  10. package/dist/extension.d.ts +2 -1
  11. package/dist/extension.js +27 -6
  12. package/dist/index.d.ts +3 -3
  13. package/dist/index.js +2 -1
  14. package/dist/store.js +55 -11
  15. package/dist/subagent-backend.js +155 -29
  16. package/dist/types.d.ts +6 -0
  17. package/dist/workflow-runtime.js +10 -1
  18. package/dist/workflow-view.js +3 -1
  19. package/dist/workflow-web-source-extension.js +167 -48
  20. package/dist/workflow-web-source.d.ts +2 -1
  21. package/dist/workflow-web-source.js +84 -19
  22. package/node_modules/@agwab/pi-subagent/README.md +3 -3
  23. package/node_modules/@agwab/pi-subagent/api.mjs +1 -0
  24. package/node_modules/@agwab/pi-subagent/docs/usage.md +63 -12
  25. package/node_modules/@agwab/pi-subagent/package.json +2 -2
  26. package/node_modules/@agwab/pi-subagent/src/api.ts +54 -1
  27. package/node_modules/@agwab/pi-subagent/src/artifacts/registry.ts +9 -4
  28. package/node_modules/@agwab/pi-subagent/src/artifacts/result.ts +8 -0
  29. package/node_modules/@agwab/pi-subagent/src/core/constants.ts +9 -0
  30. package/node_modules/@agwab/pi-subagent/src/core/validation.ts +21 -0
  31. package/node_modules/@agwab/pi-subagent/src/index.ts +995 -573
  32. package/node_modules/@agwab/pi-subagent/src/orchestrate/async.ts +279 -156
  33. package/node_modules/@agwab/pi-subagent/src/orchestrate/interrupt.ts +165 -89
  34. package/node_modules/@agwab/pi-subagent/src/orchestrate/reconcile.ts +111 -65
  35. package/node_modules/@agwab/pi-subagent/src/orchestrate/run-ref.ts +219 -0
  36. package/node_modules/@agwab/pi-subagent/src/orchestrate/run.ts +88 -8
  37. package/node_modules/@agwab/pi-subagent/src/orchestrate/status.ts +614 -298
  38. package/node_modules/@agwab/pi-subagent/src/panel.ts +1352 -560
  39. package/node_modules/@agwab/pi-subagent/src/runners/headless-model.ts +53 -5
  40. package/node_modules/@agwab/pi-subagent/src/runners/tmux.ts +13 -6
  41. package/package.json +2 -2
  42. package/src/compiler.ts +14 -9
  43. package/src/dynamic-decision.ts +0 -11
  44. package/src/dynamic-profiles.ts +0 -4
  45. package/src/engine-run-graph.ts +185 -2
  46. package/src/engine.ts +145 -24
  47. package/src/extension.ts +33 -4
  48. package/src/index.ts +3 -1
  49. package/src/store.ts +74 -11
  50. package/src/subagent-backend.ts +201 -28
  51. package/src/types.ts +6 -0
  52. package/src/workflow-runtime.ts +18 -2
  53. package/src/workflow-view.ts +2 -1
  54. package/src/workflow-web-source-extension.ts +621 -228
  55. package/src/workflow-web-source.ts +118 -28
  56. package/workflows/deep-research/helpers/claim-evidence-gate.mjs +56 -16
  57. package/workflows/deep-research/helpers/final-audit-packet.mjs +1 -4
  58. package/workflows/deep-research/helpers/normalize-input-packet.mjs +1 -1
  59. package/workflows/deep-research/helpers/render-executive.mjs +8 -21
  60. package/workflows/deep-research/helpers/sanitize-verification-candidates.mjs +89 -15
  61. package/workflows/deep-research/schemas/deep-research-executive-render-control.schema.json +0 -1
  62. package/workflows/deep-research/schemas/deep-research-verify-claims-control.schema.json +4 -1
  63. package/workflows/impact-review/spec.json +3 -3
  64. package/workflows/spec-review/helpers/spec-review-pipeline.mjs +1 -8
  65. package/dist/dynamic-loader.d.ts +0 -25
  66. package/dist/dynamic-loader.js +0 -13
  67. package/src/dynamic-loader.ts +0 -49
  68. package/workflows/impact-review/schemas/docs-release-impact-control.schema.json +0 -42
  69. package/workflows/impact-review/schemas/security-performance-impact-control.schema.json +0 -42
  70. package/workflows/impact-review/schemas/state-data-impact-control.schema.json +0 -42
@@ -33,20 +33,23 @@ Every call has an `action`. The default is `run`, so omitting `action` starts a
33
33
  | `action` | Purpose | Key parameters |
34
34
  |---|---|---|
35
35
  | `run` (default) | Start a new subagent run, or launch independent runs in parallel. | `agent`/`task` or `tasks`; plus `sandbox`, `worktree`, `model`, `async`, etc. |
36
- | `status` | Read a run's current state. | `runId`, optional `attemptId` |
37
- | `logs` | Read a run's captured logs. | `runId`, optional `attemptId` |
38
- | `wait` | Block until a run finishes. | `runId`, optional `timeoutMs`, `pollIntervalMs` |
39
- | `interrupt` | Signal a process-backed run. | `runId`, optional `attemptId`, `signal`, `escalateAfterMs`, `killAfterMs`, `reason` |
40
- | `mark-background` | Mark a run as not needed before the final answer. | `runId` |
41
- | `reconcile` | Re-read durable artifacts and repair stale/orphaned state when possible. | `runId` |
36
+ | `status` | Read a run's current state. | `runId`, optional `cwd`, `attemptId` |
37
+ | `logs` | Read a run's captured logs. | `runId`, optional `cwd`, `attemptId` |
38
+ | `wait` | Block until a run finishes. | `runId`, optional `cwd`, `timeoutMs`, `pollIntervalMs` |
39
+ | `interrupt` | Signal a process-backed run. | `runId`, optional `cwd`, `attemptId`, `signal`, `escalateAfterMs`, `killAfterMs`, `reason` |
40
+ | `mark-background` | Mark a run as not needed before the final answer. | `runId`, optional `cwd` |
41
+ | `reconcile` | Re-read durable artifacts and repair stale/orphaned state when possible. | `runId`, optional `cwd` |
42
42
 
43
- State is file-based under `.pi/agent/runs/<run-id>/`. `status`/`logs`/`wait` read those files; `interrupt` sends a real OS signal; `mark-background` updates run metadata; `reconcile` repairs local metadata from durable attempt artifacts without relaunching work.
43
+ State is file-based under `.pi/agent/runs/<run-id>/`. `status`/`logs`/`wait` read those files; `interrupt` sends a real OS signal; `mark-background` updates run metadata; `reconcile` repairs local metadata from durable attempt artifacts without relaunching work. Recent runs also write a global locator pointer, so existing-run actions can often resolve a `runId` even when `cwd` is omitted or the run was launched from another cwd.
44
+
45
+ Parent orchestrators may record descendant state with `recordSubagentChildEvent`, which appends `child.*` events to the parent run's `events.jsonl` (`child.started`, `child.failed`, `child.completed`, or `child.cancelled`). `status` and `/subagent panel` aggregate those into `childSummary`, including failure counts, active child run IDs, and the latest child failure. This keeps parent status distinct from descendant failures and makes retry attempts distinguishable from newly-started child work.
44
46
 
45
47
  Model:
46
48
 
47
49
  ```text
48
50
  run = one subagent execution
49
51
  attempt = one launch attempt
52
+ child = descendant work reported by an orchestrator through child.* events
50
53
  correlationId = optional external trace label
51
54
  ```
52
55
 
@@ -68,6 +71,7 @@ import {
68
71
  waitForSubagent,
69
72
  interruptSubagent,
70
73
  reconcileSubagentRun,
74
+ recordSubagentChildEvent,
71
75
  } from "@agwab/pi-subagent/api";
72
76
 
73
77
  const run = await runSubagent({
@@ -83,9 +87,17 @@ const logs = await getSubagentLogs({ cwd: process.cwd(), runId: run.runId });
83
87
  await waitForSubagent({ cwd: process.cwd(), runId: run.runId, timeoutMs: 300000 });
84
88
  await interruptSubagent({ cwd: process.cwd(), runId: run.runId, reason: "caller cancelled" });
85
89
  await reconcileSubagentRun({ cwd: process.cwd(), runId: run.runId });
90
+ await recordSubagentChildEvent({
91
+ cwd: process.cwd(),
92
+ runId: run.runId,
93
+ event: "failed",
94
+ childRunId: "run_child_123",
95
+ childTaskId: "task-4",
96
+ failureKind: "model",
97
+ });
86
98
  ```
87
99
 
88
- `runSubagent` accepts the same run options as the tool, plus an optional `signal`. Existing-run helpers accept `cwd`, `runId`, optional `attemptId`, and optional `runsDir`. The API is intentionally object-only and does not expose the lower-level runner internals.
100
+ `runSubagent` accepts the same run options as the tool, plus an optional `signal`. Existing-run helpers accept `runId`, optional `cwd`, optional `attemptId`, and optional `runsDir`; when `cwd` is omitted they use the global locator index first and fall back to the current cwd for legacy records. The API is intentionally object-only and does not expose the lower-level runner internals.
89
101
 
90
102
  The code API is ESM-only. Import `@agwab/pi-subagent/api`; do not deep-import internal files such as `src/orchestrate/*` because only documented package subpaths are public.
91
103
 
@@ -128,6 +140,21 @@ Use `concurrency` to cap parallel fan-out:
128
140
  }
129
141
  ```
130
142
 
143
+ For synchronous parallel fan-out, `failFast:true` stops scheduling additional siblings after the first failed result. Add `cancelSiblingsOnFailure:true` to also abort siblings that are already running:
144
+
145
+ ```json
146
+ {
147
+ "failFast": true,
148
+ "cancelSiblingsOnFailure": true,
149
+ "tasks": [
150
+ { "task": "Run check A." },
151
+ { "task": "Run check B." }
152
+ ]
153
+ }
154
+ ```
155
+
156
+ The parallel response includes `totalTasks`, `startedCount`, `skippedCount`, and `failFastTriggered` so callers can distinguish skipped siblings from completed/failed runs. Async parallel launches return once children are started, so fail-fast decisions for later runtime failures must be handled by the parent/workflow layer.
157
+
131
158
  Chain/sequential execution is intentionally not supported by this engine. If step B needs output from step A, keep that sequencing in the parent agent or a workflow layer.
132
159
 
133
160
  ## Async and existing runs
@@ -177,14 +204,26 @@ Interrupt a process-backed run:
177
204
 
178
205
  `interrupt` is conservative. It can signal runs with registered process metadata. Unsupported or already-terminal runs return explicit status rather than pretending cancellation succeeded.
179
206
 
207
+ ### Existing-run resolution
208
+
209
+ For `status`, `logs`, `wait`, `interrupt`, `mark-background`, and `reconcile`, the lookup order is:
210
+
211
+ 1. Use the explicit `cwd`/`runsDir` when provided.
212
+ 2. Otherwise, check the current cwd's `.pi/agent/runs` for legacy/local records.
213
+ 3. Otherwise, resolve `runId` through the global locator index and read the pointed-to run directory.
214
+
215
+ The locator index is only a pointer for finding runs across cwd boundaries. `run.json`, `events.jsonl`, and attempt `result.json` files remain the source of truth.
216
+
180
217
  ## Common run options
181
218
 
182
219
  | Option | Use |
183
220
  |---|---|
184
- | `cwd` | Run from a specific project directory. Existing-run actions also accept `cwd` to find that run registry. |
221
+ | `cwd` | Run from a specific project directory. Existing-run actions accept `cwd` to force a registry location; if omitted, recent runs can be found by global locator and older runs fall back to the current cwd. |
185
222
  | `timeoutMs` | Limit worker execution time for `run`; limit polling duration for `action: "wait"`. Omit it for no runtime kill deadline; `wait` alone defaults to 60s polling. |
186
223
  | `visible` | Use a visible tmux-backed worker (`visible: true`). |
187
224
  | `concurrency` | Cap parallel run fan-out. |
225
+ | `failFast` | For synchronous parallel runs, stop scheduling new siblings after the first failed result. |
226
+ | `cancelSiblingsOnFailure` | For synchronous parallel runs, abort already-running siblings after the first failed result; implies fail-fast scheduling. |
188
227
  | `model` | Select a Pi model/provider for model-backed workers. |
189
228
  | `thinking` / `thinkingLevel` / `reasoningLevel` | Set the reasoning level. |
190
229
  | `tools` | Tool allowlist. With a named agent this may only narrow agent-declared tools; it cannot expand authority. For agentless runs it sets the full tool allowlist. |
@@ -247,8 +286,8 @@ There are three inputs for worktree isolation, in order of preference:
247
286
  | Input | When to use |
248
287
  |---|---|
249
288
  | `worktree` | Primary switch. `true` to isolate; or a string path for an explicit worktree location. |
250
- | `workspace` | Advanced. `"shared" | "worktree" | "auto"`, or `{ mode, path }` for an explicit path. |
251
- | `worktreePolicy` | Advanced. `"auto" | "required" | "never"` to force or forbid isolation. |
289
+ | `workspace` | Advanced. `"shared" \| "worktree" \| "auto"`, or `{ mode, path }` for an explicit path. |
290
+ | `worktreePolicy` | Advanced. `"auto" \| "required" \| "never"` to force or forbid isolation. |
252
291
 
253
292
  Most calls only need `worktree`:
254
293
 
@@ -389,6 +428,8 @@ Runs write durable evidence under:
389
428
 
390
429
  `run.json` records a `parentSessionId` field: the Pi session id of the session that launched the run, injected from the tool context (not a model-settable argument). Consumers (e.g. status panels) can use it to scope a shared per-`cwd` runs directory to the session that owns each run. The field is omitted when no session id is available, and older records simply lack it.
391
430
 
431
+ Recent runs also write a small locator file under Pi's global subagent-run index. A locator contains the `runId`, absolute `cwd`, optional `runsDir`, optional `parentSessionId`, optional `correlationId`, and `updatedAt`. It is not authoritative evidence and can become stale if the pointed-to run directory is moved or deleted; use `run.json`, `events.jsonl`, and attempt `result.json` as the source of truth.
432
+
392
433
  Older `schemaVersion: 1` artifacts under `<run-id>/<task-id>/` are still readable for compatibility.
393
434
 
394
435
  Tool responses return compact status and artifact references rather than raw logs.
@@ -399,7 +440,17 @@ Tool responses return compact status and artifact references rather than raw log
399
440
  /subagent panel
400
441
  ```
401
442
 
402
- The panel shows all/completed/failed filters, run/attempt details, workspace/artifact paths, dependency metadata, event tail, and log tail. The panel is for human inspection; existing-run tool actions remain the programmatic interface.
443
+ The panel shows run/attempt details, workspace/artifact paths, dependency metadata, event tail, and log tail. It has three scopes:
444
+
445
+ - `session`: runs whose `run.json.parentSessionId` matches the current Pi session. This is the default when a session id is available.
446
+ - `cwd`: runs under the current workspace's `.pi/agent/runs`, including legacy records that lack `parentSessionId`.
447
+ - `all`: the global locator index plus current-cwd legacy records.
448
+
449
+ Status filters are `all`, `running`, `completed`, and `failed`. In the `all` status view, the default list shows all active runs plus recent terminal runs only: 20 for `session`/`cwd`, 50 for `all`. The `completed` and `failed` filters use the same recent terminal cap; `running` is uncapped. The header reports `shown/total`, and when older matching runs are hidden, press `m` in the panel to show more; no separate command is needed. The panel keeps a fixed-height layout, uses an internally scrollable detail pane, and never renders raw `parentSessionId` values.
450
+
451
+ Stale or malformed locators are counted in the header and skipped. Active runs whose process metadata is dead and whose heartbeat/update timestamp is stale are rendered read-only as `failed` with failure `stale`; the panel does not mutate or delete records. Use `action:"reconcile"` to repair local registry state from durable artifacts when possible.
452
+
453
+ The panel is for human inspection; existing-run tool actions remain the programmatic interface.
403
454
 
404
455
  ## Development validation
405
456
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agwab/pi-subagent",
3
- "version": "0.3.6",
3
+ "version": "0.4.0",
4
4
  "description": "Minimal subagent runtime for Pi.",
5
5
  "type": "module",
6
6
  "private": false,
@@ -37,7 +37,7 @@
37
37
  "image": "https://raw.githubusercontent.com/AgwaB/pi-subagent/main/assets/subagent-panel.png"
38
38
  },
39
39
  "scripts": {
40
- "check:scripts": "for d in scripts test internal/scripts; do [ -d \"$d\" ] && find \"$d\" -name '*.mjs' -print; done | xargs -n1 node --check",
40
+ "check:scripts": "for d in scripts test; do [ -d \"$d\" ] && find \"$d\" -name '*.mjs' -print; done | xargs -n1 node --check",
41
41
  "check:resolver": "node ./test/checks/resolver.mjs",
42
42
  "check:api": "node ./test/checks/api.mjs",
43
43
  "check:artifacts": "node ./test/checks/artifacts.mjs",
@@ -1,10 +1,16 @@
1
1
  import { resolve } from "node:path";
2
2
  import { loadAgentByName, type AgentDefinition } from "./agents.ts";
3
- import type { ResultEnvelope } from "./artifacts/index.ts";
3
+ import {
4
+ appendRunEvent,
5
+ type ResultEnvelope,
6
+ type RunEvent,
7
+ } from "./artifacts/index.ts";
4
8
  import type {
5
9
  ExecutionMode,
10
+ FailureKind,
6
11
  ResolveInput,
7
12
  ResolvedBackend,
13
+ Status,
8
14
  } from "./core/constants.ts";
9
15
  import { resolveBackend } from "./core/resolver.ts";
10
16
  import { validateResolveInput } from "./core/validation.ts";
@@ -22,6 +28,7 @@ import {
22
28
  type ReconcileSubagentRunOptions as ReconcileRunOptions,
23
29
  type ReconcileSubagentRunResult,
24
30
  } from "./orchestrate/reconcile.ts";
31
+ import { resolveRunRef } from "./orchestrate/run-ref.ts";
25
32
  import {
26
33
  runParallelSubagentTasks,
27
34
  runSubagentTask,
@@ -50,6 +57,16 @@ export type WaitForSubagentOptions = WaitForRunOptions;
50
57
  export type InterruptSubagentOptions = InterruptRunOptions;
51
58
  export type ReconcileSubagentOptions = ReconcileRunOptions;
52
59
 
60
+ export interface RecordSubagentChildEventOptions extends RunStatusRef {
61
+ event: "started" | "updated" | "completed" | "failed" | "cancelled";
62
+ childRunId: string;
63
+ workflowRunId?: string;
64
+ childTaskId?: string;
65
+ status?: Status;
66
+ failureKind?: FailureKind | string | null;
67
+ message?: string;
68
+ }
69
+
53
70
  export class SubagentValidationError extends Error {
54
71
  readonly failureKind = "validation" as const;
55
72
  readonly backend?: ResolvedBackend;
@@ -269,6 +286,40 @@ export async function reconcileSubagentRun(
269
286
  return await reconcileRun(options);
270
287
  }
271
288
 
289
+ function defaultChildStatus(
290
+ event: RecordSubagentChildEventOptions["event"],
291
+ ): Status {
292
+ if (event === "started" || event === "updated") return "running";
293
+ if (event === "completed") return "completed";
294
+ if (event === "cancelled") return "cancelled";
295
+ return "failed";
296
+ }
297
+
298
+ export async function recordSubagentChildEvent(
299
+ options: RecordSubagentChildEventOptions,
300
+ ): Promise<RunEvent> {
301
+ const {
302
+ event,
303
+ childRunId,
304
+ workflowRunId,
305
+ childTaskId,
306
+ failureKind,
307
+ message,
308
+ } = options;
309
+ const ref = await resolveRunRef(options);
310
+ return await appendRunEvent(ref, {
311
+ type: `child.${event}`,
312
+ status: options.status ?? defaultChildStatus(event),
313
+ ...(message === undefined ? {} : { message }),
314
+ data: {
315
+ childRunId,
316
+ ...(workflowRunId === undefined ? {} : { workflowRunId }),
317
+ ...(childTaskId === undefined ? {} : { taskId: childTaskId }),
318
+ ...(failureKind === undefined ? {} : { failureKind }),
319
+ },
320
+ });
321
+ }
322
+
272
323
  export type {
273
324
  ArtifactRef,
274
325
  CompletionMetadata,
@@ -294,6 +345,8 @@ export type { InterruptRunResult } from "./orchestrate/interrupt.ts";
294
345
  export type { ReconcileSubagentRunResult } from "./orchestrate/reconcile.ts";
295
346
  export type { ParallelRunResult } from "./orchestrate/run.ts";
296
347
  export type {
348
+ RunChildFailureSummary,
349
+ RunChildSummary,
297
350
  RunLogRef,
298
351
  RunLogsSnapshot,
299
352
  RunStatusRef,
@@ -18,6 +18,11 @@ export type RunEventType =
18
18
  | "run.cancelled"
19
19
  | "run.interrupt_requested"
20
20
  | "run.mark_background"
21
+ | "child.started"
22
+ | "child.updated"
23
+ | "child.completed"
24
+ | "child.failed"
25
+ | "child.cancelled"
21
26
  | "attempt.started"
22
27
  | "attempt.process_started"
23
28
  | "attempt.heartbeat"
@@ -716,11 +721,11 @@ export async function readRunEvents(ref: RunRef, limit = 50): Promise<RunEvent[]
716
721
  const paths = runPaths(ref);
717
722
  try {
718
723
  const text = await readFile(paths.eventsPath, "utf8");
719
- return text
724
+ const lines = text
720
725
  .split(/\r?\n/)
721
- .filter(Boolean)
722
- .slice(-limit)
723
- .map((line) => JSON.parse(line) as RunEvent);
726
+ .filter(Boolean);
727
+ const selected = Number.isFinite(limit) ? lines.slice(-limit) : lines;
728
+ return selected.map((line) => JSON.parse(line) as RunEvent);
724
729
  } catch (error) {
725
730
  if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") return [];
726
731
  throw error;
@@ -80,6 +80,7 @@ export interface ResultSessionMetadata {
80
80
 
81
81
  export interface ResultMetadata {
82
82
  contextLengthExceeded: boolean;
83
+ contextOverflowRecovered?: boolean;
83
84
  provider?: string;
84
85
  model?: string;
85
86
  usage?: unknown;
@@ -89,6 +90,7 @@ export interface ResultMetadata {
89
90
  session?: ResultSessionMetadata;
90
91
  streamErrors?: string[];
91
92
  nonFatalStreamErrors?: string[];
93
+ recoveredStreamErrors?: string[];
92
94
  parseErrors?: string[];
93
95
  }
94
96
 
@@ -199,6 +201,9 @@ function normalizeMetadata(input: ResultEnvelopeInput): ResultMetadata {
199
201
  const metadata = input.metadata ?? {};
200
202
  return {
201
203
  contextLengthExceeded: metadata.contextLengthExceeded ?? false,
204
+ ...(metadata.contextOverflowRecovered === undefined
205
+ ? {}
206
+ : { contextOverflowRecovered: metadata.contextOverflowRecovered }),
202
207
  ...(metadata.provider === undefined ? {} : { provider: metadata.provider }),
203
208
  ...(metadata.model === undefined ? {} : { model: metadata.model }),
204
209
  ...(metadata.usage === undefined ? {} : { usage: metadata.usage }),
@@ -218,6 +223,9 @@ function normalizeMetadata(input: ResultEnvelopeInput): ResultMetadata {
218
223
  ...(metadata.nonFatalStreamErrors === undefined
219
224
  ? {}
220
225
  : { nonFatalStreamErrors: metadata.nonFatalStreamErrors }),
226
+ ...(metadata.recoveredStreamErrors === undefined
227
+ ? {}
228
+ : { recoveredStreamErrors: metadata.recoveredStreamErrors }),
221
229
  ...(metadata.parseErrors === undefined
222
230
  ? {}
223
231
  : { parseErrors: metadata.parseErrors }),
@@ -16,6 +16,11 @@ export const FAILURE_KINDS = [
16
16
  "sandbox",
17
17
  "rpc",
18
18
  "model",
19
+ "provider_error",
20
+ "model_error",
21
+ "output_schema_error",
22
+ "guard_failure",
23
+ "user_cancelled",
19
24
  "tool",
20
25
  "exit",
21
26
  "parse",
@@ -109,6 +114,10 @@ export interface ResolveInput {
109
114
  mode?: ExecutionMode;
110
115
  tasks?: SubagentTaskInput[];
111
116
  concurrency?: number;
117
+ /** Stop scheduling additional parallel siblings after the first failed result. */
118
+ failFast?: boolean;
119
+ /** Abort already-running parallel siblings after the first failed result. Implies fail-fast scheduling. */
120
+ cancelSiblingsOnFailure?: boolean;
112
121
  asyncDependency?: AsyncDependency;
113
122
  workspace?: WorkspaceInput | WorkspaceMode;
114
123
  worktree?: boolean | string;
@@ -572,6 +572,27 @@ export function validateResolveInput(
572
572
  input.concurrency = concurrency;
573
573
  }
574
574
 
575
+ if (raw.failFast !== undefined) {
576
+ const failFast = validateBoolean(
577
+ raw.failFast,
578
+ "failFast",
579
+ backendForKnownFailure,
580
+ );
581
+ if (typeof failFast !== "boolean") return failFast;
582
+ input.failFast = failFast;
583
+ }
584
+
585
+ if (raw.cancelSiblingsOnFailure !== undefined) {
586
+ const cancelSiblingsOnFailure = validateBoolean(
587
+ raw.cancelSiblingsOnFailure,
588
+ "cancelSiblingsOnFailure",
589
+ backendForKnownFailure,
590
+ );
591
+ if (typeof cancelSiblingsOnFailure !== "boolean")
592
+ return cancelSiblingsOnFailure;
593
+ input.cancelSiblingsOnFailure = cancelSiblingsOnFailure;
594
+ }
595
+
575
596
  if (raw.chain !== undefined) {
576
597
  return failure(
577
598
  'chain mode is not supported by pi-subagent; use mode:"parallel" for fanout or have the parent orchestrate sequencing.',