@claudexor/orchestrator 6.0.0 → 6.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/dist/attemptUsageCost.d.ts +2 -2
  2. package/dist/attemptUsageCost.d.ts.map +1 -1
  3. package/dist/attemptUsageCost.js +3 -3
  4. package/dist/attemptUsageCost.js.map +1 -1
  5. package/dist/candidateOutputs.d.ts +3 -2
  6. package/dist/candidateOutputs.d.ts.map +1 -1
  7. package/dist/candidateOutputs.js +5 -4
  8. package/dist/candidateOutputs.js.map +1 -1
  9. package/dist/candidateReview.js +3 -3
  10. package/dist/candidateReview.js.map +1 -1
  11. package/dist/candidateTerminals.js +3 -3
  12. package/dist/candidateTerminals.js.map +1 -1
  13. package/dist/candidateWorkProduct.js +2 -2
  14. package/dist/candidateWorkProduct.js.map +1 -1
  15. package/dist/continuation-custody.d.ts +15 -3
  16. package/dist/continuation-custody.d.ts.map +1 -1
  17. package/dist/continuation-custody.js +42 -14
  18. package/dist/continuation-custody.js.map +1 -1
  19. package/dist/continue-from.d.ts.map +1 -1
  20. package/dist/continue-from.js +6 -3
  21. package/dist/continue-from.js.map +1 -1
  22. package/dist/deepScanReducer.d.ts +1 -1
  23. package/dist/deepScanReducer.d.ts.map +1 -1
  24. package/dist/deepScanReducer.js +7 -5
  25. package/dist/deepScanReducer.js.map +1 -1
  26. package/dist/delegationFailure.js +1 -1
  27. package/dist/delegationFailure.js.map +1 -1
  28. package/dist/directoryCandidate.js +4 -4
  29. package/dist/directoryCandidate.js.map +1 -1
  30. package/dist/git-precondition.d.ts +0 -2
  31. package/dist/git-precondition.d.ts.map +1 -1
  32. package/dist/git-precondition.js +2 -21
  33. package/dist/git-precondition.js.map +1 -1
  34. package/dist/inrun-continuity.d.ts.map +1 -1
  35. package/dist/inrun-continuity.js +5 -6
  36. package/dist/inrun-continuity.js.map +1 -1
  37. package/dist/interaction.d.ts +4 -3
  38. package/dist/interaction.d.ts.map +1 -1
  39. package/dist/interaction.js +10 -6
  40. package/dist/interaction.js.map +1 -1
  41. package/dist/orchestrator.d.ts.map +1 -1
  42. package/dist/orchestrator.js +65 -72
  43. package/dist/orchestrator.js.map +1 -1
  44. package/dist/planRun.d.ts +1 -1
  45. package/dist/planRun.d.ts.map +1 -1
  46. package/dist/planRun.js +5 -5
  47. package/dist/planRun.js.map +1 -1
  48. package/dist/plannerAttempt.d.ts.map +1 -1
  49. package/dist/plannerAttempt.js +6 -5
  50. package/dist/plannerAttempt.js.map +1 -1
  51. package/dist/retainedOutput.d.ts.map +1 -1
  52. package/dist/retainedOutput.js +4 -3
  53. package/dist/retainedOutput.js.map +1 -1
  54. package/dist/reviewerPanel.js +2 -2
  55. package/dist/reviewerPanel.js.map +1 -1
  56. package/dist/routeContext.d.ts +1 -1
  57. package/dist/routeContext.d.ts.map +1 -1
  58. package/dist/runEventLog.d.ts +11 -7
  59. package/dist/runEventLog.d.ts.map +1 -1
  60. package/dist/runEventLog.js +17 -13
  61. package/dist/runEventLog.js.map +1 -1
  62. package/dist/runScope.d.ts +3 -2
  63. package/dist/runScope.d.ts.map +1 -1
  64. package/dist/runScope.js +5 -4
  65. package/dist/runScope.js.map +1 -1
  66. package/dist/runSupport.d.ts +18 -1
  67. package/dist/runSupport.d.ts.map +1 -1
  68. package/dist/runSupport.js +16 -1
  69. package/dist/runSupport.js.map +1 -1
  70. package/dist/runTerminalHelpers.d.ts +14 -0
  71. package/dist/runTerminalHelpers.d.ts.map +1 -1
  72. package/dist/runTerminalHelpers.js +54 -1
  73. package/dist/runTerminalHelpers.js.map +1 -1
  74. package/dist/runTerminalResults.d.ts +5 -1
  75. package/dist/runTerminalResults.d.ts.map +1 -1
  76. package/dist/runTerminalResults.js +37 -4
  77. package/dist/runTerminalResults.js.map +1 -1
  78. package/dist/runTerminals.d.ts.map +1 -1
  79. package/dist/runTerminals.js +140 -138
  80. package/dist/runTerminals.js.map +1 -1
  81. package/dist/secretDiff.d.ts +4 -3
  82. package/dist/secretDiff.d.ts.map +1 -1
  83. package/dist/secretDiff.js +4 -3
  84. package/dist/secretDiff.js.map +1 -1
  85. package/package.json +17 -17
