@claudexor/orchestrator 4.8.2 → 5.0.1

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 (223) hide show
  1. package/dist/account-pool.d.ts +4 -1
  2. package/dist/account-pool.d.ts.map +1 -1
  3. package/dist/account-pool.js +25 -19
  4. package/dist/account-pool.js.map +1 -1
  5. package/dist/account-resolution.d.ts +1 -4
  6. package/dist/account-resolution.d.ts.map +1 -1
  7. package/dist/account-resolution.js +19 -42
  8. package/dist/account-resolution.js.map +1 -1
  9. package/dist/attemptOutputMarkers.d.ts +7 -0
  10. package/dist/attemptOutputMarkers.d.ts.map +1 -1
  11. package/dist/attemptOutputMarkers.js +1 -1
  12. package/dist/attemptOutputMarkers.js.map +1 -1
  13. package/dist/attemptTelemetry.d.ts +4 -1
  14. package/dist/attemptTelemetry.d.ts.map +1 -1
  15. package/dist/attemptTelemetry.js +9 -26
  16. package/dist/attemptTelemetry.js.map +1 -1
  17. package/dist/candidateEvidence.d.ts +12 -9
  18. package/dist/candidateEvidence.d.ts.map +1 -1
  19. package/dist/candidateEvidence.js +38 -13
  20. package/dist/candidateEvidence.js.map +1 -1
  21. package/dist/candidateOutputs.d.ts +44 -22
  22. package/dist/candidateOutputs.d.ts.map +1 -1
  23. package/dist/candidateOutputs.js +116 -72
  24. package/dist/candidateOutputs.js.map +1 -1
  25. package/dist/candidateReview.d.ts.map +1 -1
  26. package/dist/candidateReview.js +44 -8
  27. package/dist/candidateReview.js.map +1 -1
  28. package/dist/candidateTerminals.d.ts +1 -0
  29. package/dist/candidateTerminals.d.ts.map +1 -1
  30. package/dist/candidateTerminals.js +11 -6
  31. package/dist/candidateTerminals.js.map +1 -1
  32. package/dist/candidateWorkProduct.d.ts +2 -1
  33. package/dist/candidateWorkProduct.d.ts.map +1 -1
  34. package/dist/candidateWorkProduct.js +6 -3
  35. package/dist/candidateWorkProduct.js.map +1 -1
  36. package/dist/captureRefusal.d.ts +29 -1
  37. package/dist/captureRefusal.d.ts.map +1 -1
  38. package/dist/captureRefusal.js +114 -8
  39. package/dist/captureRefusal.js.map +1 -1
  40. package/dist/continuation-chain.test-support.d.ts +473 -0
  41. package/dist/continuation-chain.test-support.d.ts.map +1 -0
  42. package/dist/continuation-chain.test-support.js +206 -0
  43. package/dist/continuation-chain.test-support.js.map +1 -0
  44. package/dist/continuation-custody.d.ts +4 -1
  45. package/dist/continuation-custody.d.ts.map +1 -1
  46. package/dist/continuation-custody.js +68 -28
  47. package/dist/continuation-custody.js.map +1 -1
  48. package/dist/continuation-evidence-io.d.ts +30 -4
  49. package/dist/continuation-evidence-io.d.ts.map +1 -1
  50. package/dist/continuation-evidence-io.js +162 -46
  51. package/dist/continuation-evidence-io.js.map +1 -1
  52. package/dist/continuation-evidence.d.ts +40 -3
  53. package/dist/continuation-evidence.d.ts.map +1 -1
  54. package/dist/continuation-evidence.js +85 -11
  55. package/dist/continuation-evidence.js.map +1 -1
  56. package/dist/continuation-input.d.ts +10 -1
  57. package/dist/continuation-input.d.ts.map +1 -1
  58. package/dist/continuation-input.js +21 -20
  59. package/dist/continuation-input.js.map +1 -1
  60. package/dist/continuation-later.d.ts +30 -0
  61. package/dist/continuation-later.d.ts.map +1 -0
  62. package/dist/continuation-later.js +108 -0
  63. package/dist/continuation-later.js.map +1 -0
  64. package/dist/continue-from.d.ts +4 -3
  65. package/dist/continue-from.d.ts.map +1 -1
  66. package/dist/continue-from.js +62 -26
  67. package/dist/continue-from.js.map +1 -1
  68. package/dist/continuity-notice.d.ts +3 -1
  69. package/dist/continuity-notice.d.ts.map +1 -1
  70. package/dist/continuity-notice.js +3 -1
  71. package/dist/continuity-notice.js.map +1 -1
  72. package/dist/continuity-terminal.d.ts +16 -0
  73. package/dist/continuity-terminal.d.ts.map +1 -1
  74. package/dist/continuity-terminal.js +21 -0
  75. package/dist/continuity-terminal.js.map +1 -1
  76. package/dist/continuity.d.ts +1 -1
  77. package/dist/continuity.d.ts.map +1 -1
  78. package/dist/contract-gates.d.ts.map +1 -1
  79. package/dist/contract-gates.js +11 -6
  80. package/dist/contract-gates.js.map +1 -1
  81. package/dist/credential-cooldown.d.ts +2 -1
  82. package/dist/credential-cooldown.d.ts.map +1 -1
  83. package/dist/credential-cooldown.js +16 -7
  84. package/dist/credential-cooldown.js.map +1 -1
  85. package/dist/credential-preflight.d.ts.map +1 -1
  86. package/dist/credential-preflight.js +10 -7
  87. package/dist/credential-preflight.js.map +1 -1
  88. package/dist/credential-profile-rotation.d.ts +10 -3
  89. package/dist/credential-profile-rotation.d.ts.map +1 -1
  90. package/dist/credential-profile-rotation.js +28 -26
  91. package/dist/credential-profile-rotation.js.map +1 -1
  92. package/dist/deepScanReducer.d.ts +0 -3
  93. package/dist/deepScanReducer.d.ts.map +1 -1
  94. package/dist/deepScanReducer.js +22 -26
  95. package/dist/deepScanReducer.js.map +1 -1
  96. package/dist/delegationFailure.d.ts +34 -5
  97. package/dist/delegationFailure.d.ts.map +1 -1
  98. package/dist/delegationFailure.js +6 -4
  99. package/dist/delegationFailure.js.map +1 -1
  100. package/dist/delegationToolEvidence.d.ts +2 -0
  101. package/dist/delegationToolEvidence.d.ts.map +1 -1
  102. package/dist/delegationToolEvidence.js +21 -0
  103. package/dist/delegationToolEvidence.js.map +1 -1
  104. package/dist/diffReview.d.ts +1 -8
  105. package/dist/diffReview.d.ts.map +1 -1
  106. package/dist/diffReview.js +3 -18
  107. package/dist/diffReview.js.map +1 -1
  108. package/dist/directoryCandidate.d.ts +1 -1
  109. package/dist/directoryCandidate.d.ts.map +1 -1
  110. package/dist/directoryCandidate.js +13 -13
  111. package/dist/directoryCandidate.js.map +1 -1
  112. package/dist/inrun-continuity-carrier.d.ts +11 -1
  113. package/dist/inrun-continuity-carrier.d.ts.map +1 -1
  114. package/dist/inrun-continuity-carrier.js +32 -10
  115. package/dist/inrun-continuity-carrier.js.map +1 -1
  116. package/dist/inrun-continuity-types.d.ts +17 -4
  117. package/dist/inrun-continuity-types.d.ts.map +1 -1
  118. package/dist/inrun-continuity.d.ts +8 -3
  119. package/dist/inrun-continuity.d.ts.map +1 -1
  120. package/dist/inrun-continuity.js +41 -37
  121. package/dist/inrun-continuity.js.map +1 -1
  122. package/dist/inrun-continuity.test-support.d.ts +61 -4
  123. package/dist/inrun-continuity.test-support.d.ts.map +1 -1
  124. package/dist/inrun-continuity.test-support.js +12 -2
  125. package/dist/inrun-continuity.test-support.js.map +1 -1
  126. package/dist/modelGovernance.d.ts +87 -9
  127. package/dist/modelGovernance.d.ts.map +1 -1
  128. package/dist/modelGovernance.js +110 -15
  129. package/dist/modelGovernance.js.map +1 -1
  130. package/dist/orchestrator-credentials.d.ts +9 -4
  131. package/dist/orchestrator-credentials.d.ts.map +1 -1
  132. package/dist/orchestrator-credentials.js +14 -6
  133. package/dist/orchestrator-credentials.js.map +1 -1
  134. package/dist/orchestrator.d.ts +0 -1
  135. package/dist/orchestrator.d.ts.map +1 -1
  136. package/dist/orchestrator.js +247 -223
  137. package/dist/orchestrator.js.map +1 -1
  138. package/dist/planFinalize.d.ts.map +1 -1
  139. package/dist/planFinalize.js +3 -7
  140. package/dist/planFinalize.js.map +1 -1
  141. package/dist/planRun.js +2 -2
  142. package/dist/planRun.js.map +1 -1
  143. package/dist/plannerAttempt.d.ts +0 -2
  144. package/dist/plannerAttempt.d.ts.map +1 -1
  145. package/dist/plannerAttempt.js +64 -30
  146. package/dist/plannerAttempt.js.map +1 -1
  147. package/dist/policyFindings.d.ts +3 -0
  148. package/dist/policyFindings.d.ts.map +1 -1
  149. package/dist/policyFindings.js +11 -1
  150. package/dist/policyFindings.js.map +1 -1
  151. package/dist/pool-attempt-evidence.d.ts +1 -1
  152. package/dist/pool-attempt-evidence.d.ts.map +1 -1
  153. package/dist/pool-attempt-evidence.js +1 -1
  154. package/dist/pool-attempt-evidence.js.map +1 -1
  155. package/dist/primary-divergence.d.ts +7 -2
  156. package/dist/primary-divergence.d.ts.map +1 -1
  157. package/dist/primary-divergence.js +13 -5
  158. package/dist/primary-divergence.js.map +1 -1
  159. package/dist/quota-refusal-scope.d.ts +39 -1
  160. package/dist/quota-refusal-scope.d.ts.map +1 -1
  161. package/dist/quota-refusal-scope.js +95 -1
  162. package/dist/quota-refusal-scope.js.map +1 -1
  163. package/dist/readonlyTerminals.d.ts +0 -1
  164. package/dist/readonlyTerminals.d.ts.map +1 -1
  165. package/dist/readonlyTerminals.js +5 -3
  166. package/dist/readonlyTerminals.js.map +1 -1
  167. package/dist/requestRequirements.d.ts +23 -0
  168. package/dist/requestRequirements.d.ts.map +1 -1
  169. package/dist/requestRequirements.js +22 -0
  170. package/dist/requestRequirements.js.map +1 -1
  171. package/dist/resultNotice.d.ts +14 -0
  172. package/dist/resultNotice.d.ts.map +1 -0
  173. package/dist/resultNotice.js +21 -0
  174. package/dist/resultNotice.js.map +1 -0
  175. package/dist/reviewerInventory.d.ts +47 -0
  176. package/dist/reviewerInventory.d.ts.map +1 -0
  177. package/dist/reviewerInventory.js +74 -0
  178. package/dist/reviewerInventory.js.map +1 -0
  179. package/dist/reviewerPanel.d.ts.map +1 -1
  180. package/dist/reviewerPanel.js +37 -60
  181. package/dist/reviewerPanel.js.map +1 -1
  182. package/dist/routing-failure.d.ts +2 -1
  183. package/dist/routing-failure.d.ts.map +1 -1
  184. package/dist/routing-failure.js +2 -0
  185. package/dist/routing-failure.js.map +1 -1
  186. package/dist/runAdmission.d.ts.map +1 -1
  187. package/dist/runAdmission.js +1 -14
  188. package/dist/runAdmission.js.map +1 -1
  189. package/dist/runEventLog.js +2 -2
  190. package/dist/runEventLog.js.map +1 -1
  191. package/dist/runFactsBuilder.d.ts.map +1 -1
  192. package/dist/runFactsBuilder.js +18 -10
  193. package/dist/runFactsBuilder.js.map +1 -1
  194. package/dist/runFactsDeliverable.d.ts.map +1 -1
  195. package/dist/runFactsDeliverable.js +7 -1
  196. package/dist/runFactsDeliverable.js.map +1 -1
  197. package/dist/runSupport.d.ts +5 -10
  198. package/dist/runSupport.d.ts.map +1 -1
  199. package/dist/runSupport.js +38 -40
  200. package/dist/runSupport.js.map +1 -1
  201. package/dist/runTerminalResults.d.ts +9 -1
  202. package/dist/runTerminalResults.d.ts.map +1 -1
  203. package/dist/runTerminalResults.js +27 -5
  204. package/dist/runTerminalResults.js.map +1 -1
  205. package/dist/savedCandidate.d.ts +43 -0
  206. package/dist/savedCandidate.d.ts.map +1 -0
  207. package/dist/savedCandidate.js +88 -0
  208. package/dist/savedCandidate.js.map +1 -0
  209. package/dist/secretDiff.d.ts +85 -46
  210. package/dist/secretDiff.d.ts.map +1 -1
  211. package/dist/secretDiff.js +123 -126
  212. package/dist/secretDiff.js.map +1 -1
  213. package/dist/task-contract-builder.d.ts.map +1 -1
  214. package/dist/task-contract-builder.js +5 -7
  215. package/dist/task-contract-builder.js.map +1 -1
  216. package/dist/workReportEnvelope.d.ts.map +1 -1
  217. package/dist/workReportEnvelope.js +4 -16
  218. package/dist/workReportEnvelope.js.map +1 -1
  219. package/package.json +17 -17
  220. package/dist/persistedPatch.d.ts +0 -55
  221. package/dist/persistedPatch.d.ts.map +0 -1
  222. package/dist/persistedPatch.js +0 -132
  223. package/dist/persistedPatch.js.map +0 -1
