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
@@ -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,
@@ -232,10 +175,10 @@ async function runSingleAttempt(
232
175
  childIndex: options.index ?? 0,
233
176
  parentEventSink: options.nestedRoute?.eventSink,
234
177
  parentControlInbox: options.nestedRoute?.controlInbox,
235
- parentRootRunId: options.nestedRoute?.rootRunId,
236
- parentCapabilityToken: options.nestedRoute?.capabilityToken,
237
- structuredOutput: options.structuredOutput,
238
- });
178
+ parentRootRunId: options.nestedRoute?.rootRunId,
179
+ parentCapabilityToken: options.nestedRoute?.capabilityToken,
180
+ structuredOutput: options.structuredOutput,
181
+ });
239
182
 
240
183
  const result: SingleResult = {
241
184
  agent: agent.name,
@@ -301,12 +244,6 @@ async function runSingleAttempt(
301
244
  let detached = false;
302
245
  let intercomStarted = false;
303
246
  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
247
  let removeAbortListener: (() => void) | undefined;
311
248
  let removeInterruptListener: (() => void) | undefined;
312
249
  let activityTimer: NodeJS.Timeout | undefined;
@@ -378,22 +315,6 @@ async function runSingleAttempt(
378
315
  settled = true;
379
316
  clearFinalDrainTimers();
380
317
  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
318
  if (activityTimer) {
398
319
  clearInterval(activityTimer);
399
320
  activityTimer = undefined;
@@ -488,26 +409,6 @@ async function runSingleAttempt(
488
409
  };
489
410
 
490
411
 
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
412
  const emitUpdateSnapshot = (text: string) => {
512
413
  if (!options.onUpdate || processClosed) return;
513
414
  const progressSnapshot = snapshotProgress(progress);
@@ -592,9 +493,6 @@ async function runSingleAttempt(
592
493
  result.usage.cacheWrite += u.cacheWrite || 0;
593
494
  result.usage.cost += u.cost?.total || 0;
594
495
  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
496
  }
599
497
  if (!result.model && evt.message.model) result.model = evt.message.model;
600
498
  if (evt.message.errorMessage) assistantError = evt.message.errorMessage;
@@ -723,45 +621,9 @@ async function runSingleAttempt(
723
621
  }
724
622
  }
725
623
 
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
624
  if (options.interruptSignal) {
763
625
  const interrupt = () => {
764
- if (processClosed || detached || settled || timedOut || resourceLimited) return;
626
+ if (processClosed || detached || settled) return;
765
627
  interruptedByControl = true;
766
628
  progress.status = "running";
767
629
  progress.durationMs = Date.now() - startTime;
@@ -783,40 +645,6 @@ async function runSingleAttempt(
783
645
  }
784
646
  });
785
647
  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
648
  if (interruptedByControl) {
821
649
  result.exitCode = 0;
822
650
  result.interrupted = true;
@@ -881,9 +709,9 @@ async function runSingleAttempt(
881
709
  durationMs: progress.durationMs,
882
710
  };
883
711
 
884
- const acceptanceOutput = getFinalOutput(result.messages);
885
- let fullOutput = stripAcceptanceReport(acceptanceOutput);
886
- const completionGuard = result.exitCode === 0 && !result.error && shared.completionPolicy === "mutation-guard"
712
+ const acceptanceOutput = getFinalOutput(result.messages);
713
+ let fullOutput = stripAcceptanceReport(acceptanceOutput);
714
+ const completionGuard = result.exitCode === 0 && !result.error && agent.completionGuard !== false
887
715
  ? evaluateCompletionMutationGuard({
888
716
  agent: agent.name,
889
717
  task: shared.originalTask ?? task,
@@ -892,8 +720,7 @@ async function runSingleAttempt(
892
720
  mcpDirectTools: agent.mcpDirectTools,
893
721
  })
894
722
  : undefined;
895
- const completionGuardTriggered = completionGuard?.triggered === true && !observedMutationAttempt;
896
- if (completionGuardTriggered) {
723
+ if (completionGuard?.triggered && !observedMutationAttempt) {
897
724
  result.exitCode = 1;
898
725
  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
726
  progress.status = "failed";
@@ -909,17 +736,17 @@ async function runSingleAttempt(
909
736
  reason: "completion_guard",
910
737
  }));
911
738
  }
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
- }
739
+ if (options.outputPath && result.exitCode === 0) {
740
+ const resolvedOutput = resolveSingleOutput(options.outputPath, fullOutput, shared.outputSnapshot);
741
+ fullOutput = stripAcceptanceReport(resolvedOutput.fullOutput);
742
+ result.savedOutputPath = resolvedOutput.savedPath;
743
+ result.outputSaveError = resolvedOutput.saveError;
744
+ if (resolvedOutput.savedPath) {
745
+ result.outputReference = formatSavedOutputReference(resolvedOutput.savedPath, fullOutput);
746
+ }
920
747
  }
921
- artifactOutputByResult.set(result, fullOutput);
922
- acceptanceOutputByResult.set(result, acceptanceOutput);
748
+ artifactOutputByResult.set(result, fullOutput);
749
+ acceptanceOutputByResult.set(result, acceptanceOutput);
923
750
  result.outputMode = options.outputMode ?? "inline";
924
751
  result.finalOutput = options.outputMode === "file-only" && result.savedOutputPath && result.outputReference
925
752
  ? result.outputReference.message
@@ -942,99 +769,6 @@ async function runSingleAttempt(
942
769
  return result;
943
770
  }
944
771
 
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
772
  /**
1039
773
  * Run a subagent synchronously (blocking until complete)
1040
774
  */
@@ -1068,16 +802,8 @@ export async function runSync(
1068
802
  error: outputModeValidationError,
1069
803
  };
1070
804
  }
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
805
 
1080
- const shareEnabled = effectiveOptions.share === true;
806
+ const shareEnabled = options.share === true;
1081
807
  const effectiveAcceptance = resolveEffectiveAcceptance({
1082
808
  explicit: options.acceptance,
1083
809
  agentName,
@@ -1087,10 +813,6 @@ export async function runSync(
1087
813
  dynamic: options.acceptanceContext?.dynamic,
1088
814
  dynamicGroup: options.acceptanceContext?.dynamicGroup,
1089
815
  });
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
816
  const acceptancePrompt = formatAcceptancePrompt(effectiveAcceptance);
1095
817
  const taskWithAcceptance = acceptancePrompt ? `${task}\n${acceptancePrompt}` : task;
1096
818
  const sessionEnabled = Boolean(options.sessionFile || options.sessionDir) || shareEnabled;
@@ -1128,13 +850,13 @@ export async function runSync(
1128
850
 
1129
851
  let artifactPathsResult: ArtifactPaths | undefined;
1130
852
  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) {
853
+ if (options.artifactsDir && options.artifactConfig?.enabled !== false) {
854
+ artifactPathsResult = getArtifactPaths(options.artifactsDir, options.runId, agentName, options.index);
855
+ ensureArtifactsDir(options.artifactsDir);
856
+ if (options.artifactConfig?.includeInput !== false) {
1135
857
  writeArtifact(artifactPathsResult.inputPath, `# Task for ${agentName}\n\n${taskWithAcceptance}`);
1136
858
  }
1137
- if (effectiveOptions.artifactConfig?.includeJsonl !== false) {
859
+ if (options.artifactConfig?.includeJsonl !== false) {
1138
860
  jsonlPath = artifactPathsResult.jsonlPath;
1139
861
  }
1140
862
  }
@@ -1144,8 +866,8 @@ export async function runSync(
1144
866
  for (let i = 0; i < modelsToTry.length; i++) {
1145
867
  const candidate = modelsToTry[i];
1146
868
  if (candidate) attemptedModels.push(candidate);
1147
- const outputSnapshot = captureSingleOutputSnapshot(effectiveOptions.outputPath);
1148
- const result = await runSingleAttempt(runtimeCwd, agent, taskWithAcceptance, candidate, effectiveOptions, {
869
+ const outputSnapshot = captureSingleOutputSnapshot(options.outputPath);
870
+ const result = await runSingleAttempt(runtimeCwd, agent, taskWithAcceptance, candidate, options, {
1149
871
  sessionEnabled,
1150
872
  systemPrompt,
1151
873
  resolvedSkillNames: resolvedSkills.length > 0 ? resolvedSkills.map((skill) => skill.name) : undefined,
@@ -1155,14 +877,6 @@ export async function runSync(
1155
877
  attemptNotes,
1156
878
  outputSnapshot,
1157
879
  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
880
  });
1167
881
  lastResult = result;
1168
882
  sumUsage(aggregateUsage, result.usage);
@@ -1180,7 +894,7 @@ export async function runSync(
1180
894
  if (attemptSucceeded) {
1181
895
  break;
1182
896
  }
1183
- if (result.timedOut || result.resourceLimitExceeded || !isRetryableModelFailure(result.error) || i === modelsToTry.length - 1) {
897
+ if (!isRetryableModelFailure(result.error) || i === modelsToTry.length - 1) {
1184
898
  break;
1185
899
  }
1186
900
  attemptNotes.push(formatModelAttemptNote(attempt, modelsToTry[i + 1]));
@@ -1252,33 +966,14 @@ export async function runSync(
1252
966
  if (sessionFile) result.sessionFile = sessionFile;
1253
967
  }
1254
968
 
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,
969
+ result.acceptance = await evaluateAcceptance({
1272
970
  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(", ")}` } : {}),
971
+ output: acceptanceOutputByResult.get(result) ?? result.finalOutput ?? "",
972
+ cwd: options.cwd ?? runtimeCwd,
1277
973
  });
1278
- }
1279
- const acceptanceFailure = acceptanceFailureMessage(result.acceptance);
1280
- stripAcceptanceReportsFromMessages(result.messages);
1281
- if (acceptanceFailure && result.acceptance.explicit && result.exitCode === 0 && !result.detached && !result.interrupted) {
974
+ const acceptanceFailure = acceptanceFailureMessage(result.acceptance);
975
+ stripAcceptanceReportsFromMessages(result.messages);
976
+ if (acceptanceFailure && result.acceptance.explicit && result.exitCode === 0 && !result.detached && !result.interrupted) {
1282
977
  result.exitCode = 1;
1283
978
  result.error = result.error ? `${result.error}\n${acceptanceFailure}` : acceptanceFailure;
1284
979
  if (result.progress) {