pi-subagents 0.28.0 → 0.30.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 (47) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +26 -62
  3. package/package.json +1 -1
  4. package/skills/pi-subagents/SKILL.md +29 -35
  5. package/src/agents/agent-management.ts +29 -22
  6. package/src/agents/agent-selection.ts +2 -0
  7. package/src/agents/agent-serializer.ts +5 -10
  8. package/src/agents/agents.ts +339 -47
  9. package/src/agents/chain-serializer.ts +4 -9
  10. package/src/agents/proactive-skills.ts +191 -0
  11. package/src/extension/doctor.ts +4 -3
  12. package/src/extension/fanout-child.ts +1 -3
  13. package/src/extension/index.ts +6 -9
  14. package/src/extension/schemas.ts +63 -26
  15. package/src/intercom/intercom-bridge.ts +11 -1
  16. package/src/intercom/result-intercom.ts +0 -5
  17. package/src/runs/background/async-execution.ts +186 -74
  18. package/src/runs/background/async-resume.ts +53 -5
  19. package/src/runs/background/async-status.ts +4 -1
  20. package/src/runs/background/chain-append.ts +282 -0
  21. package/src/runs/background/chain-root-attachment.ts +161 -0
  22. package/src/runs/background/run-status.ts +2 -7
  23. package/src/runs/background/subagent-runner.ts +160 -219
  24. package/src/runs/foreground/chain-execution.ts +62 -58
  25. package/src/runs/foreground/execution.ts +39 -343
  26. package/src/runs/foreground/subagent-executor.ts +316 -111
  27. package/src/runs/shared/acceptance.ts +605 -22
  28. package/src/runs/shared/chain-outputs.ts +23 -8
  29. package/src/runs/shared/completion-guard.ts +3 -26
  30. package/src/runs/shared/dynamic-fanout.ts +1 -1
  31. package/src/runs/shared/model-fallback.ts +38 -0
  32. package/src/runs/shared/parallel-utils.ts +13 -10
  33. package/src/runs/shared/pi-args.ts +3 -2
  34. package/src/runs/shared/subagent-control.ts +8 -11
  35. package/src/runs/shared/subagent-prompt-runtime.ts +3 -2
  36. package/src/runs/shared/workflow-graph.ts +2 -6
  37. package/src/shared/atomic-json.ts +68 -11
  38. package/src/shared/settings.ts +1 -0
  39. package/src/shared/types.ts +20 -49
  40. package/src/shared/utils.ts +2 -8
  41. package/src/slash/slash-bridge.ts +3 -1
  42. package/src/slash/slash-commands.ts +1 -1
  43. package/src/tui/render.ts +14 -29
  44. package/src/runs/shared/acceptance-contract.ts +0 -318
  45. package/src/runs/shared/acceptance-evaluation.ts +0 -221
  46. package/src/runs/shared/acceptance-finalization.ts +0 -173
  47. package/src/runs/shared/acceptance-reports.ts +0 -127
@@ -3,9 +3,7 @@
3
3
  */
4
4
 
5
5
  import { spawn } from "node:child_process";
6
- import { existsSync, mkdtempSync, unlinkSync } from "node:fs";
7
- import * as os from "node:os";
8
- import * as path from "node:path";
6
+ import { existsSync, unlinkSync } from "node:fs";
9
7
  import type { Message } from "@earendil-works/pi-ai";
10
8
  import type { AgentConfig } from "../../agents/agents.ts";
