pi-subagents 0.32.0 → 0.33.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 (52) hide show
  1. package/CHANGELOG.md +30 -3
  2. package/README.md +147 -58
  3. package/install.mjs +2 -1
  4. package/package.json +3 -2
  5. package/skills/pi-subagents/SKILL.md +70 -14
  6. package/src/agents/agent-management.ts +177 -5
  7. package/src/agents/agent-memory.ts +254 -0
  8. package/src/agents/agent-serializer.ts +11 -0
  9. package/src/agents/agents.ts +142 -12
  10. package/src/agents/chain-serializer.ts +27 -2
  11. package/src/extension/doctor.ts +1 -9
  12. package/src/extension/fanout-child.ts +2 -2
  13. package/src/extension/index.ts +65 -90
  14. package/src/extension/rpc.ts +369 -0
  15. package/src/extension/schemas.ts +52 -8
  16. package/src/extension/tool-description.ts +200 -0
  17. package/src/intercom/intercom-bridge.ts +21 -253
  18. package/src/intercom/native-supervisor-channel.ts +510 -0
  19. package/src/runs/background/async-execution.ts +51 -7
  20. package/src/runs/background/async-job-tracker.ts +12 -2
  21. package/src/runs/background/async-status.ts +27 -2
  22. package/src/runs/background/completion-batcher.ts +166 -0
  23. package/src/runs/background/control-channel.ts +106 -1
  24. package/src/runs/background/fleet-view.ts +515 -0
  25. package/src/runs/background/notify.ts +161 -44
  26. package/src/runs/background/result-watcher.ts +1 -2
  27. package/src/runs/background/run-id-resolver.ts +3 -2
  28. package/src/runs/background/run-status.ts +166 -6
  29. package/src/runs/background/scheduled-runs.ts +514 -0
  30. package/src/runs/background/subagent-runner.ts +409 -35
  31. package/src/runs/background/wait.ts +353 -0
  32. package/src/runs/foreground/chain-execution.ts +95 -21
  33. package/src/runs/foreground/execution.ts +150 -21
  34. package/src/runs/foreground/subagent-executor.ts +378 -64
  35. package/src/runs/shared/dynamic-fanout.ts +1 -1
  36. package/src/runs/shared/model-fallback.ts +167 -20
  37. package/src/runs/shared/model-scope.ts +128 -0
  38. package/src/runs/shared/nested-events.ts +31 -0
  39. package/src/runs/shared/parallel-utils.ts +1 -0
  40. package/src/runs/shared/pi-args.ts +30 -1
  41. package/src/runs/shared/subagent-prompt-runtime.ts +108 -2
  42. package/src/runs/shared/tool-budget.ts +74 -0
  43. package/src/runs/shared/turn-budget.ts +52 -0
  44. package/src/shared/artifacts.ts +1 -0
  45. package/src/shared/atomic-json.ts +15 -2
  46. package/src/shared/child-transcript.ts +212 -0
  47. package/src/shared/settings.ts +3 -1
  48. package/src/shared/types.ts +134 -19
  49. package/src/slash/prompt-workflows.ts +330 -0
  50. package/src/slash/slash-commands.ts +16 -2
  51. package/src/tui/render.ts +16 -8
  52. package/src/extension/companion-suggestions.ts +0 -359
@@ -12,6 +12,7 @@ import {
12
12
  writeArtifact,
13
13
  writeMetadata,
14
14
  } from "../../shared/artifacts.ts";
15
+ import { createChildTranscriptWriter, type ChildTranscriptWriter } from "../../shared/child-transcript.ts";
15
16
  import {
16
17
  type AgentProgress,
17
18
  type ArtifactPaths,
@@ -43,6 +44,7 @@ import {
43
44
  extractTextFromContent,
44
45
  } from "../../shared/utils.ts";
45
46
  import { buildSkillInjection, resolveSkillsWithFallback } from "../../agents/skills.ts";
47
+ import { buildAgentMemoryInjection } from "../../agents/agent-memory.ts";
46
48
  import { evaluateCompletionMutationGuard } from "../shared/completion-guard.ts";
47
49
  import { getPiSpawnCommand } from "../shared/pi-spawn.ts";
48
50
  import { createJsonlWriter } from "../../shared/jsonl-writer.ts";
@@ -67,6 +69,8 @@ import {
67
69
  summarizeRecentMutatingFailures,
68
70
  } from "../shared/long-running-guard.ts";
69
71
  import { acceptanceFailureMessage, evaluateAcceptance, formatAcceptancePrompt, resolveEffectiveAcceptance, stripAcceptanceReport } from "../shared/acceptance.ts";
72
+ import { appendTurnBudgetSystemPrompt, formatTurnBudgetOutput, initialTurnBudgetState, shouldAbortForTurnBudget, turnBudgetExceededMessage, turnBudgetSoftNote, turnBudgetState } from "../shared/turn-budget.ts";
73
+ import { initialToolBudgetState, toolBudgetState } from "../shared/tool-budget.ts";
70
74
 
71
75
  const artifactOutputByResult = new WeakMap<SingleResult, string>();
72
76
  const acceptanceOutputByResult = new WeakMap<SingleResult, string>();
@@ -98,7 +102,7 @@ function resolveAttemptTimeout(options: RunSyncOptions): { timeoutMs: number; re
98
102
  };
99
103
  }