@@ -6,12 +6,12 @@ import { requestRefusalFailure } from "./runTerminalResults.js";
6
6
  import { openRunScopeCustody, scopedRouteContext, scopedSpecEnv, } from "./runScope.js";
7
7
  import { delegationBeltFor } from "./delegationBelt.js";
8
8
  import { routePromptCacheTtl, withReviewerPromptCacheTtl } from "./prompt-cache-routing.js";
9
- import { bindProcessingAdmission, processingAttemptCostEvidence, processingAdmissionForLease, updateProcessingStreamHold, ProcessingBudgetAdmissionError, } from "./processing-dispatch.js";
9
+ import { bindProcessingAdmission, processingAttemptCostEvidence, processingAdmissionForLease, updateProcessingStreamHold, } from "./processing-dispatch.js";
10
10
  import { cancelledCandidatesResult, emptyCandidateResult, failedCandidatesResult, failAfterCandidates, } 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";
14
- import { continuityWorkspace, resumableOf, resumableTerminal, } from "./continuity-terminal.js";
14
+ import { continuityWorkspace, carryingAttempts, resumableOf, resumableTerminal, } from "./continuity-terminal.js";
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";
@@ -33,8 +33,8 @@ 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, AuxiliaryWritableRootsUnsupportedError, CLAUDEXOR_BROWSER_ARTIFACT_SUBDIR, countsAsAgentProgress, HarnessUnavailableError, summarizeDiffPaths as diffStats, withInactivityWatchdog, } from "@claudexor/core";
37
- import { assertRouteModelsAllowed, runModelGovernedRoute } from "./modelGovernance.js";
36
+ import { acceptedTryOutput, AccessProfileIncompatibleError, AnswerAssembly, appendHarnessEvent, AuxiliaryWritableRootsUnsupportedError, CLAUDEXOR_BROWSER_ARTIFACT_SUBDIR, countsAsAgentProgress, HarnessUnavailableError, withInactivityWatchdog, } from "@claudexor/core";
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";
40
40
  import { activateDelegationParent } from "./delegation-parent-activation.js";
@@ -51,7 +51,7 @@ import { budgetFailureRecord, classifyBudgetFailure } from "./budgetFailure.js";
51
51
  import { finalizeStructuredOutput } from "./structuredOutput.js";
52
52
  import { prepareRunAdmission } from "./runAdmission.js";
53
53
  import { dropDeltaPastBudget, emitPlanProgress, emitTransientExhausted, observeReadonlySpend, recordAdapterThrow, } from "./laneStreamEvents.js";
54
- import { promptWithEngineConstraints, sleep, redactHarnessEvent, harnessEventPayload, safeErrorMessage, renderSummary, observeBudgetSignals, rotateOnStall, recordCleanAttemptMetrics, envInheritance, transientRetryPolicy, reviewerTimeoutMs, harnessInactivityTimeoutMs, observeAuthSwitch, emitPoolDegraded, deliveryRefusalFailure, winnerNeedsHuman, writeRaceDeliveryDecision, } from "./runSupport.js";
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";
@@ -72,16 +72,17 @@ import { finalVerifyRun, gateSpecsFromContract, renderTestsEvidence } from "./co
72
72
  import { buildTaskContract } from "./task-contract-builder.js";
73
73
  import { recordProcessingIntent } from "./retry-processing-intent.js";
74
74
  import { prepareReviewEvidenceDir, recordReviewEvidenceCleanup } from "./reviewEvidence.js";
75
+ import { savedForSynthesis } from "./savedCandidate.js";
75
76
  import { ArtifactStore } from "@claudexor/artifact-store";
76
- import { buildContextPack, rawContextForEnvelope, writeEvidencePacket } from "@claudexor/context";
77
- import { WorkspaceManager, captureRawPatchEnvelope, createRevertAnchorFromPatchOrNull, createRevertAnchorOrNull, consumeRawPatchEnvelope, snapshotTree, } from "@claudexor/workspace";
77
+ import { buildContextPack, rawContextSizer, writeEvidencePacket } from "@claudexor/context";
78
+ import { WorkspaceManager, captureRawPatchEnvelope, consumeRawPatchEnvelope, snapshotTree, } from "@claudexor/workspace";
78
79
  import { blockedDecisionOverride, finalVerifyBlocks, verifyAndDeliverFiles, verifyAndDeliver, } from "@claudexor/delivery";
79
80
  import { HarnessGateway } from "@claudexor/gateway";
80
81
  import { ReadinessLedger, evaluateConvergence, failureSignature, gatesPassed, reviewCandidate, runGates, } from "@claudexor/review";
81
82
  import { arbitrate } from "@claudexor/arbitration";
82
83
  import { buildSynthesisPlan, decideSynthesis } from "@claudexor/synthesis";
83
- import { billingKnowledgeForAuthRoute, attemptUsageCostSettlement, BudgetLedger, isBudgetTerminal, RoutingPreflightError, explainRanking, loadHarnessMetrics, promptFingerprint, rankHarnesses, } from "@claudexor/budget";
84
- import { readTextSafe, appendLine, hashJson, newId, noProjectRepoRoot, redactSecrets, safeInvoke, sha256, userConfigDir, } from "@claudexor/util";
84
+ import { billingKnowledgeForAuthRoute, attemptUsageCostSettlement, BudgetLedger, isBudgetTerminal, explainRanking, loadHarnessMetrics, promptFingerprint, rankHarnesses, } from "@claudexor/budget";
85
+ import { readTextSafe, hashJson, newId, noProjectRepoRoot, redactSecrets, safeInvoke, userConfigDir, } from "@claudexor/util";
85
86
  const LABELS = "ABCDEFGHIJ".split("");
86
87
  const NO_PROJECT_ROOT = noProjectRepoRoot();
87
88
  export class Orchestrator {
@@ -188,20 +189,24 @@ export class Orchestrator {
188
189
  await custody.settleUnannounced();
189
190
  }
190
191
  }
