@claudexor/orchestrator 5.0.2 → 6.0.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 (249) hide show
  1. package/dist/account-evidence.d.ts +7 -0
  2. package/dist/account-evidence.d.ts.map +1 -1
  3. package/dist/account-evidence.js +12 -0
  4. package/dist/account-evidence.js.map +1 -1
  5. package/dist/account-pool.d.ts +4 -0
  6. package/dist/account-pool.d.ts.map +1 -1
  7. package/dist/account-pool.js +8 -1
  8. package/dist/account-pool.js.map +1 -1
  9. package/dist/account-resolution.d.ts.map +1 -1
  10. package/dist/account-resolution.js +7 -5
  11. package/dist/account-resolution.js.map +1 -1
  12. package/dist/attemptFinalize.d.ts +22 -7
  13. package/dist/attemptFinalize.d.ts.map +1 -1
  14. package/dist/attemptFinalize.js +29 -7
  15. package/dist/attemptFinalize.js.map +1 -1
  16. package/dist/attemptTelemetry.d.ts +19 -18
  17. package/dist/attemptTelemetry.d.ts.map +1 -1
  18. package/dist/attemptTelemetry.js +33 -79
  19. package/dist/attemptTelemetry.js.map +1 -1
  20. package/dist/attemptUsage.d.ts.map +1 -1
  21. package/dist/attemptUsage.js +6 -14
  22. package/dist/attemptUsage.js.map +1 -1
  23. package/dist/attemptUsageCost.d.ts +11 -2
  24. package/dist/attemptUsageCost.d.ts.map +1 -1
  25. package/dist/attemptUsageCost.js +16 -3
  26. package/dist/attemptUsageCost.js.map +1 -1
  27. package/dist/candidateEvidence.d.ts +33 -8
  28. package/dist/candidateEvidence.d.ts.map +1 -1
  29. package/dist/candidateEvidence.js +58 -24
  30. package/dist/candidateEvidence.js.map +1 -1
  31. package/dist/candidateReview.d.ts.map +1 -1
  32. package/dist/candidateReview.js +4 -2
  33. package/dist/candidateReview.js.map +1 -1
  34. package/dist/candidateTerminals.d.ts +27 -1
  35. package/dist/candidateTerminals.d.ts.map +1 -1
  36. package/dist/candidateTerminals.js +51 -2
  37. package/dist/candidateTerminals.js.map +1 -1
  38. package/dist/carrier-planner.d.ts +5 -2
  39. package/dist/carrier-planner.d.ts.map +1 -1
  40. package/dist/carrier-planner.js +6 -3
  41. package/dist/carrier-planner.js.map +1 -1
  42. package/dist/command-telemetry.d.ts +15 -0
  43. package/dist/command-telemetry.d.ts.map +1 -0
  44. package/dist/command-telemetry.js +27 -0
  45. package/dist/command-telemetry.js.map +1 -0
  46. package/dist/continuation-evidence-io.d.ts +13 -3
  47. package/dist/continuation-evidence-io.d.ts.map +1 -1
  48. package/dist/continuation-evidence-io.js +36 -4
  49. package/dist/continuation-evidence-io.js.map +1 -1
  50. package/dist/continuation-evidence.d.ts +7 -2
  51. package/dist/continuation-evidence.d.ts.map +1 -1
  52. package/dist/continuation-evidence.js +16 -4
  53. package/dist/continuation-evidence.js.map +1 -1
  54. package/dist/continuation-later.d.ts.map +1 -1
  55. package/dist/continuation-later.js +6 -6
  56. package/dist/continuation-later.js.map +1 -1
  57. package/dist/continuity-facts.d.ts +27 -1
  58. package/dist/continuity-facts.d.ts.map +1 -1
  59. package/dist/continuity-facts.js +72 -4
  60. package/dist/continuity-facts.js.map +1 -1
  61. package/dist/continuity-notice.d.ts.map +1 -1
  62. package/dist/continuity-notice.js +2 -1
  63. package/dist/continuity-notice.js.map +1 -1
  64. package/dist/continuity-summary.d.ts +7 -1
  65. package/dist/continuity-summary.d.ts.map +1 -1
  66. package/dist/continuity-summary.js +33 -13
  67. package/dist/continuity-summary.js.map +1 -1
  68. package/dist/continuity.d.ts +36 -8
  69. package/dist/continuity.d.ts.map +1 -1
  70. package/dist/continuity.js +96 -21
  71. package/dist/continuity.js.map +1 -1
  72. package/dist/credential-cooldown.d.ts +7 -0
  73. package/dist/credential-cooldown.d.ts.map +1 -1
  74. package/dist/credential-cooldown.js +31 -5
  75. package/dist/credential-cooldown.js.map +1 -1
  76. package/dist/credential-differential.d.ts +15 -1
  77. package/dist/credential-differential.d.ts.map +1 -1
  78. package/dist/credential-differential.js +32 -17
  79. package/dist/credential-differential.js.map +1 -1
  80. package/dist/credential-execution.d.ts.map +1 -1
  81. package/dist/credential-execution.js +14 -15
  82. package/dist/credential-execution.js.map +1 -1
  83. package/dist/credential-preflight.js +1 -1
  84. package/dist/credential-preflight.js.map +1 -1
  85. package/dist/credential-profile-rotation.d.ts +1 -0
  86. package/dist/credential-profile-rotation.d.ts.map +1 -1
  87. package/dist/credential-profile-rotation.js +1 -1
  88. package/dist/credential-profile-rotation.js.map +1 -1
  89. package/dist/deepScanReducer.d.ts.map +1 -1
  90. package/dist/deepScanReducer.js +8 -18
  91. package/dist/deepScanReducer.js.map +1 -1
  92. package/dist/delegatedHome.d.ts +12 -7
  93. package/dist/delegatedHome.d.ts.map +1 -1
  94. package/dist/delegatedHome.js +16 -4
  95. package/dist/delegatedHome.js.map +1 -1
  96. package/dist/delegationFailure.d.ts +9 -8
  97. package/dist/delegationFailure.d.ts.map +1 -1
  98. package/dist/delegationFailure.js +7 -5
  99. package/dist/delegationFailure.js.map +1 -1
  100. package/dist/directoryCandidate.d.ts.map +1 -1
  101. package/dist/directoryCandidate.js +35 -5
  102. package/dist/directoryCandidate.js.map +1 -1
  103. package/dist/harnessFailure.d.ts +26 -5
  104. package/dist/harnessFailure.d.ts.map +1 -1
  105. package/dist/harnessFailure.js +41 -6
  106. package/dist/harnessFailure.js.map +1 -1
  107. package/dist/index.d.ts +2 -2
  108. package/dist/index.d.ts.map +1 -1
  109. package/dist/index.js +2 -1
  110. package/dist/index.js.map +1 -1
  111. package/dist/inrun-continuity-carrier.d.ts +4 -1
  112. package/dist/inrun-continuity-carrier.d.ts.map +1 -1
  113. package/dist/inrun-continuity-carrier.js +2 -0
  114. package/dist/inrun-continuity-carrier.js.map +1 -1
  115. package/dist/inrun-continuity.d.ts +2 -2
  116. package/dist/inrun-continuity.d.ts.map +1 -1
  117. package/dist/inrun-continuity.js +22 -19
  118. package/dist/inrun-continuity.js.map +1 -1
  119. package/dist/inrun-retained-output.d.ts +18 -4
  120. package/dist/inrun-retained-output.d.ts.map +1 -1
  121. package/dist/inrun-retained-output.js +39 -8
  122. package/dist/inrun-retained-output.js.map +1 -1
  123. package/dist/interaction.d.ts +14 -1
  124. package/dist/interaction.d.ts.map +1 -1
  125. package/dist/interaction.js +91 -22
  126. package/dist/interaction.js.map +1 -1
  127. package/dist/laneStreamEvents.d.ts +9 -10
  128. package/dist/laneStreamEvents.d.ts.map +1 -1
  129. package/dist/laneStreamEvents.js +21 -7
  130. package/dist/laneStreamEvents.js.map +1 -1
  131. package/dist/modelGovernance.d.ts +10 -4
  132. package/dist/modelGovernance.d.ts.map +1 -1
  133. package/dist/modelGovernance.js +21 -1
  134. package/dist/modelGovernance.js.map +1 -1
  135. package/dist/native-retry-telemetry.d.ts +4 -1
  136. package/dist/native-retry-telemetry.d.ts.map +1 -1
  137. package/dist/native-retry-telemetry.js +6 -1
  138. package/dist/native-retry-telemetry.js.map +1 -1
  139. package/dist/orchestrator.d.ts +7 -1
  140. package/dist/orchestrator.d.ts.map +1 -1
  141. package/dist/orchestrator.js +129 -139
  142. package/dist/orchestrator.js.map +1 -1
  143. package/dist/planRun.d.ts.map +1 -1
  144. package/dist/planRun.js +7 -8
  145. package/dist/planRun.js.map +1 -1
  146. package/dist/plannerAttempt.d.ts +1 -0
  147. package/dist/plannerAttempt.d.ts.map +1 -1
  148. package/dist/plannerAttempt.js +14 -19
  149. package/dist/plannerAttempt.js.map +1 -1
  150. package/dist/pool-attempt-evidence.d.ts +5 -1
  151. package/dist/pool-attempt-evidence.d.ts.map +1 -1
  152. package/dist/pool-attempt-evidence.js +36 -4
  153. package/dist/pool-attempt-evidence.js.map +1 -1
  154. package/dist/processing-telemetry.d.ts +3 -1
  155. package/dist/processing-telemetry.d.ts.map +1 -1
  156. package/dist/processing-telemetry.js +11 -0
  157. package/dist/processing-telemetry.js.map +1 -1
  158. package/dist/quota-refusal-scope.d.ts +1 -0
  159. package/dist/quota-refusal-scope.d.ts.map +1 -1
  160. package/dist/quota-refusal-scope.js +29 -1
  161. package/dist/quota-refusal-scope.js.map +1 -1
  162. package/dist/rawPatchLane.d.ts +68 -0
  163. package/dist/rawPatchLane.d.ts.map +1 -0
  164. package/dist/rawPatchLane.js +116 -0
  165. package/dist/rawPatchLane.js.map +1 -0
  166. package/dist/readonlyTerminals.d.ts +0 -3
  167. package/dist/readonlyTerminals.d.ts.map +1 -1
  168. package/dist/readonlyTerminals.js +36 -14
  169. package/dist/readonlyTerminals.js.map +1 -1
  170. package/dist/requestRequirements.d.ts +5 -4
  171. package/dist/requestRequirements.d.ts.map +1 -1
  172. package/dist/requestRequirements.js +6 -5
  173. package/dist/requestRequirements.js.map +1 -1
  174. package/dist/retainedOutput.d.ts +19 -2
  175. package/dist/retainedOutput.d.ts.map +1 -1
  176. package/dist/retainedOutput.js +83 -12
  177. package/dist/retainedOutput.js.map +1 -1
  178. package/dist/reviewerInventory.d.ts +2 -8
  179. package/dist/reviewerInventory.d.ts.map +1 -1
  180. package/dist/reviewerInventory.js +2 -3
  181. package/dist/reviewerInventory.js.map +1 -1
  182. package/dist/reviewerPanel.d.ts +1 -1
  183. package/dist/reviewerPanel.d.ts.map +1 -1
  184. package/dist/reviewerPanel.js +9 -4
  185. package/dist/reviewerPanel.js.map +1 -1
  186. package/dist/route-admission.d.ts.map +1 -1
  187. package/dist/route-admission.js +3 -1
  188. package/dist/route-admission.js.map +1 -1
  189. package/dist/routeControls.d.ts +32 -0
  190. package/dist/routeControls.d.ts.map +1 -0
  191. package/dist/routeControls.js +47 -0
  192. package/dist/routeControls.js.map +1 -0
  193. package/dist/run-input-resolution.d.ts +5 -0
  194. package/dist/run-input-resolution.d.ts.map +1 -1
  195. package/dist/run-input-resolution.js +12 -6
  196. package/dist/run-input-resolution.js.map +1 -1
  197. package/dist/run-runtime-root.d.ts +16 -0
  198. package/dist/run-runtime-root.d.ts.map +1 -0
  199. package/dist/run-runtime-root.js +16 -0
  200. package/dist/run-runtime-root.js.map +1 -0
  201. package/dist/runAdmission.d.ts.map +1 -1
  202. package/dist/runAdmission.js +2 -1
  203. package/dist/runAdmission.js.map +1 -1
  204. package/dist/runFacts.d.ts +5 -2
  205. package/dist/runFacts.d.ts.map +1 -1
  206. package/dist/runFacts.js +11 -11
  207. package/dist/runFacts.js.map +1 -1
  208. package/dist/runScope.d.ts +2 -0
  209. package/dist/runScope.d.ts.map +1 -1
  210. package/dist/runScope.js +3 -2
  211. package/dist/runScope.js.map +1 -1
  212. package/dist/runSupport.d.ts +3 -1
  213. package/dist/runSupport.d.ts.map +1 -1
  214. package/dist/runSupport.js +6 -3
  215. package/dist/runSupport.js.map +1 -1
  216. package/dist/runTelemetryWriter.d.ts +18 -5
  217. package/dist/runTelemetryWriter.d.ts.map +1 -1
  218. package/dist/runTelemetryWriter.js +7 -2
  219. package/dist/runTelemetryWriter.js.map +1 -1
  220. package/dist/runTerminalResults.d.ts.map +1 -1
  221. package/dist/runTerminalResults.js +36 -9
  222. package/dist/runTerminalResults.js.map +1 -1
  223. package/dist/runTerminals.d.ts.map +1 -1
  224. package/dist/runTerminals.js +13 -5
  225. package/dist/runTerminals.js.map +1 -1
  226. package/dist/secretDiff.d.ts.map +1 -1
  227. package/dist/secretDiff.js +2 -1
  228. package/dist/secretDiff.js.map +1 -1
  229. package/dist/transientClassify.d.ts +17 -9
  230. package/dist/transientClassify.d.ts.map +1 -1
  231. package/dist/transientClassify.js +33 -9
  232. package/dist/transientClassify.js.map +1 -1
  233. package/dist/webEvidence.d.ts +41 -0
  234. package/dist/webEvidence.d.ts.map +1 -0
  235. package/dist/webEvidence.js +99 -0
  236. package/dist/webEvidence.js.map +1 -0
  237. package/dist/workReportEnvelope.d.ts +12 -0
  238. package/dist/workReportEnvelope.d.ts.map +1 -1
  239. package/dist/workReportEnvelope.js +19 -0
  240. package/dist/workReportEnvelope.js.map +1 -1
  241. package/package.json +17 -17
  242. package/dist/continuation-chain.test-support.d.ts +0 -473
  243. package/dist/continuation-chain.test-support.d.ts.map +0 -1
  244. package/dist/continuation-chain.test-support.js +0 -206
  245. package/dist/continuation-chain.test-support.js.map +0 -1
  246. package/dist/inrun-continuity.test-support.d.ts +0 -504
  247. package/dist/inrun-continuity.test-support.d.ts.map +0 -1
  248. package/dist/inrun-continuity.test-support.js +0 -212
  249. package/dist/inrun-continuity.test-support.js.map +0 -1