100
104
 
101
- function buildTimedOutAcceptanceLedger(acceptance: ResolvedAcceptanceConfig): AcceptanceLedger {
105
+ function buildSkippedAcceptanceLedger(acceptance: ResolvedAcceptanceConfig, input: { id: string; message: string }): AcceptanceLedger {
102
106
  return {
103
107
  status: acceptance.level === "none" ? "not-required" : "rejected",
104
108
  explicit: acceptance.explicit,
@@ -107,7 +111,7 @@ function buildTimedOutAcceptanceLedger(acceptance: ResolvedAcceptanceConfig): Ac
107
111
  criteria: acceptance.criteria,
108
112
  runtimeChecks: acceptance.level === "none"
109
113
  ? []
110
- : [{ id: "timeout", status: "failed", message: "Acceptance was not evaluated because the subagent timed out." }],
114
+ : [{ id: input.id, status: "failed", message: input.message }],
111
115
  verifyRuns: [],
112
116
  };
113
117
  }
@@ -175,6 +179,7 @@ async function runSingleAttempt(
175
179
  skillsWarning?: string;
176
180
  jsonlPath?: string;
177
181
  artifactPaths?: ArtifactPaths;
182
+ transcriptWriter?: ChildTranscriptWriter;
178
183
  attemptNotes: string[];
179
184
  outputSnapshot?: SingleOutputSnapshot;
180
185
  originalTask?: string;
@@ -197,7 +202,7 @@ async function runSingleAttempt(
197
202
  tools: agent.tools,
198
203
  extensions: agent.extensions,
199
204
  subagentOnlyExtensions: agent.subagentOnlyExtensions,
200
- systemPrompt: shared.systemPrompt,
205
+ systemPrompt: appendTurnBudgetSystemPrompt(shared.systemPrompt, options.turnBudget),
201
206
  mcpDirectTools: agent.mcpDirectTools,
202
207
  cwd: options.cwd ?? runtimeCwd,
203
208
  promptFileStem: agent.name,
@@ -208,11 +213,12 @@ async function runSingleAttempt(
208
213
  childIndex: options.index ?? 0,
209
214
  parentEventSink: options.nestedRoute?.eventSink,
210
215
  parentControlInbox: options.nestedRoute?.controlInbox,
211
- parentRootRunId: options.nestedRoute?.rootRunId,
212
- parentCapabilityToken: options.nestedRoute?.capabilityToken,
213
- parentSessionId: options.parentSessionId,
214
- structuredOutput: options.structuredOutput,
215
- });
216
+ parentRootRunId: options.nestedRoute?.rootRunId,
217
+ parentCapabilityToken: options.nestedRoute?.capabilityToken,
218
+ parentSessionId: options.parentSessionId,
219
+ structuredOutput: options.structuredOutput,
220
+ toolBudget: options.toolBudget,
221
+ });
216
222
 
