pi-subagents 0.28.0 → 0.29.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 (36) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +18 -61
  3. package/package.json +1 -1
  4. package/skills/pi-subagents/SKILL.md +4 -35
  5. package/src/agents/agent-management.ts +10 -20
  6. package/src/agents/agent-selection.ts +2 -0
  7. package/src/agents/agent-serializer.ts +0 -10
  8. package/src/agents/agents.ts +304 -47
  9. package/src/agents/chain-serializer.ts +4 -9
  10. package/src/extension/doctor.ts +4 -3
  11. package/src/extension/fanout-child.ts +0 -2
  12. package/src/extension/index.ts +3 -8
  13. package/src/extension/schemas.ts +32 -22
  14. package/src/intercom/intercom-bridge.ts +11 -1
  15. package/src/intercom/result-intercom.ts +0 -5
  16. package/src/runs/background/async-execution.ts +20 -11
  17. package/src/runs/background/run-status.ts +1 -7
  18. package/src/runs/background/subagent-runner.ts +81 -211
  19. package/src/runs/foreground/chain-execution.ts +62 -58
  20. package/src/runs/foreground/execution.ts +38 -343
  21. package/src/runs/foreground/subagent-executor.ts +28 -99
  22. package/src/runs/shared/acceptance.ts +605 -22
  23. package/src/runs/shared/completion-guard.ts +3 -26
  24. package/src/runs/shared/model-fallback.ts +38 -0
  25. package/src/runs/shared/parallel-utils.ts +6 -10
  26. package/src/runs/shared/subagent-prompt-runtime.ts +3 -2
  27. package/src/runs/shared/workflow-graph.ts +2 -6
  28. package/src/shared/atomic-json.ts +68 -11
  29. package/src/shared/settings.ts +1 -0
  30. package/src/shared/types.ts +10 -48
  31. package/src/shared/utils.ts +2 -8
  32. package/src/tui/render.ts +14 -29
  33. package/src/runs/shared/acceptance-contract.ts +0 -318
  34. package/src/runs/shared/acceptance-evaluation.ts +0 -221
  35. package/src/runs/shared/acceptance-finalization.ts +0 -173
  36. package/src/runs/shared/acceptance-reports.ts +0 -127
@@ -8,8 +8,6 @@ import { appendJsonl, getArtifactPaths } from "../../shared/artifacts.ts";
8
8
  import { PI_CODING_AGENT_PACKAGE, getPiSpawnCommand, resolveInstalledPiPackageRoot } from "../shared/pi-spawn.ts";
9
9
  import { captureSingleOutputSnapshot, finalizeSingleOutput, formatSavedOutputReference, resolveSingleOutput, type SingleOutputSnapshot } from "../shared/single-output.ts";
