pi-subagents 0.66.0 → 0.68.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 (172) hide show
  1. package/CHANGELOG.md +138 -0
  2. package/README.md +5 -4
  3. package/agents/evidence-auditor.md +34 -0
  4. package/agents/reviewer.md +3 -2
  5. package/docs/agents.md +43 -15
  6. package/docs/configuration.md +67 -23
  7. package/docs/extension-api.md +38 -19
  8. package/docs/missions.md +10 -2
  9. package/docs/models.md +11 -79
  10. package/docs/observability.md +22 -12
  11. package/docs/standalone-background.md +59 -0
  12. package/docs/tool-reference.md +33 -20
  13. package/docs/watchdog.md +39 -10
  14. package/docs/workflows.md +37 -13
  15. package/index.ts +5 -2
  16. package/inspector-runner.mjs +2 -2
  17. package/package.json +4 -2
  18. package/prompts/parallel-review.md +1 -1
  19. package/runner-peer-loader.mjs +24 -0
  20. package/runner-peer-preload.mjs +32 -0
  21. package/skills/pi-subagents/SKILL.md +32 -21
  22. package/skills/pi-subagents/references/constraints-and-recipes.md +3 -2
  23. package/skills/pi-subagents/references/execution-controls.md +11 -9
  24. package/skills/pi-subagents/references/management-authoring-rpc.md +0 -1
  25. package/skills/pi-subagents/references/multi-lane-orchestration.md +1 -1
  26. package/skills/pi-subagents/references/prompting-and-roles.md +17 -13
  27. package/skills/pi-subagents/references/review-and-validation.md +3 -3
  28. package/src/agents/advertised-agent-prompt.ts +34 -3
  29. package/src/agents/agent-management.ts +57 -58
  30. package/src/agents/agent-serializer.ts +4 -3
  31. package/src/agents/agents.ts +190 -71
  32. package/src/agents/builtin-names.ts +1 -0
  33. package/src/agents/chain-serializer.ts +5 -0
  34. package/src/agents/runtime-agent-registry.ts +7 -6
  35. package/src/api/delegation.ts +4 -0
  36. package/src/api/preflight.ts +94 -59
  37. package/src/api/required-child-extensions.ts +6 -0
  38. package/src/api/shared-types.ts +2 -0
  39. package/src/extension/config.ts +10 -37
  40. package/src/extension/fanout-child.ts +66 -4
  41. package/src/extension/herdr-pi-bridge.ts +160 -0
  42. package/src/extension/index.ts +62 -39
  43. package/src/extension/public-execution.ts +7 -5
  44. package/src/extension/rpc.ts +4 -0
  45. package/src/extension/schemas.ts +81 -81
  46. package/src/extension/tool-description.ts +30 -82
  47. package/src/inspectors/actions.ts +148 -0
  48. package/src/inspectors/ghostty/actions.ts +74 -0
  49. package/src/inspectors/ghostty/plugin.ts +17 -0
  50. package/src/inspectors/herdr/actions.ts +99 -179
  51. package/src/inspectors/herdr/plugin.ts +20 -0
  52. package/src/inspectors/herdr/project-panes.ts +1 -1
  53. package/src/inspectors/{herdr/inspector-runner.ts → inspector-runner.ts} +12 -12
  54. package/src/inspectors/plugins.ts +8 -0
  55. package/src/inspectors/{herdr/session-roots-codec.ts → session-roots-codec.ts} +3 -14
  56. package/src/inspectors/types.ts +51 -0
  57. package/src/intercom/intercom-bridge.ts +50 -8
  58. package/src/intercom/native-supervisor-channel.ts +44 -31
  59. package/src/policy/authority.ts +4 -0
  60. package/src/profiles/profiles.ts +12 -6
  61. package/src/runs/background/active-async-capacity.ts +4 -0
  62. package/src/runs/background/active-run-index.ts +17 -1
  63. package/src/runs/background/async-execution.ts +348 -176
  64. package/src/runs/background/async-job-tracker.ts +8 -6
  65. package/src/runs/background/async-resume.ts +17 -12
  66. package/src/runs/background/async-status.ts +15 -4
  67. package/src/runs/background/auto-drain.ts +23 -10
  68. package/src/runs/background/binary-bootstrap.ts +38 -0
  69. package/src/runs/background/chain-append.ts +1 -1
  70. package/src/runs/background/chain-root-attachment.ts +14 -33
  71. package/src/runs/background/fleet-view.ts +30 -2
  72. package/src/runs/background/notify.ts +105 -7
  73. package/src/runs/background/owned-process-tree.ts +29 -2
  74. package/src/runs/background/result-files.ts +8 -4
  75. package/src/runs/background/result-watcher.ts +19 -2
  76. package/src/runs/background/run-child-session.ts +81 -33
  77. package/src/runs/background/run-status.ts +3 -0
  78. package/src/runs/background/runner-aliases.ts +12 -33
  79. package/src/runs/background/runner-child-launch.ts +6 -1
  80. package/src/runs/background/runner-child-sessions.ts +5 -4
  81. package/src/runs/background/runner-http-dispatcher.ts +119 -0
  82. package/src/runs/background/scheduled-runs.ts +51 -18
  83. package/src/runs/background/stale-run-reconciler.ts +35 -11
  84. package/src/runs/background/steering.ts +20 -2
  85. package/src/runs/background/subagent-runner.ts +441 -304
  86. package/src/runs/background/subagent-wait.ts +176 -28
  87. package/src/runs/background/wait-completions.ts +75 -27
  88. package/src/runs/background/wait-subscriptions.ts +9 -3
  89. package/src/runs/background/wait-tool.ts +5 -3
  90. package/src/runs/foreground/async-steering-action.ts +18 -7
  91. package/src/runs/foreground/async-stop-action.ts +93 -3
  92. package/src/runs/foreground/execution.ts +134 -248
  93. package/src/runs/foreground/foreground-history.ts +2 -1
  94. package/src/runs/foreground/prompt-audit.ts +3 -1
  95. package/src/runs/foreground/subagent-executor.ts +374 -178
  96. package/src/runs/foreground/workflow-detach-reconcile.ts +2 -0
  97. package/src/runs/foreground/workflow-foreground-steering.ts +2 -1
  98. package/src/runs/shared/acceptance.ts +38 -11
  99. package/src/runs/shared/async-status-projection.ts +127 -33
  100. package/src/runs/shared/capability-ceiling.ts +2 -0
  101. package/src/runs/shared/child-hooks.ts +25 -10
  102. package/src/runs/shared/child-launch-plan.ts +15 -3
  103. package/src/runs/shared/child-launch.ts +28 -5
  104. package/src/runs/shared/child-lifecycle.ts +6 -3
  105. package/src/runs/shared/child-runtime-config.ts +8 -1
  106. package/src/runs/shared/child-session.ts +127 -52
  107. package/src/runs/shared/child-tool-plan.ts +142 -11
  108. package/src/runs/shared/completion-guard.ts +5 -3
  109. package/src/runs/shared/dynamic-fanout.ts +2 -2
  110. package/src/runs/shared/effective-system-prompt.ts +33 -0
  111. package/src/runs/shared/external-cli-contract.ts +11 -1
  112. package/src/runs/shared/external-cli-preflight.ts +6 -2
  113. package/src/runs/shared/external-cli-runner.ts +9 -7
  114. package/src/runs/shared/herdr-connection.ts +134 -0
  115. package/src/runs/shared/herdr-external-adapters.ts +169 -0
  116. package/src/runs/shared/herdr-machine.ts +279 -0
  117. package/src/runs/shared/herdr-pi-protocol.ts +59 -0
  118. package/src/runs/shared/herdr-placed-run.ts +263 -0
  119. package/src/runs/shared/llm-intent-arbiter.ts +12 -3
  120. package/src/runs/shared/model-resolution-diagnostic.ts +76 -0
  121. package/src/runs/shared/{model-fallback.ts → model-resolution.ts} +22 -235
  122. package/src/runs/shared/model-scope.ts +1 -1
  123. package/src/runs/shared/nested-events.ts +11 -2
  124. package/src/runs/shared/orca-progress-tabs.ts +1 -1
  125. package/src/runs/shared/parallel-utils.ts +7 -2
  126. package/src/runs/shared/pi-spawn.ts +10 -0
  127. package/src/runs/shared/subagent-prompt-runtime.ts +12 -4
  128. package/src/runs/shared/task-intent.ts +46 -13
  129. package/src/runs/shared/workflow-async-child-guidance.ts +18 -0
  130. package/src/runs/shared/worktree-setup-command.ts +27 -4
  131. package/src/runs/shared/worktree.ts +45 -15
  132. package/src/shared/child-cache-retention.ts +43 -0
  133. package/src/shared/fork-context.ts +15 -72
  134. package/src/shared/launch-contract.ts +68 -8
  135. package/src/shared/opencode-session-headers.ts +30 -0
  136. package/src/shared/pruned-fork.ts +1 -1
  137. package/src/shared/required-child-extensions.ts +81 -0
  138. package/src/shared/settings.ts +5 -2
  139. package/src/shared/shortcuts.ts +0 -4
  140. package/src/shared/types.ts +74 -30
  141. package/src/slash/delegation-adapters.ts +3 -1
  142. package/src/slash/delegation-request.ts +14 -0
  143. package/src/slash/slash-commands.ts +2 -7
  144. package/src/slash/subagents-admin.ts +24 -13
  145. package/src/tui/fleet-status.ts +164 -19
  146. package/src/tui/fleet.ts +16 -14
  147. package/src/tui/render.ts +168 -37
  148. package/src/watchdog/child-status.ts +28 -28
  149. package/src/watchdog/lsp-diagnostics.ts +1 -1
  150. package/src/watchdog/model-selection.ts +21 -1
  151. package/src/watchdog/permission-arbiter.ts +3 -1
  152. package/src/watchdog/register-child.ts +10 -2
  153. package/src/watchdog/register-main.ts +39 -35
  154. package/src/watchdog/render.ts +1 -1
  155. package/src/watchdog/review.ts +123 -74
  156. package/src/watchdog/rules.ts +1 -1
  157. package/src/watchdog/runtime.ts +100 -27
  158. package/src/watchdog/scope.ts +1 -1
  159. package/src/watchdog/settings.ts +3 -0
  160. package/src/watchdog/tool-actions.ts +13 -12
  161. package/src/watchdog/turn-delta.ts +23 -0
  162. package/src/watchdog/types.ts +5 -3
  163. package/src/watchdog/warning-format.ts +1 -1
  164. package/src/workflows/scripted-workflow.ts +279 -10
  165. package/src/workflows/workflow-checklist.ts +2 -2
  166. package/src/workflows/workflow-receipt.ts +21 -3
  167. package/src/workflows/workflow-resources.ts +13 -2
  168. package/runner-server-preload.mjs +0 -13
  169. package/src/runs/shared/model-exclusions.ts +0 -374
  170. package/src/runs/shared/readonly-model-continuation.ts +0 -69
  171. package/src/runs/shared/readonly-session-evidence.ts +0 -307
  172. /package/src/inspectors/{herdr/shell-command.ts → shell-command.ts} +0 -0