217
223
  const result: SingleResult = {
218
224
  agent: agent.name,
@@ -222,8 +228,11 @@ async function runSingleAttempt(
222
228
  usage: emptyUsage(),
223
229
  model: modelArg,
224
230
  artifactPaths: shared.artifactPaths,
231
+ transcriptPath: shared.transcriptWriter ? shared.artifactPaths?.transcriptPath : undefined,
225
232
  skills: shared.resolvedSkillNames,
226
233
  skillsWarning: shared.skillsWarning,
234
+ ...(options.turnBudget ? { turnBudget: initialTurnBudgetState(options.turnBudget) } : {}),
235
+ ...(options.toolBudget ? { toolBudget: initialToolBudgetState(options.toolBudget) } : {}),
227
236
  };
228
237
  const startTime = Date.now();
229
238
  if (options.structuredOutput) {
@@ -299,6 +308,19 @@ async function runSingleAttempt(
299
308
  let timeoutTimer: NodeJS.Timeout | undefined;
300
309
  let timeoutTerminationTimer: NodeJS.Timeout | undefined;
301
310
  let timeoutHardKillTimer: NodeJS.Timeout | undefined;
311
+ let turnBudgetSoftReached = false;
312
+ let turnBudgetTerminationTimer: NodeJS.Timeout | undefined;
313
+ let turnBudgetHardKillTimer: NodeJS.Timeout | undefined;
314
+ const clearTurnBudgetTimers = () => {
315
+ if (turnBudgetTerminationTimer) {
316
+ clearTimeout(turnBudgetTerminationTimer);
317
+ turnBudgetTerminationTimer = undefined;
318
+ }
319
+ if (turnBudgetHardKillTimer) {
320
+ clearTimeout(turnBudgetHardKillTimer);
321
+ turnBudgetHardKillTimer = undefined;
322
+ }
323
+ };
302
324
  const clearTimeoutTimers = () => {
303
325
  if (timeoutTimer) {
304
326
  clearTimeout(timeoutTimer);
@@ -382,6 +404,7 @@ async function runSingleAttempt(
382
404
  clearFinalDrainTimers();
383
405
  clearStdioGuard();
384
406
  clearTimeoutTimers();
407
+ clearTurnBudgetTimers();
385
408
  if (activityTimer) {
386
409
  clearInterval(activityTimer);
387
410
  activityTimer = undefined;
@@ -455,6 +478,50 @@ async function runSingleAttempt(
455
478
  }));
456
479
  return true;
457
480
  };
481
+ const requestTurnBudgetAbort = (turnCount: number) => {
482
+ const budget = options.turnBudget;
483
+ if (!budget || result.timedOut || result.turnBudgetExceeded || interruptedByControl || processClosed || settled || detached) return;
484
+ const message = turnBudgetExceededMessage(budget, turnCount);
485
+ result.turnBudgetExceeded = true;
486
+ result.wrapUpRequested = true;
487
+ result.turnBudget = turnBudgetState(budget, turnCount, true);
488
+ result.error = message;
489
+ result.finalOutput = message;
490
+ progress.status = "failed";
491
+ progress.error = message;
492
+ progress.durationMs = Date.now() - startTime;
493
+ fireUpdate();
494
+ trySignalChild(proc, "SIGINT");
495
+ turnBudgetTerminationTimer = setTimeout(() => {
496
+ if (processClosed || settled || detached || result.timedOut) return;
497
+ trySignalChild(proc, "SIGTERM");
498
+ }, 1000);
499
+ turnBudgetTerminationTimer.unref?.();
500
+ turnBudgetHardKillTimer = setTimeout(() => {
501
+ if (processClosed || settled || detached || result.timedOut) return;
502
+ trySignalChild(proc, "SIGKILL");
503
+ }, 4000);
504
+ turnBudgetHardKillTimer.unref?.();
505
+ };
506
+
507
+ const updateTurnBudget = (turnCount: number, terminalAssistantStop: boolean) => {
508
+ const budget = options.turnBudget;
509
+ if (!budget || result.timedOut || result.turnBudgetExceeded) return;
510
+ if (turnCount < budget.maxTurns) {
511
+ result.turnBudget = { ...budget, outcome: "within-budget", turnCount };
512
+ return;
513
+ }
514
+ if (!turnBudgetSoftReached) {
515
+ turnBudgetSoftReached = true;
516
+ result.wrapUpRequested = true;
517
+ appendRecentOutput(progress, [turnBudgetSoftNote(budget, turnCount)]);
518
+ }
519
+ result.turnBudget = turnBudgetState(budget, turnCount, false);
520
+ if (shouldAbortForTurnBudget(budget, turnCount, terminalAssistantStop)) {
521
+ requestTurnBudgetAbort(turnCount);
522
+ }
523
+ };
524
+
458
525
  const updateActivityState = (now: number): boolean => {
459
526
  if (!controlConfig.enabled) return false;
460
527
  const idleState = deriveActivityState({
@@ -495,7 +562,7 @@ async function runSingleAttempt(
495
562
  const fireUpdate = () => {
496
563
  if (!options.onUpdate || processClosed) return;
497
564
  progress.durationMs = Date.now() - startTime;
498
- const output = result.timedOut && result.finalOutput ? result.finalOutput : getFinalOutput(result.messages);
565
+ const output = (result.timedOut || result.turnBudgetExceeded) && result.finalOutput ? result.finalOutput : getFinalOutput(result.messages);
499
566
  emitUpdateSnapshot(output || "(running...)");
500
567
  };
501
568
 
@@ -506,9 +573,11 @@ async function runSingleAttempt(
506
573
  try {
507
574
  evt = JSON.parse(line) as { type?: string; message?: Message; toolName?: string; args?: unknown };
508
575
  } catch {
576
+ shared.transcriptWriter?.writeStdoutLine(line);
509
577
  // Non-JSON stdout lines are expected; only structured events are parsed.
510
578
  return;
511
579
  }
580
+ shared.transcriptWriter?.writeChildEvent(evt);
512
581
 
513
582
  const now = Date.now();
514
583
  progress.durationMs = now - startTime;
@@ -526,6 +595,9 @@ async function runSingleAttempt(
526
595
  || (evt.toolName === "contact_supervisor" && (toolArgs.reason === "need_decision" || toolArgs.reason === "interview_request"));
527
596
  }
528
597
  progress.toolCount++;
598
+ if (options.toolBudget) {
599
+ result.toolBudget = toolBudgetState(options.toolBudget, progress.toolCount);
600
+ }
529
601
  progress.currentTool = evt.toolName;
530
602
  progress.currentToolArgs = extractToolArgsPreview(toolArgs);
531
603
  progress.currentToolStartedAt = now;
@@ -559,6 +631,11 @@ async function runSingleAttempt(
559
631
  if (evt.message.role === "assistant") {
560
632
  result.usage.turns++;
561
633
  progress.turnCount = result.usage.turns;
634
+ const stopReason = (evt.message as { stopReason?: string }).stopReason;
635
+ const hasToolCall = Array.isArray(evt.message.content)
636
+ && evt.message.content.some((part) => (part as { type?: string }).type === "toolCall");
637
+ const terminalAssistantStop = stopReason === "stop" && !hasToolCall;
638
+ updateTurnBudget(result.usage.turns, terminalAssistantStop);
562
639
  const u = evt.message.usage;
563
640
  if (u) {
564
641
  result.usage.input += u.input || 0;
@@ -573,10 +650,7 @@ async function runSingleAttempt(
573
650
  const assistantText = extractTextFromContent(evt.message.content);
574
651
  appendRecentOutput(progress, assistantText.split("\n").slice(-10));
575
652
  // Final assistant message: start the exit drain window.
576
- const stopReason = (evt.message as { stopReason?: string }).stopReason;
577
- const hasToolCall = Array.isArray(evt.message.content)
578
- && evt.message.content.some((part) => (part as { type?: string }).type === "toolCall");
579
- if (stopReason === "stop" && !hasToolCall) {
653
+ if (terminalAssistantStop) {
580
654
  if (!evt.message.errorMessage && assistantText.trim()) assistantError = undefined;
581
655
  cleanTerminalAssistantStopReceived ||= !evt.message.errorMessage;
582
656
  startFinalDrain();
@@ -589,6 +663,10 @@ async function runSingleAttempt(
589
663
  if (evt.type === "tool_result_end" && evt.message) {
590
664
  result.messages.push(evt.message);
591
665
  const resultText = extractTextFromContent(evt.message.content);
666
+ if (options.toolBudget && pendingToolResult && resultText.includes("Tool budget hard limit reached")) {
667
+ result.toolBudgetBlocked = true;
668
+ result.toolBudget = toolBudgetState(options.toolBudget, progress.toolCount, pendingToolResult.tool);
669
+ }
592
670
  appendRecentOutput(progress, resultText.split("\n").slice(-10));
593
671
  const toolSnapshot = pendingToolResult;
594
672
  pendingToolResult = undefined;
@@ -676,18 +754,38 @@ async function runSingleAttempt(
676
754
  // JSONL artifact flush is best effort.
677
755
  });
678
756
  cleanupTempDir(tempDir);
679
- if (detached) {
680
- finish(-2);
681
- return;
682
- }
683
- processClosed = true;
684
757
  if (buf.trim()) processLine(buf);
758
+ if (stderrBuf.trim()) shared.transcriptWriter?.writeStderrText(stderrBuf);
685
759
  if (!result.error && assistantError) result.error = assistantError;
686
760
  const forcedDrainAfterFinalSuccess = forcedTerminationSignal && cleanTerminalAssistantStopReceived && !result.error;
687
761
  if (code !== 0 && stderrBuf.trim() && !result.error && !forcedDrainAfterFinalSuccess) {
688
762
  result.error = stderrBuf.trim();
689
763
  }
690
764
  const finalCode = forcedDrainAfterFinalSuccess ? 0 : forcedTerminationSignal || signal ? (code ?? 1) : (code ?? 0);
765
+ if (detached) {
766
+ result.exitCode = result.error && finalCode === 0 ? 1 : finalCode;
767
+ progress.status = result.exitCode === 0 ? "completed" : "failed";
768
+ progress.durationMs = Date.now() - startTime;
769
+ if (result.error) progress.error = result.error;
770
+ result.progressSummary = {
771
+ toolCount: progress.toolCount,
772
+ tokens: progress.tokens,
773
+ durationMs: progress.durationMs,
774
+ };
775
+ const finalOutput = getFinalOutput(result.messages);
776
+ result.finalOutput = finalOutput.trim() || result.error || result.finalOutput || "Detached child exited without final output.";
777
+ if (result.artifactPaths && options.artifactConfig?.enabled !== false && options.artifactConfig?.includeOutput !== false) {
778
+ try {
779
+ writeArtifact(result.artifactPaths.outputPath, result.finalOutput);
780
+ } catch {
781
+ // Detached children may outlive test/temp cleanup; recovered status is best-effort.
782
+ }
783
+ }
784
+ options.onDetachedExit?.(snapshotResult(result, snapshotProgress(progress)));
785
+ finish(-2);
786
+ return;
787
+ }
788
+ processClosed = true;
691
789
  finish(finalCode);
692
790
  });
693
791
  proc.on("error", (error) => {
@@ -697,6 +795,7 @@ async function runSingleAttempt(
697
795
  // JSONL artifact flush is best effort.
698
796
  });
699
797
  cleanupTempDir(tempDir);
798
+ if (stderrBuf.trim()) shared.transcriptWriter?.writeStderrText(stderrBuf);
700
799
  if (!result.error) {
701
800
  result.error = error instanceof Error ? error.message : String(error);
702
801
  }
@@ -827,6 +926,11 @@ async function runSingleAttempt(
827
926
  fullOutput = fullOutput.trim()
828
927
  ? `${timeoutMessage}\n\nPartial output before timeout:\n${fullOutput}`
829
928
  : timeoutMessage;
929
+ } else if (result.turnBudgetExceeded && result.turnBudget) {
930
+ fullOutput = formatTurnBudgetOutput(turnBudgetExceededMessage(result.turnBudget, result.turnBudget.turnCount), fullOutput);
931
+ } else if (result.wrapUpRequested && result.turnBudget?.outcome === "wrap-up-requested") {
932
+ const note = turnBudgetSoftNote(result.turnBudget, result.turnBudget.wrapUpRequestedAtTurn ?? result.turnBudget.turnCount);
933
+ fullOutput = fullOutput.trim() ? `${note}\n\n${fullOutput}` : note;
830
934
  }
831
935
  const completionGuard = result.exitCode === 0 && !result.error && agent.completionGuard !== false
832
936
  ? evaluateCompletionMutationGuard({
@@ -951,6 +1055,10 @@ export async function runSync(
951
1055
  const skillInjection = buildSkillInjection(resolvedSkills);
952
1056
  systemPrompt = systemPrompt ? `${systemPrompt}\n\n${skillInjection}` : skillInjection;
953
1057
  }
1058
+ const memoryInjection = buildAgentMemoryInjection(agent, skillCwd);
1059
+ if (memoryInjection) {
1060
+ systemPrompt = systemPrompt ? `${systemPrompt}\n\n${memoryInjection}` : memoryInjection;
1061
+ }
954
1062
  systemPrompt = injectOutputPathSystemPrompt(systemPrompt, options.outputPath);
955
1063
 
956
1064
  const candidates = buildModelCandidates(
@@ -958,6 +1066,7 @@ export async function runSync(
958
1066
  agent.fallbackModels,
959
1067
  options.availableModels,
960
1068
  options.preferredModelProvider,
1069
+ { scope: options.modelScope },
961
1070
  );
962
1071
  const attemptedModels: string[] = [];
963
1072
  const modelAttempts: ModelAttempt[] = [];
@@ -968,6 +1077,7 @@ export async function runSync(
968
1077
 
969
1078
  let artifactPathsResult: ArtifactPaths | undefined;
970
1079
  let jsonlPath: string | undefined;
1080
+ let transcriptWriter: ChildTranscriptWriter | undefined;
971
1081
  if (options.artifactsDir && options.artifactConfig?.enabled !== false) {
972
1082
  artifactPathsResult = getArtifactPaths(options.artifactsDir, options.runId, agentName, options.index);
973
1083
  ensureArtifactsDir(options.artifactsDir);
@@ -977,6 +1087,17 @@ export async function runSync(
977
1087
  if (options.artifactConfig?.includeJsonl !== false) {
978
1088
  jsonlPath = artifactPathsResult.jsonlPath;
979
1089
  }
1090
+ if (options.artifactConfig?.includeTranscript !== false) {
1091
+ transcriptWriter = createChildTranscriptWriter({
1092
+ transcriptPath: artifactPathsResult.transcriptPath,
1093
+ source: "foreground",
1094
+ runId: options.runId,
1095
+ agent: agentName,
1096
+ childIndex: options.index,
1097
+ cwd: options.cwd ?? runtimeCwd,
1098
+ });
1099
+ transcriptWriter.writeInitialUserMessage(taskWithAcceptance);
1100
+ }
980
1101
  }
981
1102
 
982
1103
  let lastResult: SingleResult | undefined;
@@ -991,6 +1112,7 @@ export async function runSync(
991
1112
  skillsWarning: missingSkills.length > 0 ? `Skills not found: ${missingSkills.join(", ")}` : undefined,
992
1113
  jsonlPath,
993
1114
  artifactPaths: artifactPathsResult,
1115
+ transcriptWriter,
994
1116
  attemptNotes,
995
1117
  outputSnapshot,
996
1118
  originalTask: task,
@@ -1010,7 +1132,7 @@ export async function runSync(
1010
1132
  usage: { ...result.usage },
1011
1133
  };
1012
1134
  modelAttempts.push(attempt);
1013
- if (result.timedOut) {
1135
+ if (result.timedOut || result.turnBudgetExceeded) {
1014
1136
  break;
1015
1137
  }
1016
1138
  if (attemptSucceeded) {
@@ -1046,6 +1168,9 @@ export async function runSync(
1046
1168
  }
1047
1169
  }
1048
1170
 
1171
+ if (transcriptWriter) result.transcriptPath = artifactPathsResult?.transcriptPath;
1172
+ if (transcriptWriter?.getError()) result.transcriptError = transcriptWriter.getError();
1173
+
1049
1174
  if (artifactPathsResult && options.artifactConfig?.enabled !== false) {
1050
1175
  result.artifactPaths = artifactPathsResult;
1051
1176
  if (options.artifactConfig?.includeOutput !== false) {
@@ -1064,6 +1189,8 @@ export async function runSync(
1064
1189
  durationMs: result.progressSummary?.durationMs,
1065
1190
  toolCount: result.progressSummary?.toolCount,
1066
1191
  error: result.error,
1192
+ ...(transcriptWriter ? { transcriptPath: artifactPathsResult.transcriptPath } : {}),
1193
+ transcriptError: result.transcriptError,
1067
1194
  skills: result.skills,
1068
1195
  skillsWarning: result.skillsWarning,
1069
1196
  timestamp: Date.now(),
@@ -1089,8 +1216,10 @@ export async function runSync(
1089
1216
  }
1090
1217
 
1091
1218
  result.acceptance = result.timedOut
1092
- ? buildTimedOutAcceptanceLedger(effectiveAcceptance)
1093
- : await evaluateAcceptance({
1219
+ ? buildSkippedAcceptanceLedger(effectiveAcceptance, { id: "timeout", message: "Acceptance was not evaluated because the subagent timed out." })
1220
+ : result.turnBudgetExceeded
1221
+ ? buildSkippedAcceptanceLedger(effectiveAcceptance, { id: "turn-budget", message: "Acceptance was not evaluated because the subagent exceeded its turn budget." })
1222
+ : await evaluateAcceptance({
1094
1223
  acceptance: effectiveAcceptance,
1095
1224
  output: acceptanceOutputByResult.get(result) ?? result.finalOutput ?? "",
1096
1225
  cwd: options.cwd ?? runtimeCwd,