11
9
  import {
@@ -15,13 +13,10 @@ import {
15
13
  writeMetadata,
16
14
  } from "../../shared/artifacts.ts";
17
15
  import {
18
- type AcceptanceFinalizationTurn,
19
- type AcceptanceLedger,
20
16
  type AgentProgress,
21
17
  type ArtifactPaths,
22
18
  type ControlEvent,
23
19
  type ModelAttempt,
24
- type ResolvedAcceptanceConfig,
25
20
  type RunSyncOptions,
26
21
  type SingleResult,
27
22
  type Usage,
@@ -44,10 +39,9 @@ import {
44
39
  detectSubagentError,
45
40
  extractToolArgsPreview,
46
41
  extractTextFromContent,
47
- formatResourceLimitExceeded,
48
42
  } from "../../shared/utils.ts";
49
43
  import { buildSkillInjection, resolveSkillsWithFallback } from "../../agents/skills.ts";
50
- import { evaluateCompletionMutationGuard, resolveCompletionPolicy, type CompletionPolicy } from "../shared/completion-guard.ts";
44
+ import { evaluateCompletionMutationGuard } from "../shared/completion-guard.ts";
51
45
  import { getPiSpawnCommand } from "../shared/pi-spawn.ts";
52
46
  import { createJsonlWriter } from "../../shared/jsonl-writer.ts";
53
47
  import { attachPostExitStdioGuard, trySignalChild } from "../../shared/post-exit-stdio-guard.ts";
@@ -70,20 +64,7 @@ import {
70
64
  shouldEscalateMutatingFailures,
71
65
  summarizeRecentMutatingFailures,
72
66
  } from "../shared/long-running-guard.ts";
73
- import {
74
- acceptanceFailureMessage,
75
- acceptanceSelfReviewConfig,
76
- attachFinalizationToLedger,
77
- buildFinalizationProcessFailureLedger,
78
- createFinalizationProcessFailureTurn,
79
- createFinalizationTurn,
80
- evaluateAcceptance,
81
- formatAcceptanceFinalizationPrompt,
82
- formatAcceptancePrompt,
83
- resolveEffectiveAcceptance,
84
- shouldRunAcceptanceFinalization,
85
- stripAcceptanceReport,
86
- } from "../shared/acceptance.ts";
67
+ import { acceptanceFailureMessage, evaluateAcceptance, formatAcceptancePrompt, resolveEffectiveAcceptance, stripAcceptanceReport } from "../shared/acceptance.ts";
87
68
 
88
69
  const artifactOutputByResult = new WeakMap<SingleResult, string>();
89
70
  const acceptanceOutputByResult = new WeakMap<SingleResult, string>();
@@ -109,43 +90,6 @@ function appendRecentOutput(progress: AgentProgress, lines: string[]): void {
109
90
  }
110
91
  }
111
92
 
112
- const FOREGROUND_TIMEOUT_EXIT_CODE = 124;
113
-
114
- function formatForegroundTimeoutMessage(timeoutMs: number | undefined): string {
115
- return timeoutMs ? `Timed out after ${timeoutMs}ms.` : "Timed out.";
116
- }
117
-
118
- function createTimedOutResult(agent: string, task: string, options: RunSyncOptions): SingleResult {
119
- const message = formatForegroundTimeoutMessage(options.timeoutMs);
120
- return {
121
- agent,
122
- task,
123
- exitCode: FOREGROUND_TIMEOUT_EXIT_CODE,
124
- messages: [],
125
- usage: emptyUsage(),
126
- error: message,
127
- finalOutput: message,
128
- timedOut: true,
129
- progress: {
130
- index: options.index ?? 0,
131
- agent,
132
- status: "failed",
133
- task,
134
- recentTools: [],
135
- recentOutput: [message],
136
- toolCount: 0,
137
- tokens: 0,
138
- durationMs: 0,
139
- lastActivityAt: Date.now(),
140
- },
141
- progressSummary: {
142
- toolCount: 0,
143
- tokens: 0,
144
- durationMs: 0,
145
- },
146
- };
147
- }
148
-
149
93
  function stripAcceptanceReportsFromMessages(messages: Message[] | undefined): void {
150
94
  for (const message of messages ?? []) {
151
95
  if (message.role !== "assistant" || !Array.isArray(message.content)) continue;
@@ -204,11 +148,10 @@ async function runSingleAttempt(
204
148
  attemptNotes: string[];
205
149
  outputSnapshot?: SingleOutputSnapshot;
206
150
  originalTask?: string;
207
- completionPolicy: CompletionPolicy;
208
151
  },
209
152
  ): Promise<SingleResult> {
210
153
  const modelArg = applyThinkingSuffix(model, agent.thinking);
211
- const { args, env: sharedEnv, tempDir } = buildPiArgs({
154
+ const { args, env: sharedEnv, tempDir } = buildPiArgs({
212
155
  baseArgs: ["--mode", "json", "-p"],
213
156
  task,
214
157
  sessionEnabled: shared.sessionEnabled,
@@ -221,6 +164,7 @@ async function runSingleAttempt(
221
164
  inheritSkills: agent.inheritSkills,
222
165
  tools: agent.tools,
223
166
  extensions: agent.extensions,
167
+ subagentOnlyExtensions: agent.subagentOnlyExtensions,
224
168
  systemPrompt: shared.systemPrompt,
225
169
  mcpDirectTools: agent.mcpDirectTools,
226
170
  cwd: options.cwd ?? runtimeCwd,
@@ -232,10 +176,10 @@ async function runSingleAttempt(
232
176
  childIndex: options.index ?? 0,
233
177
  parentEventSink: options.nestedRoute?.eventSink,
234
178
  parentControlInbox: options.nestedRoute?.controlInbox,
235
- parentRootRunId: options.nestedRoute?.rootRunId,
236
- parentCapabilityToken: options.nestedRoute?.capabilityToken,
237
- structuredOutput: options.structuredOutput,
238
- });
179
+ parentRootRunId: options.nestedRoute?.rootRunId,
180
+ parentCapabilityToken: options.nestedRoute?.capabilityToken,
181
+ structuredOutput: options.structuredOutput,
182
+ });
239
183
 
240
184
  const result: SingleResult = {
241
185
  agent: agent.name,
@@ -301,12 +245,6 @@ async function runSingleAttempt(
301
245
  let detached = false;
302
246
  let intercomStarted = false;
303
247
  let assistantError: string | undefined;
304
- let timedOut = false;
305
- let resourceLimited = false;
306
- let timeoutTimer: NodeJS.Timeout | undefined;
307
- let timeoutEscalationTimer: NodeJS.Timeout | undefined;
308
- let resourceLimitTimer: NodeJS.Timeout | undefined;
309
- let resourceLimitEscalationTimer: NodeJS.Timeout | undefined;
310
248
  let removeAbortListener: (() => void) | undefined;
311
249
  let removeInterruptListener: (() => void) | undefined;
312
250
  let activityTimer: NodeJS.Timeout | undefined;
@@ -378,22 +316,6 @@ async function runSingleAttempt(
378
316
  settled = true;
379
317
  clearFinalDrainTimers();
380
318
  clearStdioGuard();
381
- if (timeoutTimer) {
382
- clearTimeout(timeoutTimer);
383
- timeoutTimer = undefined;
384
- }
385
- if (timeoutEscalationTimer) {
386
- clearTimeout(timeoutEscalationTimer);
387
- timeoutEscalationTimer = undefined;
388
- }
389
- if (resourceLimitTimer) {
390
- clearTimeout(resourceLimitTimer);
391
- resourceLimitTimer = undefined;
392
- }
393
- if (resourceLimitEscalationTimer) {
394
- clearTimeout(resourceLimitEscalationTimer);
395
- resourceLimitEscalationTimer = undefined;
396
- }
397
319
  if (activityTimer) {
398
320
  clearInterval(activityTimer);
399
321
  activityTimer = undefined;
@@ -488,26 +410,6 @@ async function runSingleAttempt(
488
410
  };
489
411
 
490
412
 
491
- const triggerResourceLimit = (kind: "maxExecutionTimeMs" | "maxTokens", limit: number, observed?: number) => {
492
- if (processClosed || detached || settled || timedOut || resourceLimited) return;
493
- resourceLimited = true;
494
- const message = formatResourceLimitExceeded({ agent: agent.name, kind, limit, observed });
495
- result.resourceLimitExceeded = { kind, limit, ...(observed !== undefined ? { observed } : {}), message };
496
- result.error = message;
497
- result.finalOutput = message;
498
- progress.status = "failed";
499
- progress.durationMs = Date.now() - startTime;
500
- appendRecentOutput(progress, [message]);
501
- progress.activityState = undefined;
502
- fireUpdate();
503
- trySignalChild(proc, "SIGINT");
504
- resourceLimitEscalationTimer = setTimeout(() => {
505
- if (settled || processClosed || detached) return;
506
- trySignalChild(proc, "SIGTERM");
507
- }, 1000);
508
- resourceLimitEscalationTimer.unref?.();
509
- };
510
-
511
413
  const emitUpdateSnapshot = (text: string) => {
512
414
  if (!options.onUpdate || processClosed) return;
513
415
  const progressSnapshot = snapshotProgress(progress);
@@ -592,9 +494,6 @@ async function runSingleAttempt(
592
494
  result.usage.cacheWrite += u.cacheWrite || 0;
593
495
  result.usage.cost += u.cost?.total || 0;
594
496
  progress.tokens = result.usage.input + result.usage.output;
595
- if (options.maxTokens !== undefined && progress.tokens >= options.maxTokens) {
596
- triggerResourceLimit("maxTokens", options.maxTokens, progress.tokens);
597
- }
598
497
  }
599
498
  if (!result.model && evt.message.model) result.model = evt.message.model;
600
499
  if (evt.message.errorMessage) assistantError = evt.message.errorMessage;
@@ -723,45 +622,9 @@ async function runSingleAttempt(
723
622
  }
724
623
  }
725
624
 
726
- if (options.timeoutAt !== undefined) {
727
- const triggerTimeout = () => {
728
- if (processClosed || detached || settled || timedOut || resourceLimited) return;
729
- timedOut = true;
730
- const message = formatForegroundTimeoutMessage(options.timeoutMs);
731
- result.timedOut = true;
732
- result.error = message;
733
- result.finalOutput = message;
734
- progress.status = "failed";
735
- progress.durationMs = Date.now() - startTime;
736
- appendRecentOutput(progress, [message]);
737
- progress.activityState = undefined;
738
- fireUpdate();
739
- trySignalChild(proc, "SIGINT");
740
- timeoutEscalationTimer = setTimeout(() => {
741
- if (settled || processClosed || detached) return;
742
- trySignalChild(proc, "SIGTERM");
743
- }, 1000);
744
- timeoutEscalationTimer.unref?.();
745
- };
746
- const delay = options.timeoutAt - Date.now();
747
- if (delay <= 0) triggerTimeout();
748
- else {
749
- timeoutTimer = setTimeout(triggerTimeout, delay);
750
- timeoutTimer.unref?.();
751
- }
752
- }
753
-
754
- if (options.maxExecutionTimeMs !== undefined) {
755
- const maxExecutionTimeMs = options.maxExecutionTimeMs;
756
- resourceLimitTimer = setTimeout(() => {
757
- triggerResourceLimit("maxExecutionTimeMs", maxExecutionTimeMs);
758
- }, maxExecutionTimeMs);
759
- resourceLimitTimer.unref?.();
760
- }
761
-
762
625
  if (options.interruptSignal) {
763
626
  const interrupt = () => {
764
- if (processClosed || detached || settled || timedOut || resourceLimited) return;
627
+ if (processClosed || detached || settled) return;
765
628
  interruptedByControl = true;
766
629
  progress.status = "running";
767
630
  progress.durationMs = Date.now() - startTime;
@@ -783,40 +646,6 @@ async function runSingleAttempt(
783
646
  }
784
647
  });
785
648
  result.exitCode = exitCode;
786
- if (result.resourceLimitExceeded) {
787
- result.exitCode = 1;
788
- result.error = result.error ?? result.resourceLimitExceeded.message;
789
- result.finalOutput = result.finalOutput || result.error;
790
- if (result.progress) {
791
- result.progress.status = "failed";
792
- result.progress.activityState = undefined;
793
- result.progress.durationMs = Date.now() - startTime;
794
- }
795
- result.progressSummary = {
796
- toolCount: progress.toolCount,
797
- tokens: progress.tokens,
798
- durationMs: result.progress?.durationMs ?? Date.now() - startTime,
799
- };
800
- result.controlEvents = allControlEvents.length ? allControlEvents : undefined;
801
- return result;
802
- }
803
- if (result.timedOut) {
804
- result.exitCode = FOREGROUND_TIMEOUT_EXIT_CODE;
805
- result.error = result.error ?? formatForegroundTimeoutMessage(options.timeoutMs);
806
- result.finalOutput = result.finalOutput || result.error;
807
- if (result.progress) {
808
- result.progress.status = "failed";
809
- result.progress.activityState = undefined;
810
- result.progress.durationMs = Date.now() - startTime;
811
- }
812
- result.progressSummary = {
813
- toolCount: progress.toolCount,
814
- tokens: progress.tokens,
815
- durationMs: result.progress?.durationMs ?? Date.now() - startTime,
816
- };
817
- result.controlEvents = allControlEvents.length ? allControlEvents : undefined;
818
- return result;
819
- }
820
649
  if (interruptedByControl) {
821
650
  result.exitCode = 0;
822
651
  result.interrupted = true;
@@ -881,9 +710,9 @@ async function runSingleAttempt(
881
710
  durationMs: progress.durationMs,
882
711
  };
883
712
 
884
- const acceptanceOutput = getFinalOutput(result.messages);
885
- let fullOutput = stripAcceptanceReport(acceptanceOutput);
886
- const completionGuard = result.exitCode === 0 && !result.error && shared.completionPolicy === "mutation-guard"
713
+ const acceptanceOutput = getFinalOutput(result.messages);
714
+ let fullOutput = stripAcceptanceReport(acceptanceOutput);
715
+ const completionGuard = result.exitCode === 0 && !result.error && agent.completionGuard !== false
887
716
  ? evaluateCompletionMutationGuard({
888
717
  agent: agent.name,
889
718
  task: shared.originalTask ?? task,
@@ -892,8 +721,7 @@ async function runSingleAttempt(
892
721
  mcpDirectTools: agent.mcpDirectTools,
893
722
  })
894
723
  : undefined;
895
- const completionGuardTriggered = completionGuard?.triggered === true && !observedMutationAttempt;
896
- if (completionGuardTriggered) {
724
+ if (completionGuard?.triggered && !observedMutationAttempt) {
897
725
  result.exitCode = 1;
898
726
  result.error = "Subagent completed without making edits for an implementation task.\nIt appears to have returned planning or scratchpad output instead of applying changes.";
899
727
  progress.status = "failed";
@@ -909,17 +737,17 @@ async function runSingleAttempt(
909
737
  reason: "completion_guard",
910
738
  }));
911
739
  }
912
- if (options.outputPath && result.exitCode === 0) {
913
- const resolvedOutput = resolveSingleOutput(options.outputPath, fullOutput, shared.outputSnapshot);
914
- fullOutput = stripAcceptanceReport(resolvedOutput.fullOutput);
915
- result.savedOutputPath = resolvedOutput.savedPath;
916
- result.outputSaveError = resolvedOutput.saveError;
917
- if (resolvedOutput.savedPath) {
918
- result.outputReference = formatSavedOutputReference(resolvedOutput.savedPath, fullOutput);
919
- }
740
+ if (options.outputPath && result.exitCode === 0) {
741
+ const resolvedOutput = resolveSingleOutput(options.outputPath, fullOutput, shared.outputSnapshot);
742
+ fullOutput = stripAcceptanceReport(resolvedOutput.fullOutput);
743
+ result.savedOutputPath = resolvedOutput.savedPath;
744
+ result.outputSaveError = resolvedOutput.saveError;
745
+ if (resolvedOutput.savedPath) {
746
+ result.outputReference = formatSavedOutputReference(resolvedOutput.savedPath, fullOutput);
747
+ }
920
748
  }
921
- artifactOutputByResult.set(result, fullOutput);
922
- acceptanceOutputByResult.set(result, acceptanceOutput);
749
+ artifactOutputByResult.set(result, fullOutput);
750
+ acceptanceOutputByResult.set(result, acceptanceOutput);
923
751
  result.outputMode = options.outputMode ?? "inline";
924
752
  result.finalOutput = options.outputMode === "file-only" && result.savedOutputPath && result.outputReference
925
753
  ? result.outputReference.message
@@ -942,99 +770,6 @@ async function runSingleAttempt(
942
770
  return result;
943
771
  }
944
772
 
945
- async function runAcceptanceFinalizationLoop(input: {
946
- runtimeCwd: string;
947
- agent: AgentConfig;
948
- result: SingleResult;
949
- initialLedger: AcceptanceLedger;
950
- initialOutput: string;
951
- acceptance: ResolvedAcceptanceConfig;
952
- options: RunSyncOptions;
953
- systemPrompt: string;
954
- resolvedSkillNames?: string[];
955
- skillsWarning?: string;
956
- }): Promise<AcceptanceLedger> {
957
- const sessionFile = input.result.sessionFile ?? input.options.sessionFile;
958
- const maxTurns = input.acceptance.finalization.maxTurns;
959
- const turns: AcceptanceFinalizationTurn[] = [];
960
- if (!sessionFile) {
961
- const message = "Acceptance finalization requires a session file for same-session continuation.";
962
- turns.push(createFinalizationProcessFailureTurn({ turn: 1, prompt: "", message }));
963
- return buildFinalizationProcessFailureLedger({ initialLedger: input.initialLedger, turns, maxTurns, message });
964
- }
965
-
966
- const selfReviewAcceptance = acceptanceSelfReviewConfig(input.acceptance);
967
- let previousFailure = acceptanceFailureMessage(input.initialLedger);
968
- let authoritativeLedger = input.initialLedger;
969
- for (let turn = 1; turn <= maxTurns; turn++) {
970
- const prompt = formatAcceptanceFinalizationPrompt({
971
- acceptance: input.acceptance,
972
- initialOutput: input.initialOutput,
973
- initialLedger: input.initialLedger,
974
- turn,
975
- maxTurns,
976
- ...(previousFailure ? { previousFailure } : {}),
977
- });
978
- const finalizationOptions: RunSyncOptions = { ...input.options, sessionFile, outputMode: "inline" };
979
- delete finalizationOptions.sessionDir;
980
- delete finalizationOptions.outputPath;
981
- delete finalizationOptions.structuredOutput;
982
- delete finalizationOptions.onUpdate;
983
- finalizationOptions.allowIntercomDetach = false;
984
- const finalizationResult = await runSingleAttempt(
985
- input.runtimeCwd,
986
- input.agent,
987
- prompt,
988
- input.result.model,
989
- finalizationOptions,
990
- {
991
- sessionEnabled: true,
992
- systemPrompt: input.systemPrompt,
993
- resolvedSkillNames: input.resolvedSkillNames,
994
- skillsWarning: input.skillsWarning,
995
- attemptNotes: [],
996
- originalTask: prompt,
997
- completionPolicy: "acceptance-contract",
998
- },
999
- );
1000
- sumUsage(input.result.usage, finalizationResult.usage);
1001
- input.result.progressSummary = {
1002
- toolCount: (input.result.progressSummary?.toolCount ?? 0) + (finalizationResult.progressSummary?.toolCount ?? 0),
1003
- tokens: input.result.usage.input + input.result.usage.output,
1004
- durationMs: (input.result.progressSummary?.durationMs ?? 0) + (finalizationResult.progressSummary?.durationMs ?? 0),
1005
- };
1006
- if (finalizationResult.controlEvents?.length) {
1007
- input.result.controlEvents = [...(input.result.controlEvents ?? []), ...finalizationResult.controlEvents];
1008
- }
1009
- const rawOutput = acceptanceOutputByResult.get(finalizationResult) ?? getFinalOutput(finalizationResult.messages) ?? finalizationResult.finalOutput ?? "";
1010
- if (finalizationResult.exitCode !== 0 || finalizationResult.error || finalizationResult.detached || finalizationResult.interrupted) {
1011
- const message = finalizationResult.error ?? "Acceptance finalization turn did not complete successfully.";
1012
- turns.push(createFinalizationProcessFailureTurn({ turn, prompt, rawOutput, message }));
1013
- return buildFinalizationProcessFailureLedger({ initialLedger: input.initialLedger, turns, maxTurns, message });
1014
- }
1015
- const selfReviewLedger = await evaluateAcceptance({
1016
- acceptance: selfReviewAcceptance,
1017
- output: rawOutput,
1018
- cwd: input.options.cwd ?? input.runtimeCwd,
1019
- });
1020
- authoritativeLedger = selfReviewLedger;
1021
- turns.push(createFinalizationTurn({ turn, prompt, rawOutput, ledger: selfReviewLedger }));
1022
- const failure = acceptanceFailureMessage(selfReviewLedger);
1023
- if (!failure) {
1024
- authoritativeLedger = input.acceptance === selfReviewAcceptance
1025
- ? selfReviewLedger
1026
- : await evaluateAcceptance({
1027
- acceptance: input.acceptance,
1028
- output: rawOutput,
1029
- cwd: input.options.cwd ?? input.runtimeCwd,
1030
- });
1031
- return attachFinalizationToLedger({ initialLedger: input.initialLedger, authoritativeLedger, turns, status: "completed", maxTurns });
1032
- }
1033
- previousFailure = failure;
1034
- }
1035
- return attachFinalizationToLedger({ initialLedger: input.initialLedger, authoritativeLedger, turns, status: "failed", maxTurns });
1036
- }
1037
-
1038
773
  /**
1039
774
  * Run a subagent synchronously (blocking until complete)
1040
775
  */
@@ -1068,16 +803,8 @@ export async function runSync(
1068
803
  error: outputModeValidationError,
1069
804
  };
1070
805
  }
1071
- if (options.timeoutAt !== undefined && Date.now() >= options.timeoutAt) {
1072
- return createTimedOutResult(agentName, task, options);
1073
- }
1074
- const effectiveOptions: RunSyncOptions = {
1075
- ...options,
1076
- maxExecutionTimeMs: options.maxExecutionTimeMs ?? agent.maxExecutionTimeMs,
1077
- maxTokens: options.maxTokens ?? agent.maxTokens,
1078
- };
1079
806
 
1080
- const shareEnabled = effectiveOptions.share === true;
807
+ const shareEnabled = options.share === true;
1081
808
  const effectiveAcceptance = resolveEffectiveAcceptance({
1082
809
  explicit: options.acceptance,
1083
810
  agentName,
@@ -1087,10 +814,6 @@ export async function runSync(
1087
814
  dynamic: options.acceptanceContext?.dynamic,
1088
815
  dynamicGroup: options.acceptanceContext?.dynamicGroup,
1089
816
  });
1090
- if (shouldRunAcceptanceFinalization(effectiveAcceptance) && !options.sessionFile) {
1091
- const sessionDir = options.sessionDir ?? mkdtempSync(path.join(os.tmpdir(), "pi-subagent-finalization-"));
1092
- options.sessionFile = path.join(sessionDir, "session.jsonl");
1093
- }
1094
817
  const acceptancePrompt = formatAcceptancePrompt(effectiveAcceptance);
1095
818
  const taskWithAcceptance = acceptancePrompt ? `${task}\n${acceptancePrompt}` : task;
1096
819
  const sessionEnabled = Boolean(options.sessionFile || options.sessionDir) || shareEnabled;
@@ -1128,13 +851,13 @@ export async function runSync(
1128
851
 
1129
852
  let artifactPathsResult: ArtifactPaths | undefined;
1130
853
  let jsonlPath: string | undefined;
1131
- if (effectiveOptions.artifactsDir && effectiveOptions.artifactConfig?.enabled !== false) {
1132
- artifactPathsResult = getArtifactPaths(effectiveOptions.artifactsDir, effectiveOptions.runId, agentName, effectiveOptions.index);
1133
- ensureArtifactsDir(effectiveOptions.artifactsDir);
1134
- if (effectiveOptions.artifactConfig?.includeInput !== false) {
854
+ if (options.artifactsDir && options.artifactConfig?.enabled !== false) {
855
+ artifactPathsResult = getArtifactPaths(options.artifactsDir, options.runId, agentName, options.index);
856
+ ensureArtifactsDir(options.artifactsDir);
857
+ if (options.artifactConfig?.includeInput !== false) {
1135
858
  writeArtifact(artifactPathsResult.inputPath, `# Task for ${agentName}\n\n${taskWithAcceptance}`);
1136
859
  }
1137
- if (effectiveOptions.artifactConfig?.includeJsonl !== false) {
860
+ if (options.artifactConfig?.includeJsonl !== false) {
1138
861
  jsonlPath = artifactPathsResult.jsonlPath;
1139
862
  }
1140
863
  }
@@ -1144,8 +867,8 @@ export async function runSync(
1144
867
  for (let i = 0; i < modelsToTry.length; i++) {
1145
868
  const candidate = modelsToTry[i];
1146
869
  if (candidate) attemptedModels.push(candidate);
1147
- const outputSnapshot = captureSingleOutputSnapshot(effectiveOptions.outputPath);
1148
- const result = await runSingleAttempt(runtimeCwd, agent, taskWithAcceptance, candidate, effectiveOptions, {
870
+ const outputSnapshot = captureSingleOutputSnapshot(options.outputPath);
871
+ const result = await runSingleAttempt(runtimeCwd, agent, taskWithAcceptance, candidate, options, {
1149
872
  sessionEnabled,
1150
873
  systemPrompt,
1151
874
  resolvedSkillNames: resolvedSkills.length > 0 ? resolvedSkills.map((skill) => skill.name) : undefined,
@@ -1155,14 +878,6 @@ export async function runSync(
1155
878
  attemptNotes,
1156
879
  outputSnapshot,
1157
880
  originalTask: task,
1158
- completionPolicy: resolveCompletionPolicy({
1159
- agent: agent.name,
1160
- task,
1161
- completionGuardEnabled: agent.completionGuard !== false,
1162
- usesAcceptanceContract: effectiveAcceptance.explicit,
1163
- tools: agent.tools,
1164
- mcpDirectTools: agent.mcpDirectTools,
1165
- }),
1166
881
  });
1167
882
  lastResult = result;
1168
883
  sumUsage(aggregateUsage, result.usage);
@@ -1180,7 +895,7 @@ export async function runSync(
1180
895
  if (attemptSucceeded) {
1181
896
  break;
1182
897
  }
1183
- if (result.timedOut || result.resourceLimitExceeded || !isRetryableModelFailure(result.error) || i === modelsToTry.length - 1) {
898
+ if (!isRetryableModelFailure(result.error) || i === modelsToTry.length - 1) {
1184
899
  break;
1185
900
  }
1186
901
  attemptNotes.push(formatModelAttemptNote(attempt, modelsToTry[i + 1]));
@@ -1252,33 +967,14 @@ export async function runSync(
1252
967
  if (sessionFile) result.sessionFile = sessionFile;
1253
968
  }
1254
969
 
1255
- const initialAcceptanceOutput = acceptanceOutputByResult.get(result) ?? result.finalOutput ?? "";
1256
- const acceptanceForInitialReport = shouldRunAcceptanceFinalization(effectiveAcceptance)
1257
- ? acceptanceSelfReviewConfig(effectiveAcceptance)
1258
- : effectiveAcceptance;
1259
- const initialAcceptance = await evaluateAcceptance({
1260
- acceptance: acceptanceForInitialReport,
1261
- output: initialAcceptanceOutput,
1262
- cwd: options.cwd ?? runtimeCwd,
1263
- });
1264
- result.acceptance = initialAcceptance;
1265
- if (shouldRunAcceptanceFinalization(effectiveAcceptance) && result.exitCode === 0 && !result.detached && !result.interrupted) {
1266
- result.acceptance = await runAcceptanceFinalizationLoop({
1267
- runtimeCwd,
1268
- agent,
1269
- result,
1270
- initialLedger: initialAcceptance,
1271
- initialOutput: initialAcceptanceOutput,
970
+ result.acceptance = await evaluateAcceptance({
1272
971
  acceptance: effectiveAcceptance,
1273
- options,
1274
- systemPrompt,
1275
- resolvedSkillNames: resolvedSkills.length > 0 ? resolvedSkills.map((skill) => skill.name) : undefined,
1276
- ...(missingSkills.length > 0 ? { skillsWarning: `Skills not found: ${missingSkills.join(", ")}` } : {}),
972
+ output: acceptanceOutputByResult.get(result) ?? result.finalOutput ?? "",
973
+ cwd: options.cwd ?? runtimeCwd,
1277
974
  });
1278
- }
1279
- const acceptanceFailure = acceptanceFailureMessage(result.acceptance);
1280
- stripAcceptanceReportsFromMessages(result.messages);
1281
- if (acceptanceFailure && result.acceptance.explicit && result.exitCode === 0 && !result.detached && !result.interrupted) {
975
+ const acceptanceFailure = acceptanceFailureMessage(result.acceptance);
976
+ stripAcceptanceReportsFromMessages(result.messages);
977
+ if (acceptanceFailure && result.acceptance.explicit && result.exitCode === 0 && !result.detached && !result.interrupted) {
1282
978
  result.exitCode = 1;
1283
979
  result.error = result.error ? `${result.error}\n${acceptanceFailure}` : acceptanceFailure;
1284
980
  if (result.progress) {