@bastani/atomic 0.8.29 → 0.8.30-alpha.2

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 (144) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/builtin/cursor/CHANGELOG.md +4 -0
  3. package/dist/builtin/cursor/package.json +2 -2
  4. package/dist/builtin/intercom/CHANGELOG.md +4 -0
  5. package/dist/builtin/intercom/package.json +2 -2
  6. package/dist/builtin/mcp/CHANGELOG.md +4 -0
  7. package/dist/builtin/mcp/package.json +3 -3
  8. package/dist/builtin/subagents/CHANGELOG.md +9 -0
  9. package/dist/builtin/subagents/README.md +10 -30
  10. package/dist/builtin/subagents/package.json +4 -4
  11. package/dist/builtin/subagents/skills/subagent/SKILL.md +5 -11
  12. package/dist/builtin/subagents/src/agents/agent-management.ts +0 -5
  13. package/dist/builtin/subagents/src/agents/agent-serializer.ts +7 -3
  14. package/dist/builtin/subagents/src/agents/agents.ts +4 -29
  15. package/dist/builtin/subagents/src/agents/chain-serializer.ts +27 -25
  16. package/dist/builtin/subagents/src/extension/schemas.ts +0 -75
  17. package/dist/builtin/subagents/src/runs/background/async-execution.ts +0 -29
  18. package/dist/builtin/subagents/src/runs/background/run-status.ts +1 -2
  19. package/dist/builtin/subagents/src/runs/background/subagent-runner.ts +134 -239
  20. package/dist/builtin/subagents/src/runs/foreground/chain-execution.ts +1 -52
  21. package/dist/builtin/subagents/src/runs/foreground/execution.ts +103 -94
  22. package/dist/builtin/subagents/src/runs/foreground/subagent-executor.ts +0 -10
  23. package/dist/builtin/subagents/src/runs/shared/dynamic-fanout.ts +16 -8
  24. package/dist/builtin/subagents/src/runs/shared/model-fallback.ts +0 -1
  25. package/dist/builtin/subagents/src/runs/shared/parallel-utils.ts +0 -3
  26. package/dist/builtin/subagents/src/runs/shared/structured-output.ts +67 -2
  27. package/dist/builtin/subagents/src/runs/shared/subagent-control.ts +6 -20
  28. package/dist/builtin/subagents/src/runs/shared/subagent-prompt-runtime.ts +1 -1
  29. package/dist/builtin/subagents/src/runs/shared/workflow-graph.ts +2 -6
  30. package/dist/builtin/subagents/src/shared/settings.ts +1 -4
  31. package/dist/builtin/subagents/src/shared/types.ts +1 -156
  32. package/dist/builtin/subagents/src/tui/render.ts +0 -1
  33. package/dist/builtin/web-access/CHANGELOG.md +4 -0
  34. package/dist/builtin/web-access/package.json +2 -2
  35. package/dist/builtin/workflows/CHANGELOG.md +11 -0
  36. package/dist/builtin/workflows/README.md +2 -2
  37. package/dist/builtin/workflows/package.json +2 -2
  38. package/dist/builtin/workflows/src/extension/index.ts +8 -1
  39. package/dist/builtin/workflows/src/extension/wiring.ts +66 -10
  40. package/dist/builtin/workflows/src/runs/foreground/executor.ts +70 -19
  41. package/dist/builtin/workflows/src/runs/foreground/stage-runner.ts +98 -14
  42. package/dist/builtin/workflows/src/runs/shared/model-fallback.ts +0 -1
  43. package/dist/builtin/workflows/src/shared/persistence-restore.ts +4 -0
  44. package/dist/builtin/workflows/src/shared/persistence-session-entries.ts +4 -0
  45. package/dist/builtin/workflows/src/shared/store.ts +2 -0
  46. package/dist/config.d.ts.map +1 -1
  47. package/dist/config.js +18 -4
  48. package/dist/config.js.map +1 -1
  49. package/dist/core/agent-session.d.ts +2 -2
  50. package/dist/core/agent-session.d.ts.map +1 -1
  51. package/dist/core/agent-session.js +21 -9
  52. package/dist/core/agent-session.js.map +1 -1
  53. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  54. package/dist/core/compaction/branch-summarization.js +2 -2
  55. package/dist/core/compaction/branch-summarization.js.map +1 -1
  56. package/dist/core/compaction/compaction.d.ts +6 -0
  57. package/dist/core/compaction/compaction.d.ts.map +1 -1
  58. package/dist/core/compaction/compaction.js +2 -0
  59. package/dist/core/compaction/compaction.js.map +1 -1
  60. package/dist/core/compaction/context-compaction.d.ts +43 -16
  61. package/dist/core/compaction/context-compaction.d.ts.map +1 -1
  62. package/dist/core/compaction/context-compaction.js +561 -189
  63. package/dist/core/compaction/context-compaction.js.map +1 -1
  64. package/dist/core/extensions/loader.d.ts +4 -2
  65. package/dist/core/extensions/loader.d.ts.map +1 -1
  66. package/dist/core/extensions/loader.js +11 -7
  67. package/dist/core/extensions/loader.js.map +1 -1
  68. package/dist/core/extensions/types.d.ts +14 -3
  69. package/dist/core/extensions/types.d.ts.map +1 -1
  70. package/dist/core/extensions/types.js.map +1 -1
  71. package/dist/core/model-registry.d.ts.map +1 -1
  72. package/dist/core/model-registry.js +2 -1
  73. package/dist/core/model-registry.js.map +1 -1
  74. package/dist/core/package-manager.d.ts +1 -1
  75. package/dist/core/package-manager.d.ts.map +1 -1
  76. package/dist/core/package-manager.js +52 -18
  77. package/dist/core/package-manager.js.map +1 -1
  78. package/dist/core/resource-loader.d.ts +20 -0
  79. package/dist/core/resource-loader.d.ts.map +1 -1
  80. package/dist/core/resource-loader.js +89 -24
  81. package/dist/core/resource-loader.js.map +1 -1
  82. package/dist/core/session-manager.d.ts +14 -1
  83. package/dist/core/session-manager.d.ts.map +1 -1
  84. package/dist/core/session-manager.js +145 -3
  85. package/dist/core/session-manager.js.map +1 -1
  86. package/dist/core/settings-manager.d.ts +9 -0
  87. package/dist/core/settings-manager.d.ts.map +1 -1
  88. package/dist/core/settings-manager.js +16 -0
  89. package/dist/core/settings-manager.js.map +1 -1
  90. package/dist/core/thinking-blocks.d.ts +7 -0
  91. package/dist/core/thinking-blocks.d.ts.map +1 -0
  92. package/dist/core/thinking-blocks.js +16 -0
  93. package/dist/core/thinking-blocks.js.map +1 -0
  94. package/dist/core/tools/bash.d.ts.map +1 -1
  95. package/dist/core/tools/bash.js +4 -0
  96. package/dist/core/tools/bash.js.map +1 -1
  97. package/dist/index.d.ts +2 -2
  98. package/dist/index.d.ts.map +1 -1
  99. package/dist/index.js +1 -1
  100. package/dist/index.js.map +1 -1
  101. package/dist/main.d.ts.map +1 -1
  102. package/dist/main.js +30 -0
  103. package/dist/main.js.map +1 -1
  104. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  105. package/dist/modes/interactive/components/tree-selector.js +87 -12
  106. package/dist/modes/interactive/components/tree-selector.js.map +1 -1
  107. package/dist/modes/interactive/interactive-mode.d.ts +1 -0
  108. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  109. package/dist/modes/interactive/interactive-mode.js +37 -18
  110. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  111. package/dist/modes/interactive/theme/theme.d.ts +24 -1
  112. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  113. package/dist/modes/interactive/theme/theme.js +58 -13
  114. package/dist/modes/interactive/theme/theme.js.map +1 -1
  115. package/dist/utils/child-process.d.ts +9 -4
  116. package/dist/utils/child-process.d.ts.map +1 -1
  117. package/dist/utils/child-process.js +42 -10
  118. package/dist/utils/child-process.js.map +1 -1
  119. package/dist/utils/version-check.d.ts.map +1 -1
  120. package/dist/utils/version-check.js +4 -27
  121. package/dist/utils/version-check.js.map +1 -1
  122. package/docs/compaction.md +470 -51
  123. package/docs/containerization.md +37 -37
  124. package/docs/extensions.md +23 -14
  125. package/docs/models.md +6 -4
  126. package/docs/packages.md +2 -0
  127. package/docs/providers.md +1 -1
  128. package/docs/subagents.md +11 -4
  129. package/docs/workflows.md +4 -2
  130. package/examples/extensions/README.md +2 -2
  131. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  132. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  133. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  134. package/examples/extensions/gondolin/package-lock.json +2 -2
  135. package/examples/extensions/gondolin/package.json +1 -1
  136. package/examples/extensions/question.ts +39 -18
  137. package/examples/extensions/questionnaire.ts +49 -28
  138. package/examples/extensions/sandbox/package-lock.json +2 -2
  139. package/examples/extensions/sandbox/package.json +1 -1
  140. package/examples/extensions/with-deps/package-lock.json +2 -2
  141. package/examples/extensions/with-deps/package.json +1 -1
  142. package/package.json +7 -5
  143. package/dist/builtin/subagents/src/runs/shared/acceptance.ts +0 -612
  144. package/dist/builtin/subagents/src/runs/shared/completion-guard.ts +0 -147