@@ -51,10 +51,10 @@ 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, observeUsageSpend, recordAdapterThrow, } from "./laneStreamEvents.js";
54
- import { promptWithEngineConstraints, sleep, 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, laneSuspension, 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
- import { ensureClaudeBridgeForRun, ensureWriteModeGitBoundary } from "./git-precondition.js";
57
+ import { ensureWriteModeGitBoundary } from "./git-precondition.js";
58
58
  import { buildContinuation } from "./continuity.js";
59
59
  import { resolveContinuitySummary, threadContinuityRequest } from "./continuity-facts.js";
60
60
  import { runDiffReview } from "./diffReview.js";
@@ -84,7 +84,7 @@ import { ReadinessLedger, evaluateConvergence, failureSignature, gatesPassed, re
84
84
  import { arbitrate } from "@claudexor/arbitration";
85
85
  import { buildSynthesisPlan, decideSynthesis } from "@claudexor/synthesis";
86
86
  import { billingKnowledgeForAuthRoute, attemptUsageCostSettlement, BudgetLedger, isBudgetTerminal, explainRanking, loadHarnessMetrics, promptFingerprint, rankHarnesses, } from "@claudexor/budget";
87
- import { hashJson, newId, noProjectRepoRoot, redactSecrets, safeInvoke, userConfigDir, } from "@claudexor/util";
87
+ import { hashJson, measureSync, newId, noProjectRepoRoot, pacedByAppends, redactSecrets, safeInvoke, userConfigDir, } from "@claudexor/util";
88
88
  const LABELS = "ABCDEFGHIJ".split("");
89
89
  const NO_PROJECT_ROOT = noProjectRepoRoot();
90
90
  export class Orchestrator {
@@ -231,14 +231,14 @@ export class Orchestrator {
231
231
  }
232
232
  catch (err) {
233
233
  const message = safeErrorMessage(err);
234
- store.writeText(join(paths.contextDir, "context_error.md"), `# Reviewer Panel Error\n\n${message}\n`);
234
+ await store.writeTextAsync(join(paths.contextDir, "context_error.md"), `# Reviewer Panel Error\n\n${message}\n`);
235
235
  writeFailure(store, paths, {
236
236
  phase: "review_preflight",
237
237
  category: "harness_unavailable",
238
238
  safeMessage: message,
239
239
  runDir: paths.root,
240
240
  });
241
- store.writeText(join(paths.finalDir, "summary.md"), `# Run ${runId} (${mode})\n\n- Lifecycle: failed\n- Phase: review preflight\n\n${message}\n`);
241
+ await store.writeTextAsync(join(paths.finalDir, "summary.md"), `# Run ${runId} (${mode})\n\n- Lifecycle: failed\n- Phase: review preflight\n\n${message}\n`);
242
242
  log.emit("output.ready", { kind: "summary", path: "final/summary.md", state: "diagnostic" });
243
243
  log.emit("run.failed", {
244
244
  lifecycle: "failed",
@@ -971,7 +971,7 @@ export class Orchestrator {
971
971
  include: projectCtx.include,
972
972
  exclude: projectCtx.exclude,
973
973
  });
974
- store.writeYaml(join(paths.contextDir, "context_pack.yaml"), pack);
974
+ await store.writeYamlAsync(join(paths.contextDir, "context_pack.yaml"), pack);
975
975
  log.emit("context.pack.created", {
976
976
  hash: pack.hash,
977
977
  files: pack.atlas.length,
@@ -1188,7 +1188,7 @@ export class Orchestrator {
1188
1188
  if (!result.packetMarkdown)
1189
1189
  return { pointerLine: null };
1190
1190
  const briefPath = join(paths.contextDir, "THREAD.md");
1191
- store.writeText(briefPath, result.packetMarkdown);
1191
+ await store.writeTextAsync(briefPath, result.packetMarkdown);
1192
1192
  return {
1193
1193
  pointerLine: threadContextPointer(briefPath),
1194
1194
  };
@@ -1419,6 +1419,7 @@ export class Orchestrator {
1419
1419
  const rateLimitStart = telemetry.rateLimits.length;
1420
1420
  rawLane.beginTry();
1421
1421
  try {
1422
+ const pacing = { waiting: false, version: 0 };
1422
1423
  const watched = withInactivityWatchdog(runModelGovernedRoute(routed, runSpec, dispatch), {
1423
1424
  timeoutMs: inactivityMs,
1424
1425
  countsAsProgress: countsAsAgentProgress,
@@ -1428,12 +1429,11 @@ export class Orchestrator {
1428
1429
  },
1429
1430
  // Waiting on the USER is legitimate silence. The interaction policy
1430
1431
  // either enforces a finite deadline or waits until answer/cancel/
1431
- // terminal/restart when the timeout is disabled.
1432
- isSuspended: () => (interaction?.pendingCount?.() ?? 0) > 0,
1433
- suspensionVersion: () => interaction?.suspensionVersion?.() ?? 0,
1432
+ // terminal/restart when the timeout is disabled. So is the journal wait.
1433
+ ...laneSuspension(pacing, interaction),
1434
1434
  stopSignal: signal, // after Stop: drained events are recorded, never used
1435
1435
  });
1436
- for await (const ev of watched) {
1436
+ for await (const ev of pacedByAppends(watched, [paths.eventsPath], pacing)) {
1437
1437
  // After Stop: stored evidence only, but file paths are still collected for the capture.
1438
1438
  const stopped = signal?.aborted === true;
1439
1439
  if (!stopped)
@@ -1708,7 +1708,7 @@ export class Orchestrator {
1708
1708
  aborted: signal?.aborted === true,
1709
1709
  authMode: telemetry.authMode,
1710
1710
  });
1711
- const { produced: producedFiles, omitted: producedOmitted } = AC.withAttemptFailureCost(() => writeCandidateAttemptArtifacts({
1711
+ const { produced: producedFiles, omitted: producedOmitted } = await AC.withAttemptFailureCost(() => writeCandidateAttemptArtifacts({
1712
1712
  store,
1713
1713
  attemptDir,
1714
1714
  worktreePath: envelope.worktree_path,
@@ -1838,7 +1838,7 @@ export class Orchestrator {
1838
1838
  const mutatingRun = isMutatingAccess(contract.access.effective_profile);
1839
1839
  const planBrief = verifiedPlanBrief(input);
1840
1840
  const quotaSnapshots = this.quotaSnapshotPreflight();
1841
- const { store, paths, log, ledger, runContext } = beginAnnouncedRun({
1841
+ const { store, paths, log, ledger, runContext } = await beginAnnouncedRun({
1842
1842
  input,
1843
1843
  contract,
1844
1844
  quotaSnapshots,
@@ -1860,7 +1860,7 @@ export class Orchestrator {
1860
1860
  const projectRoot = input.repoRoot;
1861
1861
  const wsm = new WorkspaceManager(execRoot, { projectRoot, runtimeRoot: runRuntimeRoot(input) });
1862
1862
  const anchors = secretDiff.revertAnchors(execRoot, projectRoot);
1863
- store.writeYaml(join(paths.contextDir, "task.yaml"), contract);
1863
+ await store.writeYamlAsync(join(paths.contextDir, "task.yaml"), contract);
1864
1864
  log.emit("task.contract.created", { task_contract_hash: hashJson(contract) });
1865
1865
  // Write modes need a git boundary for worktree isolation and honest diffs.
1866
1866
  // A non-git project folder is initialized automatically (baseline commit),
@@ -1884,11 +1884,6 @@ export class Orchestrator {
1884
1884
  };
1885
1885
  }
1886
1886
  }
1887
- // Same run-prep stage as the git boundary: if the PROJECT root uses AGENTS.md
1888
- // with no CLAUDE.md, bridge it so a Claude Code candidate reads it (INV-113).
1889
- if (mutatingRun && input.workspaceKind !== "directory") {
1890
- ensureClaudeBridgeForRun(input.repoRoot, input.inPlace === true, log);
1891
- }
1892
1887
  // Pre-turn snapshot of the live tree for in-place runs: the revert restore
1893
1888
  // target (server-owned revertInPlace). A snapshot failure must never fail the
1894
1889
  // run — revert is simply unavailable then.
@@ -2213,7 +2208,7 @@ export class Orchestrator {
2213
2208
  error: message,
2214
2209
  phase: infraPhase,
2215
2210
  });
2216
- store.writeYaml(join(paths.attemptsDir, slot.attemptId, "attempt.yaml"), AC.attemptFailureRecord(slot.attemptId, adapter.id, failureCost, infraPhase, message, applied));
2211
+ await store.writeYamlAsync(join(paths.attemptsDir, slot.attemptId, "attempt.yaml"), AC.attemptFailureRecord(slot.attemptId, adapter.id, failureCost, infraPhase, message, applied));
2217
2212
  runsBySlot[slotIdx] = {
2218
2213
  attemptId: slot.attemptId,
2219
2214
  harnessId: adapter.id,
@@ -2410,7 +2405,7 @@ export class Orchestrator {
2410
2405
  sources: evidences.map((evidence) => evidence.attemptId),
2411
2406
  };
2412
2407
  try {
2413
- store.writeYaml(join(paths.arbitrationDir, "synthesis.yaml"), synth);
2408
+ await store.writeYamlAsync(join(paths.arbitrationDir, "synthesis.yaml"), synth);
2414
2409
  log.emit("synthesis.started", { synthesize: synth.synthesize, reason: synth.reason });
2415
2410
  if (synth.synthesize && !budgetStopped) {
2416
2411
  const synthRouted = adapters[0];
@@ -2583,7 +2578,7 @@ export class Orchestrator {
2583
2578
  facts = { ...facts, checks: "failed", reason: "checks_failed" };
2584
2579
  }
2585
2580
  const needsDec = facts.review === "blocked" || facts.checks === "failed";
2586
- store.writeYaml(join(paths.arbitrationDir, "decision.yaml"), {
2581
+ await store.writeYamlAsync(join(paths.arbitrationDir, "decision.yaml"), {
2587
2582
  ...result.decision,
2588
2583
  ...(needsDec
2589
2584
  ? blockedDecisionOverride(result.decision.evidence_facts, facts, finalVerify)
@@ -2591,7 +2586,7 @@ export class Orchestrator {
2591
2586
  review_verified: actualReviewVerified,
2592
2587
  final_verify: finalVerify,
2593
2588
  });
2594
- store.writeYaml(join(paths.arbitrationDir, "pairwise.yaml"), result.pairwise);
2589
+ await store.writeYamlAsync(join(paths.arbitrationDir, "pairwise.yaml"), result.pairwise);
2595
2590
  const decisionPath = join(paths.arbitrationDir, "decision.yaml");
2596
2591
  // Every attempt's failed tries by the FINAL outcome (delivery below can
2597
2592
  // still fail the checks); a review or check block is not an account failure.
@@ -2606,7 +2601,7 @@ export class Orchestrator {
2606
2601
  }
2607
2602
  const winnerAnswer = winnerRun.answerText ?? "";
2608
2603
  if (winnerAnswer.length > 0) {
2609
- store.writeText(join(paths.finalDir, "answer.md"), winnerAnswer + "\n");
2604
+ await store.writeTextAsync(join(paths.finalDir, "answer.md"), winnerAnswer + "\n");
2610
2605
  }
2611
2606
  if (contract.output_schema) {
2612
2607
  finalizeStructuredOutput({
@@ -2628,7 +2623,7 @@ export class Orchestrator {
2628
2623
  const delivered = await verifyAndDeliverFiles(execRoot, winnerRun.files, {}, gateSpecsFromContract(contract, input.runScopeMarker), (freshVerify) => finalVerifyBlocks(freshVerify)
2629
2624
  ? (freshVerify.reason ?? "final verify failed before directory race adoption")
2630
2625
  : null, log);
2631
- store.writeYaml(join(paths.finalDir, "delivery_receipt.yaml"), delivered);
2626
+ await store.writeYamlAsync(join(paths.finalDir, "delivery_receipt.yaml"), delivered);
2632
2627
  directoryDeliveryReceipt = true;
2633
2628
  directoryDelivery = delivered;
2634
2629
  finalVerify = delivered.finalVerify;
@@ -2660,7 +2655,7 @@ export class Orchestrator {
2660
2655
  });
2661
2656
  }
2662
2657
  else if (mutatingRun) {
2663
- const savedPatch = secretDiff.persistFinalPatch(store, paths.finalDir, winnerRun);
2658
+ const savedPatch = await secretDiff.persistFinalPatch(store, paths.finalDir, winnerRun);
2664
2659
  const patchSha256 = savedPatch.patchSha256;
2665
2660
  const wstats = secretDiff.resultDiffStats(winnerRun);
2666
2661
  const hasDiff = secretDiff.hasCandidateChanges(winnerRun);
@@ -2695,7 +2690,7 @@ export class Orchestrator {
2695
2690
  : null;
2696
2691
  }, log);
2697
2692
  raceDeliveryReceipt = applied;
2698
- store.writeYaml(join(paths.finalDir, "delivery_receipt.yaml"), applied);
2693
+ await store.writeYamlAsync(join(paths.finalDir, "delivery_receipt.yaml"), applied);
2699
2694
  finalVerify = applied.finalVerify;
2700
2695
  if (applied.applied) {
2701
2696
  adopted = true;
@@ -2741,7 +2736,7 @@ export class Orchestrator {
2741
2736
  revertAnchorId = await anchors.turn(preTurnSha, postTurnSha);
2742
2737
  }
2743
2738
  const capturedNoGitChange = capturedNoGitChangeValue(winnerRun);
2744
- store.writeYaml(join(paths.finalDir, "work_product.yaml"), {
2739
+ await store.writeYamlAsync(join(paths.finalDir, "work_product.yaml"), {
2745
2740
  id: newId("wp"),
2746
2741
  kind: input.create === true ? "new_repo" : "patch",
2747
2742
  source_task_id: taskId,
@@ -2776,7 +2771,7 @@ export class Orchestrator {
2776
2771
  },
2777
2772
  });
2778
2773
  }
2779
- store.writeText(join(paths.finalDir, "summary.md"), withPoolAttempts(renderSummary(runId, mode, { ...result.decision, facts }, evidences, synth.reason, actualReviewVerified, delegateFailure.raceSummaryDisclosure(winnerRun, runs)), racePool()));
2774
+ await store.writeTextAsync(join(paths.finalDir, "summary.md"), withPoolAttempts(renderSummary(runId, mode, { ...result.decision, facts }, evidences, synth.reason, actualReviewVerified, delegateFailure.raceSummaryDisclosure(winnerRun, runs)), racePool()));
2780
2775
  // summary.md is a DIAGNOSTIC artifact only (V8/PLAN addendum 2): it no
2781
2776
  // longer carries primary-output authority. A clean applyable success or a
2782
2777
  // winner answer still marks it ready for legacy INV-116 ordering; any
@@ -2888,7 +2883,7 @@ export class Orchestrator {
2888
2883
  ...(decisionBlock ? {} : erroredWinnerFailure(winnerRun, keptSources)),
2889
2884
  });
2890
2885
  if (!winnerRun) {
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`);
2886
+ await store.writeTextAsync(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`);
2892
2887
  log.emit("output.ready", {
2893
2888
  kind: "summary",
2894
2889
  path: "final/summary.md",
@@ -2954,9 +2949,10 @@ export class Orchestrator {
2954
2949
  writeRunTelemetry(store, paths, contract, runId, taskId, mode, attempts, finalAttemptId, deepScanSynthesis) {
2955
2950
  // QA-034: attach the routing rationale recorded at pool ordering (if this
2956
2951
  // run computed one), then clear it — telemetry is written once at terminal.
2952
+ // It stays synchronous (several callers are synchronous callbacks), labeled (#688).
2957
2953
  const routingRationale = this.routingRationaleByRun.get(runId) ?? null;
2958
2954
  this.routingRationaleByRun.delete(runId);
2959
- writeRunTelemetryArtifact({
2955
+ measureSync("run.telemetry_write", () => writeRunTelemetryArtifact({
2960
2956
  store,
2961
2957
  finalDir: paths.finalDir,
2962
2958
  contract,
@@ -2968,7 +2964,7 @@ export class Orchestrator {
2968
2964
  routingRationale,
2969
2965
  deepScanSynthesis: deepScanSynthesis ?? null,
2970
2966
  resolveAuthPreference: (harnessId) => this.authPreferenceForHarness(contract.repo.root, harnessId, contract.auth_preference),
2971
- });
2967
+ }));
2972
2968
  }
2973
2969
  /**
2974
2970
  * SINGLE funnel for every reviewer-panel invocation: run it inside a per-review
@@ -3022,7 +3018,7 @@ export class Orchestrator {
3022
3018
  const contract = this.buildContract(input, taskId, mode);
3023
3019
  const planBrief = verifiedPlanBrief(input);
3024
3020
  const quotaSnapshots = this.quotaSnapshotPreflight();
3025
- const { store, paths, log, ledger, runContext } = beginAnnouncedRun({
3021
+ const { store, paths, log, ledger, runContext } = await beginAnnouncedRun({
3026
3022
  input,
3027
3023
  contract,
3028
3024
  quotaSnapshots,
@@ -3047,7 +3043,7 @@ export class Orchestrator {
3047
3043
  });
3048
3044
  const anchors = secretDiff.revertAnchors(execRoot, input.repoRoot);
3049
3045
  const readiness = new ReadinessLedger();
3050
- store.writeYaml(join(paths.contextDir, "task.yaml"), contract);
3046
+ await store.writeYamlAsync(join(paths.contextDir, "task.yaml"), contract);
3051
3047
  // Legacy Git envelopes need their boundary; explicit directory execution
3052
3048
  // keeps the requested strategy without initializing the selected folder.
3053
3049
  if (!input.inPlace && input.workspaceKind !== "directory") {
@@ -3066,11 +3062,6 @@ export class Orchestrator {
3066
3062
  candidates: [],
3067
3063
  };
3068
3064
  }
3069
- // Same run-prep stage as the git boundary (and the same `!inPlace`
3070
- // exclusion — we are inside that branch, so inPlace is false here): bridge
3071
- // an AGENTS.md-only PROJECT root so a Claude Code convergence attempt reads
3072
- // it (INV-113).
3073
- ensureClaudeBridgeForRun(input.repoRoot, false, log);
3074
3065
  }
3075
3066
  const reviewDir = join(paths.root, "review-evidence");
3076
3067
  if (contract.review_requested !== false)
@@ -3118,7 +3109,7 @@ export class Orchestrator {
3118
3109
  if (contract.convergence.require_final_cross_family_clean_review && !reviewVerified) {
3119
3110
  const message = `convergence requires a cross-family clean review (>=2 healthy reviewer provider families); found ${new Set(reviewers.map((r) => r.providerFamily)).size}. ` +
3120
3111
  "Configure reviewers from a second provider family and check `claudexor doctor` for reviewer readiness.";
3121
- store.writeText(join(paths.contextDir, "context_error.md"), `# Convergence Preflight Error\n\n${message}\n`);
3112
+ await store.writeTextAsync(join(paths.contextDir, "context_error.md"), `# Convergence Preflight Error\n\n${message}\n`);
3122
3113
  writeFailure(store, paths, {
3123
3114
  phase: "review",
3124
3115
  category: "policy",
@@ -3129,7 +3120,7 @@ export class Orchestrator {
3129
3120
  "Check harness doctor for reviewer readiness",
3130
3121
  ],
3131
3122
  });
3132
- store.writeText(join(paths.finalDir, "summary.md"), `# Run ${runId} (${mode})\n\n- Lifecycle: failed\n- Phase: review preflight\n\n${message}\n`);
3123
+ await store.writeTextAsync(join(paths.finalDir, "summary.md"), `# Run ${runId} (${mode})\n\n- Lifecycle: failed\n- Phase: review preflight\n\n${message}\n`);
3133
3124
  log.emit("output.ready", { kind: "summary", path: "final/summary.md", state: "diagnostic" });
3134
3125
  log.emit("run.failed", {
3135
3126
  lifecycle: "failed",
@@ -3294,7 +3285,7 @@ export class Orchestrator {
3294
3285
  status: "failed",
3295
3286
  error: message,
3296
3287
  });
3297
- store.writeYaml(join(paths.attemptsDir, attemptId, "attempt.yaml"), AC.attemptFailureRecord(attemptId, adapter.id, failureCost, "harness", message, applied));
3288
+ await store.writeYamlAsync(join(paths.attemptsDir, attemptId, "attempt.yaml"), AC.attemptFailureRecord(attemptId, adapter.id, failureCost, "harness", message, applied));
3298
3289
  run = {
3299
3290
  applied,
3300
3291
  attemptId,
@@ -3550,7 +3541,7 @@ export class Orchestrator {
3550
3541
  toCandidateEvidence(lastRun, contract, lastFindings, lastFinalReviewClean, actualReviewVerified),
3551
3542
  ], { ...arbitrationBudgetOptions(ledger), reviewRequested: contract.review_requested });
3552
3543
  decision = arb.decision;
3553
- store.writeYaml(join(paths.arbitrationDir, "decision.yaml"), decision);
3544
+ await store.writeYamlAsync(join(paths.arbitrationDir, "decision.yaml"), decision);
3554
3545
  // A converged run adopts the arbitration axes (checks/review); an
3555
3546
  // otherwise not-converged loop that nonetheless produced an applyable
3556
3547
  // decision adopts it too.
@@ -3626,7 +3617,7 @@ export class Orchestrator {
3626
3617
  : { facts }),
3627
3618
  final_verify: convFinalVerify,
3628
3619
  };
3629
- store.writeYaml(join(paths.arbitrationDir, "decision.yaml"), decision);
3620
+ await store.writeYamlAsync(join(paths.arbitrationDir, "decision.yaml"), decision);
3630
3621
  }
3631
3622
  this.writeRunTelemetry(store, paths, contract, runId, taskId, mode, attemptTelemetries, lastRun?.attemptId ?? null);
3632
3623
  // Deliver the converged/last work to final/ so `apply` and `inspect` can
@@ -3648,7 +3639,7 @@ export class Orchestrator {
3648
3639
  });
3649
3640
  }
3650
3641
  else {
3651
- const savedPatch = secretDiff.persistFinalPatch(store, paths.finalDir, lastRun);
3642
+ const savedPatch = await secretDiff.persistFinalPatch(store, paths.finalDir, lastRun);
3652
3643
  const patchSha256 = savedPatch.patchSha256;
3653
3644
  // Honest apply-state (parity with runRace single-candidate in-place): a
3654
3645
  // convergence run with inPlace mutated the live tree directly across its
@@ -3662,7 +3653,7 @@ export class Orchestrator {
3662
3653
  : "not_applied";
3663
3654
  const revertAnchorId = convAdopted === true ? await anchors.turn(preTurnSha, lastPostTurnSha) : null;
3664
3655
  const capturedNoGitChange = capturedNoGitChangeValue(lastRun);
3665
- store.writeYaml(join(paths.finalDir, "work_product.yaml"), {
3656
+ await store.writeYamlAsync(join(paths.finalDir, "work_product.yaml"), {
3666
3657
  id: newId("wp"),
3667
3658
  kind: "patch",
3668
3659
  source_task_id: taskId,
@@ -3688,10 +3679,10 @@ export class Orchestrator {
3688
3679
  }
3689
3680
  // The exact answer is kept for every convergence result, patch or files.
3690
3681
  if (lastRun.answerText) {
3691
- store.writeText(join(paths.finalDir, "answer.md"), lastRun.answerText);
3682
+ await store.writeTextAsync(join(paths.finalDir, "answer.md"), lastRun.answerText);
3692
3683
  log.emit("output.ready", { kind: "answer", path: "final/answer.md" });
3693
3684
  }
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`);
3685
+ await store.writeTextAsync(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`);
3695
3686
  // Lifecycle invariant (all modes): output.ready precedes the terminal
3696
3687
  // event so a client that applied the terminal event has the output.
3697
3688
  log.emit("output.ready", {
@@ -3774,7 +3765,7 @@ export class Orchestrator {
3774
3765
  // ARCHITECTURE event contract guarantees output.ready precedes the
3775
3766
  // terminal in every mode.
3776
3767
  if (!lastRun || (interrupted && input.inPlace !== true && !lastRun.files)) {
3777
- store.writeText(join(paths.finalDir, "summary.md"), `${withPoolAttempts(`# Run ${runId} (${mode})\n\n- Lifecycle: ${facts.lifecycle}${facts.reason ? ` (${facts.reason})` : ""}\n- Attempts: ${attempt}`, convPool)}\n`);
3768
+ await store.writeTextAsync(join(paths.finalDir, "summary.md"), `${withPoolAttempts(`# Run ${runId} (${mode})\n\n- Lifecycle: ${facts.lifecycle}${facts.reason ? ` (${facts.reason})` : ""}\n- Attempts: ${attempt}`, convPool)}\n`);
3778
3769
  log.emit("output.ready", {
3779
3770
  kind: "summary",
3780
3771
  path: "final/summary.md",
@@ -3918,7 +3909,7 @@ export class Orchestrator {
3918
3909
  // contract validation runs BEFORE the run is announced (see runRace).
3919
3910
  const contract = this.buildContract(input, taskId, "plan");
3920
3911
  const quotaSnapshots = this.quotaSnapshotPreflight();
3921
- const { store, paths, log, ledger } = beginAnnouncedRun({
3912
+ const { store, paths, log, ledger } = await beginAnnouncedRun({
3922
3913
  input,
3923
3914
  contract,
3924
3915
  quotaSnapshots,
@@ -3930,7 +3921,7 @@ export class Orchestrator {
3930
3921
  phase: "plan",
3931
3922
  prompt: input.prompt,
3932
3923
  }, announce);
3933
- store.writeYaml(join(paths.contextDir, "task.yaml"), contract);
3924
+ await store.writeYamlAsync(join(paths.contextDir, "task.yaml"), contract);
3934
3925
  log.emit("task.contract.created", { task_contract_hash: hashJson(contract) });
3935
3926
  // W3.3: ONE resolved read-only context — the routing point-probe and every
3936
3927
  // planner spawn consume the SAME scoped env (see routeContext.ts).
@@ -3946,7 +3937,7 @@ export class Orchestrator {
3946
3937
  adapters = await this.resolveCandidateAdapters({ ...input, n: undefined }, "plan", ledger, log, roHome, runId);
3947
3938
  }
3948
3939
  catch (err) {
3949
- roHome.dispose();
3940
+ await roHome.dispose();
3950
3941
  const message = safeErrorMessage(err);
3951
3942
  writeRoutingFailureTerminal(store, paths, log, {
3952
3943
  runId,
@@ -3973,16 +3964,16 @@ export class Orchestrator {
3973
3964
  contextSection = await this.lazyContextSection(input, contract, store, paths, log);
3974
3965
  }
3975
3966
  catch (err) {
3976
- roHome.dispose();
3967
+ await roHome.dispose();
3977
3968
  const message = safeErrorMessage(err);
3978
- store.writeText(join(paths.contextDir, "context_error.md"), `# Context Error\n\n${message}\n`);
3969
+ await store.writeTextAsync(join(paths.contextDir, "context_error.md"), `# Context Error\n\n${message}\n`);
3979
3970
  writeFailure(store, paths, {
3980
3971
  phase: "context",
3981
3972
  category: "project",
3982
3973
  safeMessage: message,
3983
3974
  runDir: paths.root,
3984
3975
  });
3985
- store.writeText(join(paths.finalDir, "summary.md"), `# Run ${runId} (plan)\n\n- Lifecycle: failed\n- Phase: context\n\n${message}\n`);
3976
+ await store.writeTextAsync(join(paths.finalDir, "summary.md"), `# Run ${runId} (plan)\n\n- Lifecycle: failed\n- Phase: context\n\n${message}\n`);
3986
3977
  log.emit("output.ready", { kind: "summary", path: "final/summary.md", state: "diagnostic" });
3987
3978
  log.emit("run.failed", {
3988
3979
  lifecycle: "failed",
@@ -4095,7 +4086,7 @@ export class Orchestrator {
4095
4086
  const text = outcome.text ?? "(no output)";
4096
4087
  plans.push({ id: outcome.harnessId, text });
4097
4088
  // The planner's own saved copy keeps its exact text (INV-146).
4098
- store.writeText(join(paths.root, "plans", `${outcome.harnessId}.md`), text + "\n");
4089
+ await store.writeTextAsync(join(paths.root, "plans", `${outcome.harnessId}.md`), text + "\n");
4099
4090
  // Solo planning (D31): the FIRST successful planner is the plan; later
4100
4091
  // pool members are a sequential fallback chain (ask parity), not
4101
4092
  // parallel co-authors. Council re-enables the multi-draft round.
@@ -4112,7 +4103,7 @@ export class Orchestrator {
4112
4103
  }
4113
4104
  finally {
4114
4105
  // Planners done (or threw) — reclaim scoped scratch/API-route state.
4115
- roHome.dispose();
4106
+ await roHome.dispose();
4116
4107
  }
4117
4108
  if (input.signal?.aborted) {
4118
4109
  return cancelledResult(log, runId, taskId, "plan", paths.root, planAttempts.map((p) => ({
@@ -4282,7 +4273,7 @@ export class Orchestrator {
4282
4273
  // recorded user intent is the CALLER's goal.
4283
4274
  const contract = this.buildContract({ ...input, prompt: opts.contractIntent ?? prompt }, taskId, opts.mode);
4284
4275
  const quotaSnapshots = this.quotaSnapshotPreflight();
4285
- const { store, paths, log, ledger, runContext } = beginAnnouncedRun({
4276
+ const { store, paths, log, ledger, runContext } = await beginAnnouncedRun({
4286
4277
  input,
4287
4278
  contract,
4288
4279
  quotaSnapshots,
@@ -4294,7 +4285,7 @@ export class Orchestrator {
4294
4285
  phase: "report",
4295
4286
  prompt,
4296
4287
  }, announce);
4297
- store.writeYaml(join(paths.contextDir, "task.yaml"), contract);
4288
+ await store.writeYamlAsync(join(paths.contextDir, "task.yaml"), contract);
4298
4289
  log.emit("task.contract.created", { task_contract_hash: hashJson(contract) });
4299
4290
  // The ask/deep-scan report stays bare (its scouts read the tree themselves);
4300
4291
  // no lazy ContextPack section is attached here.
@@ -4321,7 +4312,7 @@ export class Orchestrator {
4321
4312
  }));
4322
4313
  }
4323
4314
  catch (err) {
4324
- roHome.dispose();
4315
+ await roHome.dispose();
4325
4316
  const message = safeErrorMessage(err);
4326
4317
  writeRoutingFailureTerminal(store, paths, log, {
4327
4318
  runId,
@@ -4549,7 +4540,7 @@ export class Orchestrator {
4549
4540
  });
4550
4541
  attemptTelemetries.push({ attemptId, harnessId: adapter.id, telemetry });
4551
4542
  if (opts.deepScan) {
4552
- store.writeText(join(paths.findingsDir, `${attemptId}-error.md`), `# Explorer ${attemptId} failed\n\n${message}\n`);
4543
+ await store.writeTextAsync(join(paths.findingsDir, `${attemptId}-error.md`), `# Explorer ${attemptId} failed\n\n${message}\n`);
4553
4544
  }
4554
4545
  return { status: "launched" };
4555
4546
  }
@@ -4618,6 +4609,7 @@ export class Orchestrator {
4618
4609
  ...(knobs.ignored.length > 0 ? { ignored_settings: knobs.ignored } : {}),
4619
4610
  });
4620
4611
  try {
4612
+ const pacing = { waiting: false, version: 0 };
4621
4613
  const watchedReport = withInactivityWatchdog(runModelGovernedRoute(routed, runSpec), {
4622
4614
  timeoutMs: harnessInactivityTimeoutMs(this.config(input.repoRoot)),
4623
4615
  countsAsProgress: countsAsAgentProgress,
@@ -4625,11 +4617,12 @@ export class Orchestrator {
4625
4617
  tryAbort.abort();
4626
4618
  void adapter.cancel?.(activeSessionId)?.catch(() => { });
4627
4619
  },
4628
- isSuspended: () => (reportInteraction?.pendingCount?.() ?? 0) > 0,
4629
- suspensionVersion: () => reportInteraction?.suspensionVersion?.() ?? 0,
4620
+ ...laneSuspension(pacing, reportInteraction),
4630
4621
  stopSignal: input.signal, // after Stop: drained events are recorded, never used
4631
4622
  });
4632
- for await (const ev of watchedReport) {
4623
+ const files = [paths.eventsPath, attemptEventsPath];
4624
+ const read = pacedByAppends(watchedReport, files, pacing);
4625
+ for await (const ev of read) {
4633
4626
  // A read after Stop is stored evidence only; a callback may itself press Stop.
4634
4627
  const shown = !input.signal?.aborted && ev.payload?.["buffered"] !== true;
4635
4628
  if (shown)
@@ -4814,7 +4807,7 @@ export class Orchestrator {
4814
4807
  });
4815
4808
  launched.delete(tried);
4816
4809
  if (opts.deepScan) {
4817
- store.writeText(join(paths.findingsDir, `${attemptId}-error.md`), `# Explorer ${attemptId} failed\n\n${harnessError}\n`);
4810
+ await store.writeTextAsync(join(paths.findingsDir, `${attemptId}-error.md`), `# Explorer ${attemptId} failed\n\n${harnessError}\n`);
4818
4811
  }
4819
4812
  return { status: "launched" };
4820
4813
  }
@@ -4846,7 +4839,7 @@ export class Orchestrator {
4846
4839
  const scoutTag = scoutInterrupted
4847
4840
  ? ` interrupted (${interruptionDetail(roFinalized.reason)})`
4848
4841
  : ` (${adapter.id})`;
4849
- store.writeText(join(paths.findingsDir, `${attemptId}${scoutInterrupted ? "-interrupted" : ""}.md`), `# Explorer ${attemptId}${scoutTag}\n\n${report || "(no output)"}${warningNote}\n`);
4842
+ await store.writeTextAsync(join(paths.findingsDir, `${attemptId}${scoutInterrupted ? "-interrupted" : ""}.md`), `# Explorer ${attemptId}${scoutTag}\n\n${report || "(no output)"}${warningNote}\n`);
4850
4843
  }
4851
4844
  return { status: "launched" };
4852
4845
  };
@@ -4991,7 +4984,7 @@ export class Orchestrator {
4991
4984
  finally {
4992
4985
  // All read-only attempts done (or threw) — reclaim scoped scratch and
4993
4986
  // injected API-route state. Vendor-owned native credentials were not copied.
4994
- roHome.dispose();
4987
+ await roHome.dispose();
4995
4988
  }
4996
4989
  const candidateSummaries = attempts.map((a) => ({
4997
4990
  attemptId: a.attemptId,
@@ -5047,7 +5040,7 @@ export class Orchestrator {
5047
5040
  : reducedReport !== null
5048
5041
  ? reducedReport
5049
5042
  : rawScoutBundle({ succeeded, unsuccessful, status: deepScanSynthesis });
5050
- store.writeText(join(paths.finalDir, opts.artifactName), `# ${opts.title}\n\n${report}\n`);
5043
+ await store.writeTextAsync(join(paths.finalDir, opts.artifactName), `# ${opts.title}\n\n${report}\n`);
5051
5044
  // ask is the only read-only strategy that can carry a structured-output
5052
5045
  // contract (the boundary refuses the rest); validate the FINAL aggregate
5053
5046
  // (the reduced synthesis, or the honest bundle for a degraded scan) — never
@@ -5068,7 +5061,7 @@ export class Orchestrator {
5068
5061
  path: `final/${opts.artifactName}`,
5069
5062
  });
5070
5063
  if (opts.deepScan) {
5071
- store.writeYaml(join(paths.finalDir, "explore-findings.yaml"), {
5064
+ await store.writeYamlAsync(join(paths.finalDir, "explore-findings.yaml"), {
5072
5065
  mode: "explore",
5073
5066
  width,
5074
5067
  attempts: attempts.map((a) => ({
@@ -5087,7 +5080,7 @@ export class Orchestrator {
5087
5080
  })),
5088
5081
  read_only: true,
5089
5082
  });
5090
- store.writeText(join(paths.finalDir, "omissions.md"), `# Omissions\n\n${unsuccessful.map((a) => `- ${a.attemptId} / ${a.harnessId} (${a.status}): ${a.error}`).join("\n") || "- None recorded by the runner. Synthesis claims still require evidence checks."}\n`);
5083
+ await store.writeTextAsync(join(paths.finalDir, "omissions.md"), `# Omissions\n\n${unsuccessful.map((a) => `- ${a.attemptId} / ${a.harnessId} (${a.status}): ${a.error}`).join("\n") || "- None recorded by the runner. Synthesis claims still require evidence checks."}\n`);
5091
5084
  }
5092
5085
  let terminalFacts = makeOutcomeFacts("succeeded");
5093
5086
  const reportBudgetTerminal = ledger.terminal();
@@ -5116,13 +5109,13 @@ export class Orchestrator {
5116
5109
  .join(", ");
5117
5110
  // A delivered report that still ends unsuccessfully or blocked keeps it and names every failed try.
5118
5111
  const reportPool = terminalPoolAttempts(terminalFacts, attempts);
5119
- 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`);
5112
+ await store.writeTextAsync(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`);
5120
5113
  const reportProducerAttemptId = opts.deepScan &&
5121
5114
  deepScanSynthesis?.status === "succeeded" &&
5122
5115
  deepScanSynthesis.reducer_attempt_id
5123
5116
  ? deepScanSynthesis.reducer_attempt_id
5124
5117
  : (succeeded[0]?.attemptId ?? "a01");
5125
- store.writeYaml(join(paths.finalDir, "work_product.yaml"), {
5118
+ await store.writeYamlAsync(join(paths.finalDir, "work_product.yaml"), {
5126
5119
  id: newId("wp"),
5127
5120
  kind: "report",
5128
5121
  source_task_id: taskId,