10
10
  import {
11
- type AcceptanceFinalizationTurn,
12
- type AcceptanceLedger,
13
11
  type ActivityState,
14
12
  type ArtifactConfig,
15
13
  type ArtifactPaths,
@@ -19,9 +17,7 @@ import {
19
17
  type ModelAttempt,
20
18
  type NestedRouteInfo,
21
19
  type ResolvedControlConfig,
22
- type ResourceLimitExceeded,
23
20
  type SubagentRunMode,
24
- type TokenUsage,
25
21
  type Usage,
26
22
  type WorkflowGraphSnapshot,
27
23
  DEFAULT_MAX_OUTPUT,
@@ -54,8 +50,8 @@ import { collectDynamicResults, DynamicFanoutError, materializeDynamicParallelSt
54
50
  import { nestedSummaryFromAsyncStatus, writeNestedEvent } from "../shared/nested-events.ts";
55
51
  import { formatModelAttemptNote, isRetryableModelFailure } from "../shared/model-fallback.ts";
56
52
  import { attachPostExitStdioGuard, trySignalChild } from "../../shared/post-exit-stdio-guard.ts";
57
- import { detectSubagentError, extractTextFromContent, extractToolArgsPreview, formatResourceLimitExceeded, getFinalOutput } from "../../shared/utils.ts";
58
- import { evaluateCompletionMutationGuard, resolveCompletionPolicy } from "../shared/completion-guard.ts";
53
+ import { detectSubagentError, extractTextFromContent, extractToolArgsPreview, getFinalOutput } from "../../shared/utils.ts";
54
+ import { evaluateCompletionMutationGuard } from "../shared/completion-guard.ts";
59
55
  import {
60
56
  createMutatingFailureState,
61
57
  didMutatingToolFail,
@@ -68,6 +64,7 @@ import {
68
64
  summarizeRecentMutatingFailures,
69
65
  } from "../shared/long-running-guard.ts";
70
66
  import { parseSessionTokens } from "../../shared/session-tokens.ts";
67
+ import type { TokenUsage } from "../../shared/types.ts";
71
68
  import {
72
69
  cleanupWorktrees,
73
70
  createWorktrees,
@@ -80,19 +77,7 @@ import {
80
77
  import { resolveEffectiveThinking } from "../../shared/model-info.ts";
81
78
  import { writeInitialProgressFile } from "../../shared/settings.ts";
82
79
  import { resolveSubagentIntercomTarget } from "../../intercom/intercom-bridge.ts";
83
- import {
84
- acceptanceFailureMessage,
85
- acceptanceSelfReviewConfig,
86
- attachFinalizationToLedger,
87
- buildFinalizationProcessFailureLedger,
88
- createFinalizationProcessFailureTurn,
89
- createFinalizationTurn,
90
- evaluateAcceptance,
91
- formatAcceptanceFinalizationPrompt,
92
- formatAcceptancePrompt,
93
- shouldRunAcceptanceFinalization,
94
- stripAcceptanceReport,
95
- } from "../shared/acceptance.ts";
80
+ import { acceptanceFailureMessage, aggregateAcceptanceReport, evaluateAcceptance, formatAcceptancePrompt, stripAcceptanceReport } from "../shared/acceptance.ts";
96
81
 
97
82
  interface SubagentRunConfig {
98
83
  id: string;
@@ -140,8 +125,7 @@ interface StepResult {
140
125
  structuredOutput?: unknown;
141
126
  structuredOutputPath?: string;
142
127
  structuredOutputSchemaPath?: string;
143
- acceptance?: AcceptanceLedger;
144
- resourceLimitExceeded?: ResourceLimitExceeded;
128
+ acceptance?: import("../../shared/types.ts").AcceptanceLedger;
145
129
  }
146
130
 
147
131
  const ASYNC_INTERRUPT_SIGNAL: NodeJS.Signals = process.platform === "win32" ? "SIGBREAK" : "SIGUSR2";
@@ -236,7 +220,6 @@ interface RunPiStreamingResult {
236
220
  finalOutput: string;
237
221
  interrupted?: boolean;
238
222
  observedMutationAttempt?: boolean;
239
- resourceLimitExceeded?: ResourceLimitExceeded;
240
223
  }
241
224
 
242
225
  function runPiStreaming(
@@ -250,8 +233,6 @@ function runPiStreaming(
250
233
  childEventContext?: ChildEventContext,
251
234
  registerInterrupt?: (interrupt: (() => void) | undefined) => void,
252
235
  onChildEvent?: (event: ChildEvent) => void,
253
- maxExecutionTimeMs?: number,
254
- maxTokens?: number,
255
236
  ): Promise<RunPiStreamingResult> {
256
237
  return new Promise((resolve) => {
257
238
  const outputStream = fs.createWriteStream(outputFile, { flags: "w" });
@@ -275,10 +256,7 @@ function runPiStreaming(
275
256
  let error: string | undefined;
276
257
  let assistantError: string | undefined;
277
258
  let interrupted = false;
278
- let resourceLimitExceeded: ResourceLimitExceeded | undefined;
279
259
  let observedMutationAttempt = false;
280
- let resourceLimitTimer: NodeJS.Timeout | undefined;
281
- let resourceLimitEscalationTimer: NodeJS.Timeout | undefined;
282
260
  const rawStdoutLines: string[] = [];
283
261
 
284
262
  const writeOutputLine = (line: string) => {
@@ -292,19 +270,6 @@ function runPiStreaming(
292
270
  }
293
271
  };
294
272
 
295
- const triggerResourceLimit = (kind: ResourceLimitExceeded["kind"], limit: number, observed?: number) => {
296
- if (settled || resourceLimitExceeded) return;
297
- const message = formatResourceLimitExceeded({ agent: childEventContext?.agent ?? "subagent", kind, limit, observed });
298
- resourceLimitExceeded = { kind, limit, ...(observed !== undefined ? { observed } : {}), message };
299
- error = message;
300
- writeOutputLine(message);
301
- trySignalChild(child, "SIGINT");
302
- resourceLimitEscalationTimer = setTimeout(() => {
303
- if (!settled) trySignalChild(child, "SIGTERM");
304
- }, 1000);
305
- resourceLimitEscalationTimer.unref?.();
306
- };
307
-
308
273
  const appendChildEvent = (event: Record<string, unknown>) => {
309
274
  if (!childEventContext) return;
310
275
  appendJsonl(childEventContext.eventsPath, JSON.stringify({
@@ -359,10 +324,6 @@ function runPiStreaming(
359
324
  usage.cacheRead += eventUsage.cacheRead ?? 0;
360
325
  usage.cacheWrite += eventUsage.cacheWrite ?? 0;
361
326
  usage.cost += eventUsage.cost?.total ?? 0;
362
- const observedTokens = usage.input + usage.output;
363
- if (maxTokens !== undefined && observedTokens >= maxTokens) {
364
- triggerResourceLimit("maxTokens", maxTokens, observedTokens);
365
- }
366
327
  }
367
328
  const stopReason = (event.message as { stopReason?: string }).stopReason;
368
329
  const hasToolCall = Array.isArray(event.message.content)
@@ -398,12 +359,6 @@ function runPiStreaming(
398
359
  let finalDrainTimer: NodeJS.Timeout | undefined;
399
360
  let finalHardKillTimer: NodeJS.Timeout | undefined;
400
361
  let settled = false;
401
- if (maxExecutionTimeMs !== undefined) {
402
- resourceLimitTimer = setTimeout(() => {
403
- triggerResourceLimit("maxExecutionTimeMs", maxExecutionTimeMs);
404
- }, maxExecutionTimeMs);
405
- resourceLimitTimer.unref?.();
406
- }
407
362
  const clearStdioGuard = attachPostExitStdioGuard(child, { idleMs: 2000, hardMs: 8000 });
408
363
  child.stdout.on("data", (chunk: Buffer) => {
409
364
  const text = chunk.toString();
@@ -417,7 +372,7 @@ function runPiStreaming(
417
372
  processStderrText(chunk.toString());
418
373
  });
419
374
  registerInterrupt?.(() => {
420
- if (settled || resourceLimitExceeded) return;
375
+ if (settled) return;
421
376
  interrupted = true;
422
377
  if (!error) error = "Interrupted. Waiting for explicit next action.";
423
378
  trySignalChild(child, "SIGINT");
@@ -434,14 +389,6 @@ function runPiStreaming(
434
389
  clearTimeout(finalHardKillTimer);
435
390
  finalHardKillTimer = undefined;
436
391
  }
437
- if (resourceLimitTimer) {
438
- clearTimeout(resourceLimitTimer);
439
- resourceLimitTimer = undefined;
440
- }
441
- if (resourceLimitEscalationTimer) {
442
- clearTimeout(resourceLimitEscalationTimer);
443
- resourceLimitEscalationTimer = undefined;
444
- }
445
392
  };
446
393
  function startFinalDrain(): void {
447
394
  if (childExited || finalDrainTimer || settled) return;
@@ -473,12 +420,12 @@ function runPiStreaming(
473
420
  if (stdoutBuf.trim()) processStdoutLine(stdoutBuf);
474
421
  if (stderrBuf.trim()) appendChildLine("subagent.child.stderr", stderrBuf);
475
422
  outputStream.end();
476
- const finalOutput = resourceLimitExceeded?.message ?? (getFinalOutput(messages) || rawStdoutLines.join("\n").trim());
477
- const finalError = resourceLimitExceeded?.message ?? error ?? assistantError;
423
+ const finalOutput = getFinalOutput(messages) || rawStdoutLines.join("\n").trim();
424
+ const finalError = error ?? assistantError;
478
425
  const forcedDrainAfterFinalSuccess = forcedTerminationSignal && cleanTerminalAssistantStopReceived && !finalError;
479
426
  resolve({
480
427
  stderr,
481
- exitCode: resourceLimitExceeded ? 1 : interrupted || forcedDrainAfterFinalSuccess ? 0 : forcedTerminationSignal || signal ? (exitCode ?? 1) : exitCode,
428
+ exitCode: interrupted || forcedDrainAfterFinalSuccess ? 0 : forcedTerminationSignal || signal ? (exitCode ?? 1) : exitCode,
482
429
  messages,
483
430
  usage,
484
431
  model,
@@ -486,7 +433,6 @@ function runPiStreaming(
486
433
  finalOutput,
487
434
  interrupted,
488
435
  observedMutationAttempt,
489
- resourceLimitExceeded,
490
436
  });
491
437
  });
492
438
 
@@ -496,9 +442,9 @@ function runPiStreaming(
496
442
  clearDrainTimers();
497
443
  clearStdioGuard();
498
444
  outputStream.end();
499
- const finalOutput = resourceLimitExceeded?.message ?? (getFinalOutput(messages) || rawStdoutLines.join("\n").trim());
445
+ const finalOutput = getFinalOutput(messages) || rawStdoutLines.join("\n").trim();
500
446
  const spawnErrorMessage = spawnError instanceof Error ? spawnError.message : String(spawnError);
501
- resolve({ stderr, exitCode: 1, messages, usage, model, error: resourceLimitExceeded?.message ?? error ?? assistantError ?? spawnErrorMessage, finalOutput, observedMutationAttempt, resourceLimitExceeded });
447
+ resolve({ stderr, exitCode: 1, messages, usage, model, error: error ?? assistantError ?? spawnErrorMessage, finalOutput, observedMutationAttempt });
502
448
  });
503
449
  });
504
450
  }
@@ -653,8 +599,7 @@ async function runSingleStep(
653
599
  structuredOutput?: unknown;
654
600
  structuredOutputPath?: string;
655
601
  structuredOutputSchemaPath?: string;
656
- acceptance?: AcceptanceLedger;
657
- resourceLimitExceeded?: ResourceLimitExceeded;
602
+ acceptance?: import("../../shared/types.ts").AcceptanceLedger;
658
603
  }> {
659
604
  const effectiveStructuredOutput = step.structuredOutput ?? (step.structuredOutputSchema
660
605
  ? createStructuredOutputRuntime(step.structuredOutputSchema, path.join(path.dirname(ctx.outputFile), "structured-output"))
@@ -742,8 +687,6 @@ async function runSingleStep(
742
687
  { eventsPath, runId: ctx.id, stepIndex: ctx.flatIndex, agent: step.agent },
743
688
  ctx.registerInterrupt,
744
689
  ctx.onChildEvent,
745
- step.maxExecutionTimeMs,
746
- step.maxTokens,
747
690
  );
748
691
  cleanupTempDir(tempDir);
749
692
 
@@ -759,15 +702,7 @@ async function runSingleStep(
759
702
  if (structured.error) structuredError = structured.error;
760
703
  else structuredOutput = structured.value;
761
704
  }
762
- const completionPolicy = resolveCompletionPolicy({
763
- agent: step.agent,
764
- task: taskForCompletionGuard,
765
- completionGuardEnabled: step.completionGuard !== false,
766
- usesAcceptanceContract: step.effectiveAcceptance?.explicit === true,
767
- tools: step.tools,
768
- mcpDirectTools: step.mcpDirectTools,
769
- });
770
- const completionGuard = run.exitCode === 0 && !run.error && !hiddenError?.hasError && completionPolicy === "mutation-guard"
705
+ const completionGuard = run.exitCode === 0 && !run.error && !hiddenError?.hasError && step.completionGuard !== false
771
706
  ? evaluateCompletionMutationGuard({
772
707
  agent: step.agent,
773
708
  task: taskForCompletionGuard,
@@ -780,7 +715,7 @@ async function runSingleStep(
780
715
  const completionGuardError = completionGuardTriggered
781
716
  ? "Subagent completed without making edits for an implementation task.\nIt appears to have returned planning or scratchpad output instead of applying changes."
782
717
  : undefined;
783
- const effectiveExitCode = completionGuardError
718
+ const effectiveExitCode = completionGuardTriggered
784
719
  ? 1
785
720
  : structuredError
786
721
  ? 1
@@ -808,8 +743,8 @@ async function runSingleStep(
808
743
  completionGuardTriggeredFinal = completionGuardTriggered;
809
744
  finalOutputSnapshot = outputSnapshot;
810
745
  finalResult = { ...run, exitCode: effectiveExitCode, model: candidate ?? run.model, error, structuredOutput } as RunPiStreamingResult & { structuredOutput?: unknown };
811
- if (attempt.success || completionGuardError) break;
812
- if (run.resourceLimitExceeded || !isRetryableModelFailure(error) || index === candidates.length - 1) break;
746
+ if (attempt.success || completionGuardTriggered) break;
747
+ if (!isRetryableModelFailure(error) || index === candidates.length - 1) break;
813
748
  attemptNotes.push(formatModelAttemptNote(attempt, candidates[index + 1]));
814
749
  }
815
750
 
@@ -821,12 +756,12 @@ async function runSingleStep(
821
756
  const output = resolvedOutput.fullOutput;
822
757
  const outputReference = resolvedOutput.savedPath ? formatSavedOutputReference(resolvedOutput.savedPath, output) : undefined;
823
758
  let outputForSummary = output;
824
- if (attemptNotes.length > 0) {
825
- outputForSummary = `${attemptNotes.join("\n")}\n\n${outputForSummary}`.trim();
826
- }
759
+ if (attemptNotes.length > 0) {
760
+ outputForSummary = `${attemptNotes.join("\n")}\n\n${outputForSummary}`.trim();
761
+ }
827
762
  const outputForAcceptance = rawOutput;
828
- const finalizedOutput = finalizeSingleOutput({
829
- fullOutput: outputForSummary,
763
+ const finalizedOutput = finalizeSingleOutput({
764
+ fullOutput: outputForSummary,
830
765
  outputPath: step.outputPath,
831
766
  outputMode: step.outputMode,
832
767
  exitCode: finalResult?.exitCode ?? 1,
@@ -835,117 +770,13 @@ async function runSingleStep(
835
770
  saveError: resolvedOutput.saveError,
836
771
  });
837
772
  outputForSummary = finalizedOutput.displayOutput;
838
- const acceptanceForInitialReport = step.effectiveAcceptance && shouldRunAcceptanceFinalization(step.effectiveAcceptance)
839
- ? acceptanceSelfReviewConfig(step.effectiveAcceptance)
840
- : step.effectiveAcceptance;
841
- let acceptance = acceptanceForInitialReport
842
- ? await evaluateAcceptance({
843
- acceptance: acceptanceForInitialReport,
844
- output: outputForAcceptance,
845
- cwd: step.cwd ?? ctx.cwd,
846
- })
773
+ const acceptance = step.effectiveAcceptance
774
+ ? await evaluateAcceptance({
775
+ acceptance: step.effectiveAcceptance,
776
+ output: outputForAcceptance,
777
+ cwd: step.cwd ?? ctx.cwd,
778
+ })
847
779
  : undefined;
848
- if (acceptance && step.effectiveAcceptance && shouldRunAcceptanceFinalization(step.effectiveAcceptance) && (finalResult?.exitCode ?? 1) === 0 && !finalResult?.interrupted) {
849
- const sessionFile = step.sessionFile ?? (sessionDir ? findLatestSessionFile(sessionDir) ?? undefined : undefined);
850
- const maxTurns = step.effectiveAcceptance.finalization.maxTurns;
851
- const turns: AcceptanceFinalizationTurn[] = [];
852
- if (!sessionFile) {
853
- const message = "Acceptance finalization requires a session file for same-session continuation.";
854
- turns.push(createFinalizationProcessFailureTurn({ turn: 1, prompt: "", message }));
855
- acceptance = buildFinalizationProcessFailureLedger({ initialLedger: acceptance, turns, maxTurns, message });
856
- } else {
857
- const selfReviewAcceptance = acceptanceSelfReviewConfig(step.effectiveAcceptance);
858
- let previousFailure = acceptanceFailureMessage(acceptance);
859
- let authoritativeLedger = acceptance;
860
- for (let turn = 1; turn <= maxTurns; turn++) {
861
- const prompt = formatAcceptanceFinalizationPrompt({
862
- acceptance: step.effectiveAcceptance,
863
- initialOutput: outputForAcceptance,
864
- initialLedger: acceptance,
865
- turn,
866
- maxTurns,
867
- ...(previousFailure ? { previousFailure } : {}),
868
- });
869
- const { args, env, tempDir } = buildPiArgs({
870
- baseArgs: ["--mode", "json", "-p"],
871
- task: prompt,
872
- sessionEnabled: true,
873
- sessionFile,
874
- model: finalResult?.model ?? step.model,
875
- thinking: step.thinking,
876
- inheritProjectContext: step.inheritProjectContext,
877
- inheritSkills: step.inheritSkills,
878
- tools: step.tools,
879
- extensions: step.extensions,
880
- systemPrompt: step.systemPrompt,
881
- systemPromptMode: step.systemPromptMode,
882
- mcpDirectTools: step.mcpDirectTools,
883
- cwd: step.cwd ?? ctx.cwd,
884
- promptFileStem: `${step.agent}-acceptance-finalization`,
885
- intercomSessionName: ctx.childIntercomTarget,
886
- orchestratorIntercomTarget: ctx.orchestratorIntercomTarget,
887
- runId: ctx.id,
888
- childAgentName: step.agent,
889
- childIndex: ctx.flatIndex,
890
- parentEventSink: ctx.nestedRoute?.eventSink,
891
- parentControlInbox: ctx.nestedRoute?.controlInbox,
892
- parentRootRunId: ctx.nestedRoute?.rootRunId,
893
- parentCapabilityToken: ctx.nestedRoute?.capabilityToken,
894
- });
895
- ctx.onAttemptStart?.({ model: finalResult?.model ?? step.model, thinking: resolveEffectiveThinking(finalResult?.model ?? step.model, step.thinking) });
896
- const finalizationRun = await runPiStreaming(
897
- args,
898
- step.cwd ?? ctx.cwd,
899
- `${ctx.outputFile}.finalization-${turn}.log`,
900
- env,
901
- ctx.piPackageRoot,
902
- ctx.piArgv1,
903
- step.maxSubagentDepth,
904
- { eventsPath, runId: ctx.id, stepIndex: ctx.flatIndex, agent: step.agent },
905
- ctx.registerInterrupt,
906
- ctx.onChildEvent,
907
- step.maxExecutionTimeMs,
908
- step.maxTokens,
909
- );
910
- cleanupTempDir(tempDir);
911
- modelAttempts.push({
912
- model: finalResult?.model ?? finalizationRun.model ?? step.model ?? "default",
913
- success: finalizationRun.exitCode === 0 && !finalizationRun.error,
914
- exitCode: finalizationRun.exitCode,
915
- error: finalizationRun.error,
916
- usage: finalizationRun.usage,
917
- });
918
- const finalizationOutput = finalizationRun.finalOutput;
919
- if (finalizationRun.exitCode !== 0 || finalizationRun.error || finalizationRun.interrupted) {
920
- const message = finalizationRun.error ?? "Acceptance finalization turn did not complete successfully.";
921
- turns.push(createFinalizationProcessFailureTurn({ turn, prompt, rawOutput: finalizationOutput, message }));
922
- acceptance = buildFinalizationProcessFailureLedger({ initialLedger: acceptance, turns, maxTurns, message });
923
- break;
924
- }
925
- const selfReviewLedger = await evaluateAcceptance({
926
- acceptance: selfReviewAcceptance,
927
- output: finalizationOutput,
928
- cwd: step.cwd ?? ctx.cwd,
929
- });
930
- authoritativeLedger = selfReviewLedger;
931
- turns.push(createFinalizationTurn({ turn, prompt, rawOutput: finalizationOutput, ledger: selfReviewLedger }));
932
- const failure = acceptanceFailureMessage(selfReviewLedger);
933
- if (!failure) {
934
- authoritativeLedger = step.effectiveAcceptance === selfReviewAcceptance
935
- ? selfReviewLedger
936
- : await evaluateAcceptance({
937
- acceptance: step.effectiveAcceptance,
938
- output: finalizationOutput,
939
- cwd: step.cwd ?? ctx.cwd,
940
- });
941
- acceptance = attachFinalizationToLedger({ initialLedger: acceptance, authoritativeLedger, turns, status: "completed", maxTurns });
942
- break;
943
- }
944
- previousFailure = failure;
945
- if (turn === maxTurns) acceptance = attachFinalizationToLedger({ initialLedger: acceptance, authoritativeLedger, turns, status: "failed", maxTurns });
946
- }
947
- }
948
- }
949
780
  const acceptanceFailure = acceptance ? acceptanceFailureMessage(acceptance) : undefined;
950
781
  const acceptanceCanFailRun = acceptanceFailure && acceptance?.explicit && (finalResult?.exitCode ?? 1) === 0 && !finalResult?.interrupted;
951
782
  const effectiveFinalExitCode = acceptanceCanFailRun ? 1 : finalResult?.exitCode ?? 1;
@@ -968,7 +799,6 @@ async function runSingleStep(
968
799
  model: finalResult?.model,
969
800
  attemptedModels: attemptedModels.length > 0 ? attemptedModels : undefined,
970
801
  modelAttempts,
971
- resourceLimitExceeded: finalResult?.resourceLimitExceeded,
972
802
  skills: step.skills,
973
803
  timestamp: Date.now(),
974
804
  }, null, 2),
@@ -994,7 +824,6 @@ async function runSingleStep(
994
824
  structuredOutputPath: effectiveStructuredOutput?.outputPath,
995
825
  structuredOutputSchemaPath: effectiveStructuredOutput?.schemaPath,
996
826
  acceptance,
997
- resourceLimitExceeded: finalResult?.resourceLimitExceeded,
998
827
  };
999
828
  }
1000
829
 
@@ -1283,7 +1112,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
1283
1112
  writeAtomicJson(statusPath, statusPayload);
1284
1113
  emitNestedSelfEvent(statusPayload.state === "running" || statusPayload.state === "queued" ? "subagent.nested.updated" : "subagent.nested.completed");
1285
1114
  };
1286
- const markDynamicGraphGroup = (stepIndex: number, status: "completed" | "failed" | "running", error?: string, acceptance?: AcceptanceLedger): void => {
1115
+ const markDynamicGraphGroup = (stepIndex: number, status: "completed" | "failed" | "running", error?: string, acceptance?: import("../../shared/types.ts").AcceptanceLedger): void => {
1287
1116
  const groupNode = statusPayload.workflowGraph?.nodes.find((node) => node.id === `step-${stepIndex}`);
1288
1117
  if (!groupNode) return;
1289
1118
  groupNode.status = status;
@@ -1625,9 +1454,36 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
1625
1454
  placeholder.durationMs = 0;
1626
1455
  }
1627
1456
  previousOutput = "Dynamic fanout produced 0 results.";
1457
+ const groupAcceptance = step.effectiveAcceptance?.explicit
1458
+ ? await evaluateAcceptance({
1459
+ acceptance: step.effectiveAcceptance,
1460
+ output: "",
1461
+ report: aggregateAcceptanceReport({
1462
+ results: [],
1463
+ notes: "Dynamic fanout produced 0 results.",
1464
+ }),
1465
+ cwd,
1466
+ })
1467
+ : undefined;
1468
+ if (placeholder && groupAcceptance) placeholder.acceptance = groupAcceptance;
1469
+ const groupAcceptanceFailure = groupAcceptance ? acceptanceFailureMessage(groupAcceptance) : undefined;
1470
+ if (groupAcceptanceFailure) {
1471
+ statusPayload.state = "failed";
1472
+ statusPayload.error = groupAcceptanceFailure;
1473
+ if (placeholder) {
1474
+ placeholder.status = "failed";
1475
+ placeholder.error = groupAcceptanceFailure;
1476
+ placeholder.exitCode = 1;
1477
+ }
1478
+ markDynamicGraphGroup(stepIndex, "failed", groupAcceptanceFailure, groupAcceptance);
1479
+ statusPayload.lastUpdate = now;
1480
+ writeStatusPayload();
1481
+ results.push({ agent: step.parallel.agent, output: groupAcceptanceFailure, error: groupAcceptanceFailure, success: false, exitCode: 1, acceptance: groupAcceptance });
1482
+ break;
1483
+ }
1628
1484
  flatIndex++;
1629
1485
  statusPayload.lastUpdate = now;
1630
- markDynamicGraphGroup(stepIndex, "completed");
1486
+ markDynamicGraphGroup(stepIndex, "completed", undefined, groupAcceptance);
1631
1487
  writeStatusPayload();
1632
1488
  continue;
1633
1489
  }
@@ -1759,14 +1615,12 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
1759
1615
  statusPayload.steps[fi].structuredOutputPath = singleResult.structuredOutputPath;
1760
1616
  statusPayload.steps[fi].structuredOutputSchemaPath = singleResult.structuredOutputSchemaPath;
1761
1617
  statusPayload.steps[fi].acceptance = singleResult.acceptance;
1762
- statusPayload.steps[fi].resourceLimitExceeded = singleResult.resourceLimitExceeded;
1763
1618
  statusPayload.lastUpdate = taskEndTime;
1764
1619
  writeStatusPayload();
1765
1620
  appendJsonl(eventsPath, JSON.stringify({
1766
1621
  type: singleResult.exitCode === 0 ? "subagent.step.completed" : "subagent.step.failed",
1767
1622
  ts: taskEndTime, runId: id, stepIndex: fi, agent: task.agent,
1768
1623
  exitCode: singleResult.exitCode, durationMs: taskEndTime - taskStartTime,
1769
- resourceLimitExceeded: singleResult.resourceLimitExceeded,
1770
1624
  }));
1771
1625
  if (singleResult.exitCode !== 0 && failFast) aborted = true;
1772
1626
  return { ...singleResult, skipped: false };
@@ -1791,7 +1645,6 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
1791
1645
  structuredOutputPath: pr.structuredOutputPath,
1792
1646
  structuredOutputSchemaPath: pr.structuredOutputSchemaPath,
1793
1647
  acceptance: pr.acceptance,
1794
- resourceLimitExceeded: pr.resourceLimitExceeded,
1795
1648
  });
1796
1649
  }
1797
1650
  const collection = collectDynamicResults(step as Parameters<typeof collectDynamicResults>[0], materialized.items, parallelResults);
@@ -1806,7 +1659,31 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
1806
1659
  stepIndex,
1807
1660
  };
1808
1661
  statusPayload.outputs = outputs;
1809
- markDynamicGraphGroup(stepIndex, "completed");
1662
+ const groupAcceptance = step.effectiveAcceptance
1663
+ ? await evaluateAcceptance({
1664
+ acceptance: step.effectiveAcceptance,
1665
+ output: "",
1666
+ report: aggregateAcceptanceReport({
1667
+ results: parallelResults,
1668
+ notes: `Dynamic fanout collected ${collection.length} result(s) into ${step.collect.as}.`,
1669
+ }),
1670
+ cwd,
1671
+ })
1672
+ : undefined;
1673
+ const groupAcceptanceFailure = groupAcceptance ? acceptanceFailureMessage(groupAcceptance) : undefined;
1674
+ markDynamicGraphGroup(stepIndex, groupAcceptanceFailure ? "failed" : "completed", groupAcceptanceFailure, groupAcceptance);
1675
+ if (groupAcceptanceFailure) {
1676
+ results.push({
1677
+ agent: step.parallel.agent,
1678
+ output: groupAcceptanceFailure,
1679
+ error: groupAcceptanceFailure,
1680
+ success: false,
1681
+ exitCode: 1,
1682
+ structuredOutput: collection,
1683
+ acceptance: groupAcceptance,
1684
+ });
1685
+ statusPayload.error = groupAcceptanceFailure;
1686
+ }
1810
1687
  } catch (error) {
1811
1688
  const message = error instanceof DynamicFanoutError ? error.message : error instanceof Error ? error.message : String(error);
1812
1689
  results.push({ agent: step.parallel.agent, output: message, error: message, success: false, exitCode: 1, structuredOutput: collection });
@@ -1991,7 +1868,6 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
1991
1868
  statusPayload.steps[fi].structuredOutputPath = singleResult.structuredOutputPath;
1992
1869
  statusPayload.steps[fi].structuredOutputSchemaPath = singleResult.structuredOutputSchemaPath;
1993
1870
  statusPayload.steps[fi].acceptance = singleResult.acceptance;
1994
- statusPayload.steps[fi].resourceLimitExceeded = singleResult.resourceLimitExceeded;
1995
1871
  statusPayload.lastUpdate = taskEndTime;
1996
1872
  writeStatusPayload();
1997
1873
 
@@ -1999,7 +1875,6 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
1999
1875
  type: singleResult.exitCode === 0 ? "subagent.step.completed" : "subagent.step.failed",
2000
1876
  ts: taskEndTime, runId: id, stepIndex: fi, agent: task.agent,
2001
1877
  exitCode: singleResult.exitCode, durationMs: taskDuration,
2002
- resourceLimitExceeded: singleResult.resourceLimitExceeded,
2003
1878
  }));
2004
1879
  if (singleResult.completionGuardTriggered) {
2005
1880
  const event = buildControlEvent({
@@ -2058,7 +1933,6 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
2058
1933
  structuredOutputPath: pr.structuredOutputPath,
2059
1934
  structuredOutputSchemaPath: pr.structuredOutputSchemaPath,
2060
1935
  acceptance: pr.acceptance,
2061
- resourceLimitExceeded: pr.resourceLimitExceeded,
2062
1936
  });
2063
1937
  }
2064
1938
  for (let t = 0; t < group.parallel.length; t++) {
@@ -2160,7 +2034,6 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
2160
2034
  structuredOutputPath: singleResult.structuredOutputPath,
2161
2035
  structuredOutputSchemaPath: singleResult.structuredOutputSchemaPath,
2162
2036
  acceptance: singleResult.acceptance,
2163
- resourceLimitExceeded: singleResult.resourceLimitExceeded,
2164
2037
  });
2165
2038
  if (seqStep.outputName) {
2166
2039
  outputs[seqStep.outputName] = outputEntryFromAsyncResult({
@@ -2206,7 +2079,6 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
2206
2079
  statusPayload.steps[flatIndex].structuredOutputPath = singleResult.structuredOutputPath;
2207
2080
  statusPayload.steps[flatIndex].structuredOutputSchemaPath = singleResult.structuredOutputSchemaPath;
2208
2081
  statusPayload.steps[flatIndex].acceptance = singleResult.acceptance;
2209
- statusPayload.steps[flatIndex].resourceLimitExceeded = singleResult.resourceLimitExceeded;
2210
2082
  if (stepTokens) {
2211
2083
  statusPayload.steps[flatIndex].tokens = stepTokens;
2212
2084
  statusPayload.totalTokens = { ...previousCumulativeTokens };
@@ -2223,7 +2095,6 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
2223
2095
  exitCode: singleResult.exitCode,
2224
2096
  durationMs: stepEndTime - stepStartTime,
2225
2097
  tokens: stepTokens,
2226
- resourceLimitExceeded: singleResult.resourceLimitExceeded,
2227
2098
  }));
2228
2099
  if (singleResult.completionGuardTriggered) {
2229
2100
  const event = buildControlEvent({
@@ -2370,7 +2241,6 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
2370
2241
  structuredOutputPath: r.structuredOutputPath,
2371
2242
  structuredOutputSchemaPath: r.structuredOutputSchemaPath,
2372
2243
  acceptance: r.acceptance,
2373
- resourceLimitExceeded: r.resourceLimitExceeded,
2374
2244
  })),
2375
2245
  outputs,
2376
2246
  workflowGraph: statusPayload.workflowGraph,