@@ -66,7 +66,6 @@ import { buildWorkflowGraphSnapshot } from "../shared/workflow-graph.ts";
66
66
  import { ChainOutputValidationError, outputEntryFromResult, resolveOutputReferences, validateChainOutputBindings } from "../shared/chain-outputs.ts";
67
67
  import { createStructuredOutputRuntime } from "../shared/structured-output.ts";
68
68
  import { collectDynamicResults, DynamicFanoutError, materializeDynamicParallelStep, validateDynamicCollection, type DynamicCollectedResult } from "../shared/dynamic-fanout.ts";
69
- import { acceptanceFailureMessage, aggregateAcceptanceReport, evaluateAcceptance, resolveEffectiveAcceptance } from "../shared/acceptance.ts";
70
69
  import type { ChainOutputMap } from "../../shared/types.ts";
71
70
 
72
71
  type RunSyncDependency = typeof runSync;
@@ -100,7 +99,7 @@ interface ChainExecutionDetailsInput {
100
99
  outputs?: ChainOutputMap;
101
100
  currentFlatIndex?: number;
102
101
  dynamicChildren?: Record<number, Array<{ agent: string; label?: string; flatIndex: number; itemKey: string; outputName?: string; structured?: boolean; error?: string }>>;
103
- dynamicGroupStatuses?: Record<number, { status: "pending" | "running" | "completed" | "failed" | "paused" | "detached"; error?: string; acceptance?: SingleResult["acceptance"] }>;
102
+ dynamicGroupStatuses?: Record<number, { status: "pending" | "running" | "completed" | "failed" | "paused" | "detached"; error?: string }>;
104
103
  }
105
104
 