@@ -6,8 +6,8 @@ import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
6
6
  import * as path from "node:path";
7
7
  import type { Message } from "@earendil-works/pi-ai";
8
8
  import { discoverAgents, formatUnknownAgentError, unknownAgentDiagnosticContext, type AgentConfig } from "../../agents/agents.ts";
9
- import { appendAgentRefinementOverlay } from "../../agents/agent-refinements.ts";
10
9
  import { alignForkedSessionCwd } from "../../shared/fork-session-cwd.ts";
10
+ import { buildEffectiveSystemPrompt } from "../shared/effective-system-prompt.ts";
11
11
  import {
12
12
  ensureArtifactsDir,
13
13
  formatOutputArtifactContent,
@@ -20,7 +20,6 @@ import {
20
20
  type AgentProgress,
21
21
  type ArtifactPaths,
22
22
  type ControlEvent,
23
- type ModelAttempt,
24
23
  type RunSyncOptions,
25
24
  type SingleResult,
26
25
  type Usage,
@@ -52,15 +51,10 @@ import {
52
51
  boundStreamedRecentOutput,
53
52
  boundStreamedToolCalls,
54
53
  } from "../../shared/utils.ts";
55
- import { buildSkillInjection, resolveSkillsWithFallback } from "../../agents/skills.ts";
56
- import { buildAgentMemoryInjection } from "../../agents/agent-memory.ts";
54
+ import { resolveSkillsWithFallback } from "../../agents/skills.ts";
57
55
  import { effectiveToolTimeoutMs, formatToolTimeoutMessage, resolveToolTimeoutMs, toolTimeoutCallKey, toolTimeoutFromEnv } from "../shared/tool-timeout.ts";
58
56
  import { evaluateCompletionMutationGuard, expectsImplementationMutation, hasMutationToolCapability, validateImplementationToolContract } from "../shared/completion-guard.ts";
59
57
  import { planCompletionEvidence } from "../shared/completion-evidence.ts";
60
- import { planAbortRecovery } from "../shared/abort-recovery.ts";
61
- import { planReadonlyModelContinuation, type LogicalRecoveryState } from "../shared/readonly-model-continuation.ts";
62
- import { getReadonlySessionEvidence, requestReadonlySessionEvidence, type SettledReadonlyEvidence } from "../shared/readonly-session-evidence.ts";
63
- import { getReadonlyChildModels } from "../shared/child-session.ts";
64
58
  import { arbitrateCompletionGuardRescue } from "../shared/llm-intent-arbiter.ts";
65
59
  import { preflightLaunchCwd } from "../shared/launch-cwd.ts";
66
60
  import { createJsonlWriter } from "../../shared/jsonl-writer.ts";
@@ -74,16 +68,15 @@ import { assertThinkingWithinCeiling, intersectThinkingCeilings } from "../../sh
74
68
  import { MISSING_STRUCTURED_ACCEPTANCE_REPORT_ERROR, MISSING_STRUCTURED_OUTPUT_CALL_ERROR } from "../shared/structured-output.ts";
75
69
  import { formatMidToolExitError, isOrdinaryToolForMidToolExit } from "../shared/process-signal.ts";
76
70
  import { formatChildToolDiagnostic } from "../shared/tool-availability.ts";
71
+ import { formatChildModelResolutionDiagnostic, isChildModelResolutionFailure } from "../shared/model-resolution-diagnostic.ts";
72
+ import { planAbortRecovery } from "../shared/abort-recovery.ts";
77
73
  import { buildTimeoutRecoverySummary, collectTrackedMutationEvidence, snapshotTrackedMutations } from "../shared/mutation-evidence.ts";
78
- import { captureSingleOutputSnapshot, extractChildWrittenOutput, finalizeSingleOutput, formatSavedOutputReference, hasSingleOutputChangedSinceSnapshot, injectOutputPathSystemPrompt, resolveSingleOutput, validateFileOnlyOutputMode, type SingleOutputSnapshot } from "../shared/single-output.ts";
74
+ import { captureSingleOutputSnapshot, extractChildWrittenOutput, finalizeSingleOutput, formatSavedOutputReference, hasSingleOutputChangedSinceSnapshot, resolveSingleOutput, validateFileOnlyOutputMode, type SingleOutputSnapshot } from "../shared/single-output.ts";
79
75
  import {
80
- buildModelCandidates,
81
76
  formatSubagentModelVerificationError,
82
- formatModelAttemptNote,
83
77
  isContextOverflow,
84
- isRetryableModelFailureAttempt,
85
- recordRetryableModelFailure,
86
- } from "../shared/model-fallback.ts";
78
+ resolveModelSelection,
79
+ } from "../shared/model-resolution.ts";
87
80
  import {
88
81
  createMutatingFailureState,
89
82
  didMutatingToolFail,
@@ -100,7 +93,7 @@ import { PROMPT_REDACTED } from "../../shared/utils.ts";
100
93
  import { attachContractProjections, isAgentContract } from "../shared/agent-contract.ts";
101
94
  import { initialToolBudgetState, toolBudgetState } from "../shared/tool-budget.ts";
102
95
  import { resolveWatchdogConfig } from "../../watchdog/settings.ts";
103
- import { agentDefinitionDigest, launchBindingDigest } from "../../shared/launch-contract.ts";
96
+ import { resolveLaunchBinding } from "../../shared/launch-contract.ts";
104
97
  import { consumeWorkflowChildPermit } from "../../shared/workflow-child-permit.ts";
105
98
  import { projectChildLifecycle, type ChildLifecycleAction, type ChildLifecycleState } from "../shared/child-lifecycle.ts";
106
99
  import {
@@ -113,7 +106,7 @@ import {
113
106
  type ChildWatchdogStatusEvent,
114
107
  } from "../../watchdog/child-status.ts";
115
108
  import { buildInProcessChildLaunch, createReportedChildSessionInput } from "../shared/child-launch.ts";
116
- import { childSessionFactory, projectChildSessionEventForJson, type ChildSession, type ChildSessionEvent } from "../shared/child-session.ts";
109
+ import { childSessionFactory, childSessionHasQueuedMessages, projectChildSessionEventForJson, type ChildSession, type ChildSessionEvent } from "../shared/child-session.ts";
117
110
 
118
111
  const artifactOutputByResult = new WeakMap<SingleResult, string>();
119
112
  const acceptanceOutputByResult = new WeakMap<SingleResult, string>();
@@ -161,8 +154,7 @@ function persistSingleResultMetadata(input: {
161
154
  processSignal: target.processSignal,
162
155
  usage: target.usage,
163
156
  model: target.model,
164
- attemptedModels: target.attemptedModels,
165
- modelAttempts: target.modelAttempts,
157
+ requestedModel: target.requestedModel,
166
158
  durationMs: target.progressSummary?.durationMs,
167
159
  toolCount: target.progressSummary?.toolCount,
168
160
  error: target.error,
@@ -262,13 +254,6 @@ function snapshotResult(result: SingleResult, progress: AgentProgress): SingleRe
262
254
  messages: result.outputMode === "file-only" && result.savedOutputPath ? undefined : result.messages ? [...result.messages] : undefined,
263
255
  usage: { ...result.usage },
264
256
  skills: result.skills ? [...result.skills] : undefined,
265
- attemptedModels: result.attemptedModels ? [...result.attemptedModels] : undefined,
266
- modelAttempts: result.modelAttempts
267
- ? result.modelAttempts.map((attempt) => ({
268
- ...attempt,
269
- usage: attempt.usage ? { ...attempt.usage } : undefined,
270
- }))
271
- : undefined,
272
257
  controlEvents: result.controlEvents ? result.controlEvents.map((event) => ({ ...event })) : undefined,
273
258
  progress,
274
259
  progressSummary: result.progressSummary ? { ...result.progressSummary } : undefined,
@@ -341,11 +326,10 @@ function structuredDelegationProgressChanged(
341
326
  return false;
342
327
  }
343
328
 
344
- const AFTER_COMPACTION_SETTLEMENT = Symbol("afterCompactionSettlement");
345
- type AbortRecoverySingleResult = SingleResult & { [AFTER_COMPACTION_SETTLEMENT]?: true };
346
- const settledReadonlySource = new WeakMap<SingleResult, ChildSession>();
347
329
 
348
330
  const STOPPED_BEFORE_COMPLETION_ERROR = "Subagent stopped before completion.";
331
+ const AFTER_COMPACTION_SETTLEMENT = Symbol("afterCompactionSettlement");
332
+ type AbortRecoverySingleResult = SingleResult & { [AFTER_COMPACTION_SETTLEMENT]?: true };
349
333
 
350
334
 
351
335
  async function runSingleAttempt(
@@ -359,7 +343,6 @@ async function runSingleAttempt(
359
343
  systemPrompt: string;
360
344
  acceptancePrompt: string;
361
345
  resolvedSkillNames?: string[];
362
- modelCandidates?: string[];
363
346
  skillsWarning?: string;
364
347
  jsonlPath?: string;
365
348
  artifactPaths?: ArtifactPaths;
@@ -370,9 +353,6 @@ async function runSingleAttempt(
370
353
  orcaProgressTab?: OrcaProgressTab;
371
354
  launchWarnings: { emitted: boolean };
372
355
  verifyModel: boolean;
373
- readonlyExpected?: SettledReadonlyEvidence;
374
- readonlyModel?: string;
375
- readonlyHandoffAllowed?: () => boolean;
376
356
  },
377
357
  ): Promise<SingleResult> {
378
358
  const effectiveThinking = options.thinkingOverride ?? agent.thinking;
@@ -399,6 +379,11 @@ async function runSingleAttempt(
399
379
  : undefined;
400
380
  let onWatchdogStatus: ((event: ChildWatchdogStatusEvent) => void) | undefined;
401
381
  const launch = buildInProcessChildLaunch({
382
+ machine: options.machine,
383
+ remoteSkillNames: options.skills ?? agent.skills,
384
+ remoteReads: options.remoteReads,
385
+ extensionBindings: options.extensionBindings,
386
+ requiredExtensions: options.requiredExtensions,
402
387
  sessionEnabled: shared.sessionEnabled,
403
388
  sessionDir: options.sessionDir,
404
389
  sessionFile: options.sessionFile,
@@ -429,7 +414,6 @@ async function runSingleAttempt(
429
414
  forkCacheKey: options.context === "fork" ? deriveForkPromptCacheKey(options.parentSessionId) : undefined,
430
415
  structuredOutput: options.structuredOutput,
431
416
  fast: options.fast ?? agent.fast,
432
- modelCandidates: shared.modelCandidates,
433
417
  toolBudget: options.toolBudget,
434
418
  permissionRules,
435
419
  permissionAuditPath,
@@ -442,9 +426,11 @@ async function runSingleAttempt(
442
426
  thinkingCeiling: options.thinkingCeiling,
443
427
  maxSubagentDepth: options.maxSubagentDepth,
444
428
  runtimeSnapshotHost: options.runtimeSnapshotHost,
429
+ hostAvailableBuiltins: options.hostAvailableBuiltins,
445
430
  inherited: options.childRuntime,
446
431
  host: "parent",
447
432
  });
433
+ if (!options.machine && options.parentProviderRegistry) launch.session.parentProviderRegistry = options.parentProviderRegistry;
448
434
  const { toolPlan, capabilityAudit, warnings, launchResolvedExtensions, capture } = launch;
449
435
  if (!shared.launchWarnings.emitted && warnings.length > 0) {
450
436
  for (const warning of warnings) console.warn(`[pi-subagents] ${warning}`);
@@ -475,31 +461,21 @@ async function runSingleAttempt(
475
461
  error: contractError,
476
462
  usage: emptyUsage(),
477
463
  model: modelArg,
478
- modelAttempts: [],
479
- attemptedModels: [],
480
464
  progressSummary: { status: "failed", toolCount: 0, tokens: 0, durationMs: 0 },
481
465
  ...(toolPlan.capabilityCeiling ? { capabilityCeiling: toolPlan.capabilityCeiling } : {}),
482
466
  ...(toolPlan.capabilityAudit ? { capabilityAudit: toolPlan.capabilityAudit } : {}),
483
467
  };
484
468
  }
485
- const launchContractDigest = launchBindingDigest({
486
- definitionDigest: agentDefinitionDigest(agent),
469
+ const fast = options.fast ?? agent.fast;
470
+ const { launchContractDigest } = resolveLaunchBinding({
471
+ agent,
487
472
  task: shared.originalTask ?? task,
488
- ...(modelArg ? { model: modelArg } : {}),
489
- modelCandidates: shared.modelCandidates,
490
- ...((options.fast ?? agent.fast) !== undefined ? { fast: options.fast ?? agent.fast } : {}),
473
+ model: modelArg,
474
+ ...(fast !== undefined ? { fast } : {}),
491
475
  ...(resolvedThinking ? { thinking: resolvedThinking } : {}),
492
- ...(options.thinkingCeiling ? { thinkingCeiling: options.thinkingCeiling } : {}),
493
476
  systemPrompt: effectiveSystemPrompt,
494
- systemPromptMode: agent.systemPromptMode,
495
- inheritProjectContext: agent.inheritProjectContext,
496
- inheritGlobalContext: agent.inheritGlobalContext,
497
- inheritSkills: agent.inheritSkills,
498
477
  skills: shared.resolvedSkillNames ?? [],
499
- tools: toolPlan.effectiveToolAllowlist,
500
- ...(toolPlan.excludeTools.length > 0 ? { excludeTools: toolPlan.excludeTools } : {}),
501
- extensions: toolPlan.extensionArgs,
502
- mcpDirectTools: toolPlan.effectiveMcpTools,
478
+ toolPlan,
503
479
  ...(options.outputPath ? { outputPath: options.outputPath } : {}),
504
480
  outputMode: options.outputMode ?? "inline",
505
481
  ...(options.structuredOutput ? { structuredOutputSchema: options.structuredOutput.schema } : {}),
@@ -575,12 +551,13 @@ async function runSingleAttempt(
575
551
  };
576
552
  return result;
577
553
  }
578
- const mutationSnapshot = snapshotTrackedMutations(options.cwd ?? runtimeCwd);
554
+ const mutationSnapshot = options.machine ? { source: "tracked-files" as const, trackedOnly: true as const, cwd: options.cwd ?? runtimeCwd, dirtyFiles: [], fingerprints: {}, unavailable: "Local Git evidence is not authoritative for a pane-native remote run." } : snapshotTrackedMutations(options.cwd ?? runtimeCwd);
579
555
  let observedMutationAttempt = false;
580
556
  let structuredOutputToolInvoked = false;
581
557
  let structuredOutputMessageStartIndex: number | undefined;
582
558
  let toolAvailabilityError: string | undefined;
583
559
  let abortedBySignal = options.signal?.aborted === true;
560
+ let afterCompactionSettlement = false;
584
561
 
585
562
  if (options.workflowChildPermitLaunch) {
586
563
  const permitError = consumeWorkflowChildPermit(options.workflowChildPermitLaunch.permit, {
@@ -601,7 +578,6 @@ async function runSingleAttempt(
601
578
  }
602
579
  }
603
580
  const childSessions = options.childSessionFactory ?? childSessionFactory();
604
- let afterCompactionSettlement = false;
605
581
  const exitCode = await new Promise<number>((resolve) => {
606
582
  const jsonlWriter = createJsonlWriter(shared.jsonlPath, { pause() {}, resume() {} });
607
583
  let session: ChildSession | undefined;
@@ -681,6 +657,7 @@ async function runSingleAttempt(
681
657
  let forcedTermination = false;
682
658
  let cleanTerminalAssistantStopReceived = false;
683
659
  let agentSettledReceived = false;
660
+ let queuedDrainHold = false;
684
661
  let compactionStartedReceived = false;
685
662
  let finalDrainTimer: NodeJS.Timeout | undefined;
686
663
  let finalHardFinishTimer: NodeJS.Timeout | undefined;
@@ -707,14 +684,28 @@ async function runSingleAttempt(
707
684
  finalHardFinishTimer = undefined;
708
685
  }
709
686
  };
687
+ const observeQueuedDrainHold = (): boolean => {
688
+ if (childSessionHasQueuedMessages(session)) queuedDrainHold = true;
689
+ return queuedDrainHold;
690
+ };
710
691
  const startFinalDrain = () => {
711
692
  if (childWatchdogIsActive(childWatchdogState)) {
712
693
  armWatchdogTail();
713
694
  return;
714
695
  }
696
+ if (sessionSettled || finalDrainTimer || lifecycleFinished) return;
697
+ if (observeQueuedDrainHold()) return;
698
+ armFinalDrainTimer();
699
+ };
700
+ const armFinalDrainTimer = () => {
715
701
  if (sessionSettled || finalDrainTimer || lifecycleFinished) return;
716
702
  finalDrainTimer = setTimeout(() => {
717
703
  if (lifecycleFinished || sessionSettled) return;
704
+ if (capture.finalDrainHeld() || observeQueuedDrainHold()) {
705
+ finalDrainTimer = undefined;
706
+ armFinalDrainTimer();
707
+ return;
708
+ }
718
709
  forcedTermination = true;
719
710
  if (!cleanTerminalAssistantStopReceived && !agentSettledReceived && !assistantError) {
720
711
  result.error = result.error ?? `Subagent session did not settle within ${FINAL_STOP_GRACE_MS}ms after its terminal event. Aborting it.`;
@@ -746,6 +737,8 @@ async function runSingleAttempt(
746
737
  }
747
738
  const applyChildLifecycle = (action: ChildLifecycleAction): void => {
748
739
  if (action === "cancel-drain") {
740
+ cleanTerminalAssistantStopReceived = false;
741
+ agentSettledReceived = false;
749
742
  clearFinalDrainTimers();
750
743
  clearWatchdogTailTimer();
751
744
  return;
@@ -791,7 +784,6 @@ async function runSingleAttempt(
791
784
  });
792
785
  // Report the run only after the child's extensions have shut down.
793
786
  void Promise.resolve().then(() => session?.dispose()).catch(() => undefined).then(() => {
794
- if (session && getReadonlySessionEvidence(session)) settledReadonlySource.set(result, session);
795
787
  resolve(code);
796
788
  });
797
789
  };
@@ -992,6 +984,9 @@ async function runSingleAttempt(
992
984
  compactionStartedReceived = false;
993
985
  afterCompactionSettlement = false;
994
986
  }
987
+ if (evt.type === "turn_start" || evt.type === "agent_start" || evt.type === "auto_retry_start") {
988
+ queuedDrainHold = false;
989
+ }
995
990
  if (evt.type === "agent_start" || evt.type === "auto_retry_start") {
996
991
  compactionStartedReceived = false;
997
992
  afterCompactionSettlement = false;
@@ -1295,9 +1290,23 @@ async function runSingleAttempt(
1295
1290
  const toolDiagnosticError = diagnostic ? formatChildToolDiagnostic(diagnostic, { host: "parent" }) : undefined;
1296
1291
  toolAvailabilityError = toolDiagnosticError;
1297
1292
  result.runtimeAcknowledgedExtensions = capture.runtimeAcknowledgedExtensions();
1293
+ if (session?.machineEvidence) result.nativeMachine = { provider: "herdr", machineId: session.machineEvidence.machineId, ...(session.machineEvidence.initial ? { initialGit: session.machineEvidence.initial } : {}), ...(session.machineEvidence.final ? { finalGit: session.machineEvidence.final } : {}) };
1298
1294
  let closeError = result.error ?? toolDiagnosticError ?? assistantError;
1299
- if (!closeError && promptError !== undefined) {
1300
- closeError = promptError instanceof Error ? promptError.message : String(promptError);
1295
+ const promptErrorMessage = promptError === undefined ? undefined : promptError instanceof Error ? promptError.message : String(promptError);
1296
+ if (!closeError && promptErrorMessage !== undefined) {
1297
+ closeError = promptErrorMessage;
1298
+ }
1299
+ // A foreground child never loads the parent's ambient extensions, so a
1300
+ // provider one registers resolves as "not found" before the child starts.
1301
+ // Annotate only a creation/prompt failure that produced no turn; keep the
1302
+ // core error and add the host rule and both remedies after it.
1303
+ if (promptErrorMessage !== undefined
1304
+ && closeError === promptErrorMessage
1305
+ && isChildModelResolutionFailure(promptErrorMessage)
1306
+ && (result.messages?.length ?? 0) === 0
1307
+ && result.usage.turns === 0
1308
+ && !launch.session.ambientExtensions) {
1309
+ closeError = `${promptErrorMessage}\n\n${formatChildModelResolutionDiagnostic({ agent: agent.name, model: launch.session.model, host: "parent", capabilityCeiling: launch.toolPlan.capabilityCeiling })}`;
1301
1310
  }
1302
1311
  const forcedDrainAfterFinalSuccess = (forced || forcedTermination) && (cleanTerminalAssistantStopReceived || agentSettledReceived) && !closeError;
1303
1312
  const forcedDrainAfterEmptyTerminal = forcedDrainAfterFinalSuccess && hasEmptyTerminalAssistantResponse(result.messages ?? []);
@@ -1370,24 +1379,28 @@ async function runSingleAttempt(
1370
1379
  void (async () => {
1371
1380
  try {
1372
1381
  const input = createReportedChildSessionInput(launch, shared.transcriptWriter);
1373
- requestReadonlySessionEvidence(input, shared.readonlyExpected);
1374
- if (shared.readonlyHandoffAllowed && !shared.readonlyHandoffAllowed()) throw new Error("Read-only continuation handoff vetoed.");
1375
1382
  const created = await childSessions.create(input);
1376
1383
  if (lifecycleFinished) {
1377
1384
  void created.dispose();
1378
1385
  return;
1379
1386
  }
1380
1387
  session = created;
1388
+ const steer = created.steer.bind(created);
1389
+ const followUp = created.followUp.bind(created);
1390
+ created.steer = async (text) => {
1391
+ if (cleanTerminalAssistantStopReceived || agentSettledReceived) queuedDrainHold = true;
1392
+ return steer(text);
1393
+ };
1394
+ created.followUp = async (text) => {
1395
+ if (cleanTerminalAssistantStopReceived || agentSettledReceived) queuedDrainHold = true;
1396
+ return followUp(text);
1397
+ };
1381
1398
  created.detached = detached;
1382
1399
  unsubscribe = created.subscribe((event) => processEvent(event as Parameters<typeof processEvent>[0]));
1383
1400
  if (abortedBySignal || interruptedByControl || result.timedOut) {
1384
1401
  abortChild();
1385
1402
  }
1386
1403
  options.onChildSession?.({ steer: (text) => created.steer(text), followUp: (text) => created.followUp(text) });
1387
- const actualReadonlyModel = shared.readonlyExpected && getReadonlyChildModels(created)?.current;
1388
- if (shared.readonlyExpected && (!actualReadonlyModel || actualReadonlyModel.fullId !== shared.readonlyModel
1389
- || actualReadonlyModel.api !== shared.readonlyExpected.api || created.modelId !== shared.readonlyModel || abortedBySignal || interruptedByControl || result.timedOut
1390
- || !shared.readonlyHandoffAllowed?.())) throw new Error("Read-only continuation handoff vetoed.");
1391
1404
  await created.prompt(`Task: ${task}`);
1392
1405
  settle(undefined);
1393
1406
  } catch (error) {
@@ -1486,7 +1499,8 @@ async function runSingleAttempt(
1486
1499
  tokens: progress.tokens,
1487
1500
  durationMs: progress.durationMs,
1488
1501
  };
1489
- const mutationEvidence = collectTrackedMutationEvidence(mutationSnapshot, options.cwd ?? runtimeCwd);
1502
+ const remoteGitChanged = result.nativeMachine?.initialGit && result.nativeMachine.finalGit ? result.nativeMachine.initialGit.head !== result.nativeMachine.finalGit.head || result.nativeMachine.initialGit.dirty !== result.nativeMachine.finalGit.dirty : undefined;
1503
+ const mutationEvidence = result.nativeMachine ? { source: "tracked-files" as const, trackedOnly: true as const, changedFiles: [], attemptedMutation: remoteGitChanged === true, ...(remoteGitChanged === undefined ? { unavailable: "Remote Git before/after evidence was incomplete." } : {}) } : collectTrackedMutationEvidence(mutationSnapshot, options.cwd ?? runtimeCwd);
1490
1504
 
1491
1505
  const acceptanceOutput = getFinalOutput(result.messages ?? []);
1492
1506
  let fullOutput = stripAcceptanceReport(acceptanceOutput);
@@ -1779,21 +1793,10 @@ async function runSyncCompletionInner(
1779
1793
  error: "Skills not found: pi-subagents",
1780
1794
  }, options.context));
1781
1795
  }
1782
- let systemPrompt = agent.systemPrompt?.trim() || "";
1783
- if (resolvedSkills.length > 0) {
1784
- const skillInjection = buildSkillInjection(resolvedSkills);
1785
- systemPrompt = systemPrompt ? `${systemPrompt}\n\n${skillInjection}` : skillInjection;
1786
- }
1787
- const memoryInjection = buildAgentMemoryInjection(agent, skillCwd);
1788
- if (memoryInjection) {
1789
- systemPrompt = systemPrompt ? `${systemPrompt}\n\n${memoryInjection}` : memoryInjection;
1790
- }
1791
- systemPrompt = appendAgentRefinementOverlay(systemPrompt, { cwd: skillCwd, agentName });
1792
- systemPrompt = injectOutputPathSystemPrompt(systemPrompt, options.outputPath, agent);
1796
+ const systemPrompt = buildEffectiveSystemPrompt({ agent, resolvedSkills, cwd: skillCwd, ...(options.outputPath ? { outputPath: options.outputPath } : {}) });
1793
1797
 
1794
- const candidates = buildModelCandidates(
1798
+ const { model: selectedModel, requestedModel } = resolveModelSelection(
1795
1799
  options.modelOverride ?? agent.model,
1796
- agent.fallbackModels,
1797
1800
  options.availableModels,
1798
1801
  agent.modelProvider ?? options.preferredModelProvider,
1799
1802
  {
@@ -1802,23 +1805,9 @@ async function runSyncCompletionInner(
1802
1805
  origin: options.modelOrigin ?? (options.modelOverrideFromParent ? "inherited" : "configured"),
1803
1806
  },
1804
1807
  );
1805
- if (options.workflowChildPermitLaunch && candidates.length > 1) {
1806
- const error = "Workflow child permit does not support model fallback.";
1807
- return redactResultPrompt(withRunContext({
1808
- index: options.index ?? 0,
1809
- agent: agent.name,
1810
- task,
1811
- exitCode: 1,
1812
- messages: [],
1813
- usage: emptyUsage(),
1814
- error,
1815
- }, options.context));
1816
- }
1817
1808
  try {
1818
- for (const candidate of candidates) {
1819
- const model = applyThinkingSuffix(candidate, options.thinkingOverride ?? agent.thinking, options.thinkingOverride !== undefined);
1820
- assertThinkingWithinCeiling({ model, configThinking: options.thinkingOverride ?? agent.thinking, ceiling: options.thinkingCeiling, agent: agent.name, runId: options.runId });
1821
- }
1809
+ const model = applyThinkingSuffix(selectedModel, options.thinkingOverride ?? agent.thinking, options.thinkingOverride !== undefined);
1810
+ assertThinkingWithinCeiling({ model, configThinking: options.thinkingOverride ?? agent.thinking, ceiling: options.thinkingCeiling, agent: agent.name, runId: options.runId });
1822
1811
  } catch (error) {
1823
1812
  return redactResultPrompt(withRunContext({
1824
1813
  index: options.index ?? 0,
@@ -1830,8 +1819,6 @@ async function runSyncCompletionInner(
1830
1819
  error: error instanceof Error ? error.message : String(error),
1831
1820
  }, options.context));
1832
1821
  }
1833
- const attemptedModels: string[] = [];
1834
- const modelAttempts: ModelAttempt[] = [];
1835
1822
  const aggregateUsage = emptyUsage();
1836
1823
  const attemptNotes: string[] = [];
1837
1824
  const launchWarnings = { emitted: false };
@@ -1882,10 +1869,11 @@ async function runSyncCompletionInner(
1882
1869
  });
1883
1870
  };
1884
1871
 
1885
- let intercomDetached = false;
1886
1872
  let detachedReason: string | undefined;
1873
+ const logicalDeadline = options.deadlineAt ?? (options.timeoutMs === undefined ? undefined : Date.now() + options.timeoutMs);
1887
1874
  const attemptOptions: RunSyncOptions = {
1888
1875
  ...options,
1876
+ deadlineAt: logicalDeadline,
1889
1877
  onDetachReceipt: (receipt) => {
1890
1878
  receipt.acceptance = buildPendingAcceptanceLedger(effectiveAcceptance);
1891
1879
  try {
@@ -1896,165 +1884,65 @@ async function runSyncCompletionInner(
1896
1884
  const accepted = options.onDetachReceipt?.(receipt) === true;
1897
1885
  if (accepted) {
1898
1886
  detachedReason = receipt.detachedReason;
1899
- if (receipt.detachedReason === "intercom coordination") intercomDetached = true;
1900
1887
  }
1901
1888
  return accepted;
1902
1889
  },
1903
1890
  };
1891
+ const candidate = selectedModel;
1892
+ const verifyModel = Boolean(candidate) && !options.modelOverrideFromParent;
1904
1893
  let lastResult: SingleResult | undefined;
1905
- const modelsToTry = candidates.length > 0 ? candidates : [undefined];
1906
- let recoveryState: LogicalRecoveryState = "unused";
1907
- let readonlyExpected: SettledReadonlyEvidence | undefined;
1908
- let readonlyModel: string | undefined;
1909
- let readonlySource: ChildSession | undefined;
1910
- // Ordinary startup retries retain their per-attempt timeout. Only retained
1911
- // continuation uses the original logical deadline, never a renewed allowance.
1912
- const continuationDeadline = options.deadlineAt ?? (options.timeoutMs === undefined ? undefined : Date.now() + options.timeoutMs);
1913
- const readonlyHandoffAllowed = () => !options.signal?.aborted && !options.interruptSignal?.aborted
1914
- && !intercomDetached && !detachedReason && !options.workflowChildPermitLaunch
1915
- && options.usageBudget === undefined && options.toolBudget === undefined
1916
- && (continuationDeadline === undefined || Date.now() < continuationDeadline)
1917
- && (!readonlySource || getReadonlySessionEvidence(readonlySource) === readonlyExpected)
1918
- && !readonlySource?.detached && !readonlySource?.shutDown;
1919
- let nextAttemptTask = task;
1920
- modelAttemptsLoop: for (let modelIndex = 0; modelIndex < modelsToTry.length; modelIndex++) {
1921
- const candidate = modelsToTry[modelIndex];
1922
- // The inner loop re-runs the same candidate at most once, for abort recovery.
1923
- for (;;) {
1924
- const recoveringAbort = recoveryState === "abort-recovery";
1925
- const attemptTask = nextAttemptTask;
1926
- const verifyModel = Boolean(candidate) && !(options.modelOverrideFromParent && modelIndex === 0);
1927
- const outputSnapshot = captureSingleOutputSnapshot(options.outputPath);
1928
- if (recoveryState === "readonly-continuation") attemptOptions.deadlineAt = continuationDeadline;
1929
- const result = await runSingleAttempt(runtimeCwd, agent, attemptTask, candidate, attemptOptions, {
1930
- sessionEnabled,
1931
- systemPrompt,
1932
- acceptancePrompt,
1933
- resolvedSkillNames: resolvedSkills.length > 0 ? resolvedSkills.map((skill) => skill.name) : undefined,
1934
- skillsWarning: missingSkills.length > 0 ? `Skills not found: ${missingSkills.join(", ")}` : undefined,
1935
- jsonlPath,
1936
- artifactPaths: artifactPathsResult,
1937
- transcriptWriter,
1938
- attemptNotes,
1939
- modelCandidates: candidates
1940
- .map((modelCandidate) => applyThinkingSuffix(modelCandidate, options.thinkingOverride ?? agent.thinking, options.thinkingOverride !== undefined))
1941
- .filter((modelCandidate): modelCandidate is string => Boolean(modelCandidate)),
1942
- outputSnapshot,
1943
- originalTask: task,
1944
- orcaProgressTab,
1945
- launchWarnings,
1946
- verifyModel,
1947
- readonlyExpected,
1948
- readonlyModel,
1949
- readonlyHandoffAllowed: readonlyExpected ? readonlyHandoffAllowed : undefined,
1950
- });
1951
- lastResult = result;
1952
- if (!recoveringAbort) {
1953
- if (result.model) attemptedModels.push(result.model);
1954
- else if (candidate) attemptedModels.push(candidate);
1955
- }
1956
- sumUsage(aggregateUsage, result.usage);
1957
- totalToolCount += result.progressSummary?.toolCount ?? 0;
1958
- totalDurationMs += result.progressSummary?.durationMs ?? 0;
1959
- const attemptSucceeded = result.exitCode === 0 && !result.error;
1960
- const attempt: ModelAttempt = {
1961
- model: result.model ?? candidate ?? agent.model ?? "default",
1962
- success: attemptSucceeded,
1963
- exitCode: result.exitCode,
1964
- error: result.error,
1965
- usage: { ...result.usage },
1966
- };
1967
- modelAttempts.push(attempt);
1968
- // A consumed retained continuation is terminal even on a startup error or abort.
1969
- if (recoveryState === "readonly-continuation") break modelAttemptsLoop;
1970
- const source = settledReadonlySource.get(result);
1971
- const evidence = source && getReadonlySessionEvidence(source);
1972
- const models = source && getReadonlyChildModels(source);
1973
- // Deny non-text retained inputs, including unknown blocks. Count bytes once,
1974
- // not restored usage (the latter belongs to earlier attempts/runs).
1975
- const textOnly = evidence && (JSON.parse(evidence.contextJson) as Array<{ role: string; content: unknown }>).every((message) =>
1976
- typeof message.content === "string" || Array.isArray(message.content) && message.content.every((block) =>
1977
- block?.type === "text" || message.role === "assistant" && block?.type === "toolCall"));
1978
- const retainedBytes = evidence && models ? Buffer.byteLength(evidence.contextJson) + models.requestBytes : Infinity;
1979
- const resolvedCandidates = modelsToTry.map((reference, index) => index === modelIndex ? models?.current : reference ? models?.resolve(reference) : undefined);
1980
- const continuation = planReadonlyModelContinuation({
1981
- source, recoveryState, currentIndex: modelIndex,
1982
- candidates: resolvedCandidates.map((resolved, index) => {
1983
- // A conservative byte ceiling includes serialized history, actual system
1984
- // prompt/tools, framing/continuation headroom and full output allowance.
1985
- const required = resolved?.maxTokens ? retainedBytes + 4096 + resolved.maxTokens : Infinity;
1986
- return {
1987
- resolved: resolved?.api ? { provider: resolved.provider, model: resolved.id, api: resolved.api } : undefined,
1988
- tried: index <= modelIndex,
1989
- compatibility: textOnly && resolved?.input?.includes("text") && (resolved.contextWindow ?? 0) >= required ? "compatible" as const : "unknown" as const,
1990
- };
1991
- }),
1992
- lifecycleAllowsContinuation: !attemptSucceeded && readonlyHandoffAllowed() && !result.stopped && !result.detached && !result.interrupted && !result.timedOut,
1993
- effectsAllowContinuation: !result.structuredOutputFailed && !result.toolBudgetBlocked && !result.progress?.currentTool
1994
- && !result.outputSaveError && (!result.effects?.fileMutation || result.effects.fileMutation.status === "not-applicable"),
1995
- budget: options.toolBudget ? "tool-budget-configured" : options.usageBudget ? "unknown" : "unconfigured",
1996
- knownContextOverflow: Boolean(result.contextOverflow || isContextOverflow(result.error)),
1997
- });
1998
- if (continuation.kind === "continue") {
1999
- recoveryState = continuation.recoveryState; // consume BEFORE any sibling creation
2000
- readonlyExpected = continuation.expected;
2001
- readonlySource = source;
2002
- readonlyModel = resolvedCandidates[continuation.candidateIndex]?.fullId;
2003
- nextAttemptTask = continuation.prompt;
2004
- attemptNotes.push(`[readonly-continuation] ${attempt.model} failed with HTTP 429 after read-only progress; continuing retained session once with ${readonlyModel}.`);
2005
- modelIndex = continuation.candidateIndex - 1;
2006
- continue modelAttemptsLoop;
2007
- }
2008
- if (!attemptSucceeded) {
2009
- const afterCompactionSettlement = (result as AbortRecoverySingleResult)[AFTER_COMPACTION_SETTLEMENT];
2010
- const abortRecovery = planAbortRecovery({
2011
- messages: result.messages ?? [],
2012
- error: result.error,
2013
- processSignal: result.processSignal,
2014
- sessionAvailable: Boolean(options.sessionFile && existsSync(options.sessionFile)),
2015
- alreadyResumed: recoveryState !== "unused",
2016
- stopped: result.stopped || result.detached || options.signal?.aborted,
2017
- interrupted: result.interrupted || intercomDetached || options.interruptSignal?.aborted,
2018
- timedOut: result.timedOut,
2019
- toolBudgetExhausted: result.toolBudgetBlocked,
2020
- usageBudgetExhausted: false,
2021
- structuredOutputFailed: result.structuredOutputFailed,
2022
- acceptanceFailed: false,
2023
- currentTool: result.progress?.currentTool,
2024
- afterCompactionSettlement,
2025
- });
2026
- if (abortRecovery.action === "resume") {
2027
- recoveryState = "abort-recovery";
2028
- nextAttemptTask = abortRecovery.prompt;
2029
- attemptNotes.push("[abort-recovery] provider/transport abort after useful progress; resuming the retained child session once.");
2030
- continue;
2031
- }
2032
- if (abortRecovery.diagnostic) {
2033
- result.error = result.error ? `${result.error}\n${abortRecovery.diagnostic}` : abortRecovery.diagnostic;
2034
- attempt.error = result.error;
2035
- break modelAttemptsLoop;
2036
- }
2037
- }
2038
- if (recoveringAbort && !attemptSucceeded) break modelAttemptsLoop;
2039
- if (options.workflowChildPermitLaunch && !attemptSucceeded) break modelAttemptsLoop;
2040
- // Preserve the legacy intercom handoff contract: once this logical run has
2041
- // been handed to a supervisor, terminating that attempt must not launch a
2042
- // model fallback. Explicit user detach retains fallback.
2043
- if (intercomDetached || result.timedOut) break modelAttemptsLoop;
2044
- if (attemptSucceeded) break modelAttemptsLoop;
2045
-
2046
- const retryableModelFailure = isRetryableModelFailureAttempt({ error: result.error, messages: result.messages, toolCount: result.progressSummary?.toolCount });
2047
- if (retryableModelFailure) recordRetryableModelFailure(result.model ?? candidate, result.error);
2048
- if (isContextOverflow(result.error)) {
2049
- result.contextOverflow = true;
2050
- attemptNotes.push(`[fallback] ${attempt.model} failed: context overflow — the input exceeds this model's context window. Reduce the task input or use a model with a larger context window.`);
2051
- break modelAttemptsLoop;
2052
- }
2053
- if (!retryableModelFailure || modelIndex === modelsToTry.length - 1) break modelAttemptsLoop;
2054
- attemptNotes.push(formatModelAttemptNote(attempt, modelsToTry[modelIndex + 1]));
2055
- break;
1894
+ let recoveryPrompt = task;
1895
+ for (let attemptIndex = 0; attemptIndex < 2; attemptIndex++) {
1896
+ const outputSnapshot = captureSingleOutputSnapshot(options.outputPath);
1897
+ const attemptResult = await runSingleAttempt(runtimeCwd, agent, recoveryPrompt, candidate, attemptOptions, {
1898
+ sessionEnabled,
1899
+ systemPrompt,
1900
+ acceptancePrompt,
1901
+ resolvedSkillNames: resolvedSkills.length > 0 ? resolvedSkills.map((skill) => skill.name) : undefined,
1902
+ skillsWarning: missingSkills.length > 0 ? `Skills not found: ${missingSkills.join(", ")}` : undefined,
1903
+ jsonlPath,
1904
+ artifactPaths: artifactPathsResult,
1905
+ transcriptWriter,
1906
+ attemptNotes,
1907
+ outputSnapshot,
1908
+ originalTask: task,
1909
+ orcaProgressTab,
1910
+ launchWarnings,
1911
+ verifyModel,
1912
+ });
1913
+ lastResult = attemptResult;
1914
+ sumUsage(aggregateUsage, attemptResult.usage);
1915
+ totalToolCount += attemptResult.progressSummary?.toolCount ?? 0;
1916
+ totalDurationMs += attemptResult.progressSummary?.durationMs ?? 0;
1917
+ if (attemptResult.exitCode === 0 && !attemptResult.error) break;
1918
+ const recovery = planAbortRecovery({
1919
+ messages: attemptResult.messages ?? [],
1920
+ error: attemptResult.error,
1921
+ processSignal: attemptResult.processSignal,
1922
+ sessionAvailable: Boolean(options.sessionFile && existsSync(options.sessionFile)),
1923
+ alreadyResumed: attemptIndex > 0,
1924
+ stopped: attemptResult.stopped || attemptResult.detached || Boolean(detachedReason) || Boolean(options.workflowChildPermitLaunch) || options.signal?.aborted,
1925
+ interrupted: attemptResult.interrupted || options.interruptSignal?.aborted,
1926
+ timedOut: attemptResult.timedOut,
1927
+ toolBudgetExhausted: attemptResult.toolBudgetBlocked,
1928
+ usageBudgetExhausted: false,
1929
+ structuredOutputFailed: attemptResult.structuredOutputFailed,
1930
+ acceptanceFailed: false,
1931
+ currentTool: attemptResult.progress?.currentTool,
1932
+ afterCompactionSettlement: (attemptResult as AbortRecoverySingleResult)[AFTER_COMPACTION_SETTLEMENT],
1933
+ });
1934
+ if (recovery.action === "resume") {
1935
+ recoveryPrompt = recovery.prompt;
1936
+ attemptNotes.push("[abort-recovery] compaction abort after useful progress; resuming the retained child session once on the same model.");
1937
+ continue;
1938
+ }
1939
+ if (recovery.diagnostic) {
1940
+ attemptResult.error = attemptResult.error ? `${attemptResult.error}\n${recovery.diagnostic}` : recovery.diagnostic;
2056
1941
  }
1942
+ break;
2057
1943
  }
1944
+ if (!lastResult) throw new Error("Subagent did not produce a result.");
1945
+ if (isContextOverflow(lastResult.error)) lastResult.contextOverflow = true;
2058
1946
 
2059
1947
  const result = withRunContext(lastResult ?? {
2060
1948
  index: options.index ?? 0,
@@ -2068,8 +1956,7 @@ async function runSyncCompletionInner(
2068
1956
  result.task = task;
2069
1957
 
2070
1958
  result.usage = aggregateUsage;
2071
- result.attemptedModels = attemptedModels.length > 0 ? attemptedModels : undefined;
2072
- result.modelAttempts = modelAttempts.length > 0 ? modelAttempts : undefined;
1959
+ result.requestedModel = requestedModel;
2073
1960
  result.progressSummary = {
2074
1961
  ...(childSessionName ? { sessionName: childSessionName } : {}),
2075
1962
  toolCount: totalToolCount,
@@ -2159,7 +2046,6 @@ async function runSyncCompletionInner(
2159
2046
  savedPath: result.savedOutputPath,
2160
2047
  outputReference: result.outputReference,
2161
2048
  }).displayOutput;
2162
- artifactOutputByResult.set(result, result.finalOutput);
2163
2049
  }
2164
2050
  result.error = result.error ? `${result.error}\n${acceptanceFailure}` : acceptanceFailure;
2165
2051
  if (artifactPathsResult && options.artifactConfig?.enabled !== false && options.artifactConfig?.includeOutput !== false) {