191
- async resolveReviewers(cwd, runAuthPreference, onIgnoredSetting) {
192
+ async resolveReviewers(run, onIgnoredSetting) {
192
193
  if (this.deps.reviewers)
193
194
  return this.deps.reviewers;
194
- if (this.deps.reviewerPanel && this.deps.reviewerPanel.length > 0) {
195
- return this.resolveExplicitReviewerPanel(cwd, this.deps.reviewerPanel, runAuthPreference, onIgnoredSetting);
196
- }
197
- return resolveAutoReviewerPanel({
195
+ const { repoRoot: cwd, authPreference, signal } = run;
196
+ const panelDeps = {
198
197
  cwd,
199
198
  registry: this.deps.registry,
200
199
  harnessSettings: this.config(cwd)?.global.harnesses ?? {},
201
- authPreferenceFor: (id) => this.authPreferenceForHarness(cwd, id, runAuthPreference),
202
- resolveReviewerProfile: reviewerProfileResolver(this.credentials, cwd),
200
+ authPreferenceFor: (id) => this.authPreferenceForHarness(cwd, id, authPreference),
201
+ resolveReviewerProfile: reviewerProfileResolver(this.credentials, cwd, signal),
203
202
  onIgnoredSetting,
204
- }, { reviewerModels: this.deps.reviewerModels, reviewerEfforts: this.deps.reviewerEfforts });
203
+ };
204
+ if (this.deps.reviewerPanel && this.deps.reviewerPanel.length > 0)
205
+ return resolveExplicitReviewerPanel(panelDeps, this.deps.reviewerPanel);
206
+ return resolveAutoReviewerPanel(panelDeps, {
207
+ reviewerModels: this.deps.reviewerModels,
208
+ reviewerEfforts: this.deps.reviewerEfforts,
209
+ });
205
210
  }
206
211
  /**
207
212
  * Resolve reviewers INSIDE a strategy, after run-dir creation: an explicit
@@ -217,7 +222,7 @@ export class Orchestrator {
217
222
  // Reviewer knobs a panel omitted or dropped → ignored-settings channel (QA-070):
218
223
  const warn = (d) => void log.emit("review.preflight", { ignored_settings: [d] });
219
224
  const config = this.config(input.repoRoot).global;
220
- const reviewers = await this.resolveReviewers(input.repoRoot, input.authPreference, warn);
225
+ const reviewers = await this.resolveReviewers(input, warn);
221
226
  return {
222
227
  reviewers: await prepareReviewerProcessing(withReviewerPromptCacheTtl(reviewers, input.promptCacheTtl, warn), input, config, input.paidBudget ?? this.deps.paidBudget ?? config.budget.paid_budget_per_run),
223
228
  };
@@ -256,16 +261,6 @@ export class Orchestrator {
256
261
  };
257
262
  }
258
263
  }
259
- async resolveExplicitReviewerPanel(cwd, panel, runAuthPreference, onIgnoredSetting) {
260
- return resolveExplicitReviewerPanel({
261
- cwd,
262
- registry: this.deps.registry,
263
- harnessSettings: this.config(cwd)?.global.harnesses ?? {},
264
- authPreferenceFor: (id) => this.authPreferenceForHarness(cwd, id, runAuthPreference),
265
- resolveReviewerProfile: reviewerProfileResolver(this.credentials, cwd),
266
- onIgnoredSetting,
267
- }, panel);
268
- }
269
264
  authPreferenceForHarness(repoRoot, harnessId, runAuthPreference) {
270
265
  const cfg = this.config(repoRoot)?.global;
271
266
  return effectiveAuthPreference(runAuthPreference, cfg?.harnesses?.[harnessId]?.auth_preference, cfg?.routing?.auth_preference);
@@ -587,6 +582,11 @@ export class Orchestrator {
587
582
  dropLane(id, "access", `${id} (cannot apply execution.auxiliaryWritableRoots under ${requiredAccess})`);
588
583
  continue;
589
584
  }
585
+ const readRefusal = this.requestRequirements.projectReadRefusal(intent, input, status);
586
+ if (readRefusal) {
587
+ dropLane(id, "capability", readRefusal);
588
+ continue;
589
+ }
590
590
  const reason = status.reasons.length > 0 ? `: ${status.reasons.join("; ")}` : "";
591
591
  if (status.enabledIntents.includes(intent) && accessSupported) {
592
592
  pool.push({
@@ -720,7 +720,7 @@ export class Orchestrator {
720
720
  throw primaryQuotaRefusal.error;
721
721
  throw new HarnessUnavailableError(`no harness remains eligible for '${intent}' after budget and quota routing`);
722
722
  }
723
- emitPrimaryDivergence(log, input.primaryHarness, ordered, pool, dropped, browserOmissions);
723
+ emitPrimaryDivergence(log, input.primaryHarness, ordered, pool, dropped, browserOmissions, statusById);
724
724
  const n = input.n ?? ordered.length;
725
725
  const selectionOrder = ordered;
726
726
  const out = [];
@@ -1268,7 +1268,6 @@ export class Orchestrator {
1268
1268
  artifactRoot: join(paths.attemptsDir, attemptId),
1269
1269
  observedPaths: [...observedPaths],
1270
1270
  }));
1271
- const rawContextPacket = await rawContextForEnvelope(routed.implementationTransport, envelope);
1272
1271
  const sessionFields = runInput
1273
1272
  ? await this.sessionSpecFields(runInput, adapter.id, knobs.model, log, routed.authRouteEstimate, routed.quotaAdmission)
1274
1273
  : undefined;
@@ -1334,11 +1333,20 @@ export class Orchestrator {
1334
1333
  ? { resume_session_id: sessionFields.resume_session_id }
1335
1334
  : {}),
1336
1335
  ...(scopedEnv ? { env: scopedEnv } : {}),
1337
- raw_context_packet: rawContextPacket,
1338
1336
  stream_deltas: streamDeltas,
1339
1337
  });
1340
1338
  if (runInput)
1341
1339
  this.credentials.stampProfileSelection(spec, runInput, adapter.id);
1340
+ // #147: bound to the project's context config; each try's dispatch sizes its own
1341
+ // packet for THIS request from the catalog read the gate makes for that try.
1342
+ const { context } = this.projectConfig(contract.repo.root);
1343
+ const sizeRawContext = rawContextSizer(routed.implementationTransport, envelope, context);
1344
+ let rawContextPacket = null;
1345
+ const dispatch = sizeRawContext
1346
+ ? {
1347
+ rawContext: async (capacity) => (rawContextPacket = await sizeRawContext(capacity)),
1348
+ }
1349
+ : {};
1342
1350
  bindProcessingAdmission(spec, ledger, processingLease.lease, adapter.id, attemptId, processingLease.onDenied, processingAdmission);
1343
1351
  // Profile-less processing keeps its existing route estimate. Named rows
1344
1352
  // receive their own verification at the shared physical-dispatch gate.
@@ -1369,7 +1377,7 @@ export class Orchestrator {
1369
1377
  // QA-024: emit the belt-failure disclosure event at most once per attempt.
1370
1378
  let beltFailureDisclosed = false;
1371
1379
  const errors = [];
1372
- let answer = new secretDiff.CountedAnswerAssembly();
1380
+ let answer = new AnswerAssembly();
1373
1381
  let currentCapture;
1374
1382
  const retryPolicy = transientRetryPolicy(this.config(contract.repo.root));
1375
1383
  // QA-024: the delegation belt is the ONLY engine-owned extra MCP server
@@ -1398,13 +1406,15 @@ export class Orchestrator {
1398
1406
  workOrder: prompt,
1399
1407
  firstPrompt: spec.prompt,
1400
1408
  poolEvidence,
1409
+ later: continuationPointer ? "checkpoint" : undefined, // repair/synthesis never re-plan
1410
+ explicitModel: modelHint !== undefined,
1401
1411
  }), { spec, continuation: runInput?.continuation, store, telemetry }));
1402
1412
  try {
1403
1413
  for (let nativeTry = 0; !signal?.aborted; nativeTry += 1) {
1404
1414
  currentCapture = undefined;
1405
1415
  // A3 per-try isolation: no output, progress marker or model proof crosses tries.
1406
1416
  if (nativeTry > 0) {
1407
- answer = new secretDiff.CountedAnswerAssembly();
1417
+ answer = new AnswerAssembly();
1408
1418
  beginNativeRetry(telemetry);
1409
1419
  }
1410
1420
  const clearFileBackedContext = stageFileBackedContext(envelope.worktree_path, fileBackedContext);
@@ -1422,8 +1432,9 @@ export class Orchestrator {
1422
1432
  const transientStart = telemetry.transientFailures.length;
1423
1433
  const rateLimitStart = telemetry.rateLimits.length;
1424
1434
  let rawPatch = null;
1435
+ rawContextPacket = null; // a patch binds only the packet its own try sent
1425
1436
  try {
1426
- const watched = withInactivityWatchdog(runModelGovernedRoute(routed, runSpec), {
1437
+ const watched = withInactivityWatchdog(runModelGovernedRoute(routed, runSpec, dispatch), {
1427
1438
  timeoutMs: inactivityMs,
1428
1439
  countsAsProgress: countsAsAgentProgress,
1429
1440
  onTimeout: () => {
@@ -1435,31 +1446,34 @@ export class Orchestrator {
1435
1446
  // terminal/restart when the timeout is disabled.
1436
1447
  isSuspended: () => (interaction?.pendingCount?.() ?? 0) > 0,
1437
1448
  suspensionVersion: () => interaction?.suspensionVersion?.() ?? 0,
1449
+ stopSignal: signal, // after Stop: drained events are recorded, never used
1438
1450
  });
1439
1451
  for await (const ev of watched) {
1440
- rawPatch = captureRawPatchEnvelope(rawContextPacket !== null, rawPatch, ev);
1452
+ // After Stop: stored evidence only, but file paths are still collected for the capture.
1453
+ const stopped = signal?.aborted === true;
1454
+ if (!stopped)
1455
+ rawPatch = captureRawPatchEnvelope(sizeRawContext !== null, rawPatch, ev);
1441
1456
  if (ev.type === "patch_produced")
1442
1457
  continue;
1443
- const safeEv = redactHarnessEvent(ev);
1444
1458
  if (directory)
1445
- observeDirectoryPaths(observedPaths, safeEv, envelope.worktree_path);
1446
- const capped = dropDeltaPastBudget(safeEv, deltaFlood, Orchestrator.MAX_DELTAS_PER_ATTEMPT, (t, p) => log?.emit(t, p), adapter.id, attemptId);
1447
- const display = !capped && safeEv.payload?.["buffered"] !== true;
1448
- log?.emit("harness.event", harnessEventPayload(adapter.id, attemptId, safeEv), display);
1459
+ observeDirectoryPaths(observedPaths, ev, envelope.worktree_path);
1460
+ const capped = dropDeltaPastBudget(ev, deltaFlood, Orchestrator.MAX_DELTAS_PER_ATTEMPT, (t, p) => log?.emit(t, p), adapter.id, attemptId);
1461
+ const display = !stopped && !capped && ev.payload?.["buffered"] !== true;
1462
+ log?.emit("harness.event", harnessEventPayload(adapter.id, attemptId, ev), display);
1449
1463
  if (signal?.aborted)
1450
- break;
1464
+ continue; // a live observer may itself have pressed Stop
1451
1465
  if (display)
1452
- safeInvoke(onHarnessEvent, safeEv);
1453
- observeAuthSwitch(log, adapter.id, attemptId, safeEv);
1454
- observeAttemptTelemetry(telemetry, safeEv);
1455
- const mismatch = continuity.observe(safeEv, runSpec, telemetry.outputMarkers);
1466
+ safeInvoke(onHarnessEvent, ev);
1467
+ observeAuthSwitch(log, adapter.id, attemptId, ev);
1468
+ observeAttemptTelemetry(telemetry, ev);
1469
+ const mismatch = continuity.observe(ev, runSpec, telemetry.outputMarkers);
1456
1470
  if (mismatch) {
1457
1471
  harnessErrored = true;
1458
1472
  errors.push(mismatch);
1459
1473
  break;
1460
1474
  }
1461
1475
  if (inPlaceEnvelope && continuity.mayPublishSession)
1462
- observeNativeSessionEvent(runInput, adapter.id, safeEv);
1476
+ observeNativeSessionEvent(runInput, adapter.id, ev);
1463
1477
  // QA-024: the injected delegation belt's MCP server reported a
1464
1478
  // terminal startup failure. Disclose it ONCE while live; recoverable
1465
1479
  // exact tool-result failures are evaluated at attempt finalization.
@@ -1476,10 +1490,10 @@ export class Orchestrator {
1476
1490
  reason: "mcp_server_failed_to_start",
1477
1491
  });
1478
1492
  }
1479
- emitPlanProgress((t, p) => log?.emit(t, p), adapter.id, attemptId, safeEv);
1480
- if (safeEv.type === "usage") {
1493
+ emitPlanProgress((t, p) => log?.emit(t, p), adapter.id, attemptId, ev);
1494
+ if (ev.type === "usage") {
1481
1495
  const usage = processAttemptUsage({
1482
- event: safeEv,
1496
+ event: ev,
1483
1497
  telemetry,
1484
1498
  harnessId: adapter.id,
1485
1499
  attemptId,
@@ -1497,19 +1511,19 @@ export class Orchestrator {
1497
1511
  break;
1498
1512
  }
1499
1513
  }
1500
- if (safeEv.type === "error") {
1514
+ if (ev.type === "error") {
1501
1515
  harnessErrored = true;
1502
- errors.push(redactSecrets(safeEv.error ?? safeEv.text ?? "harness emitted error"));
1516
+ errors.push(redactSecrets(ev.error ?? ev.text ?? "harness emitted error"));
1503
1517
  }
1504
1518
  // Capture assistant prose so an answer-only turn (no file changes) still
1505
1519
  // has an honest output artifact; a TYPED final message wins verbatim.
1506
- answer.observeCounted(ev, safeEv);
1520
+ answer.observe(ev);
1507
1521
  // Observe ALL budget/quota signals (one codex usage event carries
1508
1522
  // BOTH spend and quota); pressure disclosed once per attempt.
1509
- observeBudgetSignals(ledger, log, adapter.id, attemptId, safeEv, budgetSignalState);
1510
- this.deps.quotaEventSink?.(adapter.id, safeEv);
1523
+ observeBudgetSignals(ledger, log, adapter.id, attemptId, ev, budgetSignalState);
1524
+ this.deps.quotaEventSink?.(adapter.id, ev);
1511
1525
  }
1512
- if (rawContextPacket && !harnessErrored)
1526
+ if (sizeRawContext && !harnessErrored)
1513
1527
  await consumeRawPatchEnvelope({
1514
1528
  repoRoot: envelope.repo_root,
1515
1529
  worktreePath: envelope.worktree_path,
@@ -1525,8 +1539,7 @@ export class Orchestrator {
1525
1539
  // gate and required-actions read a typed category, not a bare boolean.
1526
1540
  harnessErrored = true;
1527
1541
  errors.push(safeErrorMessage(err));
1528
- if (err instanceof ProcessingBudgetAdmissionError ||
1529
- err instanceof RoutingPreflightError) {
1542
+ if (isDispatchRefusal(err)) {
1530
1543
  processingRefusal = err;
1531
1544
  break;
1532
1545
  }
@@ -1557,11 +1570,11 @@ export class Orchestrator {
1557
1570
  await captureDirectory();
1558
1571
  currentCapture = directory
1559
1572
  ? undefined
1560
- : await secretDiff.captureCandidateDiff(wsm, envelope);
1573
+ : await secretDiff.captureCandidateDiff(wsm, envelope, paths.root);
1561
1574
  const currentDiff = secretDiff.capturedDiff(currentCapture);
1562
1575
  const workspaceUnchanged = directory
1563
1576
  ? !isMutatingAccess(access) || directoryCapture.files?.noChanges === true
1564
- : currentDiff?.trim().length === 0;
1577
+ : secretDiff.captureUnchanged(currentCapture);
1565
1578
  const deliverableEmpty = workspaceUnchanged && answer.text().length === 0;
1566
1579
  const delivered = !harnessErrored && !deliverableEmpty && !signal?.aborted;
1567
1580
  this.credentials.noteTryServed(refusal, telemetry.outputMarkers, delivered);
@@ -1617,21 +1630,20 @@ export class Orchestrator {
1617
1630
  // D-16: answer.md persists only the deliverable. A3: an errored attempt
1618
1631
  // with no typed final contributes no answer material (acceptedTryOutput).
1619
1632
  const unwrapped = unwrapWorkReportEnvelope(acceptedTryOutput(answer, harnessErrored), workReportMode, { sideToolReport: telemetry.sideToolWorkReport ?? undefined });
1620
- const redacted = redactSecrets(unwrapped.deliverable);
1621
- // INV-062: the (already redacted) answer is kept whatever the patch holds.
1622
- const answerText = redacted.trim().length > 0 ? redacted : undefined;
1633
+ const answerText = unwrapped.deliverable.trim().length > 0 ? unwrapped.deliverable : undefined;
1623
1634
  if (directory)
1624
1635
  await captureDirectory();
1625
1636
  const captured = directory
1626
1637
  ? { diff: "", captureRefusal: directoryCapture.refusal }
1627
1638
  : await secretDiff.captureCandidateWorkspace({
1628
- ...{ wsm, envelope, inPlace: inPlaceEnvelope, projectRoot: store.repoRoot },
1629
- ...{ answerText, answerMatches: answer.secretLikeMatches() },
1639
+ ...{ wsm, envelope, inPlace: inPlaceEnvelope, contract },
1630
1640
  holder: { runId: paths.runId, runDir: paths.root },
1631
1641
  incompleteCapture: currentCapture?.captureIncomplete ? currentCapture : undefined,
1632
1642
  });
1633
- const { diff, captureRefusal } = captured;
1634
- const deliverableEvidence = diff.trim().length > 0 || Boolean(answerText) || directoryHasOutput(directoryCapture.files);
1643
+ const { captureRefusal } = captured;
1644
+ const deliverableEvidence = secretDiff.hasCandidateChanges(captured) ||
1645
+ Boolean(answerText) ||
1646
+ directoryHasOutput(directoryCapture.files);
1635
1647
  // Cancelled attempts skip gates: running a 600s-per-gate suite delays the ack
1636
1648
  // and burns compute on a result nobody will adopt. Diff/attempt.yaml
1637
1649
  // still land, so partial work stays inspectable.
@@ -1694,7 +1706,7 @@ export class Orchestrator {
1694
1706
  const errored = harnessErrored || webBlocked || captureRefusal !== undefined;
1695
1707
  const resumable = await continuity.finish({
1696
1708
  runSpec: spec,
1697
- errored,
1709
+ errored: harnessErrored || webBlocked, // a capture refusal is packaging, not work
1698
1710
  nonSuccess: !gatesPassed(gates),
1699
1711
  aborted: signal?.aborted === true,
1700
1712
  cancelReason: typeof signal?.reason === "string" ? signal.reason : null,
@@ -1716,14 +1728,14 @@ export class Orchestrator {
1716
1728
  aborted: signal?.aborted === true,
1717
1729
  authMode: telemetry.authMode,
1718
1730
  });
1719
- const producedFiles = AC.withAttemptFailureCost(() => writeCandidateAttemptArtifacts({
1731
+ const { produced: producedFiles, omitted: producedOmitted } = AC.withAttemptFailureCost(() => writeCandidateAttemptArtifacts({
1720
1732
  store,
1721
1733
  attemptDir,
1722
1734
  worktreePath: envelope.worktree_path,
1723
1735
  artifactRelativeDir,
1724
- ...{ diff, persistedDiff: captured.persistedDiff },
1736
+ ...secretDiff.attemptPatchInput(captured),
1725
1737
  persistPatch: !directory && captureRefusal === undefined && isMutatingAccess(access),
1726
- persistProducedMedia: !directory && captureRefusal === undefined,
1738
+ persistProducedMedia: !directory,
1727
1739
  answerText,
1728
1740
  record: {
1729
1741
  attempt_id: attemptId,
@@ -1773,6 +1785,7 @@ export class Orchestrator {
1773
1785
  reviewCwd: envelope.worktree_path,
1774
1786
  baseSha: envelope.base_sha ?? undefined,
1775
1787
  producedFiles,
1788
+ ...(producedOmitted.length ? { producedOmitted } : {}),
1776
1789
  gates,
1777
1790
  cost,
1778
1791
  errored,
@@ -1787,7 +1800,7 @@ export class Orchestrator {
1787
1800
  : {}),
1788
1801
  outcomeClass: finalized.outcomeClass,
1789
1802
  applied,
1790
- ...continuity.terminalFacts(resumable),
1803
+ ...continuity.terminalFacts(resumable, captureRefusal !== undefined),
1791
1804
  };
1792
1805
  }
1793
1806
  continuityDeps(adapter, routed, contract, attemptId, paths, runInput, log, lane) {
@@ -1803,9 +1816,11 @@ export class Orchestrator {
1803
1816
  workspace: continuityWorkspace(lane.envelope, cwd),
1804
1817
  workOrder: lane.workOrder,
1805
1818
  firstPrompt: lane.firstPrompt,
1819
+ later: lane.later,
1820
+ explicitModel: lane.explicitModel,
1806
1821
  registry: this.config(repoRoot)?.global.credential_profiles ?? [],
1807
1822
  policy: this.credentials.profilePolicy(repoRoot, adapter.id),
1808
- snapshots: this.deps.quotaSnapshots?.() ?? [],
1823
+ quotaSnapshots: () => this.deps.quotaSnapshots?.() ?? [],
1809
1824
  retryPolicy: transientRetryPolicy(this.config(repoRoot)),
1810
1825
  pinned: runInput?.credentialProfileId != null, // D-U6: a pin never rotates
1811
1826
  defaultRouteWasVendorNative: routed.authRouteEstimate === "local_session",
@@ -1860,7 +1875,9 @@ export class Orchestrator {
1860
1875
  // repoRoot. Both the WorkspaceManager and the git boundary resolve against
1861
1876
  // this SINGLE root.
1862
1877
  const execRoot = this.execRootOf(input);
1863
- const wsm = new WorkspaceManager(execRoot);
1878
+ const projectRoot = input.repoRoot;
1879
+ const wsm = new WorkspaceManager(execRoot, { projectRoot });
1880
+ const anchors = secretDiff.revertAnchors(execRoot, projectRoot);
1864
1881
  store.writeYaml(join(paths.contextDir, "task.yaml"), contract);
1865
1882
  log.emit("task.contract.created", { task_contract_hash: hashJson(contract) });
1866
1883
  // Write modes need a git boundary for worktree isolation and honest diffs.
@@ -1908,7 +1925,7 @@ export class Orchestrator {
1908
1925
  const reviewDir = join(paths.root, "review-evidence");
1909
1926
  if (contract.review_requested !== false)
1910
1927
  writeEvidencePacket(reviewDir, {
1911
- userIntent: redactSecrets(input.prompt),
1928
+ userIntent: input.prompt,
1912
1929
  diff: "(per-candidate diffs are supplied to reviewers individually)\n",
1913
1930
  tests: renderTestsEvidence(contract),
1914
1931
  });
@@ -2125,7 +2142,7 @@ export class Orchestrator {
2125
2142
  harness: adapter.id,
2126
2143
  profileId: input.credentialProfileId ?? null,
2127
2144
  priorPrompt: input.prompt,
2128
- priorOutput: run.answerText ?? run.persistedDiff ?? run.diff ?? "",
2145
+ priorOutput: run.answerText ?? run.diff ?? "",
2129
2146
  }));
2130
2147
  // Reserve the continuation lease BEFORE any disclosure: a denied lease
2131
2148
  // must never emit run.continuation (which claims a continuation
@@ -2287,6 +2304,7 @@ export class Orchestrator {
2287
2304
  requestedSingleCandidate &&
2288
2305
  runs[0]?.reviewCwd === execRoot,
2289
2306
  execRoot,
2307
+ projectRoot: input.repoRoot,
2290
2308
  preTurnSha,
2291
2309
  postTurnSha: earlyPostTurnSha,
2292
2310
  }
@@ -2321,6 +2339,7 @@ export class Orchestrator {
2321
2339
  await disposeReviewEnvelopes();
2322
2340
  const failedOutput = {
2323
2341
  ...{ store, log, paths, execRoot, preTurnSha, taskId, mode, run: failedDelegation },
2342
+ projectRoot: input.repoRoot,
2324
2343
  live: input.inPlace === true &&
2325
2344
  input.workspaceKind !== "directory" &&
2326
2345
  failedDelegation.reviewCwd === execRoot,
@@ -2376,7 +2395,7 @@ export class Orchestrator {
2376
2395
  // real verification). Compute the reviewable set first and emit a typed
2377
2396
  // `review.skipped` when nothing is reviewable, so every start has a matching
2378
2397
  // terminal and the no-diff path records `not_run` consistently.
2379
- const reviewableRuns = workingRuns.filter((r) => r.files ? r.files.noChanges !== true : r.diff.trim().length > 0);
2398
+ const reviewableRuns = workingRuns.filter((r) => secretDiff.hasCandidateChanges(r));
2380
2399
  const configuredFamilies = new Set(reviewers.map((r) => r.providerFamily)).size;
2381
2400
  if (reviewableRuns.length === 0 || reviewers.length === 0) {
2382
2401
  log.emit("review.skipped", {
@@ -2407,18 +2426,15 @@ export class Orchestrator {
2407
2426
  evidences = await this.reviewRuns(workingRuns, reviewers, reviewVerified, reviewDir, input.repoRoot, contract, store, paths, log, ledger, taskId, input.signal, this.reservationEstimateUsd(input), false, input.runScopeMarker);
2408
2427
  }
2409
2428
  catch (err) {
2410
- // Review preflight/evidence failures end TERMINALLY with artifacts —
2411
- // never as an escaped throw that orphans the run dir.
2412
- return failAfterCandidates(raceTerminal, "review", err);
2413
- }
2414
- finally {
2415
- // Review preflight failures must not leak candidate worktrees.
2429
+ // Review failures end TERMINALLY with artifacts and leak no candidate worktree.
2416
2430
  await disposeReviewEnvelopes();
2431
+ return failAfterCandidates(raceTerminal, "review", err);
2417
2432
  }
2418
2433
  if (input.signal?.aborted) {
2434
+ await disposeReviewEnvelopes();
2419
2435
  return cancelledRaceResult();
2420
2436
  }
2421
- // Synthesis: if worthwhile, run a synthesizer as a NEW, re-checked candidate.
2437
+ // Synthesis, if worthwhile: a NEW re-checked candidate; workspaces stay until it ends (#535).
2422
2438
  const synth = mutatingRun
2423
2439
  ? decideSynthesis(evidences, input.synthesis ?? "auto")
2424
2440
  : {
@@ -2426,86 +2442,95 @@ export class Orchestrator {
2426
2442
  reason: "readonly access has no write-backed synthesis lifecycle",
2427
2443
  sources: evidences.map((evidence) => evidence.attemptId),
2428
2444
  };
2429
- store.writeYaml(join(paths.arbitrationDir, "synthesis.yaml"), synth);
2430
- log.emit("synthesis.started", { synthesize: synth.synthesize, reason: synth.reason });
2431
- if (synth.synthesize && !budgetStopped) {
2432
- const synthRouted = adapters[0];
2433
- const lease = ledger.reserve({
2434
- taskId,
2435
- attemptId: "synth",
2436
- intent: "synthesize",
2437
- harnessId: synthRouted.adapter.id,
2438
- cost: processingAttemptCostEvidence(synthRouted.adapter.id, "synth", this.reservationEstimateUsd(input), this.routeBillingKnowledge(input, synthRouted.adapter.id, synthRouted), synthRouted.processing),
2439
- });
2440
- if (lease.granted) {
2441
- let envelope;
2442
- const synthTried = new PoolAttemptEvidence(synthRouted.adapter.id, "synth");
2443
- try {
2444
- const plan = buildSynthesisPlan(evidences);
2445
- const synthesisInput = buildFileBackedSynthesisInput({
2446
- instructions: plan.instructions,
2447
- findings: plan.fixFindings,
2448
- candidates: workingRuns,
2449
- });
2450
- const synthAdapter = synthRouted.adapter;
2451
- // Disclose against the PER-ROUTE policy (per-harness web defaults
2452
- // included), exactly like the candidate slots do.
2453
- const synthKnobs = this.routeSpecKnobs(synthRouted, contract, undefined, input.effort);
2454
- const effectiveWeb = this.discloseWebUpgrade(log, synthRouted, synthKnobs.webPolicy, "synth");
2455
- envelope = await wsm.create({
2456
- taskId,
2457
- attemptId: "synth",
2458
- baseRef: contract.repo.base_ref,
2459
- dirtyPolicy: "snapshot",
2460
- accessProfile: candidateAccess,
2461
- workspaceKind: input.workspaceKind,
2462
- scopePaths: input.scopePaths,
2463
- });
2464
- const synthHome = this.harnessHomeFor(wsm, envelope, synthRouted, input);
2465
- const run = await this.runCandidateInEnvelope(synthRouted, envelope, "synth", "Synthesis", contract, synthesisInput.prompt, store, paths, wsm, ledger, {
2466
- lease: lease.lease,
2467
- onDenied: (denial) => {
2468
- budgetStopped = true;
2469
- budgetDenial ??= denial;
2470
- },
2471
- }, 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);
2472
- ledger.settle(lease.lease?.lease_id ?? "", attemptUsageCostSettlement(run.cost, run.costEstimated, run.attemptId, run.harnessId, run.telemetry.authMode, run.telemetry.usageCost));
2473
- reviewEnvelopes.push(envelope);
2474
- envelope = undefined;
2475
- // D-16 r8: only a WORKING synth is reviewed/adopted (same veto owner
2476
- // as the race lane); `runs` still records it for telemetry.
2477
- runs.push(run);
2445
+ try {
2446
+ store.writeYaml(join(paths.arbitrationDir, "synthesis.yaml"), synth);
2447
+ log.emit("synthesis.started", { synthesize: synth.synthesize, reason: synth.reason });
2448
+ if (synth.synthesize && !budgetStopped) {
2449
+ const synthRouted = adapters[0];
2450
+ const lease = ledger.reserve({
2451
+ taskId,
2452
+ attemptId: "synth",
2453
+ intent: "synthesize",
2454
+ harnessId: synthRouted.adapter.id,
2455
+ cost: processingAttemptCostEvidence(synthRouted.adapter.id, "synth", this.reservationEstimateUsd(input), this.routeBillingKnowledge(input, synthRouted.adapter.id, synthRouted), synthRouted.processing),
2456
+ });
2457
+ if (lease.granted) {
2458
+ let envelope;
2459
+ const synthTried = new PoolAttemptEvidence(synthRouted.adapter.id, "synth");
2460
+ let disposeSaved;
2478
2461
  try {
2479
- if (isWorkingCandidate(run)) {
2480
- const synthEvidence = await this.reviewRuns([run], reviewers, reviewVerified, reviewDir, input.repoRoot, contract, store, paths, log, ledger, taskId, input.signal, this.reservationEstimateUsd(input), false, input.runScopeMarker);
2481
- evidences.push(...synthEvidence);
2482
- workingRuns.push(run);
2462
+ const plan = buildSynthesisPlan(evidences);
2463
+ const saved = await savedForSynthesis(workingRuns, paths.root, contract.repo.root);
2464
+ disposeSaved = saved.dispose;
2465
+ const synthesisInput = buildFileBackedSynthesisInput({
2466
+ instructions: plan.instructions,
2467
+ findings: plan.fixFindings,
2468
+ candidates: saved.candidates,
2469
+ });
2470
+ const synthAdapter = synthRouted.adapter;
2471
+ // Disclose against the PER-ROUTE policy (per-harness web defaults
2472
+ // included), exactly like the candidate slots do.
2473
+ const synthKnobs = this.routeSpecKnobs(synthRouted, contract, undefined, input.effort);
2474
+ const effectiveWeb = this.discloseWebUpgrade(log, synthRouted, synthKnobs.webPolicy, "synth");
2475
+ envelope = await wsm.create({
2476
+ taskId,
2477
+ attemptId: "synth",
2478
+ baseRef: contract.repo.base_ref,
2479
+ dirtyPolicy: "snapshot",
2480
+ accessProfile: candidateAccess,
2481
+ workspaceKind: input.workspaceKind,
2482
+ scopePaths: input.scopePaths,
2483
+ });
2484
+ const synthHome = this.harnessHomeFor(wsm, envelope, synthRouted, input);
2485
+ const run = await this.runCandidateInEnvelope(synthRouted, envelope, "synth", "Synthesis", contract, synthesisInput.prompt, store, paths, wsm, ledger, {
2486
+ lease: lease.lease,
2487
+ onDenied: (denial) => {
2488
+ budgetStopped = true;
2489
+ budgetDenial ??= denial;
2490
+ },
2491
+ }, 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);
2492
+ ledger.settle(lease.lease?.lease_id ?? "", attemptUsageCostSettlement(run.cost, run.costEstimated, run.attemptId, run.harnessId, run.telemetry.authMode, run.telemetry.usageCost));
2493
+ reviewEnvelopes.push(envelope);
2494
+ envelope = undefined;
2495
+ // D-16 r8: only a WORKING synth is reviewed/adopted (same veto owner
2496
+ // as the race lane); `runs` still records it for telemetry.
2497
+ runs.push(run);
2498
+ try {
2499
+ if (isWorkingCandidate(run)) {
2500
+ const synthEvidence = await this.reviewRuns([run], reviewers, reviewVerified, reviewDir, input.repoRoot, contract, store, paths, log, ledger, taskId, input.signal, this.reservationEstimateUsd(input), false, input.runScopeMarker);
2501
+ evidences.push(...synthEvidence);
2502
+ workingRuns.push(run);
2503
+ }
2504
+ if (input.signal?.aborted) {
2505
+ return cancelledResult(log, runId, taskId, mode, paths.root, candidateStatuses(runs), raceTerminal.writeTelemetry, ledger.spend(), input.signal, store, undefined, undefined, history());
2506
+ }
2483
2507
  }
2484
- if (input.signal?.aborted) {
2485
- return cancelledResult(log, runId, taskId, mode, paths.root, candidateStatuses(runs), raceTerminal.writeTelemetry, ledger.spend(), input.signal, store, undefined, undefined, history());
2508
+ finally {
2509
+ await disposeReviewEnvelopes();
2486
2510
  }
2487
2511
  }
2512
+ catch (err) {
2513
+ // Not yet in `runs`: the run goes on, so its tries' accounts stay in the history.
2514
+ if (!runs.some((run) => run.attemptId === "synth"))
2515
+ thrownSynthesis = { poolAttempts: synthTried.snapshot() };
2516
+ ledger.settle(lease.lease?.lease_id ?? "", AC.attemptFailureCost(err, "synthesis-error").settlement);
2517
+ log.emit("harness.completed", {
2518
+ attempt_id: "synth",
2519
+ status: "failed",
2520
+ error: safeErrorMessage(err),
2521
+ });
2522
+ }
2488
2523
  finally {
2489
- await disposeReviewEnvelopes();
2524
+ await disposeSaved?.();
2525
+ if (envelope)
2526
+ await wsm.dispose(envelope);
2490
2527
  }
2491
2528
  }
2492
- catch (err) {
2493
- // Not yet in `runs`: the run goes on, so its tries' accounts stay in the history.
2494
- if (!runs.some((run) => run.attemptId === "synth"))
2495
- thrownSynthesis = { poolAttempts: synthTried.snapshot() };
2496
- ledger.settle(lease.lease?.lease_id ?? "", AC.attemptFailureCost(err, "synthesis-error").settlement);
2497
- log.emit("harness.completed", {
2498
- attempt_id: "synth",
2499
- status: "failed",
2500
- error: safeErrorMessage(err),
2501
- });
2502
- }
2503
- finally {
2504
- if (envelope)
2505
- await wsm.dispose(envelope);
2506
- }
2507
2529
  }
2508
2530
  }
2531
+ finally {
2532
+ await disposeReviewEnvelopes();
2533
+ }
2509
2534
  if (input.signal?.aborted) {
2510
2535
  return cancelledRaceResult();
2511
2536
  }
@@ -2578,7 +2603,7 @@ export class Orchestrator {
2578
2603
  winnerRun &&
2579
2604
  !inPlaceWinner &&
2580
2605
  !deferredRaceVerify &&
2581
- (winnerRun.files ? winnerRun.files.noChanges !== true : winnerRun.diff.trim().length > 0) &&
2606
+ secretDiff.hasCandidateChanges(winnerRun) &&
2582
2607
  facts.lifecycle === "succeeded" &&
2583
2608
  facts.review !== "blocked" &&
2584
2609
  !input.signal?.aborted) {
@@ -2667,8 +2692,8 @@ export class Orchestrator {
2667
2692
  else if (mutatingRun) {
2668
2693
  const savedPatch = secretDiff.persistFinalPatch(store, paths.finalDir, winnerRun);
2669
2694
  const patchSha256 = savedPatch.patchSha256;
2670
- const wstats = diffStats(winnerRun.diff);
2671
- const hasDiff = winnerRun.diff.trim().length > 0;
2695
+ const wstats = secretDiff.resultDiffStats(winnerRun);
2696
+ const hasDiff = secretDiff.hasCandidateChanges(winnerRun);
2672
2697
  const blockers = winnerEvidence
2673
2698
  ? winnerEvidence.findings.filter((f) => isBlocking(f)).length
2674
2699
  : 0;
@@ -2693,7 +2718,7 @@ export class Orchestrator {
2693
2718
  }
2694
2719
  else if (adoptable) {
2695
2720
  // Protected apply preserves the live tree or reports tree_mutated.
2696
- const applied = await verifyAndDeliver(execRoot, winnerRun.diff, { mode: "apply", protectedApply: true }, gateSpecsFromContract(contract, input.runScopeMarker), (freshVerify) => {
2721
+ const applied = await verifyAndDeliver(execRoot, secretDiff.deliverableOf(winnerRun), { mode: "apply", protectedApply: true, projectRoot, owner: paths.root }, gateSpecsFromContract(contract, input.runScopeMarker), (freshVerify) => {
2697
2722
  finalVerify = freshVerify;
2698
2723
  return finalVerifyBlocks(freshVerify)
2699
2724
  ? (freshVerify.reason ?? "final verify failed before race adoption")
@@ -2710,17 +2735,15 @@ export class Orchestrator {
2710
2735
  patch_sha256: patchSha256,
2711
2736
  winner: winnerRun.attemptId,
2712
2737
  });
2713
- try {
2714
- postTurnSha = await snapshotTree(execRoot);
2715
- }
2716
- catch {
2717
- postTurnSha = null;
2718
- }
2719
- revertAnchorId = createRevertAnchorFromPatchOrNull(execRoot, winnerRun.diff);
2738
+ postTurnSha = await snapshotTree(execRoot).catch(() => null);
2739
+ revertAnchorId = await anchors.after(winnerRun, applied);
2720
2740
  }
2721
2741
  else {
2722
- adopted = false;
2723
- applyState = "not_applied";
2742
+ // A write that could not be taken back stays live and revertable (INV-114).
2743
+ adopted = applied.treeMutated === true;
2744
+ applyState = adopted ? "applied_review_blocked" : "not_applied";
2745
+ if (adopted)
2746
+ revertAnchorId = await anchors.after(winnerRun, applied);
2724
2747
  deliveryFailureReason = applied.detail ?? "race adoption delivery was refused";
2725
2748
  facts = { ...facts, checks: "failed", reason: "checks_failed" };
2726
2749
  if (finalVerifyBlocks(finalVerify))
@@ -2743,7 +2766,7 @@ export class Orchestrator {
2743
2766
  deliveryReceiptPath: raceDeliveryReceipt || directoryDeliveryReceipt ? "final/delivery_receipt.yaml" : null,
2744
2767
  });
2745
2768
  if (inPlaceWinner && requestedSingleCandidate && adopted === true) {
2746
- revertAnchorId = await createRevertAnchorOrNull(execRoot, preTurnSha, postTurnSha);
2769
+ revertAnchorId = await anchors.turn(preTurnSha, postTurnSha);
2747
2770
  }
2748
2771
  store.writeYaml(join(paths.finalDir, "work_product.yaml"), {
2749
2772
  id: newId("wp"),
@@ -2779,7 +2802,7 @@ export class Orchestrator {
2779
2802
  },
2780
2803
  });
2781
2804
  }
2782
- store.writeText(join(paths.finalDir, "summary.md"), withPoolAttempts(renderSummary(runId, mode, { ...result.decision, facts }, evidences, synth.reason, actualReviewVerified, secretDiff.secretLikeSummaryLine(winnerRun.secretLike)), racePool()));
2805
+ store.writeText(join(paths.finalDir, "summary.md"), withPoolAttempts(renderSummary(runId, mode, { ...result.decision, facts }, evidences, synth.reason, actualReviewVerified, secretDiff.resultSummaryLines(winnerRun)), racePool()));
2783
2806
  // summary.md is a DIAGNOSTIC artifact only (V8/PLAN addendum 2): it no
2784
2807
  // longer carries primary-output authority. A clean applyable success or a
2785
2808
  // winner answer still marks it ready for legacy INV-116 ordering; any
@@ -3033,7 +3056,8 @@ export class Orchestrator {
3033
3056
  // local `execRoot`; this path previously ensured the boundary on repoRoot,
3034
3057
  // which for an isolated thread is the project, not the mutated worktree.
3035
3058
  const execRoot = this.execRootOf(input);
3036
- const wsm = new WorkspaceManager(execRoot);
3059
+ const wsm = new WorkspaceManager(execRoot, { projectRoot: input.repoRoot });
3060
+ const anchors = secretDiff.revertAnchors(execRoot, input.repoRoot);
3037
3061
  const readiness = new ReadinessLedger();
3038
3062
  store.writeYaml(join(paths.contextDir, "task.yaml"), contract);
3039
3063
  // Legacy Git envelopes need their boundary; explicit directory execution
@@ -3063,7 +3087,7 @@ export class Orchestrator {
3063
3087
  const reviewDir = join(paths.root, "review-evidence");
3064
3088
  if (contract.review_requested !== false)
3065
3089
  writeEvidencePacket(reviewDir, {
3066
- userIntent: redactSecrets(input.prompt),
3090
+ userIntent: input.prompt,
3067
3091
  diff: "(per-attempt)\n",
3068
3092
  tests: renderTestsEvidence(contract),
3069
3093
  });
@@ -3257,7 +3281,7 @@ export class Orchestrator {
3257
3281
  exhausted = true;
3258
3282
  processingBudgetDenial ??= denial;
3259
3283
  },
3260
- }, convergenceAccess, input.onHarnessEvent, input.signal, undefined, input.effort, "repair", log, effectiveWeb, interactionChannelFor(input, log, runId, taskId, attemptId, adapter.id, routed.supportsInteractive), (streamedUsd) => {
3284
+ }, convergenceAccess, input.onHarnessEvent, input.signal, undefined, input.effort, this.requestRequirements.convergenceIntent(routed, this.candidateIntent(input)), log, effectiveWeb, interactionChannelFor(input, log, runId, taskId, attemptId, adapter.id, routed.supportsInteractive), (streamedUsd) => {
3261
3285
  ledger.updateHold(lease.lease?.lease_id ?? "", streamedUsd);
3262
3286
  return ledger.tier() === "hard";
3263
3287
  }, input, true, // convergence runs one candidate: live deltas on (W-C4)
@@ -3350,6 +3374,7 @@ export class Orchestrator {
3350
3374
  const failure = delegateFailure.candidateFailureTerminal(run, "convergence");
3351
3375
  await delegateFailure.persistFailedCandidateOutput({
3352
3376
  ...{ store, log, paths, execRoot, preTurnSha, taskId, mode, run },
3377
+ projectRoot: input.repoRoot,
3353
3378
  live: input.inPlace === true && input.workspaceKind !== "directory",
3354
3379
  kind: input.create === true ? "new_repo" : "patch",
3355
3380
  attempts: attempt,
@@ -3398,7 +3423,8 @@ export class Orchestrator {
3398
3423
  actualReviewVerified = evidence.reviewVerified ?? false;
3399
3424
  lastDiffStable = run.files
3400
3425
  ? await directoryCandidateStable(wsm, envelope, run.files)
3401
- : sha256(await wsm.diff(envelope)) === sha256(run.diff);
3426
+ : secretDiff.captureIdentity(await wsm.captureDiff(envelope, paths.root)) ===
3427
+ secretDiff.captureIdentity(run);
3402
3428
  const evaluated = evaluateConvergence({
3403
3429
  predicate: contract.convergence,
3404
3430
  gates: evidence.gates,
@@ -3448,7 +3474,7 @@ export class Orchestrator {
3448
3474
  ? run.files.manifestSha256
3449
3475
  : input.workspaceKind === "directory"
3450
3476
  ? null
3451
- : sha256(run.diff);
3477
+ : secretDiff.captureIdentity(run);
3452
3478
  if (requiredGateFailing && diffHash !== null && diffHash === lastFailingGateDiffHash) {
3453
3479
  sameFailingGateDiffs += 1;
3454
3480
  }
@@ -3461,7 +3487,7 @@ export class Orchestrator {
3461
3487
  const failedGateIds = run.gates
3462
3488
  .filter((g) => g.required && g.status !== "passed")
3463
3489
  .map((g) => g.id);
3464
- stuckNoProgressReason = `same candidate diff (${diffHash}) produced ${sameFailingGateDiffs} consecutive failing required gate round(s): ${failedGateIds.join(", ") || "unknown gate"}`;
3490
+ stuckNoProgressReason = `same candidate result (${diffHash}) produced ${sameFailingGateDiffs} consecutive failing required gate round(s): ${failedGateIds.join(", ") || "unknown gate"}`;
3465
3491
  log.emit("finding.revalidated", {
3466
3492
  attempt_id: attemptId,
3467
3493
  stuck_no_progress: true,
@@ -3599,7 +3625,7 @@ export class Orchestrator {
3599
3625
  let convFinalVerify = null;
3600
3626
  if (input.inPlace !== true &&
3601
3627
  lastRun &&
3602
- (lastRun.files ? lastRun.files.noChanges !== true : lastRun.diff.trim().length > 0) &&
3628
+ secretDiff.hasCandidateChanges(lastRun) &&
3603
3629
  facts.lifecycle === "succeeded" &&
3604
3630
  facts.review !== "blocked" &&
3605
3631
  !input.signal?.aborted) {
@@ -3635,6 +3661,7 @@ export class Orchestrator {
3635
3661
  // An incomplete capture has no exact patch: never publish an empty one.
3636
3662
  await delegateFailure.persistFailedCandidateOutput({
3637
3663
  ...{ store, log, paths, execRoot, preTurnSha, taskId, mode, facts, run: lastRun },
3664
+ projectRoot: input.repoRoot,
3638
3665
  live: input.inPlace === true && input.workspaceKind !== "directory",
3639
3666
  kind: input.create === true ? "new_repo" : "patch",
3640
3667
  attempts: attempt,
@@ -3646,16 +3673,14 @@ export class Orchestrator {
3646
3673
  // Honest apply-state (parity with runRace single-candidate in-place): a
3647
3674
  // convergence run with inPlace mutated the live tree directly across its
3648
3675
  // attempts, so it is "applied" even when review blocked (Revert offered).
3649
- const convHasDiff = lastRun.diff.trim().length > 0;
3676
+ const convHasDiff = secretDiff.hasCandidateChanges(lastRun);
3650
3677
  const convAdopted = input.inPlace === true && convHasDiff ? true : null;
3651
3678
  const convApplyState = convAdopted === true
3652
3679
  ? convAdoptable
3653
3680
  ? "applied"
3654
3681
  : "applied_review_blocked"
3655
3682
  : "not_applied";
3656
- const revertAnchorId = convAdopted === true
3657
- ? await createRevertAnchorOrNull(execRoot, preTurnSha, lastPostTurnSha)
3658
- : null;
3683
+ const revertAnchorId = convAdopted === true ? await anchors.turn(preTurnSha, lastPostTurnSha) : null;
3659
3684
  store.writeYaml(join(paths.finalDir, "work_product.yaml"), {
3660
3685
  id: newId("wp"),
3661
3686
  kind: "patch",
@@ -3679,12 +3704,12 @@ export class Orchestrator {
3679
3704
  },
3680
3705
  });
3681
3706
  }
3682
- // INV-062: the answer is kept for every convergence result, patch or files.
3707
+ // The exact answer is kept for every convergence result, patch or files.
3683
3708
  if (lastRun.answerText) {
3684
3709
  store.writeText(join(paths.finalDir, "answer.md"), lastRun.answerText);
3685
3710
  log.emit("output.ready", { kind: "answer", path: "final/answer.md" });
3686
3711
  }
3687
- 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.summaryDisclosure(lastRun.secretLike)}`, convPool)}\n`);
3712
+ 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`);
3688
3713
  // Lifecycle invariant (all modes): output.ready precedes the terminal
3689
3714
  // event so a client that applied the terminal event has the output.
3690
3715
  log.emit("output.ready", {
@@ -3716,6 +3741,8 @@ export class Orchestrator {
3716
3741
  code: convBudgetMapping?.code ?? convDeclared?.code ?? null,
3717
3742
  resetsAt: convDeclared?.resetsAt ?? null,
3718
3743
  requestRefusal: convDeclared?.requestRefusal,
3744
+ quotaRefusal: convDeclared?.quotaRefusal,
3745
+ rawContextShortfall: convDeclared?.rawContextShortfall,
3719
3746
  harnessId: lastRun?.harnessId,
3720
3747
  attemptId: lastRun?.attemptId,
3721
3748
  runDir: paths.root,
@@ -4081,7 +4108,8 @@ export class Orchestrator {
4081
4108
  }
4082
4109
  const text = outcome.text ?? "(no output)";
4083
4110
  plans.push({ id: outcome.harnessId, text });
4084
- store.writeText(join(paths.root, "plans", `${outcome.harnessId}.md`), redactSecrets(text) + "\n");
4111
+ // The planner's own saved copy keeps its exact text (INV-146).
4112
+ store.writeText(join(paths.root, "plans", `${outcome.harnessId}.md`), text + "\n");
4085
4113
  // Solo planning (D31): the FIRST successful planner is the plan; later
4086
4114
  // pool members are a sequential fallback chain (ask parity), not
4087
4115
  // parallel co-authors. Council re-enables the multi-draft round.
@@ -4387,11 +4415,8 @@ export class Orchestrator {
4387
4415
  // carries the denial reason so a refusal discloses run.continuation.denied.
4388
4416
  onLaunch) => {
4389
4417
  const adapter = routed.adapter;
4390
- const attemptId = continuationPointer
4391
- ? `a${String(idx + 1).padStart(2, "0")}-cont`
4392
- : modelOverride
4393
- ? `a${String(idx + 1).padStart(2, "0")}-fb`
4394
- : `a${String(idx + 1).padStart(2, "0")}`;
4418
+ const suffix = continuationPointer ? "-cont" : modelOverride ? "-fb" : "";
4419
+ const attemptId = `a${String(idx + 1).padStart(2, "0")}${suffix}`;
4395
4420
  const budgetSignalState = { quotaPressureDisclosed: false };
4396
4421
  const lease = ledger.reserve({
4397
4422
  taskId,
@@ -4490,7 +4515,7 @@ export class Orchestrator {
4490
4515
  const readonlyWorkEnvelope = this.workReportEnvelopeFor(routed, contract, Boolean(reportInteraction));
4491
4516
  const readonlyWorkMode = applyWorkEnvelope(spec, readonlyWorkEnvelope);
4492
4517
  const attemptEventsPath = join(paths.attemptsDir, attemptId, "events.jsonl");
4493
- const answer = new secretDiff.CountedAnswerAssembly();
4518
+ const answer = new AnswerAssembly();
4494
4519
  const telemetry = createAttemptTelemetry(knobs.webPolicy, contract.external_context.web_required, effectiveWeb, [],
4495
4520
  // Requested-model capture so ask/audit route receipts detect a silent
4496
4521
  // model downgrade (typed model_mismatch), not just agent runs.
@@ -4555,6 +4580,8 @@ export class Orchestrator {
4555
4580
  workOrder: input.prompt,
4556
4581
  firstPrompt: spec.prompt,
4557
4582
  poolEvidence: tried,
4583
+ later: continuationPointer ? "checkpoint" : "fallback",
4584
+ explicitModel: modelOverride !== undefined,
4558
4585
  }), { spec, continuation: input.continuation, store, telemetry }));
4559
4586
  const onAbort = () => void adapter.cancel?.(activeSessionId)?.catch(() => { });
4560
4587
  if (input.signal) {
@@ -4574,7 +4601,7 @@ export class Orchestrator {
4574
4601
  // A3 per-try isolation (candidate-lane parity): a failed try's output,
4575
4602
  // progress markers and model proof never carry into the next try.
4576
4603
  if (nativeTry > 0) {
4577
- answer = new secretDiff.CountedAnswerAssembly();
4604
+ answer = new AnswerAssembly();
4578
4605
  beginNativeRetry(telemetry);
4579
4606
  }
4580
4607
  const runSpec = nativeTry === 0
@@ -4611,30 +4638,33 @@ export class Orchestrator {
4611
4638
  },
4612
4639
  isSuspended: () => (reportInteraction?.pendingCount?.() ?? 0) > 0,
4613
4640
  suspensionVersion: () => reportInteraction?.suspensionVersion?.() ?? 0,
4641
+ stopSignal: input.signal, // after Stop: drained events are recorded, never used
4614
4642
  });
4615
4643
  for await (const ev of watchedReport) {
4616
- const safeEv = redactHarnessEvent(ev);
4617
- if (safeEv.payload?.["buffered"] !== true)
4618
- safeInvoke(input.onHarnessEvent, safeEv);
4619
- observeAuthSwitch(log, adapter.id, attemptId, safeEv);
4620
- log.emit("harness.event", harnessEventPayload(adapter.id, attemptId, safeEv), safeEv.payload?.["buffered"] !== true);
4621
- appendLine(attemptEventsPath, JSON.stringify(safeEv));
4644
+ // A read after Stop is stored evidence only; a callback may itself press Stop.
4645
+ const shown = !input.signal?.aborted && ev.payload?.["buffered"] !== true;
4646
+ if (shown)
4647
+ safeInvoke(input.onHarnessEvent, ev);
4648
+ observeAuthSwitch(log, adapter.id, attemptId, ev);
4649
+ const live = shown && !input.signal?.aborted; // the callback may have pressed Stop
4650
+ log.emit("harness.event", harnessEventPayload(adapter.id, attemptId, ev), live);
4651
+ appendHarnessEvent(attemptEventsPath, ev);
4622
4652
  if (input.signal?.aborted)
4623
- break;
4624
- observeAttemptTelemetry(telemetry, safeEv);
4625
- const mismatch = continuity.observe(safeEv, runSpec, telemetry.outputMarkers);
4653
+ continue;
4654
+ observeAttemptTelemetry(telemetry, ev);
4655
+ const mismatch = continuity.observe(ev, runSpec, telemetry.outputMarkers);
4626
4656
  if (mismatch) {
4627
4657
  harnessError = mismatch;
4628
4658
  break;
4629
4659
  }
4630
4660
  if (laneRun && continuity.mayPublishSession)
4631
- observeNativeSessionEvent(input, adapter.id, safeEv);
4632
- emitPlanProgress((t, p) => log.emit(t, p), adapter.id, attemptId, safeEv);
4661
+ observeNativeSessionEvent(input, adapter.id, ev);
4662
+ emitPlanProgress((t, p) => log.emit(t, p), adapter.id, attemptId, ev);
4633
4663
  // read-only routes burn quota too (the orchestrate PLANNER is
4634
4664
  // the loudest) — same single owner as the agent loop.
4635
- observeBudgetSignals(ledger, log, adapter.id, attemptId, safeEv, budgetSignalState);
4636
- this.deps.quotaEventSink?.(adapter.id, safeEv);
4637
- const spend = observeReadonlySpend(safeEv, (t, p) => log.emit(t, p), adapter.id, attemptId);
4665
+ observeBudgetSignals(ledger, log, adapter.id, attemptId, ev, budgetSignalState);
4666
+ this.deps.quotaEventSink?.(adapter.id, ev);
4667
+ const spend = observeReadonlySpend(ev, (t, p) => log.emit(t, p), adapter.id, attemptId);
4638
4668
  cost += spend.costUsd;
4639
4669
  costEstimated ||= spend.estimated;
4640
4670
  const streamDenial = updateProcessingStreamHold(runSpec, telemetry.usageCost, ledger, lease.lease.lease_id, adapter.id, attemptId);
@@ -4647,17 +4677,14 @@ export class Orchestrator {
4647
4677
  break;
4648
4678
  }
4649
4679
  // A TYPED final message wins verbatim over joined narration.
4650
- answer.observeCounted(ev, safeEv);
4651
- if (safeEv.type === "error")
4652
- harnessError = safeEv.error
4653
- ? redactSecrets(safeEv.error)
4654
- : "harness emitted an error";
4680
+ answer.observe(ev);
4681
+ if (ev.type === "error")
4682
+ harnessError = ev.error ? redactSecrets(ev.error) : "harness emitted an error";
4655
4683
  }
4656
4684
  }
4657
4685
  catch (err) {
4658
4686
  harnessError = safeErrorMessage(err);
4659
- if (err instanceof ProcessingBudgetAdmissionError ||
4660
- err instanceof RoutingPreflightError) {
4687
+ if (isDispatchRefusal(err)) {
4661
4688
  processingRefusal = err;
4662
4689
  break;
4663
4690
  }
@@ -4732,7 +4759,7 @@ export class Orchestrator {
4732
4759
  // Trim symmetrically with the plan path: a whitespace-only answer is not
4733
4760
  // a delivered report (the final-artifact wrapper heading would otherwise
4734
4761
  // make it read as present content by construction).
4735
- const report = redactSecrets(roUnwrapped.deliverable).trim();
4762
+ const report = roUnwrapped.deliverable.trim();
4736
4763
  const unrecovered = unrecoveredToolErrors(telemetry);
4737
4764
  const webBlocked = webUnsatisfied(telemetry);
4738
4765
  const reportPresent = report.length > 0;
@@ -4818,7 +4845,6 @@ export class Orchestrator {
4818
4845
  harnessId: adapter.id,
4819
4846
  status: scoutInterrupted ? "failed" : "success",
4820
4847
  report: report || "(no output)",
4821
- answerMatches: answer.secretLikeMatches(),
4822
4848
  error: scoutInterrupted ? "context capacity exhausted before the scout completed" : null,
4823
4849
  telemetry,
4824
4850
  ...(scoutInterrupted ? { interrupted: true } : {}),
@@ -4985,7 +5011,8 @@ export class Orchestrator {
4985
5011
  harnessId: a.harnessId,
4986
5012
  status: a.status,
4987
5013
  }));
4988
- const cancelledTerminal = () => cancelledResult(log, runId, taskId, opts.mode, paths.root, candidateSummaries, () => this.writeRunTelemetry(store, paths, contract, runId, taskId, opts.mode, attemptTelemetries, null), ledger.spend(), input.signal, store, undefined, resumableOf(attempts, "cancelled"), attempts);
5014
+ const carried = () => (opts.deepScan ? attempts : carryingAttempts(attempts, paths.root));
5015
+ const cancelledTerminal = () => cancelledResult(log, runId, taskId, opts.mode, paths.root, candidateSummaries, () => this.writeRunTelemetry(store, paths, contract, runId, taskId, opts.mode, attemptTelemetries, null), ledger.spend(), input.signal, store, undefined, resumableOf(carried(), "cancelled"), attempts);
4989
5016
  if (input.signal?.aborted)
4990
5017
  return cancelledTerminal();
4991
5018
  const succeededReadonly = attempts.filter((a) => a.status === "success");
@@ -5007,9 +5034,8 @@ export class Orchestrator {
5007
5034
  // whole decision + reducer spawn lives in deepScanReducer.ts (its owner).
5008
5035
  let deepScanSynthesis = null;
5009
5036
  let reducedReport = null;
5010
- let answerMatches = succeeded.reduce((sum, a) => sum + (a.answerMatches ?? 0), 0);
5011
5037
  if (opts.deepScan) {
5012
- ({ deepScanSynthesis, reducedReport, answerMatches } = await resolveDeepScanSynthesis(this.deepScanReducerDeps(input, contract, log), {
5038
+ ({ deepScanSynthesis, reducedReport } = await resolveDeepScanSynthesis(this.deepScanReducerDeps(input, contract, log), {
5013
5039
  succeeded,
5014
5040
  adapters,
5015
5041
  budgetStopped,
@@ -5101,10 +5127,9 @@ export class Orchestrator {
5101
5127
  const harnessLabel = attempts
5102
5128
  .map((a) => `${a.attemptId}:${a.harnessId}:${a.status}`)
5103
5129
  .join(", ");
5104
- const secretLike = secretDiff.answerSecretLikeFinding(answerMatches);
5105
5130
  // A delivered report that still ends unsuccessfully or blocked keeps it and names every failed try.
5106
5131
  const reportPool = terminalPoolAttempts(terminalFacts, attempts);
5107
- store.writeText(join(paths.finalDir, "summary.md"), `# Run ${runId} (${opts.mode})\n\n- Harnesses: ${harnessLabel}\n- Lifecycle: ${terminalFacts.lifecycle}${terminalFacts.reason ? ` (${terminalFacts.reason})` : ""}${secretDiff.summaryDisclosure(secretLike)}\n\n${withPoolAttempts(report, reportPool)}\n`);
5132
+ store.writeText(join(paths.finalDir, "summary.md"), `# Run ${runId} (${opts.mode})\n\n- Harnesses: ${harnessLabel}\n- Lifecycle: ${terminalFacts.lifecycle}${terminalFacts.reason ? ` (${terminalFacts.reason})` : ""}\n\n${withPoolAttempts(report, reportPool)}\n`);
5108
5133
  const reportProducerAttemptId = opts.deepScan &&
5109
5134
  deepScanSynthesis?.status === "succeeded" &&
5110
5135
  deepScanSynthesis.reducer_attempt_id
@@ -5121,7 +5146,6 @@ export class Orchestrator {
5121
5146
  mode: opts.mode,
5122
5147
  intent: opts.intent,
5123
5148
  read_only: true,
5124
- ...(secretLike ? { secret_like: secretLike } : {}),
5125
5149
  },
5126
5150
  });
5127
5151
  log.emit("work_product.emitted", { kind: "report", winner: reportProducerAttemptId });
@@ -5145,7 +5169,7 @@ export class Orchestrator {
5145
5169
  phase: "executor",
5146
5170
  pool_attempts: reportPool,
5147
5171
  failure_ref: "final/failure.yaml",
5148
- ...resumableTerminal(store, paths, attempts, true),
5172
+ ...resumableTerminal(store, paths, carried(), true),
5149
5173
  });
5150
5174
  }
5151
5175
  else if (workVetoed) {
@@ -5154,7 +5178,7 @@ export class Orchestrator {
5154
5178
  facts: terminalFacts,
5155
5179
  reason: terminalFacts.reason,
5156
5180
  pool_attempts: reportPool,
5157
- ...resumableTerminal(store, paths, attempts, true),
5181
+ ...resumableTerminal(store, paths, carried(), true),
5158
5182
  });
5159
5183
  }
5160
5184
  else {
@@ -5162,7 +5186,7 @@ export class Orchestrator {
5162
5186
  lifecycle: terminalFacts.lifecycle,
5163
5187
  facts: terminalFacts,
5164
5188
  reason: terminalFacts.reason,
5165
- ...resumableTerminal(store, paths, attempts),
5189
+ ...resumableTerminal(store, paths, carried()),
5166
5190
  });
5167
5191
  }
5168
5192
  return {
@@ -5174,7 +5198,7 @@ export class Orchestrator {
5174
5198
  facts: terminalFacts,
5175
5199
  winner: null,
5176
5200
  runDir: paths.root,
5177
- summary: withPoolAttempts(redactSecrets(report).slice(0, 400), reportPool),
5201
+ summary: withPoolAttempts(report.slice(0, 400), reportPool),
5178
5202
  candidates: candidateSummaries,
5179
5203
  };
5180
5204
  }