106
105
  interface ParallelChainRunInput {
@@ -297,8 +296,6 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
297
296
  preferredModelProvider: input.ctx.model?.provider,
298
297
  skills: behavior.skills === false ? [] : behavior.skills,
299
298
  structuredOutput: structuredRuntime,
300
- acceptance: task.acceptance,
301
- acceptanceContext: { mode: "chain" },
302
299
  onUpdate: input.onUpdate
303
300
  ? (progressUpdate) => {
304
301
  const stepResults = progressUpdate.details?.results || [];
@@ -785,30 +782,6 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
785
782
  stepIndex,
786
783
  };
787
784
  dynamicGroupStatuses[stepIndex] = { status: "completed" };
788
- if (step.acceptance !== undefined) {
789
- const effectiveGroupAcceptance = resolveEffectiveAcceptance({
790
- explicit: step.acceptance,
791
- agentName: step.parallel.agent,
792
- task: step.parallel.task ?? originalTask,
793
- mode: "chain",
794
- dynamicGroup: true,
795
- });
796
- const groupAcceptance = await evaluateAcceptance({
797
- acceptance: effectiveGroupAcceptance,
798
- output: "",
799
- report: aggregateAcceptanceReport({
800
- results: [],
801
- notes: "Dynamic fanout produced 0 results.",
802
- }),
803
- cwd: cwd ?? ctx.cwd,
804
- });
805
- dynamicGroupStatuses[stepIndex].acceptance = groupAcceptance;
806
- const groupAcceptanceFailure = acceptanceFailureMessage(groupAcceptance);
807
- if (groupAcceptanceFailure) {
808
- dynamicGroupStatuses[stepIndex] = { status: "failed", error: groupAcceptanceFailure, acceptance: groupAcceptance };
809
- return buildChainExecutionErrorResult(groupAcceptanceFailure, makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex }));
810
- }
811
- }
812
785
  prev = "Dynamic fanout produced 0 results.";
813
786
  continue;
814
787
  }
@@ -942,28 +915,6 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
942
915
  stepIndex,
943
916
  };
944
917
  dynamicGroupStatuses[stepIndex] = { status: "completed" };