@@ -7,7 +7,7 @@ import { openRunScopeCustody, scopedRouteContext, scopedSpecEnv, } from "./runSc
7
7
  import { delegationBeltFor } from "./delegationBelt.js";
8
8
  import { routePromptCacheTtl, withReviewerPromptCacheTtl } from "./prompt-cache-routing.js";
9
9
  import { bindProcessingAdmission, processingAttemptCostEvidence, processingAdmissionForLease, updateProcessingStreamHold, } from "./processing-dispatch.js";
10
- import { cancelledCandidatesResult, emptyCandidateResult, failedCandidatesResult, failAfterCandidates, } from "./candidateTerminals.js";
10
+ import { cancelledCandidatesResult, emptyCandidateResult, erroredWinnerFailure, failedCandidatesResult, failAfterCandidates, convergenceCause, raceStatement, } from "./candidateTerminals.js";
11
11
  import { deepScanFailureResult, readOnlyChainFailureResult, } from "./readonlyTerminals.js";
12
12
  import { effectiveAuthPreference, observeNativeSessionEvent, resumeSessionForProfile, } from "./credential-profiles.js";
13
13
  import { AdmissionProfileProbes, OrchestratorCredentials, reviewerProfileResolver, } from "./orchestrator-credentials.js";
@@ -15,14 +15,14 @@ import { continuityWorkspace, carryingAttempts, resumableOf, resumableTerminal,
15
15
  import { openContinuity } from "./continue-from.js";
16
16
  import { accountPoolRows } from "./account-pool.js";
17
17
  import { PoolAttemptEvidence, terminalPoolAttempts, withPoolAttempts, } from "./pool-attempt-evidence.js";
18
- import { writeRunTelemetryArtifact } from "./runTelemetryWriter.js";
18
+ import { pickTelemetry, writeRunTelemetryArtifact, } from "./runTelemetryWriter.js";
19
19
  import { buildFileBackedSynthesisInput, materializeWinnerOutputs, stageFileBackedContext, writeCandidateAttemptArtifacts, } from "./candidateOutputs.js";
20
20
  import { processAttemptUsage } from "./attemptUsage.js";
21
21
  import { captureDirectoryCandidate, directoryPreparationNotice, directoryHasOutput, directoryCandidateStable, observeDirectoryPaths, publishDirectoryCandidate, } from "./directoryCandidate.js";
22
22
  import { appliedAttemptFacts, assertDelegatedEvidence, isMutatingAccess, outerBoundaryNotice, scopedHarnessHome, selectLaneHome, followSpecHome, } from "./delegatedHome.js";
23
23
  import * as AC from "./attemptUsageCost.js";
24
24
  import * as custody from "./continuation-custody.js";
25
- import { candidateCaptureRefusals, candidateCompletion, candidateRoster, candidateStatuses, convergenceOutcomeFacts, isWorkingCandidate, partitionCandidates, toCandidateEvidence, } from "./candidateEvidence.js";
25
+ import { capturedNoGitChangeValue, candidateCaptureRefusals, candidateCompletion, candidateRoster, candidateStatuses, convergenceOutcomeFacts, declaredFailureSpeaks, interruptionOf, isWorkingCandidate, needsPostTurnFence, partitionCandidates, toCandidateEvidence, } from "./candidateEvidence.js";
26
26
  import { capabilityIntents } from "@claudexor/gateway";
27
27
  import { reviewCandidateRuns, unreviewedCandidateEvidence, evaluateUnreviewedConvergence, } from "./candidateReview.js";
28
28
  import { join } from "node:path";
@@ -33,7 +33,7 @@ import { globalConfigDir } from "@claudexor/config";
33
33
  import { executionRunConfig } from "./run-defaults.js";
34
34
  import { preflightOneShot } from "./one-shot-preflight.js";
35
35
  import { prepareRoutedProcessing, processingCostEvidence, prepareReviewerProcessing, } from "./processing-routing.js";
36
- import { acceptedTryOutput, AccessProfileIncompatibleError, AnswerAssembly, appendHarnessEvent, AuxiliaryWritableRootsUnsupportedError, CLAUDEXOR_BROWSER_ARTIFACT_SUBDIR, countsAsAgentProgress, HarnessUnavailableError, withInactivityWatchdog, } from "@claudexor/core";
36
+ import { acceptedTryOutput, AccessProfileIncompatibleError, AnswerAssembly, appendHarnessEvent, AuxiliaryWritableRootsUnsupportedError, CLAUDEXOR_BROWSER_ARTIFACT_SUBDIR, countsAsAgentProgress, HarnessUnavailableError, ModelSelectionRequiredError, missingRouteModel, withInactivityWatchdog, } from "@claudexor/core";
37
37
  import { assertRouteModelsAllowed, isDispatchRefusal, runModelGovernedRoute, } from "./modelGovernance.js";
38
38
  import { authModeForCredentialRoute, authModeForPreference, authRouteEvidenceFor, } from "./auth-route-classification.js";
39
39
  import { isFullAccess, RequestRequirementsResolver } from "./requestRequirements.js";
@@ -50,21 +50,21 @@ import { cancelledResult, declaredFailure, failTerminally, guardAnnouncedRun, wr
50
50
  import { budgetFailureRecord, classifyBudgetFailure } from "./budgetFailure.js";
51
51
  import { finalizeStructuredOutput } from "./structuredOutput.js";
52
52
  import { prepareRunAdmission } from "./runAdmission.js";
53
- import { dropDeltaPastBudget, emitPlanProgress, emitTransientExhausted, observeReadonlySpend, recordAdapterThrow, } from "./laneStreamEvents.js";
53
+ import { dropDeltaPastBudget, emitPlanProgress, emitTransientExhausted, observeUsageSpend, recordAdapterThrow, } from "./laneStreamEvents.js";
54
54
  import { promptWithEngineConstraints, sleep, harnessEventPayload, safeErrorMessage, renderSummary, observeBudgetSignals, rotateOnStall, recordCleanAttemptMetrics, envInheritance, transientRetryPolicy, reviewerTimeoutMs, harnessInactivityTimeoutMs, observeAuthSwitch, emitPoolDegraded, deliveryRefusalFailure, winnerNeedsHuman, writeRaceDeliveryDecision, } from "./runSupport.js";
55
55
  import { candidateStatusInRouteContext, resolveReadOnlyRouteContext, } from "./routeContext.js";
56
56
  import { resolveAutoReviewerPanel, resolveExplicitReviewerPanel } from "./reviewerPanel.js";
57
57
  import { ensureClaudeBridgeForRun, ensureWriteModeGitBoundary } from "./git-precondition.js";
58
- import { buildContinuation, } from "./continuity.js";
59
- import { activePlanPointer, resolveContinuitySummary, workspaceAnchor, } from "./continuity-facts.js";
58
+ import { buildContinuation } from "./continuity.js";
59
+ import { resolveContinuitySummary, threadContinuityRequest } from "./continuity-facts.js";
60
60
  import { runDiffReview } from "./diffReview.js";
61
61
  import { rawScoutBundle, resolveDeepScanSynthesis, } from "./deepScanReducer.js";
62
62
  import { createAttemptTelemetry, observeAttemptTelemetry, setAttemptOutcome, telemetrySummary, toolWarnings, unrecoveredToolErrors, webUnsatisfied, } from "./attemptTelemetry.js";
63
63
  import { beginNativeRetry } from "./native-retry-telemetry.js";
64
64
  import * as delegateFailure from "./delegationFailure.js";
65
65
  import * as secretDiff from "./secretDiff.js";
66
- import { finalizeAttempt, unrecoveredToolErrorFailure, webEvidenceFailure, } from "./attemptFinalize.js";
67
- import { resolveWorkReportEnvelope, unwrapWorkReportEnvelope, } from "./workReportEnvelope.js";
66
+ import { attemptInterruption, finalizeAttempt, interruptionDetail, unrecoveredToolErrorFailure, webEvidenceFailure, } from "./attemptFinalize.js";
67
+ import { resolveWorkReportEnvelope, unwrapAttemptAnswer, } from "./workReportEnvelope.js";
68
68
  import { buildContinuationPacket, decideContinuation, synthesizeContinuationRequest, } from "./continuation.js";
69
69
  import { interactionChannelFor } from "./interaction.js";
70
70
  import { liveAttempt } from "./live-input.js";
@@ -74,15 +74,17 @@ import { recordProcessingIntent } from "./retry-processing-intent.js";
74
74
  import { prepareReviewEvidenceDir, recordReviewEvidenceCleanup } from "./reviewEvidence.js";
75
75
  import { savedForSynthesis } from "./savedCandidate.js";
76
76
  import { ArtifactStore } from "@claudexor/artifact-store";
77
- import { buildContextPack, rawContextSizer, writeEvidencePacket } from "@claudexor/context";
78
- import { WorkspaceManager, captureRawPatchEnvelope, consumeRawPatchEnvelope, snapshotTree, } from "@claudexor/workspace";
77
+ import { buildContextPack, writeEvidencePacket } from "@claudexor/context";
78
+ import { RawAdoption, RawPatchLane, correctableRefusal, endsConvergence } from "./rawPatchLane.js";
79
+ import { runRuntimeRoot } from "./run-runtime-root.js";
80
+ import { WorkspaceManager, rawAwareSnapshot, } from "@claudexor/workspace";
79
81
  import { blockedDecisionOverride, finalVerifyBlocks, verifyAndDeliverFiles, verifyAndDeliver, } from "@claudexor/delivery";
80
82
  import { HarnessGateway } from "@claudexor/gateway";
81
- import { ReadinessLedger, evaluateConvergence, failureSignature, gatesPassed, reviewCandidate, runGates, } from "@claudexor/review";
83
+ import { ReadinessLedger, evaluateConvergence, failureSignature, gatesPassed, requireReadingReviewers, reviewCandidate, runGates, } from "@claudexor/review";
82
84
  import { arbitrate } from "@claudexor/arbitration";
83
85
  import { buildSynthesisPlan, decideSynthesis } from "@claudexor/synthesis";
84
86
  import { billingKnowledgeForAuthRoute, attemptUsageCostSettlement, BudgetLedger, isBudgetTerminal, explainRanking, loadHarnessMetrics, promptFingerprint, rankHarnesses, } from "@claudexor/budget";
85
- import { readTextSafe, hashJson, newId, noProjectRepoRoot, redactSecrets, safeInvoke, userConfigDir, } from "@claudexor/util";
87
+ import { hashJson, newId, noProjectRepoRoot, redactSecrets, safeInvoke, userConfigDir, } from "@claudexor/util";
86
88
  const LABELS = "ABCDEFGHIJ".split("");
87
89
  const NO_PROJECT_ROOT = noProjectRepoRoot();
88
90
  export class Orchestrator {
@@ -191,7 +193,7 @@ export class Orchestrator {
191
193
  }
192
194
  async resolveReviewers(run, onIgnoredSetting) {
193
195
  if (this.deps.reviewers)
194
- return this.deps.reviewers;
196
+ return requireReadingReviewers(this.deps.reviewers);
195
197
  const { repoRoot: cwd, authPreference, signal } = run;
196
198
  const panelDeps = {
197
199
  cwd,
@@ -269,7 +271,7 @@ export class Orchestrator {
269
271
  if (input.mode === "ask" && input.contextMode === "off" && input.repoRoot === NO_PROJECT_ROOT) {
270
272
  return new ArtifactStore(input.repoRoot, { claudexorDir: userConfigDir() });
271
273
  }
272
- return new ArtifactStore(input.repoRoot);
274
+ return new ArtifactStore(input.repoRoot, { claudexorDir: runRuntimeRoot(input) });
273
275
  }
274
276
  /** The producing intent a candidate plays (create flag switches it; not hardcoded to implement). */
275
277
  candidateIntent(input) {
@@ -425,6 +427,8 @@ export class Orchestrator {
425
427
  throw new AuxiliaryWritableRootsUnsupportedError(detail);
426
428
  if (stage === "access")
427
429
  throw new AccessProfileIncompatibleError(detail);
430
+ if (stage === "model")
431
+ throw new ModelSelectionRequiredError(detail);
428
432
  throw new HarnessUnavailableError(detail);
429
433
  }
430
434
  dropped.push(detail);
@@ -587,6 +591,13 @@ export class Orchestrator {
587
591
  dropLane(id, "capability", readRefusal);
588
592
  continue;
589
593
  }
594
+ const modelRefusal = status.enabledIntents.includes(intent)
595
+ ? missingRouteModel(adapter, input.models?.[id] ?? cfgEntry?.default_model)
596
+ : null;
597
+ if (modelRefusal) {
598
+ dropLane(id, "model", modelRefusal);
599
+ continue;
600
+ }
590
601
  const reason = status.reasons.length > 0 ? `: ${status.reasons.join("; ")}` : "";
591
602
  if (status.enabledIntents.includes(intent) && accessSupported) {
592
603
  pool.push({
@@ -594,6 +605,7 @@ export class Orchestrator {
594
605
  adapterAccess: requiredAccess,
595
606
  auxiliaryWritableRoots,
596
607
  webSupport,
608
+ accessControl: manifest.capability_profile.access_control,
597
609
  providerFamily: manifest.provider_family,
598
610
  supportsMaxTurns: manifest.capabilities.max_turns,
599
611
  supportsToolLists: manifest.capabilities.tool_lists,
@@ -1136,33 +1148,7 @@ export class Orchestrator {
1136
1148
  return null;
1137
1149
  try {
1138
1150
  const profileId = resolvedProfileId ?? ctx.profileId ?? null;
1139
- const lane = { harness: harnessId, profileId };
1140
- const checkpoint = ctx.laneCheckpoints.find((c) => c.harness === harnessId && (c.profileId ?? null) === profileId);
1141
- const headTurnId = ctx.priorTurns.length
1142
- ? ctx.priorTurns[ctx.priorTurns.length - 1].id
1143
- : null;
1144
- const priorHeadOwner = headTurnId
1145
- ? ctx.laneCheckpoints.find((c) => c.turnId === headTurnId)
1146
- : undefined;
1147
- const priorHeadLane = priorHeadOwner
1148
- ? { harness: priorHeadOwner.harness, profileId: priorHeadOwner.profileId ?? null }
1149
- : null;
1150
- const priorTurns = ctx.priorTurns.map((t) => ({
1151
- id: t.id,
1152
- prompt: t.prompt,
1153
- outputText: t.runId
1154
- ? (readTextSafe(join(store.runPaths(t.runId).finalDir, "answer.md")) ?? "")
1155
- : "",
1156
- }));
1157
- const req = {
1158
- lane,
1159
- priorTurns,
1160
- laneCheckpointTurnId: checkpoint?.turnId ?? null,
1161
- nativeResumeAvailable,
1162
- priorHeadLane,
1163
- activePlan: activePlanPointer(ctx.priorTurns, store),
1164
- anchor: await workspaceAnchor(repoRoot),
1165
- };
1151
+ const req = await threadContinuityRequest(ctx, { harness: harnessId, profileId }, nativeResumeAvailable, store, repoRoot);
1166
1152
  // V9c: when the packet would collapse an older prefix, replace the
1167
1153
  // mechanical one-liners with a cached (or freshly summarized) prose
1168
1154
  // summary. Same credential route + scoped lane home a real read-only
@@ -1173,7 +1159,9 @@ export class Orchestrator {
1173
1159
  threadId: runInput.threadId,
1174
1160
  projectRoot: runInput.repoRoot,
1175
1161
  cwd: repoRoot,
1162
+ inputFileDir: paths.contextDir,
1176
1163
  adapter: this.deps.registry.get(harnessId),
1164
+ model: this.deps.registry.get(harnessId)?.modelRequirement && runInput.models?.[harnessId],
1177
1165
  credentialProfile: sessionFields.credential_profile,
1178
1166
  authPreference: sessionFields.auth_preference ?? "auto",
1179
1167
  laneEnv: scopedSpecEnv(runInput.runScopeMarker, this.laneHomeEnvFor(runInput, harnessId, profileId) ?? {}) ?? {},
@@ -1320,6 +1308,7 @@ export class Orchestrator {
1320
1308
  : join(envelope.worktree_path, artifactRelativeDir, CLAUDEXOR_BROWSER_ARTIFACT_SUBDIR)),
1321
1309
  extra_mcp_servers: delegationBeltFor(runInput, intent, routed, contract.budget.paid_budget),
1322
1310
  cwd: envelope.worktree_path,
1311
+ input_file_dir: paths.contextDir,
1323
1312
  access: routed.adapterAccess,
1324
1313
  // Writable lanes only: routing admitted this list for this exact lane.
1325
1314
  auxiliary_writable_roots: routed.auxiliaryWritableRoots,
@@ -1342,13 +1331,8 @@ export class Orchestrator {
1342
1331
  // #147: bound to the project's context config; each try's dispatch sizes its own
1343
1332
  // packet for THIS request from the catalog read the gate makes for that try.
1344
1333
  const { context } = this.projectConfig(contract.repo.root);
1345
- const sizeRawContext = rawContextSizer(routed.implementationTransport, envelope, context);
1346
- let rawContextPacket = null;
1347
- const dispatch = sizeRawContext
1348
- ? {
1349
- rawContext: async (capacity) => (rawContextPacket = await sizeRawContext(capacity)),
1350
- }
1351
- : {};
1334
+ const rawLane = new RawPatchLane(routed.implementationTransport, envelope, context);
1335
+ const { dispatch } = rawLane;
1352
1336
  bindProcessingAdmission(spec, ledger, processingLease.lease, adapter.id, attemptId, processingLease.onDenied, processingAdmission);
1353
1337
  // Profile-less processing keeps its existing route estimate. Named rows
1354
1338
  // receive their own verification at the shared physical-dispatch gate.
@@ -1433,8 +1417,7 @@ export class Orchestrator {
1433
1417
  const refusal = continuity.beginTry(runSpec, nativeTry, attemptAbort);
1434
1418
  const transientStart = telemetry.transientFailures.length;
1435
1419
  const rateLimitStart = telemetry.rateLimits.length;
1436
- let rawPatch = null;
1437
- rawContextPacket = null; // a patch binds only the packet its own try sent
1420
+ rawLane.beginTry();
1438
1421
  try {
1439
1422
  const watched = withInactivityWatchdog(runModelGovernedRoute(routed, runSpec, dispatch), {
1440
1423
  timeoutMs: inactivityMs,
@@ -1454,7 +1437,7 @@ export class Orchestrator {
1454
1437
  // After Stop: stored evidence only, but file paths are still collected for the capture.
1455
1438
  const stopped = signal?.aborted === true;
1456
1439
  if (!stopped)
1457
- rawPatch = captureRawPatchEnvelope(sizeRawContext !== null, rawPatch, ev);
1440
+ rawLane.observe(ev);
1458
1441
  if (ev.type === "patch_produced")
1459
1442
  continue;
1460
1443
  if (directory)
@@ -1525,14 +1508,7 @@ export class Orchestrator {
1525
1508
  observeBudgetSignals(ledger, log, adapter.id, attemptId, ev, budgetSignalState);
1526
1509
  this.deps.quotaEventSink?.(adapter.id, ev);
1527
1510
  }
1528
- if (sizeRawContext && !harnessErrored)
1529
- await consumeRawPatchEnvelope({
1530
- repoRoot: envelope.repo_root,
1531
- worktreePath: envelope.worktree_path,
1532
- baseCommitSha: envelope.base_sha ?? "HEAD",
1533
- context: rawContextPacket,
1534
- envelope: rawPatch,
1535
- });
1511
+ await rawLane.settle(harnessErrored);
1536
1512
  }
1537
1513
  catch (err) {
1538
1514
  // A throwing adapter must not lose the cost already streamed: record the
@@ -1545,14 +1521,12 @@ export class Orchestrator {
1545
1521
  processingRefusal = err;
1546
1522
  break;
1547
1523
  }
1548
- if (!recordAdapterThrow(telemetry, err))
1524
+ if (!rawLane.refused(err) && !recordAdapterThrow(telemetry, err))
1549
1525
  break;
1550
1526
  }
1551
1527
  finally {
1552
1528
  clearFileBackedContext();
1553
1529
  }
1554
- if (telemetry.requestRefusal)
1555
- break;
1556
1530
  const newTransients = telemetry.transientFailures.slice(transientStart);
1557
1531
  // #31: the centralized retry gate reads the classified `retryable` verdict.
1558
1532
  const sawRetryable = newTransients.some((f) => f.retryable);
@@ -1582,6 +1556,8 @@ export class Orchestrator {
1582
1556
  this.credentials.noteTryServed(refusal, telemetry.outputMarkers, delivered);
1583
1557
  const verdict = await continuity.afterTry({
1584
1558
  ...tryFacts,
1559
+ // A refused patch is its account row above, but no retry or rotation repairs it.
1560
+ requestRefused: tryFacts.requestRefused || rawLane.refusal !== null,
1585
1561
  // A cancel or deadline that landed during capture still stops the
1586
1562
  // attempt; the try's own facts above stay its evidence.
1587
1563
  stopped: signal?.aborted === true,
@@ -1621,6 +1597,7 @@ export class Orchestrator {
1621
1597
  if (harnessErrored &&
1622
1598
  !poolExhausted &&
1623
1599
  !processingRefusal &&
1600
+ !rawLane.refusal &&
1624
1601
  !telemetry.requestRefusal &&
1625
1602
  !signal?.aborted) {
1626
1603
  emitTransientExhausted((t, p) => log?.emit(t, p), adapter.id, attemptId, telemetry, retryPolicy.maxRetries);
@@ -1631,7 +1608,7 @@ export class Orchestrator {
1631
1608
  }
1632
1609
  // D-16: answer.md persists only the deliverable. A3: an errored attempt
1633
1610
  // with no typed final contributes no answer material (acceptedTryOutput).
1634
- const unwrapped = unwrapWorkReportEnvelope(acceptedTryOutput(answer, harnessErrored), workReportMode, { sideToolReport: telemetry.sideToolWorkReport ?? undefined });
1611
+ const unwrapped = unwrapAttemptAnswer(acceptedTryOutput(answer, harnessErrored), harnessErrored && !answer.hasFinal(), workReportMode, { sideToolReport: telemetry.sideToolWorkReport ?? undefined });
1635
1612
  const answerText = unwrapped.deliverable.trim().length > 0 ? unwrapped.deliverable : undefined;
1636
1613
  if (directory)
1637
1614
  await captureDirectory(true);
@@ -1697,7 +1674,7 @@ export class Orchestrator {
1697
1674
  workReportSource: unwrapped.source,
1698
1675
  workReportViolation: unwrapped.contractViolation,
1699
1676
  workReportUnverified: unwrapped.unverified ?? null,
1700
- contextTerminalExhausted: telemetry.contextExhausted,
1677
+ interruption: attemptInterruption(telemetry),
1701
1678
  });
1702
1679
  harnessErrored = finalized.harnessErrored;
1703
1680
  if (finalized.outcomeClass === "contract_failure" && unwrapped.contractViolation)
@@ -1725,6 +1702,7 @@ export class Orchestrator {
1725
1702
  const attemptDir = join(paths.attemptsDir, attemptId);
1726
1703
  recordCleanAttemptMetrics(globalConfigDir(), adapter.id, {
1727
1704
  costUsd: cost,
1705
+ usageCost: telemetry.usageCost,
1728
1706
  streamMs: attemptStreamEndedMs - attemptStartedMs,
1729
1707
  errored,
1730
1708
  aborted: signal?.aborted === true,
@@ -1797,8 +1775,10 @@ export class Orchestrator {
1797
1775
  // A5: the typed refusal survives NORMAL attempt finalization (no throw).
1798
1776
  ...(telemetry.requestRefusal
1799
1777
  ? { declaredFailure: requestRefusalFailure(telemetry.requestRefusal) }
1800
- : poolExhausted || processingRefusal
1801
- ? { declaredFailure: declaredFailure(processingRefusal ?? poolExhausted) }
1778
+ : poolExhausted || processingRefusal || rawLane.refusal
1779
+ ? {
1780
+ declaredFailure: declaredFailure(processingRefusal ?? rawLane.refusal ?? poolExhausted),
1781
+ }
1802
1782
  : {}),
1803
1783
  outcomeClass: finalized.outcomeClass,
1804
1784
  applied,
@@ -1878,7 +1858,7 @@ export class Orchestrator {
1878
1858
  // this SINGLE root.
1879
1859
  const execRoot = this.execRootOf(input);
1880
1860
  const projectRoot = input.repoRoot;
1881
- const wsm = new WorkspaceManager(execRoot, { projectRoot });
1861
+ const wsm = new WorkspaceManager(execRoot, { projectRoot, runtimeRoot: runRuntimeRoot(input) });
1882
1862
  const anchors = secretDiff.revertAnchors(execRoot, projectRoot);
1883
1863
  store.writeYaml(join(paths.contextDir, "task.yaml"), contract);
1884
1864
  log.emit("task.contract.created", { task_contract_hash: hashJson(contract) });
@@ -1914,12 +1894,7 @@ export class Orchestrator {
1914
1894
  // run — revert is simply unavailable then.
1915
1895
  let preTurnSha = null;
1916
1896
  if (mutatingRun && input.inPlace === true && input.workspaceKind !== "directory") {
1917
- try {
1918
- preTurnSha = await snapshotTree(execRoot);
1919
- }
1920
- catch {
1921
- preTurnSha = null;
1922
- }
1897
+ preTurnSha = await rawAwareSnapshot(execRoot).catch(() => null);
1923
1898
  }
1924
1899
  // ContextPack is LAZY: agent/race candidates explore the live tree
1925
1900
  // themselves inside their envelopes; only the read-only report modes
@@ -1970,6 +1945,7 @@ export class Orchestrator {
1970
1945
  for (const env of envelopes)
1971
1946
  await kept.settle(wsm, env, runsBySlot);
1972
1947
  };
1948
+ const rawAdoption = new RawAdoption(execRoot, input);
1973
1949
  const candidateAccess = contract.access.effective_profile;
1974
1950
  let budgetStopped = false;
1975
1951
  // QA-050: keep the ledger's typed denial so the zero-candidate terminal
@@ -2094,6 +2070,9 @@ export class Orchestrator {
2094
2070
  requestedSingleCandidate &&
2095
2071
  slot.routed.implementationTransport !== "git_patch_envelope"),
2096
2072
  });
2073
+ // A continueFrom envelope already owns its record: no empty source overwrites it.
2074
+ if (!input.continuation?.adopt)
2075
+ await rawAdoption.seed(envelope.worktree_path);
2097
2076
  harnessHome = this.harnessHomeFor(wsm, envelope, slot.routed, input);
2098
2077
  const run = await this.runCandidateInEnvelope(slot.routed, envelope, slot.attemptId, slot.label, contract, input.prompt, store, paths, wsm, ledger, {
2099
2078
  lease: slot.lease,
@@ -2214,6 +2193,8 @@ export class Orchestrator {
2214
2193
  }
2215
2194
  }
2216
2195
  }
2196
+ if (!effectiveRun.errored && secretDiff.hasCandidateChanges(effectiveRun))
2197
+ await rawAdoption.keep(effectiveRun.attemptId, envelope.worktree_path);
2217
2198
  runsBySlot[slotIdx] = effectiveRun;
2218
2199
  reviewEnvelopes.push(envelope);
2219
2200
  envelope = undefined;
@@ -2224,6 +2205,7 @@ export class Orchestrator {
2224
2205
  const message = safeErrorMessage(err);
2225
2206
  const declared = declaredFailure(err);
2226
2207
  const infraPhase = envelope === undefined ? "workspace" : "harness";
2208
+ const applied = appliedAttemptFacts(harnessHome, slot.routed.adapterAccess, this.credentials.effectiveProfileId(input, adapter.id));
2227
2209
  log.emit("harness.completed", {
2228
2210
  harness_id: adapter.id,
2229
2211
  attempt_id: slot.attemptId,
@@ -2231,7 +2213,7 @@ export class Orchestrator {
2231
2213
  error: message,
2232
2214
  phase: infraPhase,
2233
2215
  });
2234
- store.writeYaml(join(paths.attemptsDir, slot.attemptId, "attempt.yaml"), AC.attemptFailureRecord(slot.attemptId, adapter.id, failureCost, infraPhase, message, appliedAttemptFacts(harnessHome, slot.routed.adapterAccess, this.credentials.effectiveProfileId(input, adapter.id))));
2216
+ store.writeYaml(join(paths.attemptsDir, slot.attemptId, "attempt.yaml"), AC.attemptFailureRecord(slot.attemptId, adapter.id, failureCost, infraPhase, message, applied));
2235
2217
  runsBySlot[slotIdx] = {
2236
2218
  attemptId: slot.attemptId,
2237
2219
  harnessId: adapter.id,
@@ -2252,8 +2234,10 @@ export class Orchestrator {
2252
2234
  // Keep a TYPED pre-spawn refusal (spent quota window + its reset)
2253
2235
  // alive past this catch; `message` alone would force the terminal to
2254
2236
  // read prose back out.
2255
- ...(declared.code ? { declaredFailure: declared } : {}),
2256
- applied: appliedAttemptFacts(harnessHome, slot.routed.adapterAccess, this.credentials.effectiveProfileId(input, adapter.id)),
2237
+ ...(declared.code || declared.quotaRefusal ? { declaredFailure: declared } : {}),
2238
+ applied,
2239
+ // No HOME was decided, so no child started (#648); never read from a record.
2240
+ ...(harnessHome === undefined ? { homeUndecided: true } : {}),
2257
2241
  };
2258
2242
  }
2259
2243
  finally {
@@ -2300,24 +2284,18 @@ export class Orchestrator {
2300
2284
  : undefined,
2301
2285
  signal: input.signal,
2302
2286
  });
2303
- // Revert divergence fence for the single-candidate in-place path: the
2304
- // candidate mutated the LIVE tree during execution above, so the post-turn
2305
- // snapshot must be taken NOW — before review/synthesis/arbitration, which can
2306
- // run for a long time during which the user may edit files. Snapshotting after
2307
- // arbitration (as the race-adoption path does) would fold those user edits
2308
- // into the revert target and let a later revert clobber them.
2287
+ // Revert divergence fence for the single-candidate in-place path.
2288
+ // The candidate mutated the LIVE tree above. Snapshot NOW, before slow
2289
+ // review/synthesis/arbitration, or later user edits enter the revert target
2290
+ // and a revert can clobber them. The race-adoption path snapshots later.
2291
+ // No change, no fence.
2309
2292
  let earlyPostTurnSha = null;
2310
2293
  if (mutatingRun &&
2311
2294
  input.workspaceKind !== "directory" &&
2312
2295
  input.inPlace &&
2313
2296
  requestedSingleCandidate &&
2314
- runs.every((run) => !run.captureRefusal)) {
2315
- try {
2316
- earlyPostTurnSha = await snapshotTree(execRoot);
2317
- }
2318
- catch {
2319
- earlyPostTurnSha = null;
2320
- }
2297
+ needsPostTurnFence(runs)) {
2298
+ earlyPostTurnSha = await rawAwareSnapshot(execRoot).catch(() => null);
2321
2299
  }
2322
2300
  if (input.signal?.aborted) {
2323
2301
  await disposeReviewEnvelopes();
@@ -2470,6 +2448,7 @@ export class Orchestrator {
2470
2448
  workspaceKind: input.workspaceKind,
2471
2449
  scopePaths: input.scopePaths,
2472
2450
  });
2451
+ await rawAdoption.seed(envelope.worktree_path);
2473
2452
  const synthHome = this.harnessHomeFor(wsm, envelope, synthRouted, input);
2474
2453
  const run = await this.runCandidateInEnvelope(synthRouted, envelope, "synth", "Synthesis", contract, synthesisInput.prompt, store, paths, wsm, ledger, {
2475
2454
  lease: lease.lease,
@@ -2479,6 +2458,8 @@ export class Orchestrator {
2479
2458
  },
2480
2459
  }, candidateAccess, input.onHarnessEvent, input.signal, undefined, input.effort, "synthesize", log, effectiveWeb, interactionChannelFor(input, log, runId, taskId, "synth", synthAdapter.id, synthRouted.supportsInteractive), undefined, input, false, synthesisInput.content, undefined, synthHome, undefined, synthTried);
2481
2460
  ledger.settle(lease.lease?.lease_id ?? "", attemptUsageCostSettlement(run.cost, run.costEstimated, run.attemptId, run.harnessId, run.telemetry.authMode, run.telemetry.usageCost));
2461
+ if (!run.errored && secretDiff.hasCandidateChanges(run))
2462
+ await rawAdoption.keep(run.attemptId, envelope.worktree_path);
2482
2463
  reviewEnvelopes.push(envelope);
2483
2464
  envelope = undefined;
2484
2465
  // D-16 r8: only a WORKING synth is reviewed/adopted (same veto owner
@@ -2723,8 +2704,9 @@ export class Orchestrator {
2723
2704
  applied: true,
2724
2705
  patch_sha256: patchSha256,
2725
2706
  winner: winnerRun.attemptId,
2707
+ ...(await rawAdoption.publish(winnerRun.attemptId)),
2726
2708
  });
2727
- postTurnSha = await snapshotTree(execRoot).catch(() => null);
2709
+ postTurnSha = await rawAwareSnapshot(execRoot).catch(() => null);
2728
2710
  revertAnchorId = await anchors.after(winnerRun, applied);
2729
2711
  }
2730
2712
  else {
@@ -2742,6 +2724,7 @@ export class Orchestrator {
2742
2724
  patch_sha256: patchSha256,
2743
2725
  detail: redactSecrets(applied.detail ?? "apply failed"),
2744
2726
  tree_mutated: applied.treeMutated,
2727
+ ...(adopted ? await rawAdoption.publish(winnerRun.attemptId) : {}),
2745
2728
  });
2746
2729
  }
2747
2730
  }
@@ -2757,6 +2740,7 @@ export class Orchestrator {
2757
2740
  if (inPlaceWinner && requestedSingleCandidate && adopted === true) {
2758
2741
  revertAnchorId = await anchors.turn(preTurnSha, postTurnSha);
2759
2742
  }
2743
+ const capturedNoGitChange = capturedNoGitChangeValue(winnerRun);
2760
2744
  store.writeYaml(join(paths.finalDir, "work_product.yaml"), {
2761
2745
  id: newId("wp"),
2762
2746
  kind: input.create === true ? "new_repo" : "patch",
@@ -2776,6 +2760,7 @@ export class Orchestrator {
2776
2760
  budget_stopped: budgetStopped,
2777
2761
  ...{ patch_sha256: patchSha256, ...savedPatch.meta },
2778
2762
  result_kind: resultKind,
2763
+ ...(capturedNoGitChange !== null ? { capturedNoGitChange } : {}),
2779
2764
  diffstat: {
2780
2765
  files: wstats.paths.length,
2781
2766
  additions: wstats.additions,
@@ -2817,6 +2802,8 @@ export class Orchestrator {
2817
2802
  // (applyable, no_changes, or not-verified) is an honest completion.
2818
2803
  const needsDecisionTerminal = facts.review === "blocked" || facts.checks === "failed";
2819
2804
  const isFailureTerminal = facts.lifecycle !== "succeeded" || needsDecisionTerminal;
2805
+ const decisionBlock = needsHuman || needsDecisionTerminal;
2806
+ const statement = raceStatement(winnerRun, decisionBlock, result.decision.why_winner);
2820
2807
  const pool = racePool();
2821
2808
  // Beside whichever failure record leads, a source another attempt kept stays named.
2822
2809
  const keptSources = candidateCaptureRefusals(runs).flatMap(secretDiff.captureInspectionActions);
@@ -2861,7 +2848,6 @@ export class Orchestrator {
2861
2848
  // stays `internal`. A review/checks block is `policy` (the acceptance
2862
2849
  // path), phase `review`, and its remedy names the decision, not "retry
2863
2850
  // with a different harness".
2864
- const decisionBlock = needsHuman || needsDecisionTerminal;
2865
2851
  writeFailure(store, paths, {
2866
2852
  phase: decisionBlock ? "review" : "arbitration",
2867
2853
  category: decisionBlock
@@ -2877,7 +2863,7 @@ export class Orchestrator {
2877
2863
  ? `review escalated to a human decision: ${result.decision.why_winner}`
2878
2864
  : needsDecisionTerminal
2879
2865
  ? `review blocked before apply: ${result.decision.why_winner}`
2880
- : result.decision.why_winner, pool),
2866
+ : statement, pool),
2881
2867
  poolAttempts: pool,
2882
2868
  rawDetailRef: winnerRun?.errored
2883
2869
  ? `attempts/${winnerRun.attemptId}/attempt.yaml`
@@ -2899,6 +2885,7 @@ export class Orchestrator {
2899
2885
  ]),
2900
2886
  ...keptSources,
2901
2887
  ],
2888
+ ...(decisionBlock ? {} : erroredWinnerFailure(winnerRun, keptSources)),
2902
2889
  });
2903
2890
  if (!winnerRun) {
2904
2891
  store.writeText(join(paths.finalDir, "summary.md"), `# Run ${runId} (${mode})\n\n- Lifecycle: ${facts.lifecycle}${facts.reason ? ` (${facts.reason})` : ""}\n- Phase: arbitration\n\n${withPoolAttempts(result.decision.why_winner, pool)}\n`);
@@ -2957,7 +2944,7 @@ export class Orchestrator {
2957
2944
  facts,
2958
2945
  winner: result.decision.winner,
2959
2946
  runDir: paths.root,
2960
- summary: withPoolAttempts(result.decision.why_winner, pool),
2947
+ summary: withPoolAttempts(statement, pool),
2961
2948
  candidates: candidateStatuses(runs),
2962
2949
  decisionPath,
2963
2950
  reviewVerified: actualReviewVerified,
@@ -3054,7 +3041,10 @@ export class Orchestrator {
3054
3041
  // local `execRoot`; this path previously ensured the boundary on repoRoot,
3055
3042
  // which for an isolated thread is the project, not the mutated worktree.
3056
3043
  const execRoot = this.execRootOf(input);
3057
- const wsm = new WorkspaceManager(execRoot, { projectRoot: input.repoRoot });
3044
+ const wsm = new WorkspaceManager(execRoot, {
3045
+ projectRoot: input.repoRoot,
3046
+ runtimeRoot: runRuntimeRoot(input),
3047
+ });
3058
3048
  const anchors = secretDiff.revertAnchors(execRoot, input.repoRoot);
3059
3049
  const readiness = new ReadinessLedger();
3060
3050
  store.writeYaml(join(paths.contextDir, "task.yaml"), contract);
@@ -3170,7 +3160,7 @@ export class Orchestrator {
3170
3160
  let converged = false;
3171
3161
  let exhausted = false;
3172
3162
  let processingBudgetDenial = null;
3173
- let interrupted = false; // D-16 r8: terminalizes the run interrupted
3163
+ let interrupted = null; // D-16 r8, #299
3174
3164
  let lastFindings = [];
3175
3165
  let lastRun = null;
3176
3166
  const convergenceRuns = [];
@@ -3204,12 +3194,7 @@ export class Orchestrator {
3204
3194
  // access level the envelope did not actually run with.
3205
3195
  const convergenceAccess = contract.access.effective_profile;
3206
3196
  if (input.inPlace === true && input.workspaceKind !== "directory") {
3207
- try {
3208
- preTurnSha = await snapshotTree(execRoot);
3209
- }
3210
- catch {
3211
- preTurnSha = null;
3212
- }
3197
+ preTurnSha = await rawAwareSnapshot(execRoot).catch(() => null);
3213
3198
  }
3214
3199
  envelope = await wsm.create({
3215
3200
  taskId,
@@ -3302,15 +3287,16 @@ export class Orchestrator {
3302
3287
  // A5: keep a TYPED refusal alive past this catch (race-lane parity).
3303
3288
  const declared = declaredFailure(err);
3304
3289
  ledger.settle(lease.lease?.lease_id ?? "", failureCost.settlement);
3290
+ const applied = appliedAttemptFacts(harnessHome, routed.adapterAccess, this.credentials.effectiveProfileId(input, adapter.id));
3305
3291
  log.emit("harness.completed", {
3306
3292
  harness_id: adapter.id,
3307
3293
  attempt_id: attemptId,
3308
3294
  status: "failed",
3309
3295
  error: message,
3310
3296
  });
3311
- store.writeYaml(join(paths.attemptsDir, attemptId, "attempt.yaml"), AC.attemptFailureRecord(attemptId, adapter.id, failureCost, "harness", message, appliedAttemptFacts(harnessHome, routed.adapterAccess, this.credentials.effectiveProfileId(input, adapter.id))));
3297
+ store.writeYaml(join(paths.attemptsDir, attemptId, "attempt.yaml"), AC.attemptFailureRecord(attemptId, adapter.id, failureCost, "harness", message, applied));
3312
3298
  run = {
3313
- applied: appliedAttemptFacts(harnessHome, routed.adapterAccess, this.credentials.effectiveProfileId(input, adapter.id)),
3299
+ applied,
3314
3300
  attemptId,
3315
3301
  harnessId: adapter.id,
3316
3302
  label: `Attempt ${attempt}`,
@@ -3321,7 +3307,7 @@ export class Orchestrator {
3321
3307
  costEstimated: failureCost.estimated,
3322
3308
  errors: [message],
3323
3309
  poolAttempts: tried.snapshot(),
3324
- ...(declared.code ? { declaredFailure: declared } : {}),
3310
+ ...(declared.code || declared.quotaRefusal ? { declaredFailure: declared } : {}),
3325
3311
  telemetry: createAttemptTelemetry(knobs.webPolicy, contract.external_context.web_required, effectiveWeb, [routed.browserRequirement, routed.denyRequirement, routed.delegationRequirement], knobs.model),
3326
3312
  };
3327
3313
  if (envelope.workspace_kind === "directory") {
@@ -3333,24 +3319,22 @@ export class Orchestrator {
3333
3319
  holder: { runId: paths.runId, runDir: paths.root },
3334
3320
  }));
3335
3321
  }
3322
+ // Refused before its HOME was decided, no child ran: that refusal ends the run (#648).
3323
+ if (input.delegated === true && harnessHome === undefined)
3324
+ throw err;
3336
3325
  }
3337
3326
  lastRun = run;
3338
3327
  convergenceRuns.push(run);
3339
3328
  // Fail-closed twin of the candidate lane's gate: this loop terminalizes
3340
3329
  // per attempt, so the proof is spent per attempt.
3341
3330
  assertDelegatedEvidence(input.delegated === true, convergenceAccess, [run]);
3342
- attemptTelemetries.push({ attemptId, harnessId: adapter.id, telemetry: run.telemetry });
3331
+ attemptTelemetries.push({ attemptId, harnessId: adapter.id, ...pickTelemetry(run) });
3343
3332
  lastPostTurnSha = null;
3344
3333
  // Post-mutation fence for in-place: snapshot the live tree NOW (after the
3345
3334
  // harness mutated it, before this attempt's review). The last attempt's
3346
- // value is the revert target persisted into work_product.yaml.
3347
- if (input.inPlace === true && input.workspaceKind !== "directory" && !run.captureRefusal) {
3348
- try {
3349
- lastPostTurnSha = await snapshotTree(execRoot);
3350
- }
3351
- catch {
3352
- lastPostTurnSha = null;
3353
- }
3335
+ // value is the revert target persisted into work_product.yaml (an unchanged one has none).
3336
+ if (input.inPlace && input.workspaceKind !== "directory" && needsPostTurnFence([run])) {
3337
+ lastPostTurnSha = await rawAwareSnapshot(execRoot).catch(() => null);
3354
3338
  }
3355
3339
  // Cancellation/deadline keeps priority over a belt failure finalized concurrently.
3356
3340
  if (input.signal?.aborted || processingBudgetDenial)
@@ -3364,8 +3348,7 @@ export class Orchestrator {
3364
3348
  exhausted = true;
3365
3349
  break;
3366
3350
  }
3367
- if (run.telemetry.requestRefusal ||
3368
- run.telemetry.effortResolution?.resolution === "rejected")
3351
+ if (endsConvergence(run))
3369
3352
  break;
3370
3353
  if (delegateFailure.candidateFailureKind(run)) {
3371
3354
  const failure = delegateFailure.candidateFailureTerminal(run, "convergence");
@@ -3396,7 +3379,7 @@ export class Orchestrator {
3396
3379
  // D-16 r8: interrupted (errored===false) would CONVERGE a partial diff
3397
3380
  // as clean — break BEFORE review; a harness error still gate-retries.
3398
3381
  if (run.outcomeClass === "interrupted") {
3399
- interrupted = true;
3382
+ interrupted = interruptionOf(run);
3400
3383
  break;
3401
3384
  }
3402
3385
  // The review round is wrapped so a preflight/revalidation throw ends the
@@ -3466,7 +3449,8 @@ export class Orchestrator {
3466
3449
  converged = true;
3467
3450
  break;
3468
3451
  }
3469
- const requiredGateFailing = run.gates.length > 0 && !gatesPassed(run.gates);
3452
+ const refusal = correctableRefusal(run); // INV-047: a refused patch has no result
3453
+ const requiredGateFailing = !refusal && run.gates.length > 0 && !gatesPassed(run.gates);
3470
3454
  const diffHash = run.files
3471
3455
  ? run.files.manifestSha256
3472
3456
  : input.workspaceKind === "directory"
@@ -3475,7 +3459,7 @@ export class Orchestrator {
3475
3459
  if (requiredGateFailing && diffHash !== null && diffHash === lastFailingGateDiffHash) {
3476
3460
  sameFailingGateDiffs += 1;
3477
3461
  }
3478
- else {
3462
+ else if (!refusal) {
3479
3463
  sameFailingGateDiffs = requiredGateFailing ? 1 : 0;
3480
3464
  lastFailingGateDiffHash = requiredGateFailing ? (diffHash ?? "") : "";
3481
3465
  }
@@ -3493,11 +3477,9 @@ export class Orchestrator {
3493
3477
  });
3494
3478
  break;
3495
3479
  }
3496
- const sig = failureSignature([
3497
- ...conv.reasons,
3498
- ...(run.files ? [`files_manifest:${run.files.manifestSha256}`] : []),
3499
- ]);
3500
- readiness.recordRound(sig, conv.reasons.join("; "));
3480
+ const manifest = run.files ? [`files_manifest:${run.files.manifestSha256}`] : [];
3481
+ const sig = refusal ?? failureSignature([...conv.reasons, ...manifest]);
3482
+ readiness.recordRound(sig, refusal ?? conv.reasons.join("; "));
3501
3483
  if (sig !== lastSig) {
3502
3484
  triedSinceProgress = new Set();
3503
3485
  lastSig = sig;
@@ -3633,6 +3615,7 @@ export class Orchestrator {
3633
3615
  const convNeedsDecision = facts.review === "blocked" || facts.checks === "failed";
3634
3616
  // Every attempt's failed tries by the actual outcome; a block is not an account failure.
3635
3617
  const convPool = terminalPoolAttempts(facts, convergenceRuns);
3618
+ const convCause = convergenceCause(lastRun, facts, Boolean(convBudgetMapping));
3636
3619
  if (decision) {
3637
3620
  // Shared honesty owner (same as the race path): a needs-decision terminal
3638
3621
  // overrides the persisted decision; final_verify is recorded either way.
@@ -3678,6 +3661,7 @@ export class Orchestrator {
3678
3661
  : "applied_review_blocked"
3679
3662
  : "not_applied";
3680
3663
  const revertAnchorId = convAdopted === true ? await anchors.turn(preTurnSha, lastPostTurnSha) : null;
3664
+ const capturedNoGitChange = capturedNoGitChangeValue(lastRun);
3681
3665
  store.writeYaml(join(paths.finalDir, "work_product.yaml"), {
3682
3666
  id: newId("wp"),
3683
3667
  kind: "patch",
@@ -3686,6 +3670,7 @@ export class Orchestrator {
3686
3670
  meta: {
3687
3671
  harness_id: lastRun.harnessId,
3688
3672
  result_kind: "patch",
3673
+ ...(capturedNoGitChange !== null ? { capturedNoGitChange } : {}),
3689
3674
  mode,
3690
3675
  attempts: attempt,
3691
3676
  lifecycle: facts.lifecycle,
@@ -3706,7 +3691,7 @@ export class Orchestrator {
3706
3691
  store.writeText(join(paths.finalDir, "answer.md"), lastRun.answerText);
3707
3692
  log.emit("output.ready", { kind: "answer", path: "final/answer.md" });
3708
3693
  }
3709
- store.writeText(join(paths.finalDir, "summary.md"), `${withPoolAttempts(`# Run ${runId} (${mode})\n\n- Lifecycle: ${facts.lifecycle}${facts.reason ? ` (${facts.reason})` : ""}\n- Attempts: ${attempt}\n- Winner: ${lastRun.attemptId}\n- Review verified (cross-family): ${actualReviewVerified}\n- Apply recommendation: ${decision?.apply_recommendation ?? "inspect"}${stuckNoProgressReason ? `\n- No-progress reason: ${stuckNoProgressReason}` : ""}${secretDiff.resultSummaryDisclosure(lastRun)}`, convPool)}\n`);
3694
+ store.writeText(join(paths.finalDir, "summary.md"), `${withPoolAttempts(`# Run ${runId} (${mode})\n\n- Lifecycle: ${facts.lifecycle}${facts.reason ? ` (${facts.reason})` : ""}\n- Attempts: ${attempt}\n- Winner: ${lastRun.attemptId}\n- Review verified (cross-family): ${actualReviewVerified}\n- Apply recommendation: ${decision?.apply_recommendation ?? "inspect"}${stuckNoProgressReason ? `\n- No-progress reason: ${stuckNoProgressReason}` : ""}${convCause ? `\n- Failure: ${convCause}` : ""}${secretDiff.resultSummaryDisclosure(lastRun)}`, convPool)}\n`);
3710
3695
  // Lifecycle invariant (all modes): output.ready precedes the terminal
3711
3696
  // event so a client that applied the terminal event has the output.
3712
3697
  log.emit("output.ready", {
@@ -3742,6 +3727,7 @@ export class Orchestrator {
3742
3727
  rawContextShortfall: convDeclared?.rawContextShortfall,
3743
3728
  harnessId: lastRun?.harnessId,
3744
3729
  attemptId: lastRun?.attemptId,
3730
+ vendorFailure: convCause ? lastRun?.telemetry.vendorFailure : null,
3745
3731
  runDir: paths.root,
3746
3732
  captureFailure: lastRun?.captureRefusal?.failure,
3747
3733
  nextActions: [
@@ -3780,7 +3766,7 @@ export class Orchestrator {
3780
3766
  ? `review escalated to a human decision after ${attempt} attempt(s)`
3781
3767
  : facts.reason === "stuck_no_progress"
3782
3768
  ? (stuckNoProgressReason ?? `stuck_no_progress after ${attempt} attempt(s)`)
3783
- : `${facts.lifecycle}${facts.reason ? ` (${facts.reason})` : ""} after ${attempt} attempt(s)${lastDiffStable ? "" : " (diff changed after review; review is stale)"}`), convPool),
3769
+ : `${convCause ? `${convCause}\n` : ""}${facts.lifecycle}${facts.reason ? ` (${facts.reason})` : ""} after ${attempt} attempt(s)${lastDiffStable ? "" : " (diff changed after review; review is stale)"}`), convPool),
3784
3770
  poolAttempts: convPool,
3785
3771
  });
3786
3772
  // D-16 r8/r9: an INTERRUPTED envelope run still gets its diagnostic
@@ -3856,7 +3842,7 @@ export class Orchestrator {
3856
3842
  : {}),
3857
3843
  summary: withPoolAttempts(converged
3858
3844
  ? `converged in ${attempt} attempt(s)`
3859
- : `${facts.lifecycle} after ${attempt} attempt(s)`, convPool),
3845
+ : `${convCause ? `${convCause}\n` : ""}${facts.lifecycle} after ${attempt} attempt(s)`, convPool),
3860
3846
  candidates: lastRun
3861
3847
  ? [
3862
3848
  {
@@ -3891,6 +3877,7 @@ export class Orchestrator {
3891
3877
  ? `${args.promptBody}\n\n${laneContinuity.pointerLine}`
3892
3878
  : args.promptBody,
3893
3879
  cwd: this.execRootOf(input),
3880
+ input_file_dir: paths.contextDir,
3894
3881
  access: "readonly",
3895
3882
  attachments: input.attachments ?? [],
3896
3883
  ...sessionFields,
@@ -4084,6 +4071,8 @@ export class Orchestrator {
4084
4071
  if (outcome.status !== "success") {
4085
4072
  if (outcome.telemetry?.effortResolution?.resolution === "rejected")
4086
4073
  break;
4074
+ if (outcome.telemetry?.turnLimitReached)
4075
+ break; // #299: the cap ends the run
4087
4076
  const next = adapters[idx + 1];
4088
4077
  if (next && !input.signal?.aborted) {
4089
4078
  fallbackFrom = outcome.harnessId;
@@ -4237,7 +4226,7 @@ export class Orchestrator {
4237
4226
  * route/session/knob machinery HERE and hands the module only finished public
4238
4227
  * types (a `HarnessRunSpec`, cost evidence, a disposable home).
4239
4228
  */
4240
- deepScanReducerDeps(input, contract, log) {
4229
+ deepScanReducerDeps(input, contract, log, paths) {
4241
4230
  return {
4242
4231
  newReadOnlyHome: () => scopedRouteContext(resolveReadOnlyRouteContext(this.execRootOf(input)), input.runScopeMarker),
4243
4232
  costEvidence: (harnessId, attemptId, routed) =>
@@ -4254,6 +4243,7 @@ export class Orchestrator {
4254
4243
  intent: "synthesize",
4255
4244
  prompt,
4256
4245
  cwd: this.execRootOf(input),
4246
+ input_file_dir: paths.contextDir,
4257
4247
  access: "readonly",
4258
4248
  attachments: [],
4259
4249
  auth_preference: sessionFields.auth_preference,
@@ -4487,6 +4477,7 @@ export class Orchestrator {
4487
4477
  intent: opts.intent,
4488
4478
  prompt: promptWithPointers,
4489
4479
  cwd: this.execRootOf(input),
4480
+ input_file_dir: paths.contextDir,
4490
4481
  access: "readonly",
4491
4482
  // ASK/EXPLORE/AUDIT read-only runs must forward the user's attachments —
4492
4483
  // a live "describe this image" turn sent an image that was being dropped here, so
@@ -4553,7 +4544,8 @@ export class Orchestrator {
4553
4544
  report: "",
4554
4545
  error: message,
4555
4546
  telemetry,
4556
- ...(preDeclared.code ? { declaredFailure: preDeclared } : {}), // typed pre-spawn refusal
4547
+ // A typed pre-spawn refusal, a provider refusal with no code of its own included (#558).
4548
+ ...(declaredFailureSpeaks(preDeclared) ? { declaredFailure: preDeclared } : {}),
4557
4549
  });
4558
4550
  attemptTelemetries.push({ attemptId, harnessId: adapter.id, telemetry });
4559
4551
  if (opts.deepScan) {
@@ -4661,7 +4653,7 @@ export class Orchestrator {
4661
4653
  // the loudest) — same single owner as the agent loop.
4662
4654
  observeBudgetSignals(ledger, log, adapter.id, attemptId, ev, budgetSignalState);
4663
4655
  this.deps.quotaEventSink?.(adapter.id, ev);
4664
- const spend = observeReadonlySpend(ev, (t, p) => log.emit(t, p), adapter.id, attemptId);
4656
+ const spend = observeUsageSpend(ev, (t, p) => log.emit(t, p), adapter.id, attemptId, telemetry.usageCost);
4665
4657
  cost += spend.costUsd;
4666
4658
  costEstimated ||= spend.estimated;
4667
4659
  const streamDenial = updateProcessingStreamHold(runSpec, telemetry.usageCost, ledger, lease.lease.lease_id, adapter.id, attemptId);
@@ -4689,8 +4681,6 @@ export class Orchestrator {
4689
4681
  if (!recordAdapterThrow(telemetry, err))
4690
4682
  break;
4691
4683
  }
4692
- if (telemetry.requestRefusal)
4693
- break;
4694
4684
  const newTransients = telemetry.transientFailures.slice(transientStart);
4695
4685
  const sawRetryable = newTransients.some((f) => f.retryable);
4696
4686
  const sawTypedLimit = telemetry.rateLimits.length > rateLimitStart;
@@ -4752,7 +4742,7 @@ export class Orchestrator {
4752
4742
  attemptTelemetries.push({ attemptId, harnessId: adapter.id, telemetry });
4753
4743
  // D-16: the deliverable is the report. A3: an errored attempt with no
4754
4744
  // typed final contributes no report material (acceptedTryOutput).
4755
- const roUnwrapped = unwrapWorkReportEnvelope(acceptedTryOutput(answer, harnessError !== null), readonlyWorkMode, { sideToolReport: telemetry.sideToolWorkReport ?? undefined });
4745
+ const roUnwrapped = unwrapAttemptAnswer(acceptedTryOutput(answer, harnessError !== null), harnessError !== null && !answer.hasFinal(), readonlyWorkMode, { sideToolReport: telemetry.sideToolWorkReport ?? undefined });
4756
4746
  // Trim symmetrically with the plan path: a whitespace-only answer is not
4757
4747
  // a delivered report (the final-artifact wrapper heading would otherwise
4758
4748
  // make it read as present content by construction).
@@ -4771,7 +4761,7 @@ export class Orchestrator {
4771
4761
  workReportSource: roUnwrapped.source,
4772
4762
  workReportViolation: roUnwrapped.contractViolation,
4773
4763
  workReportUnverified: roUnwrapped.unverified ?? null,
4774
- contextTerminalExhausted: telemetry.contextExhausted,
4764
+ interruption: attemptInterruption(telemetry),
4775
4765
  });
4776
4766
  // A broken WorkReport contract is a hard failure ONLY when the finalizer
4777
4767
  // ranked it so — a concurrent terminal context exhaustion outranks it
@@ -4842,9 +4832,8 @@ export class Orchestrator {
4842
4832
  harnessId: adapter.id,
4843
4833
  status: scoutInterrupted ? "failed" : "success",
4844
4834
  report: report || "(no output)",
4845
- error: scoutInterrupted ? "context capacity exhausted before the scout completed" : null,
4835
+ error: scoutInterrupted ? interruptionDetail(roFinalized.reason) : null,
4846
4836
  telemetry,
4847
- ...(scoutInterrupted ? { interrupted: true } : {}),
4848
4837
  ...continuity.terminalFacts(roResumable),
4849
4838
  });
4850
4839
  launched.delete(tried);
@@ -4855,7 +4844,7 @@ export class Orchestrator {
4855
4844
  .join("; ")}\n`
4856
4845
  : "";
4857
4846
  const scoutTag = scoutInterrupted
4858
- ? " interrupted (context capacity exhausted)"
4847
+ ? ` interrupted (${interruptionDetail(roFinalized.reason)})`
4859
4848
  : ` (${adapter.id})`;
4860
4849
  store.writeText(join(paths.findingsDir, `${attemptId}${scoutInterrupted ? "-interrupted" : ""}.md`), `# Explorer ${attemptId}${scoutTag}\n\n${report || "(no output)"}${warningNote}\n`);
4861
4850
  }
@@ -4933,12 +4922,13 @@ export class Orchestrator {
4933
4922
  if (last?.telemetry.effortResolution?.resolution === "rejected")
4934
4923
  break;
4935
4924
  // Per-harness fallback_model: one same-harness retry on FAILURE (not
4936
- // policy blocks) before falling through to the next harness.
4925
+ // policy blocks or the caller's max-turns cap, #299) before the next harness.
4937
4926
  const fallbackModel = routed.settings?.fallbackModel;
4938
4927
  const firstModel = contract.routing_models[routed.adapter.id] ?? routed.settings?.defaultModel ?? null;
4939
4928
  if (last &&
4940
4929
  last.status === "failed" &&
4941
4930
  !last.telemetry.requestRefusal &&
4931
+ !last.telemetry.turnLimitReached &&
4942
4932
  fallbackModel &&
4943
4933
  fallbackModel !== firstModel &&
4944
4934
  !budgetStopped &&
@@ -5032,7 +5022,7 @@ export class Orchestrator {
5032
5022
  let deepScanSynthesis = null;
5033
5023
  let reducedReport = null;
5034
5024
  if (opts.deepScan) {
5035
- ({ deepScanSynthesis, reducedReport } = await resolveDeepScanSynthesis(this.deepScanReducerDeps(input, contract, log), {
5025
+ ({ deepScanSynthesis, reducedReport } = await resolveDeepScanSynthesis(this.deepScanReducerDeps(input, contract, log, paths), {
5036
5026
  succeeded,
5037
5027
  adapters,
5038
5028
  budgetStopped,