945
- const effectiveGroupAcceptance = resolveEffectiveAcceptance({
946
- explicit: step.acceptance,
947
- agentName: step.parallel.agent,
948
- task: step.parallel.task ?? originalTask,
949
- mode: "chain",
950
- dynamicGroup: true,
951
- });
952
- const groupAcceptance = await evaluateAcceptance({
953
- acceptance: effectiveGroupAcceptance,
954
- output: "",
955
- report: aggregateAcceptanceReport({
956
- results: parallelResults,
957
- notes: `Dynamic fanout collected ${collected.length} result(s) into ${step.collect.as}.`,
958
- }),
959
- cwd: cwd ?? ctx.cwd,
960
- });
961
- dynamicGroupStatuses[stepIndex].acceptance = groupAcceptance;
962
- const groupAcceptanceFailure = acceptanceFailureMessage(groupAcceptance);
963
- if (groupAcceptanceFailure) {
964
- dynamicGroupStatuses[stepIndex] = { status: "failed", error: groupAcceptanceFailure, acceptance: groupAcceptance };
965
- return buildChainExecutionErrorResult(groupAcceptanceFailure, makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex - dynamicParallelStep.parallel.length }));
966
- }
967
918
  const taskResults: ParallelTaskResult[] = parallelResults.map((result, i) => ({
968
919
  agent: result.agent,
969
920
  taskIndex: i,
@@ -1078,8 +1029,6 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
1078
1029
  preferredModelProvider: ctx.model?.provider,
1079
1030
  skills: behavior.skills === false ? [] : behavior.skills,
1080
1031
  structuredOutput: structuredRuntime,
1081
- acceptance: seqStep.acceptance,
1082
- acceptanceContext: { mode: "chain" },
1083
1032
  onUpdate: onUpdate
1084
1033
  ? (p) => {
1085
1034
  const stepResults = p.details?.results || [];
@@ -43,12 +43,17 @@ import {
43
43
  extractTextFromContent,
44
44
  } from "../../shared/utils.ts";
45
45
  import { buildSkillInjection, resolveSkillsWithFallback } from "../../agents/skills.ts";
46
- import { evaluateCompletionMutationGuard } from "../shared/completion-guard.ts";
47
46
  import { getPiSpawnCommand } from "../shared/pi-spawn.ts";
48
47
  import { createJsonlWriter } from "../../shared/jsonl-writer.ts";
49
48
  import { attachPostExitStdioGuard, trySignalChild } from "../../shared/post-exit-stdio-guard.ts";
50
49
  import { applyThinkingSuffix, buildPiArgs, cleanupTempDir } from "../shared/pi-args.ts";
51
- import { readStructuredOutput } from "../shared/structured-output.ts";
50
+ import {
51
+ STRUCTURED_OUTPUT_MAX_CORRECTIVE_PROMPTS,
52
+ formatStructuredOutputCorrectionPrompt,
53
+ isStructuredOutputContractError,
54
+ latestStructuredOutputToolErrorFromMessages,
55
+ readStructuredOutput,
56
+ } from "../shared/structured-output.ts";
52
57
  import { captureSingleOutputSnapshot, formatSavedOutputReference, resolveSingleOutput, validateFileOnlyOutputMode, type SingleOutputSnapshot } from "../shared/single-output.ts";
53
58
  import {
54
59
  buildModelCandidates,
@@ -78,10 +83,8 @@ import {
78
83
  resolveSubagentModelFastMode,
79
84
  } from "../../shared/fast-mode.ts";
80
85
  import { resolveEffectiveThinking } from "../../shared/model-info.ts";
81
- import { acceptanceFailureMessage, evaluateAcceptance, formatAcceptancePrompt, resolveEffectiveAcceptance, stripAcceptanceReport } from "../shared/acceptance.ts";
82
86
 
83
87
  const artifactOutputByResult = new WeakMap<SingleResult, string>();
84
- const acceptanceOutputByResult = new WeakMap<SingleResult, string>();
85
88
  const modelFailureSignalByResult = new WeakMap<SingleResult, unknown>();
86
89
 
87
90
  function emptyUsage(): Usage {
@@ -105,17 +108,6 @@ function appendRecentOutput(progress: AgentProgress, lines: string[]): void {
105
108
  }
106
109
  }
107
110
 
108
- function stripAcceptanceReportsFromMessages(messages: Message[] | undefined): void {
109
- for (const message of messages ?? []) {
110
- if (message.role !== "assistant" || !Array.isArray(message.content)) continue;
111
- for (const part of message.content) {
112
- if (part.type === "text" && "text" in part && typeof part.text === "string") {
113
- part.text = stripAcceptanceReport(part.text);
114
- }
115
- }
116
- }
117
- }
118
-
119
111
  function snapshotProgress(progress: AgentProgress): AgentProgress {
120
112
  return {
121
113
  ...progress,
@@ -157,38 +149,47 @@ function extractUpdateText(update: RunSyncUpdate): string | undefined {
157
149
  return text || undefined;
158
150
  }
159
151
 
160
- export function shouldSuppressIntermediateRetryableFailureUpdate(update: RunSyncUpdate): boolean {
152
+ function terminalUpdateFailureText(update: RunSyncUpdate): string | undefined {
161
153
  const result = update.details?.results?.[0];
162
- if (!result) return false;
154
+ if (!result) return undefined;
163
155
  const progress = update.details?.progress?.[0];
164
156
  const status = result.progress?.status ?? progress?.status;
165
- if (status !== "failed") return false;
166
- const failureText = result.error
157
+ if (status !== "failed") return undefined;
158
+ return result.error
167
159
  ?? result.progress?.error
168
160
  ?? progress?.error
169
161
  ?? extractUpdateText(update);
170
- return isRetryableModelFailure(failureText);
171
162
  }
172
163
 
164
+ export function shouldSuppressIntermediateRetryableFailureUpdate(update: RunSyncUpdate): boolean {
165
+ return isRetryableModelFailure(terminalUpdateFailureText(update));
166
+ }
167
+
168
+ export function shouldSuppressIntermediateStructuredOutputFailureUpdate(update: RunSyncUpdate): boolean {
169
+ return isStructuredOutputContractError(terminalUpdateFailureText(update));
170
+ }
171
+
172
+ type RunSingleAttemptShared = {
173
+ sessionEnabled: boolean;
174
+ systemPrompt: string;
175
+ resolvedSkillNames?: string[];
176
+ skillsWarning?: string;
177
+ jsonlPath?: string;
178
+ artifactPaths?: ArtifactPaths;
179
+ attemptNotes: string[];
180
+ outputSnapshot?: SingleOutputSnapshot;
181
+ fastModeSettings: CodexFastModeResolvedSettings;
182
+ fastModeScope: CodexFastModeScope;
183
+ originalTask?: string;
184
+ };
185
+
173
186
  async function runSingleAttempt(
174
187
  runtimeCwd: string,
175
188
  agent: AgentConfig,
176
189
  task: string,
177
190
  model: string | undefined,
178
191
  options: RunSyncOptions,
179
- shared: {
180
- sessionEnabled: boolean;
181
- systemPrompt: string;
182
- resolvedSkillNames?: string[];
183
- skillsWarning?: string;
184
- jsonlPath?: string;
185
- artifactPaths?: ArtifactPaths;
186
- attemptNotes: string[];
187
- outputSnapshot?: SingleOutputSnapshot;
188
- fastModeSettings: CodexFastModeResolvedSettings;
189
- fastModeScope: CodexFastModeScope;
190
- originalTask?: string;
191
- },
192
+ shared: RunSingleAttemptShared,
192
193
  ): Promise<SingleResult> {
193
194
  const modelArg = applyThinkingSuffix(model, agent.thinking);
194
195
  const runCwd = options.cwd ?? runtimeCwd;
@@ -283,8 +284,6 @@ async function runSingleAttempt(
283
284
  workflowStageSubagentGuard: options.workflowStageSubagentGuard,
284
285
  }),
285
286
  };
286
- let observedMutationAttempt = false;
287
-
288
287
  const exitCode = await new Promise<number>((resolve) => {
289
288
  const spawnSpec = getPiSpawnCommand(args);
290
289
  const proc = spawn(spawnSpec.command, spawnSpec.args, {
@@ -517,7 +516,6 @@ async function runSingleAttempt(
517
516
  progress.currentToolStartedAt = now;
518
517
  progress.currentPath = resolveCurrentPath(evt.toolName, toolArgs);
519
518
  const mutates = isMutatingTool(evt.toolName, toolArgs);
520
- observedMutationAttempt = observedMutationAttempt || mutates;
521
519
  pendingToolResult = { tool: evt.toolName ?? "tool", path: progress.currentPath, mutates, startedAt: now };
522
520
  fireUpdate();
523
521
  }
@@ -774,36 +772,10 @@ async function runSingleAttempt(
774
772
  durationMs: progress.durationMs,
775
773
  };
776
774
 
777
- const acceptanceOutput = getFinalOutput(result.messages ?? []);
778
- let fullOutput = stripAcceptanceReport(acceptanceOutput);
779
- const completionGuard = result.exitCode === 0 && !result.error && agent.completionGuard !== false
780
- ? evaluateCompletionMutationGuard({
781
- agent: agent.name,
782
- task: shared.originalTask ?? task,
783
- messages: result.messages ?? [],
784
- tools: agent.tools,
785
- mcpDirectTools: agent.mcpDirectTools,
786
- })
787
- : undefined;
788
- if (completionGuard?.triggered && !observedMutationAttempt) {
789
- result.exitCode = 1;
790
- result.error = "Subagent completed without making edits for an implementation task.\nIt appears to have returned planning or scratchpad output instead of applying changes.";
791
- progress.status = "failed";
792
- progress.error = result.error;
793
- emitControlEvent(buildControlEvent({
794
- from: progress.activityState,
795
- to: "needs_attention",
796
- runId: options.runId ?? agent.name,
797
- agent: agent.name,
798
- index: options.index,
799
- ts: Date.now(),
800
- message: `${agent.name} completed without making edits for an implementation task`,
801
- reason: "completion_guard",
802
- }));
803
- }
775
+ let fullOutput = getFinalOutput(result.messages ?? []);
804
776
  if (options.outputPath && result.exitCode === 0) {
805
777
  const resolvedOutput = resolveSingleOutput(options.outputPath, fullOutput, shared.outputSnapshot);
806
- fullOutput = stripAcceptanceReport(resolvedOutput.fullOutput);
778
+ fullOutput = resolvedOutput.fullOutput;
807
779
  result.savedOutputPath = resolvedOutput.savedPath;
808
780
  result.outputSaveError = resolvedOutput.saveError;
809
781
  if (resolvedOutput.savedPath) {
@@ -811,7 +783,6 @@ async function runSingleAttempt(
811
783
  }
812
784
  }
813
785
  artifactOutputByResult.set(result, fullOutput);
814
- acceptanceOutputByResult.set(result, acceptanceOutput);
815
786
  result.outputMode = options.outputMode ?? "inline";
816
787
  result.finalOutput = options.outputMode === "file-only" && result.savedOutputPath && result.outputReference
817
788
  ? result.outputReference.message
@@ -834,6 +805,71 @@ async function runSingleAttempt(
834
805
  return result;
835
806
  }
836
807
 
808
+ async function runSingleAttemptWithStructuredOutputRetries(
809
+ runtimeCwd: string,
810
+ agent: AgentConfig,
811
+ task: string,
812
+ model: string | undefined,
813
+ options: RunSyncOptions,
814
+ shared: RunSingleAttemptShared,
815
+ ): Promise<SingleResult> {
816
+ let nextTask = task;
817
+ let correctiveAttempts = 0;
818
+ let finalResult: SingleResult | undefined;
819
+ const aggregateUsage = emptyUsage();
820
+ let totalToolCount = 0;
821
+ let totalDurationMs = 0;
822
+
823
+ while (true) {
824
+ const suppressIntermediateStructuredOutputFailure = options.structuredOutput !== undefined
825
+ && correctiveAttempts < STRUCTURED_OUTPUT_MAX_CORRECTIVE_PROMPTS
826
+ && options.onUpdate !== undefined;
827
+ const attemptOptions = suppressIntermediateStructuredOutputFailure
828
+ ? {
829
+ ...options,
830
+ onUpdate: (update: RunSyncUpdate) => {
831
+ if (shouldSuppressIntermediateStructuredOutputFailureUpdate(update)) return;
832
+ options.onUpdate?.(update);
833
+ },
834
+ }
835
+ : options;
836
+ const result = await runSingleAttempt(runtimeCwd, agent, nextTask, model, attemptOptions, shared);
837
+ finalResult = result;
838
+ sumUsage(aggregateUsage, result.usage);
839
+ totalToolCount += result.progressSummary?.toolCount ?? 0;
840
+ totalDurationMs += result.progressSummary?.durationMs ?? 0;
841
+
842
+ if (!options.structuredOutput || !isStructuredOutputContractError(result.error)) break;
843
+ const correctionError = latestStructuredOutputToolErrorFromMessages(result.messages) ?? result.error ?? "Structured output contract failed.";
844
+ if (correctiveAttempts >= STRUCTURED_OUTPUT_MAX_CORRECTIVE_PROMPTS) {
845
+ result.error = correctionError;
846
+ break;
847
+ }
848
+ correctiveAttempts += 1;
849
+ nextTask = formatStructuredOutputCorrectionPrompt({
850
+ originalTask: task,
851
+ error: correctionError,
852
+ attempt: correctiveAttempts,
853
+ });
854
+ }
855
+
856
+ const result = finalResult ?? {
857
+ agent: agent.name,
858
+ task,
859
+ exitCode: 1,
860
+ messages: [],
861
+ usage: emptyUsage(),
862
+ error: "Subagent did not produce a result.",
863
+ } satisfies SingleResult;
864
+ result.usage = aggregateUsage;
865
+ result.progressSummary = {
866
+ toolCount: totalToolCount,
867
+ tokens: aggregateUsage.input + aggregateUsage.output,
868
+ durationMs: totalDurationMs,
869
+ };
870
+ return result;
871
+ }
872
+
837
873
  /**
838
874
  * Run a subagent synchronously (blocking until complete)
839
875
  */
@@ -869,17 +905,6 @@ export async function runSync(
869
905
  }
870
906
 
871
907
  const shareEnabled = options.share === true;
872
- const effectiveAcceptance = resolveEffectiveAcceptance({
873
- explicit: options.acceptance,
874
- agentName,
875
- task,
876
- mode: options.acceptanceContext?.mode ?? "single",
877
- async: options.acceptanceContext?.async,
878
- dynamic: options.acceptanceContext?.dynamic,
879
- dynamicGroup: options.acceptanceContext?.dynamicGroup,
880
- });
881
- const acceptancePrompt = formatAcceptancePrompt(effectiveAcceptance);
882
- const taskWithAcceptance = acceptancePrompt ? `${task}\n${acceptancePrompt}` : task;
883
908
  const sessionEnabled = Boolean(options.sessionFile || options.sessionDir) || shareEnabled;
884
909
  const skillNames = options.skills ?? agent.skills ?? [];
885
910
  const skillCwd = options.cwd ?? runtimeCwd;
@@ -925,7 +950,7 @@ export async function runSync(
925
950
  artifactPathsResult = getArtifactPaths(options.artifactsDir, options.runId, agentName, options.index);
926
951
  ensureArtifactsDir(options.artifactsDir);
927
952
  if (options.artifactConfig?.includeInput !== false) {
928
- writeArtifact(artifactPathsResult.inputPath, `# Task for ${agentName}\n\n${taskWithAcceptance}`);
953
+ writeArtifact(artifactPathsResult.inputPath, `# Task for ${agentName}\n\n${task}`);
929
954
  }
930
955
  if (options.artifactConfig?.includeJsonl !== false) {
931
956
  jsonlPath = artifactPathsResult.jsonlPath;
@@ -949,7 +974,7 @@ export async function runSync(
949
974
  },
950
975
  };
951
976
  }
952
- const result = await runSingleAttempt(runtimeCwd, agent, taskWithAcceptance, candidate, attemptOptions, {
977
+ const result = await runSingleAttemptWithStructuredOutputRetries(runtimeCwd, agent, task, candidate, attemptOptions, {
953
978
  sessionEnabled,
954
979
  systemPrompt,
955
980
  resolvedSkillNames: resolvedSkills.length > 0 ? resolvedSkills.map((skill) => skill.name) : undefined,
@@ -1056,21 +1081,5 @@ export async function runSync(
1056
1081
  if (sessionFile) result.sessionFile = sessionFile;
1057
1082
  }
1058
1083
 
1059
- result.acceptance = await evaluateAcceptance({
1060
- acceptance: effectiveAcceptance,
1061
- output: acceptanceOutputByResult.get(result) ?? result.finalOutput ?? "",
1062
- cwd: options.cwd ?? runtimeCwd,
1063
- });
1064
- const acceptanceFailure = acceptanceFailureMessage(result.acceptance);
1065
- stripAcceptanceReportsFromMessages(result.messages);
1066
- if (acceptanceFailure && result.acceptance.explicit && result.exitCode === 0 && !result.detached && !result.interrupted) {
1067
- result.exitCode = 1;
1068
- result.error = result.error ? `${result.error}\n${acceptanceFailure}` : acceptanceFailure;
1069
- if (result.progress) {
1070
- result.progress.status = "failed";
1071
- result.progress.error = result.error;
1072
- }
1073
- }
1074
-
1075
1084
  return result;
1076
1085
  }
@@ -64,7 +64,6 @@ import {
64
64
  } from "../shared/worktree.ts";
65
65
  import {
66
66
  type AgentProgress,
67
- type AcceptanceInput,
68
67
  type ArtifactConfig,
69
68
  type ArtifactPaths,
70
69
  type ControlConfig,
@@ -109,7 +108,6 @@ interface TaskParam {
109
108
  progress?: boolean;
110
109
  model?: string;
111
110
  skill?: string | string[] | boolean;
112
- acceptance?: AcceptanceInput;
113
111
  }
114
112
 
115
113
  export interface SubagentParamsLike {
@@ -143,7 +141,6 @@ export interface SubagentParamsLike {
143
141
  outputMode?: "inline" | "file-only";
144
142
  agentScope?: string;
145
143
  chainDir?: string;
146
- acceptance?: AcceptanceInput;
147
144
  }
148
145
 
149
146
  export interface SubagentExecutorRuntimeDeps {
@@ -1150,7 +1147,6 @@ function runAsyncPath(data: ExecutionContextData, deps: ResolvedExecutorDeps): S
1150
1147
  ...(task.outputMode !== undefined ? { outputMode: task.outputMode } : {}),
1151
1148
  ...(task.reads !== undefined && task.reads !== true ? { reads: task.reads } : {}),
1152
1149
  ...(task.progress !== undefined ? { progress: task.progress } : {}),
1153
- ...(task.acceptance !== undefined ? { acceptance: task.acceptance } : {}),
1154
1150
  }));
1155
1151
  return deps.runtime.executeAsyncChain(id, {
1156
1152
  chain: [{
@@ -1252,7 +1248,6 @@ function runAsyncPath(data: ExecutionContextData, deps: ResolvedExecutorDeps): S
1252
1248
  controlIntercomTarget,
1253
1249
  childIntercomTarget: childIntercomTarget ? (agent, index) => childIntercomTarget(agent, index) : undefined,
1254
1250
  nestedRoute,
1255
- acceptance: params.acceptance,
1256
1251
  });
1257
1252
  }
1258
1253
 
@@ -1577,8 +1572,6 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
1577
1572
  preferredModelProvider: input.ctx.model?.provider,
1578
1573
  currentModel: currentModelFullId(input.ctx.model),
1579
1574
  skills: effectiveSkills === false ? [] : effectiveSkills,
1580
- acceptance: task.acceptance,
1581
- acceptanceContext: { mode: "parallel" },
1582
1575
  onUpdate: input.onUpdate
1583
1576
  ? (progressUpdate) => {
1584
1577
  const stepResults = progressUpdate.details?.results || [];
@@ -1771,7 +1764,6 @@ async function runParallelPath(data: ExecutionContextData, deps: ResolvedExecuto
1771
1764
  ...(behaviorOverrides[i]?.outputMode !== undefined ? { outputMode: behaviorOverrides[i]!.outputMode } : {}),
1772
1765
  ...(behaviorOverrides[i]?.reads !== undefined ? { reads: behaviorOverrides[i]!.reads } : {}),
1773
1766
  ...(progress !== undefined ? { progress } : {}),
1774
- ...(t.acceptance !== undefined ? { acceptance: t.acceptance } : {}),
1775
1767
  };
1776
1768
  });
1777
1769
  return deps.runtime.executeAsyncChain(id, {
@@ -2154,8 +2146,6 @@ async function runSinglePath(data: ExecutionContextData, deps: ResolvedExecutorD
2154
2146
  preferredModelProvider: currentProvider,
2155
2147
  currentModel: currentModelFullId(ctx.model),
2156
2148
  skills: effectiveSkills,
2157
- acceptance: params.acceptance,
2158
- acceptanceContext: { mode: "single" },
2159
2149
  });
2160
2150
  if (foregroundControl?.currentIndex === 0) {
2161
2151
  foregroundControl.interrupt = undefined;
@@ -40,16 +40,17 @@ const SAFE_OUTPUT_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
40
40
  const ITEM_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
41
41
  const ITEM_REF_PATTERN = /\{([A-Za-z_][A-Za-z0-9_]*)(?:\.([^{}]+))?\}/g;
42
42
  const RESERVED_TEMPLATE_NAMES = new Set(["task", "previous", "chain_dir", "outputs"]);
43
- const DYNAMIC_STEP_KEYS = new Set(["expand", "parallel", "collect", "concurrency", "failFast", "phase", "label", "acceptance"]);
44
- const RUNNER_DYNAMIC_STEP_KEYS = new Set([...DYNAMIC_STEP_KEYS, "effectiveAcceptance"]);
43
+ const DYNAMIC_STEP_KEYS = new Set(["expand", "parallel", "collect", "concurrency", "failFast", "phase", "label"]);
44
+ const RUNNER_DYNAMIC_STEP_KEYS = new Set(DYNAMIC_STEP_KEYS);
45
+ const REMOVED_LEGACY_DYNAMIC_KEYS = new Set(["acceptance"]);
45
46
  const DYNAMIC_EXPAND_KEYS = new Set(["from", "item", "key", "maxItems", "onEmpty"]);
46
47
  const DYNAMIC_EXPAND_FROM_KEYS = new Set(["output", "path"]);
47
- const DYNAMIC_PARALLEL_KEYS = new Set(["agent", "task", "phase", "label", "outputSchema", "cwd", "output", "outputMode", "reads", "progress", "skill", "model", "acceptance"]);
48
+ const DYNAMIC_PARALLEL_KEYS = new Set(["agent", "task", "phase", "label", "outputSchema", "cwd", "output", "outputMode", "reads", "progress", "skill", "model"]);
48
49
  const RUNNER_DYNAMIC_PARALLEL_KEYS = new Set([
49
50
  ...DYNAMIC_PARALLEL_KEYS,
50
51
  "outputName", "structured", "inheritProjectContext", "inheritSkills", "skills", "outputPath", "maxSubagentDepth",
51
- "structuredOutput", "structuredOutputSchema", "tools", "extensions", "mcpDirectTools", "completionGuard", "systemPrompt",
52
- "systemPromptMode", "thinking", "modelCandidates", "sessionFile", "effectiveAcceptance",
52
+ "structuredOutput", "structuredOutputSchema", "tools", "extensions", "mcpDirectTools", "systemPrompt",
53
+ "systemPromptMode", "thinking", "modelCandidates", "sessionFile",
53
54
  ]);
54
55
  const DYNAMIC_COLLECT_KEYS = new Set(["as", "outputSchema"]);
55
56
 
@@ -146,10 +147,16 @@ export function resolveItemTemplate(template: string, itemName: string, item: un
146
147
  function assertOnlyKeys(value: unknown, allowed: Set<string>, label: string): void {
147
148
  if (!value || typeof value !== "object" || Array.isArray(value)) throw new DynamicFanoutError(`${label} must be an object.`);
148
149
  for (const key of Object.keys(value)) {
150
+ if (REMOVED_LEGACY_DYNAMIC_KEYS.has(key)) continue;
149
151
  if (!allowed.has(key)) throw new DynamicFanoutError(`${label} does not support field '${key}'.`);
150
152
  }
151
153
  }
152
154
 
155
+ function stripRemovedLegacyDynamicKeys<T extends object>(value: T): T {
156
+ const entries = Object.entries(value).filter(([key]) => !REMOVED_LEGACY_DYNAMIC_KEYS.has(key));
157
+ return entries.length === Object.keys(value).length ? value : Object.fromEntries(entries) as T;
158
+ }
159
+
153
160
  export function assertNoUnresolvedItemReferences(template: string, itemName: string, label: string): void {
154
161
  for (const match of template.matchAll(/\{([^{}]*)\}/g)) {
155
162
  const raw = match[0]!;
@@ -247,11 +254,12 @@ export function materializeDynamicParallelStep(step: DynamicParallelStep, output
247
254
  return { items, parallel: [], collectedOnEmpty: [] };
248
255
  }
249
256
  const itemName = step.expand.item ?? "item";
257
+ const parallelTemplate = stripRemovedLegacyDynamicKeys(step.parallel);
250
258
  const parallel = items.map((entry) => {
251
- const task = resolveItemTemplate(step.parallel.task ?? "{previous}", itemName, entry.item);
252
- const label = step.parallel.label ? resolveItemTemplate(step.parallel.label, itemName, entry.item) : undefined;
259
+ const task = resolveItemTemplate(parallelTemplate.task ?? "{previous}", itemName, entry.item);
260
+ const label = parallelTemplate.label ? resolveItemTemplate(parallelTemplate.label, itemName, entry.item) : undefined;
253
261
  return {
254
- ...step.parallel,
262
+ ...parallelTemplate,
255
263
  task,
256
264
  ...(label !== undefined ? { label } : {}),
257
265
  };
@@ -100,7 +100,6 @@ const NON_RETRYABLE_FAILURE_PATTERNS: readonly RegExp[] = [
100
100
  /shell/i,
101
101
  /missing file/i,
102
102
  /no such file/i,
103
- /completion guard/i,
104
103
  /cancel/i,
105
104
  /abort/i,
106
105
  /interrupted/i,
@@ -18,7 +18,6 @@ export interface RunnerSubagentStep {
18
18
  tools?: string[];
19
19
  extensions?: string[];
20
20
  mcpDirectTools?: string[];
21
- completionGuard?: boolean;
22
21
  systemPrompt?: string | null;
23
22
  systemPromptMode?: "append" | "replace";
24
23
  inheritProjectContext: boolean;
@@ -35,7 +34,6 @@ export interface RunnerSubagentStep {
35
34
  outputPath: string;
36
35
  };
37
36
  structuredOutputSchema?: import("../../shared/types.ts").JsonSchemaObject;
38
- effectiveAcceptance?: import("../../shared/types.ts").ResolvedAcceptanceConfig;
39
37
  }
40
38
 
41
39
  export interface ParallelStepGroup {
@@ -53,7 +51,6 @@ export interface DynamicRunnerGroup {
53
51
  failFast?: boolean;
54
52
  phase?: string;
55
53
  label?: string;
56
- effectiveAcceptance?: import("../../shared/types.ts").ResolvedAcceptanceConfig;
57
54
  }
58
55
 
59
56
  export type RunnerStep = RunnerSubagentStep | ParallelStepGroup | DynamicRunnerGroup;
@@ -3,11 +3,15 @@ import * as os from "node:os";
3
3
  import * as path from "node:path";
4
4
  import { APP_NAME } from "@bastani/atomic";
5
5
  import { Compile } from "typebox/compile";
6
+ import type { Message } from "@earendil-works/pi-ai";
6
7
  import type { JsonSchemaObject } from "../../shared/types.ts";
7
8
 
8
9
  const ENV_PREFIX = APP_NAME.toUpperCase();
9
10
  export const STRUCTURED_OUTPUT_SCHEMA_ENV = `${ENV_PREFIX}_SUBAGENT_STRUCTURED_OUTPUT_SCHEMA`;
10
11
  export const STRUCTURED_OUTPUT_CAPTURE_ENV = `${ENV_PREFIX}_SUBAGENT_STRUCTURED_OUTPUT_CAPTURE`;
12
+ export const STRUCTURED_OUTPUT_TOOL_NAME = "structured_output";
13
+ export const STRUCTURED_OUTPUT_MAX_CORRECTIVE_PROMPTS = 3;
14
+ export const STRUCTURED_OUTPUT_MISSING_ERROR = "Missing structured_output call; this step has outputSchema and must finish by calling structured_output.";
11
15
 
12
16
  export interface StructuredOutputRuntime {
13
17
  schema: JsonSchemaObject;
@@ -50,12 +54,73 @@ export function validateStructuredOutputValue(schema: JsonSchemaObject, value: u
50
54
  }
51
55
  }
52
56
 
57
+ function textFromContent(content: unknown): string | undefined {
58
+ if (!Array.isArray(content)) return undefined;
59
+ const text = content
60
+ .map((block) => {
61
+ if (!block || typeof block !== "object") return "";
62
+ const record = block as { readonly type?: unknown; readonly text?: unknown };
63
+ return record.type === "text" && typeof record.text === "string" ? record.text : "";
64
+ })
65
+ .join("\n")
66
+ .trim();
67
+ return text.length > 0 ? text : undefined;
68
+ }
69
+
70
+ export function latestStructuredOutputToolErrorFromMessages(messages: readonly Message[] | undefined): string | undefined {
71
+ if (!messages) return undefined;
72
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
73
+ const message = messages[index];
74
+ if (message?.role !== "toolResult") continue;
75
+ if (message.toolName !== STRUCTURED_OUTPUT_TOOL_NAME) continue;
76
+ if (message.isError !== true) continue;
77
+ return textFromContent(message.content) ?? "structured_output tool call failed schema validation.";
78
+ }
79
+ return undefined;
80
+ }
81
+
82
+ export function isStructuredOutputContractError(error: string | undefined): boolean {
83
+ if (error === undefined) return false;
84
+ return error === STRUCTURED_OUTPUT_MISSING_ERROR
85
+ || error.startsWith("Structured output validation failed:")
86
+ || error.startsWith("Failed to read structured output:");
87
+ }
88
+
89
+ export function formatStructuredOutputCorrectionPrompt(args: {
90
+ readonly originalTask: string;
91
+ readonly error: string;
92
+ readonly attempt: number;
93
+ readonly maxAttempts?: number;
94
+ }): string {
95
+ const maxAttempts = args.maxAttempts ?? STRUCTURED_OUTPUT_MAX_CORRECTIVE_PROMPTS;
96
+ return [
97
+ "The previous response failed this subagent's structured-output contract.",
98
+ "",
99
+ `Corrective attempt ${args.attempt}/${maxAttempts}.`,
100
+ "",
101
+ "Error:",
102
+ args.error,
103
+ "",
104
+ "You must finish by calling the `structured_output` tool exactly once with arguments matching the registered schema.",
105
+ "Do not answer with plain JSON text, Markdown, or prose. If you attempted `structured_output` and validation failed, correct the tool arguments and call `structured_output` again.",
106
+ "If the requested work is already complete, do not redo side effects unnecessarily; just report the completed result through `structured_output`.",
107
+ "",
108
+ "Original task:",
109
+ args.originalTask,
110
+ ].join("\n");
111
+ }
112
+
53
113
  export function readStructuredOutput(runtime: StructuredOutputRuntime): { value?: unknown; error?: string } {
54
114
  if (!fs.existsSync(runtime.outputPath)) {
55
- return { error: "Missing structured_output call; this step has outputSchema and must finish by calling structured_output." };
115
+ return { error: STRUCTURED_OUTPUT_MISSING_ERROR };
56
116
  }
57
117
  try {
58
- return { value: JSON.parse(fs.readFileSync(runtime.outputPath, "utf-8")) as unknown };
118
+ const value = JSON.parse(fs.readFileSync(runtime.outputPath, "utf-8")) as unknown;
119
+ const validation = validateStructuredOutputValue(runtime.schema, value);
120
+ if (validation.status === "invalid") {
121
+ return { error: `Structured output validation failed: ${validation.message}` };
122
+ }
123
+ return { value };
59
124
  } catch (error) {
60
125
  return { error: `Failed to read structured output: ${error instanceof Error ? error.message : String(error)}` };
61
126
  }