@sema-agent/core 5.56.0 → 5.58.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. package/CHANGELOG.md +122 -0
  2. package/dist/agents/cascade.d.ts +1 -1
  3. package/dist/agents/cumulative-stats.d.ts +1 -1
  4. package/dist/agents/observer.d.ts +2 -2
  5. package/dist/agents/peer-admission.d.ts +1 -1
  6. package/dist/agents/retain-ledger.d.ts +2 -2
  7. package/dist/agents/roster-store.d.ts +8 -8
  8. package/dist/agents/send-message-tool.d.ts +13 -2
  9. package/dist/agents/send-message-tool.js +34 -12
  10. package/dist/agents/subagent-steps.d.ts +1 -1
  11. package/dist/agents/subagent.d.ts +13 -13
  12. package/dist/agents/team.d.ts +15 -6
  13. package/dist/agents/team.js +1 -0
  14. package/dist/agents/tool-filter.d.ts +2 -2
  15. package/dist/agents/verify.d.ts +1 -1
  16. package/dist/bench/metrics.d.ts +35 -35
  17. package/dist/brain/anthropic.js +15 -5
  18. package/dist/brain/circuit-breaker.js +2 -1
  19. package/dist/brain/degrading.d.ts +1 -1
  20. package/dist/brain/degrading.js +4 -1
  21. package/dist/brain/errors.d.ts +3 -3
  22. package/dist/brain/failover.js +16 -1
  23. package/dist/brain/open-responses.js +15 -5
  24. package/dist/brain/openai.js +16 -5
  25. package/dist/brain/reasoning.d.ts +2 -2
  26. package/dist/brain/repetition.d.ts +1 -1
  27. package/dist/brain/request-params.d.ts +30 -27
  28. package/dist/brain/request-params.js +1 -7
  29. package/dist/brain/route-adjudicator.d.ts +190 -0
  30. package/dist/brain/route-adjudicator.js +189 -0
  31. package/dist/brain/route-conformance.d.ts +55 -0
  32. package/dist/brain/route-conformance.js +136 -0
  33. package/dist/brain/routing.js +8 -3
  34. package/dist/brain/status-sink.d.ts +2 -2
  35. package/dist/brain/stream-shared.d.ts +1 -1
  36. package/dist/config/catalog.d.ts +5 -5
  37. package/dist/core/arg-summary.d.ts +4 -4
  38. package/dist/core/ask-class.d.ts +2 -2
  39. package/dist/core/ask-question.d.ts +1 -1
  40. package/dist/core/auto-compaction.d.ts +15 -15
  41. package/dist/core/auto-mode.d.ts +5 -5
  42. package/dist/core/background-agent-store.d.ts +20 -20
  43. package/dist/core/background-shell.d.ts +4 -4
  44. package/dist/core/checkpoint-store.d.ts +35 -27
  45. package/dist/core/context-edit.d.ts +1 -1
  46. package/dist/core/context-guard.d.ts +1 -1
  47. package/dist/core/exec-output-tail.d.ts +6 -6
  48. package/dist/core/file-snapshot-store.d.ts +8 -8
  49. package/dist/core/git-worktree-env.d.ts +3 -3
  50. package/dist/core/governance-codes.js +2 -0
  51. package/dist/core/hooks.d.ts +73 -33
  52. package/dist/core/hooks.js +87 -25
  53. package/dist/core/image-downsample.d.ts +1 -1
  54. package/dist/core/locked-config.d.ts +1 -1
  55. package/dist/core/lsp.d.ts +1 -1
  56. package/dist/core/mailbox-store.d.ts +1 -1
  57. package/dist/core/mcp.d.ts +3 -3
  58. package/dist/core/mcp.js +4 -4
  59. package/dist/core/memory-engine/consolidation-driver.d.ts +207 -0
  60. package/dist/core/memory-engine/consolidation-driver.js +378 -0
  61. package/dist/core/memory-engine/consolidation.d.ts +46 -2
  62. package/dist/core/memory-engine/consolidation.js +1 -0
  63. package/dist/core/memory-engine/data-plane.d.ts +1 -1
  64. package/dist/core/memory-engine/distiller.d.ts +550 -0
  65. package/dist/core/memory-engine/distiller.js +598 -0
  66. package/dist/core/memory-engine/dual-root.d.ts +1 -1
  67. package/dist/core/memory-engine/engine.d.ts +62 -8
  68. package/dist/core/memory-engine/engine.js +40 -4
  69. package/dist/core/memory-engine/file-backend.d.ts +1 -1
  70. package/dist/core/memory-engine/index.d.ts +4 -2
  71. package/dist/core/memory-engine/index.js +4 -2
  72. package/dist/core/memory-engine/origin-clearance.d.ts +1 -1
  73. package/dist/core/memory-engine/scope-contract.d.ts +4 -4
  74. package/dist/core/memory-engine/sync-client.d.ts +16 -16
  75. package/dist/core/memory-engine/sync.d.ts +4 -4
  76. package/dist/core/memory-recall.d.ts +1 -1
  77. package/dist/core/memory.d.ts +2 -2
  78. package/dist/core/permission-rule-consent.d.ts +230 -36
  79. package/dist/core/permission-rule-consent.js +258 -54
  80. package/dist/core/permission-rule-model.d.ts +295 -97
  81. package/dist/core/permission-rule-model.js +132 -41
  82. package/dist/core/permission-rules.d.ts +9 -9
  83. package/dist/core/remote-env.d.ts +8 -8
  84. package/dist/core/roles.d.ts +3 -3
  85. package/dist/core/roles.js +1 -0
  86. package/dist/core/runner/assemble-result.d.ts +2 -2
  87. package/dist/core/runner/compaction-call-options.d.ts +3 -3
  88. package/dist/core/runner/memory-consolidation-driver.d.ts +49 -0
  89. package/dist/core/runner/memory-consolidation-driver.js +60 -0
  90. package/dist/core/runner/memory-consolidation.d.ts +1 -1
  91. package/dist/core/runner/prepare-config-doors.d.ts +3 -3
  92. package/dist/core/runner/prepare-task.d.ts +21 -21
  93. package/dist/core/runner/prepare-task.js +54 -18
  94. package/dist/core/runner/prepare-workspace-restore.d.ts +2 -2
  95. package/dist/core/runner/runtask.d.ts +15 -12
  96. package/dist/core/runner/runtask.js +48 -0
  97. package/dist/core/runner/session-rule-policy.d.ts +1 -1
  98. package/dist/core/runner/teardown-bounded.d.ts +1 -1
  99. package/dist/core/runner/tool-disclosure.d.ts +2 -2
  100. package/dist/core/runner/turn-attachments.d.ts +11 -11
  101. package/dist/core/scheduler.d.ts +10 -5
  102. package/dist/core/secret-env.d.ts +1 -1
  103. package/dist/core/sensitive-path-policy.d.ts +1 -1
  104. package/dist/core/session-policy-store.d.ts +2 -2
  105. package/dist/core/session-reconcile.d.ts +2 -2
  106. package/dist/core/session-store.d.ts +3 -3
  107. package/dist/core/session.d.ts +1 -1
  108. package/dist/core/shutdown-debug.d.ts +2 -2
  109. package/dist/core/side-query.d.ts +14 -7
  110. package/dist/core/spec-contract.d.ts +1 -1
  111. package/dist/core/store-contracts/contract-harness.d.ts +2 -2
  112. package/dist/core/store-contracts/contract-kit-version.d.ts +2 -2
  113. package/dist/core/store-contracts/mailbox-store-contract.d.ts +1 -1
  114. package/dist/core/store-contracts/mailbox-store-contract.js +1 -1
  115. package/dist/core/task-notification.d.ts +5 -5
  116. package/dist/core/task-registry-agent.d.ts +12 -12
  117. package/dist/core/task-registry-monitor.d.ts +1 -1
  118. package/dist/core/task-registry-shared.d.ts +41 -41
  119. package/dist/core/task-registry.d.ts +12 -12
  120. package/dist/core/tool-detach.d.ts +2 -2
  121. package/dist/core/tool-errors.d.ts +3 -3
  122. package/dist/core/tool-policy.d.ts +55 -28
  123. package/dist/core/tool-result-budget.d.ts +1 -1
  124. package/dist/core/tool-result-store.d.ts +2 -2
  125. package/dist/core/tools.d.ts +1 -1
  126. package/dist/core/trace.d.ts +26 -23
  127. package/dist/core/types.d.ts +155 -70
  128. package/dist/core/untrusted-egress.d.ts +1 -1
  129. package/dist/core/untrusted-text.d.ts +7 -7
  130. package/dist/core/wiring-manifest.d.ts +5 -5
  131. package/dist/core/workflow-journal-store.d.ts +14 -14
  132. package/dist/core/workflow-run-store-contract.d.ts +1 -1
  133. package/dist/core/workflow-run-store-contract.js +1 -1
  134. package/dist/core/workflow-run-store.d.ts +4 -4
  135. package/dist/engine/compaction/compaction.d.ts +3 -3
  136. package/dist/engine/compaction/utils.d.ts +2 -2
  137. package/dist/engine/execution-env/kill-tree.d.ts +1 -1
  138. package/dist/engine/execution-env/node-execution-env.d.ts +8 -8
  139. package/dist/engine/harness/agent-harness.d.ts +6 -6
  140. package/dist/engine/harness/agent-harness.js +26 -1
  141. package/dist/engine/harness/messages.d.ts +1 -1
  142. package/dist/engine/harness/types.d.ts +15 -11
  143. package/dist/engine/llm/types.d.ts +79 -14
  144. package/dist/engine/loop/agent-loop.d.ts +3 -3
  145. package/dist/engine/loop/types.d.ts +4 -4
  146. package/dist/engine/lsp/node-lsp-manager.d.ts +2 -2
  147. package/dist/engine/session/import-validate.d.ts +1 -1
  148. package/dist/engine/session/log-digest.d.ts +1 -1
  149. package/dist/engine/session/memory-repo.d.ts +2 -2
  150. package/dist/engine/session/session.d.ts +4 -4
  151. package/dist/fixtures/index.d.ts +4 -4
  152. package/dist/index.d.ts +8 -4
  153. package/dist/index.js +5 -2
  154. package/dist/internal/llm.d.ts +1 -1
  155. package/dist/orchestration/goal.d.ts +1 -1
  156. package/dist/orchestration/run-spec.d.ts +1 -1
  157. package/dist/orchestration/run-workflow-tool.d.ts +12 -12
  158. package/dist/orchestration/workflow-governance.d.ts +4 -4
  159. package/dist/orchestration/workflow-observe.d.ts +1 -1
  160. package/dist/orchestration/workflow-script-runner.d.ts +1 -1
  161. package/dist/orchestration/workflow-script-store.d.ts +9 -9
  162. package/dist/orchestration/workflow-size-guideline.d.ts +1 -1
  163. package/dist/orchestration/workflow-types.d.ts +5 -5
  164. package/dist/orchestration/workflow.d.ts +10 -10
  165. package/dist/prompt-assembly/artifact-store.d.ts +1 -1
  166. package/dist/prompt-assembly/artifact.d.ts +1 -1
  167. package/dist/prompt-assembly/assemble.d.ts +1 -1
  168. package/dist/prompt-assembly/composer.d.ts +2 -2
  169. package/dist/prompt-assembly/epoch.d.ts +2 -2
  170. package/dist/prompt-assembly/event-registry.d.ts +1 -1
  171. package/dist/prompt-assembly/explain.d.ts +3 -3
  172. package/dist/prompt-assembly/tool-catalog.d.ts +1 -1
  173. package/dist/prompt-assembly/turn-snapshot.d.ts +4 -4
  174. package/dist/prompt-assembly/types.d.ts +12 -12
  175. package/dist/prompts/coordinator.d.ts +1 -1
  176. package/dist/prompts/default.d.ts +10 -10
  177. package/dist/prompts/default.js +2 -0
  178. package/dist/prompts/simple-sections.d.ts +3 -3
  179. package/dist/prompts/supervisor.d.ts +2 -2
  180. package/dist/scenarios/full-body.d.ts +3 -3
  181. package/dist/scenarios/scenario-registry.d.ts +6 -2
  182. package/dist/scenarios/scenario-registry.js +4 -2
  183. package/dist/stores/cc/sidecar-transcript.d.ts +3 -3
  184. package/dist/stores/file/fs-atomic.d.ts +2 -2
  185. package/dist/stores/file/index.d.ts +1 -1
  186. package/dist/stores/file/session-store.d.ts +2 -2
  187. package/dist/stores/file/workflow-journal-store.d.ts +4 -4
  188. package/dist/tools/fs/bash-readonly-classifier.d.ts +1 -1
  189. package/dist/tools/fs/encoding.d.ts +4 -4
  190. package/dist/tools/fs/fs-bash.d.ts +3 -3
  191. package/dist/tools/fs/fs-pdf.d.ts +1 -1
  192. package/dist/tools/fs/fs-shared.d.ts +6 -6
  193. package/dist/tools/fs/index.d.ts +2 -2
  194. package/dist/tools/fs/index.js +8 -1
  195. package/dist/tools/fs/notebook.d.ts +1 -1
  196. package/dist/tools/fs/pdf.d.ts +1 -1
  197. package/dist/tools/fs/read-deny.d.ts +1 -1
  198. package/dist/tools/fs/safety.d.ts +9 -9
  199. package/dist/tools/fs/search.d.ts +2 -2
  200. package/dist/tools/monitor.d.ts +3 -3
  201. package/dist/tools/scheduler-tools.js +28 -6
  202. package/dist/tools/task-list.d.ts +2 -2
  203. package/dist/tools/web.d.ts +19 -4
  204. package/dist/tools/web.js +8 -2
  205. package/dist/tools/worktree.d.ts +5 -5
  206. package/dist/tools/worktree.js +2 -2
  207. package/package.json +1 -1
  208. package/test/export-surface.snapshot.json +74 -3
package/dist/core/mcp.js CHANGED
@@ -1175,7 +1175,7 @@ function buildResourceTools(resourceServers, isServerRevoked = () => false) {
1175
1175
  for (const rs of targets) {
1176
1176
  if (isServerRevoked(rs.server)) {
1177
1177
  errors.push({ server: rs.server, error: "server revoked by the operator mid-session (request not sent)" });
1178
- sections.push(`[${rs.server}] Error: server revoked by the operator — its resources are unavailable this turn.`);
1178
+ sections.push(`[${rs.server}] Error: server revoked by the operator mid-session the request was NOT sent. This server stays on this run's tool roster and every call to it is refused the same way, so don't retry it.`);
1179
1179
  continue;
1180
1180
  }
1181
1181
  if (rs.health.dead) {
@@ -1254,7 +1254,7 @@ function buildResourceTools(resourceServers, isServerRevoked = () => false) {
1254
1254
  const what = `The read of resource ${inlineUntrusted(uri)}`;
1255
1255
  if (isServerRevoked(server)) {
1256
1256
  return {
1257
- content: [{ type: "text", text: `${what} was refused: MCP server "${server}" was revoked by the operator mid-session. The request was NOT sent. The tool list updates at the next turn.` }],
1257
+ content: [{ type: "text", text: `${what} was refused: MCP server "${server}" was revoked by the operator mid-session. The request was NOT sent. This server stays on this run's tool roster every call to it is refused the same way, so don't retry it.` }],
1258
1258
  details: { error: "mcp.server_revoked", code: "mcp.server_revoked", server },
1259
1259
  terminate: false,
1260
1260
  isError: true,
@@ -1313,7 +1313,7 @@ function buildResourceTools(resourceServers, isServerRevoked = () => false) {
1313
1313
  const what = `The directory listing of ${inlineUntrusted(uri)}`;
1314
1314
  if (isServerRevoked(server)) {
1315
1315
  return {
1316
- content: [{ type: "text", text: `${what} was refused: MCP server "${server}" was revoked by the operator mid-session. The request was NOT sent. The tool list updates at the next turn.` }],
1316
+ content: [{ type: "text", text: `${what} was refused: MCP server "${server}" was revoked by the operator mid-session. The request was NOT sent. This server stays on this run's tool roster every call to it is refused the same way, so don't retry it.` }],
1317
1317
  details: { error: "mcp.server_revoked", code: "mcp.server_revoked", server },
1318
1318
  terminate: false,
1319
1319
  isError: true,
@@ -1587,7 +1587,7 @@ function intakeListedTools(listed, spec, client, health, imageResizer, reminderD
1587
1587
  const what = `The call to tool ${inlineUntrusted(remoteName)}`;
1588
1588
  if (isServerRevoked?.(spec.name) === true) {
1589
1589
  return {
1590
- content: [{ type: "text", text: `The call to MCP server "${spec.name}" was refused: the server was revoked by the operator mid-session. The call was NOT sent, so the server did not execute it. The tool list updates at the next turn.` }],
1590
+ content: [{ type: "text", text: `The call to MCP server "${spec.name}" was refused: the server was revoked by the operator mid-session. The call was NOT sent, so the server did not execute it. This server stays on this run's tool roster every call to it is refused the same way, so don't retry it.` }],
1591
1591
  details: { error: "mcp.server_revoked", code: "mcp.server_revoked", server: spec.name },
1592
1592
  terminate: false,
1593
1593
  isError: true,
@@ -0,0 +1,207 @@
1
+ /**
2
+ * design/376 片② — the host CONSOLIDATION DRIVER verb: one call that takes a long-accumulated,
3
+ * never-consolidated library to its fold fixpoint through the four engine verbs, as a PERSISTENT
4
+ * MULTI-CYCLE RUN.
5
+ *
6
+ * Why a run is a first-class object: under factory defaults a cold-start consolidation is a
7
+ * dozen-plus cycles with a hard 60-second throttle floor between them — a persistent job measured
8
+ * in minutes, not a call. The engine deliberately keeps NO driver state (its gate row settles per
9
+ * plan and clears the session count), so without a durable run account a process exit between two
10
+ * cycles would strand the work: no open plan to reconcile, no session edge to re-arm, and a naive
11
+ * re-entry would re-mint the whole plan at full model cost. The run row under the control plane is
12
+ * the resume seat: `runMemoryConsolidation` first looks for a PENDING run of the scope and
13
+ * CONTINUES it (same plan cache, same force requestId, zero new mint) — the idempotent re-entry
14
+ * GD-15 pins.
15
+ *
16
+ * Boundary reminders (design/339 §1.1, unchanged by this module):
17
+ * - the engine adjudicates and commits; this driver only proposes and schedules — every write
18
+ * goes through `commitConsolidationPlan`, nothing here touches the store;
19
+ * - the engine holds no model seat; the model reaches this module as an explicit chat function;
20
+ * - core still owns no timer: the throttle waits here run INSIDE a host's explicit call (or the
21
+ * auto lane's task-boundary tick), never on a background schedule of core's own.
22
+ */
23
+ import type { EngineNotice } from "../types.js";
24
+ import { type ConsolidationGateRow, type ConsolidationRunStopReason, type MemoryConsolidationOptions } from "./consolidation.js";
25
+ import { type ConsolidationPlanSummary } from "./engine.js";
26
+ import { mintLlmConsolidationPlan, type ConsolidationDriveCycleRow, type ConsolidationDriveEngine, type ConsolidationFoldState, type DistillerChatFn, type LlmConsolidationPlan, type LlmConsolidationPlanProduct, type LlmDistillerContract } from "./distiller.js";
27
+ export declare const CONSOLIDATION_DRIVER_RUNS_FILE = "distiller-runs.json";
28
+ /** §1.6-4 — the mint archive: what was shown to the model and what it answered, verbatim, per run
29
+ * (the fold law's audit other-half; strict-family placement beside the plan workbook). */
30
+ export declare const CONSOLIDATION_DRIVER_PLANS_DIR = "distiller-plans";
31
+ export interface ConsolidationDriverRunRow {
32
+ runId: string;
33
+ scope: string;
34
+ /** The WRITER claim: every open/resume bumps this under the run
35
+ * store's lock, and every later write of the row is conditional on (runId, attempt) still
36
+ * standing — of two invocations that raced onto one scope, exactly one keeps writing; the
37
+ * superseded one stops with a coded error instead of silently clobbering the winner's counters
38
+ * or terminal state. (Model spend under a genuine concurrent double-call is still at-least-once
39
+ * — the claim serializes the ACCOUNT; the engine's plan-seat CAS serializes the store, and
40
+ * cross-node cost suppression is the engine lease seat's job.) */
41
+ attempt: number;
42
+ /** The run-scoped force credential: ONE requestId across every forced snapshot of the run (the
43
+ * engine dedups the FORCED audit announcement on it — one line per run). */
44
+ requestId: string;
45
+ /** The contract version of the run's surviving MINT — stamped at open, RE-stamped (with
46
+ * {@link model}) by a resume that re-mints. */
47
+ contractVersion: string;
48
+ /** The id that minted the run's surviving plan: stamped at open; RE-stamped on a resume that
49
+ * re-mints (the plan cache absent — nothing of the opening mint survived, so the current seat
50
+ * mints every byte this run commits). The per-attempt plan archive is the byte-level anchor
51
+ * either way. */
52
+ model: string;
53
+ startedAt: number;
54
+ state: "pending" | "settled";
55
+ /** Terminal only; the ONE closed set the receipt and the `_incomplete` notice share. */
56
+ outcome?: ConsolidationRunStopReason;
57
+ settledAt?: number;
58
+ stopDetail?: string;
59
+ /** The minted plan's replayable half (mint ONCE per run; each cycle filters, never re-mints). */
60
+ planCache?: {
61
+ maxInputsPerProduct: number;
62
+ products: LlmConsolidationPlanProduct[];
63
+ };
64
+ /** The archived mint transcript's file name under {@link CONSOLIDATION_DRIVER_PLANS_DIR}. */
65
+ planArchive?: string;
66
+ /** COMMITTED cycles only (the store moved): a refused/fuse-blocked/zero-delta cycle row does
67
+ * not count — the receipt's "committed cycle(s)" label and the incomplete notice's zero-cycle
68
+ * branch both read this number as writes. */
69
+ cyclesDone: number;
70
+ /** Store-delta superseded total across EVERY invocation of this run (the conservation wall's
71
+ * evidence half — see the convergence check in the verb). Accumulated per cycle AFTER the
72
+ * commit, so a crash between a cycle's commit and its row write loses that cycle's delta —
73
+ * {@link foldBaseline} is the wall's immune second witness for exactly that window. */
74
+ entriesSuperseded: number;
75
+ /** The scope's fold-state `superseded` count at run OPEN (fresh claim; absent on rows opened
76
+ * before the field existed). The conservation wall's STORE anchor: `final.superseded − this`
77
+ * attests the run's folds even when a crash lands between a cycle's commit and its account
78
+ * write — the seat serializes this scope's folds, so the since-open delta is this run's. */
79
+ foldBaseline?: {
80
+ superseded: number;
81
+ };
82
+ planIds: string[];
83
+ usage: {
84
+ calls: number;
85
+ promptTokens: number;
86
+ completionTokens: number;
87
+ retries: number;
88
+ truncations: number;
89
+ };
90
+ repairs: {
91
+ hallucinatedMembers: number;
92
+ duplicateMembers: number;
93
+ nonIntegerMembers: number;
94
+ emptyGroups: number;
95
+ oversizeGroups: number;
96
+ };
97
+ writeFailures: Array<{
98
+ key: string;
99
+ members: number;
100
+ reason: string | null;
101
+ }>;
102
+ residue?: Array<{
103
+ name: string;
104
+ targets: number;
105
+ }>;
106
+ /** The next moment a snapshot can pass the hard throttle (resume scheduling hint). */
107
+ nextEligibleAt?: number;
108
+ }
109
+ /** Host read face: the scope's latest driver run row (pending = resumable; settled = audit). */
110
+ export declare function readConsolidationDriverRun(controlDir: string, scope: string): ConsolidationDriverRunRow | undefined;
111
+ /** Archive one mint (prompts + transcript verbatim) beside the run account. The name carries the
112
+ * CLAIM (`attempt`): two invocations that raced onto one run each archive
113
+ * under their own name, so a superseded worker can never overwrite the winning transcript — the
114
+ * run row's `planArchive` (written under the attempt guard) names the one that counts. */
115
+ export declare function distillerPlanArchiveName(runId: string, attempt: number): string;
116
+ export declare function archiveDistillerPlan(controlDir: string, runId: string, plan: LlmConsolidationPlan, attempt?: number): string;
117
+ /** The engine face this verb consumes — `MemoryEngine` satisfies it structurally. */
118
+ export interface ConsolidationDriverEngine extends ConsolidationDriveEngine {
119
+ readConsolidationGate(scope: string): ConsolidationGateRow | undefined;
120
+ listConsolidationPlans(): ConsolidationPlanSummary[];
121
+ readConsolidationFoldState(scope: string): Promise<ConsolidationFoldState>;
122
+ readonly controlPlaneRoot: string;
123
+ }
124
+ export interface RunMemoryConsolidationOptions {
125
+ /** The explicit model seat (design/339 §1.1: the ENGINE holds none). Build one with
126
+ * `openAiCompatChatSeat` or hand any JSON-only completion function. */
127
+ chat: DistillerChatFn;
128
+ /** The explicit model id, recorded into the run and the archive (alias spellings refuse). */
129
+ model: string;
130
+ /** Audit locator recorded into the archive when the seat is endpoint-shaped. */
131
+ baseUrl?: string;
132
+ /** The frozen prompt contract; default = the product v1 (dl-3 byte-identical). A different
133
+ * version here is a deployment's explicit measured choice. */
134
+ contract?: LlmDistillerContract;
135
+ /** The PROTOCOL knobs — pass the same object the engine was constructed with
136
+ * (`MemoryEngineOptions.consolidation`); the driver derives its throttle window and per-plan
137
+ * caps from it. Defaults = the factory defaults. */
138
+ consolidation?: MemoryConsolidationOptions;
139
+ /** Abort the run after this many cycles in THIS call (the run row stays pending only on crash;
140
+ * a reached bound settles `max_cycles`). Positive integer; default 64. */
141
+ maxCycles?: number;
142
+ /** R2 — the structural-repair budget over the mint's member references: the ratio of member
143
+ * claims the sanitiser had to DROP (hallucinated + duplicate + non-integer) to all member
144
+ * claims the model made. Above this, the plan does not represent the model's intent and the
145
+ * run aborts with zero writes rather than committing a harness-repaired fold. In [0, 1];
146
+ * default 0.10. (Empty groups are the same drops counted at group grain; oversize groups are
147
+ * not repairs — they ride on, named, and the schedule reports them infeasible. Format repairs
148
+ * and retries are disclosed separately and bounded by the contract's own retry count.) */
149
+ mintRepairBudget?: number;
150
+ /** The run's force credential; defaults to a minted id. A RESUMED run keeps its original. */
151
+ requestId?: string;
152
+ /** Host seams for the throttle waits (tests and hosts with their own clocks). */
153
+ now?: () => number;
154
+ sleep?: (ms: number) => Promise<void>;
155
+ onProgress?: Parameters<typeof mintLlmConsolidationPlan>[0]["onProgress"];
156
+ }
157
+ export interface ConsolidationRunReceipt {
158
+ runId: string;
159
+ scope: string;
160
+ /** Whether this call CONTINUED a pending run (no new mint) instead of starting one. */
161
+ resumed: boolean;
162
+ outcome: ConsolidationRunStopReason;
163
+ converged: boolean;
164
+ stopDetail?: string;
165
+ /** This call's cycles (store-delta accounted); `cyclesDone` is the run total across resumes. */
166
+ cycles: ConsolidationDriveCycleRow[];
167
+ cyclesDone: number;
168
+ productsCommitted: number;
169
+ entriesSuperseded: number;
170
+ planIds: string[];
171
+ model: string;
172
+ contractVersion: string;
173
+ usage: ConsolidationDriverRunRow["usage"];
174
+ repairs: ConsolidationDriverRunRow["repairs"];
175
+ writeFailures: ConsolidationDriverRunRow["writeFailures"];
176
+ residue: Array<{
177
+ name: string;
178
+ targets: number;
179
+ }>;
180
+ planArchive?: string;
181
+ /** Advisory notices minted for this run (the `_incomplete` disclosure when not converged). */
182
+ notices: EngineNotice[];
183
+ /** A failed announcement enqueue is disclosed, never allowed to fail the settled run. */
184
+ announceFailures?: string[];
185
+ }
186
+ /**
187
+ * Run (or RESUME) one scope's consolidation as a persistent multi-cycle job. See the module note
188
+ * for the shape; the per-refusal behavior implements the design/376 §1.2 failure table:
189
+ *
190
+ * - `_disabled` / a corrupt gate / an unreadable governance account BEFORE any commit: the coded
191
+ * error is rethrown (a configuration/store fault the host must repair — a pending run row, if
192
+ * one exists, stays resumable after the repair);
193
+ * - `_gated` (time/session gate): retried under the run's force requestId — the run-level force
194
+ * the design names; the FORCED audit line is engine-deduplicated to once per run;
195
+ * - `_gated` (hard throttle): the driver WAITS the window out (`min(minRunIntervalMs, 60s)`) and
196
+ * continues — a throttled cycle is scheduling, not a terminal;
197
+ * - `_busy`: never a blind snapshot retry (the seat refuses snapshots too — a blind loop spins
198
+ * forever): `reconcileConsolidation` first; a conflict/corrupt plan parks the run for the host
199
+ * valve; a live competing worker is waited out briefly, then reported;
200
+ * - model failures / a mint over the repair budget: the run settles `driver_failed` with ZERO
201
+ * writes — the mint sits between snapshot and commit, so no seat was taken, `lastRunAt` never
202
+ * moved and the time gate is not burned (the failure is free to retry);
203
+ * - a conflict plan mid-run parks (`parked`); fuse residue and the cycle bound settle
204
+ * `fuse_residue` / `max_cycles` with the residue named — the next run's FULL snapshot
205
+ * re-distills the shrunken library (cross-run = re-mint; in-run = plan cache).
206
+ */
207
+ export declare function runMemoryConsolidationDriver(engine: ConsolidationDriverEngine, scope: string, opts: RunMemoryConsolidationOptions): Promise<ConsolidationRunReceipt>;
@@ -0,0 +1,378 @@
1
+ import { join } from "node:path";
2
+ import { uuidv7 } from "../../internal/harness.js";
3
+ import { atomicWriteFileSync, ensureDirExists, lockedStrictUpdate, readStrictSidecar, enqueueMemoryAnnouncement, ControlPlaneCorruptError } from "./layout.js";
4
+ import { inlineUntrusted } from "../untrusted-text.js";
5
+ import { CONSOLIDATION_FORCE_THROTTLE_FLOOR_MS, screenConsolidationOptions, supersessionFuseCeiling, } from "./consolidation.js";
6
+ import { memoryConsolidationIncompleteNotice } from "./engine.js";
7
+ import { CONSOLIDATION_DRIVE_ABORT, CONSOLIDATION_SNAPSHOT_PARKED, MEMORY_DISTILLER_CONTRACT_V1, driveConsolidationToFixpoint, isAliasModelId, llmPlanDistiller, mintLlmConsolidationPlan, } from "./distiller.js";
8
+ export const CONSOLIDATION_DRIVER_RUNS_FILE = "distiller-runs.json";
9
+ export const CONSOLIDATION_DRIVER_PLANS_DIR = "distiller-plans";
10
+ function coerceRunsFile(raw) {
11
+ if (raw === undefined)
12
+ return { v: 1, scopes: {} };
13
+ if (typeof raw !== "object" || raw === null || raw.v !== 1 || typeof raw.scopes !== "object" || raw.scopes === null) {
14
+ throw new ControlPlaneCorruptError("memory consolidation driver run account has an unrecognized shape (fail-closed sidecar)");
15
+ }
16
+ for (const [scope, rowRaw] of Object.entries(raw.scopes)) {
17
+ const r = rowRaw;
18
+ if (typeof r !== "object" || r === null || typeof r["runId"] !== "string" || (r["state"] !== "pending" && r["state"] !== "settled") || typeof r["cyclesDone"] !== "number") {
19
+ throw new ControlPlaneCorruptError(`memory consolidation driver run row for scope ${JSON.stringify(scope)} is malformed (fail-closed sidecar)`);
20
+ }
21
+ }
22
+ return raw;
23
+ }
24
+ export function readConsolidationDriverRun(controlDir, scope) {
25
+ return coerceRunsFile(readStrictSidecar(controlDir, CONSOLIDATION_DRIVER_RUNS_FILE, "memory consolidation driver run account")).scopes[scope];
26
+ }
27
+ function writeDriverRun(controlDir, row) {
28
+ lockedStrictUpdate(controlDir, CONSOLIDATION_DRIVER_RUNS_FILE, "memory consolidation driver run account", coerceRunsFile, (file) => {
29
+ const cur = file.scopes[row.scope];
30
+ if (cur === undefined || cur.runId !== row.runId || (cur.attempt ?? 0) !== row.attempt) {
31
+ const e = new Error(`memory consolidation driver run seat for scope ${JSON.stringify(row.scope)} was taken over by a newer invocation${cur !== undefined ? ` (run ${cur.runId} attempt ${cur.attempt ?? 0})` : ""} — this worker stops without settling; the newer claim owns the account.`);
32
+ e.code = "memory.consolidation_driver_superseded";
33
+ e[CONSOLIDATION_DRIVE_ABORT] = true;
34
+ throw e;
35
+ }
36
+ file.scopes[row.scope] = row;
37
+ return { next: file, result: undefined };
38
+ });
39
+ }
40
+ function claimDriverRun(controlDir, scope, fresh) {
41
+ return lockedStrictUpdate(controlDir, CONSOLIDATION_DRIVER_RUNS_FILE, "memory consolidation driver run account", coerceRunsFile, (file) => {
42
+ const cur = file.scopes[scope];
43
+ if (cur !== undefined && cur.state === "pending") {
44
+ cur.attempt = (cur.attempt ?? 0) + 1;
45
+ file.scopes[scope] = cur;
46
+ return { next: file, result: { row: structuredClone(cur), resumed: true } };
47
+ }
48
+ const row = fresh();
49
+ file.scopes[scope] = row;
50
+ return { next: file, result: { row: structuredClone(row), resumed: false } };
51
+ });
52
+ }
53
+ export function distillerPlanArchiveName(runId, attempt) {
54
+ return `${runId}-a${attempt}.json`;
55
+ }
56
+ export function archiveDistillerPlan(controlDir, runId, plan, attempt = 1) {
57
+ ensureDirExists(join(controlDir, CONSOLIDATION_DRIVER_PLANS_DIR));
58
+ const name = distillerPlanArchiveName(runId, attempt);
59
+ atomicWriteFileSync(join(controlDir, CONSOLIDATION_DRIVER_PLANS_DIR, name), `${JSON.stringify(plan, null, 2)}\n`);
60
+ return name;
61
+ }
62
+ function driverConfigRefusal(message) {
63
+ const e = new Error(message);
64
+ e.code = "config.memory_consolidation_driver";
65
+ return e;
66
+ }
67
+ class DriverMintFailure extends Error {
68
+ detail;
69
+ constructor(detail) {
70
+ super(detail);
71
+ this.detail = detail;
72
+ }
73
+ }
74
+ const codeOf = (err) => {
75
+ const c = err?.code;
76
+ return typeof c === "string" ? c : undefined;
77
+ };
78
+ const MAX_THROTTLE_WAITS_PER_SNAPSHOT = 4;
79
+ const MAX_BUSY_WAITS = 2;
80
+ export async function runMemoryConsolidationDriver(engine, scope, opts) {
81
+ if (typeof opts?.chat !== "function")
82
+ throw driverConfigRefusal("runMemoryConsolidationDriver needs an explicit chat seat (opts.chat) — the engine holds no model seat and a driver that silently skips would publish nothing as a model's answer.");
83
+ if (typeof opts.model !== "string" || opts.model.trim() === "" || isAliasModelId(opts.model)) {
84
+ throw driverConfigRefusal(`runMemoryConsolidationDriver needs an EXPLICIT model id, got ${JSON.stringify(opts.model)} — an alias can be re-pointed upstream and would move the run with no trace in the archive.`);
85
+ }
86
+ if (opts.mintRepairBudget !== undefined && (typeof opts.mintRepairBudget !== "number" || !Number.isFinite(opts.mintRepairBudget) || opts.mintRepairBudget < 0 || opts.mintRepairBudget > 1)) {
87
+ throw driverConfigRefusal(`runMemoryConsolidationDriver: mintRepairBudget must be a number in [0, 1] when present (got ${String(opts.mintRepairBudget)}) — an unevaluable budget is refused loudly, never folded to the default.`);
88
+ }
89
+ if (opts.maxCycles !== undefined && (!Number.isInteger(opts.maxCycles) || opts.maxCycles < 1)) {
90
+ throw driverConfigRefusal(`runMemoryConsolidationDriver: maxCycles must be a positive integer when present (got ${String(opts.maxCycles)}).`);
91
+ }
92
+ const contract = opts.contract ?? MEMORY_DISTILLER_CONTRACT_V1;
93
+ if (typeof contract?.version !== "string" || contract.version === "" || typeof contract?.grouping?.instructions !== "string" || typeof contract?.writing?.instructions !== "string") {
94
+ throw driverConfigRefusal("runMemoryConsolidationDriver: opts.contract does not carry the contract shape (version + grouping/writing instructions).");
95
+ }
96
+ const screened = screenConsolidationOptions(opts.consolidation ?? {}, "carry");
97
+ const mintRepairBudget = opts.mintRepairBudget ?? 0.1;
98
+ const now = opts.now ?? Date.now;
99
+ const rawSleep = opts.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
100
+ const sleep = async (ms) => {
101
+ try {
102
+ await rawSleep(ms);
103
+ }
104
+ catch (err) {
105
+ const e = typeof err === "object" && err !== null ? err : new Error(String(err));
106
+ Reflect.set(e, CONSOLIDATION_DRIVE_ABORT, true);
107
+ throw e;
108
+ }
109
+ };
110
+ const throttleWindowMs = Math.min(screened.minRunIntervalMs, CONSOLIDATION_FORCE_THROTTLE_FLOOR_MS);
111
+ const controlDir = engine.controlPlaneRoot;
112
+ const parkedSeat = await recoverSeat(engine, scope, { sleep, windowMs: throttleWindowMs });
113
+ const openFold = await engine.readConsolidationFoldState(scope);
114
+ const { row: run, resumed } = claimDriverRun(controlDir, scope, () => ({
115
+ runId: uuidv7(),
116
+ scope,
117
+ attempt: 1,
118
+ requestId: opts.requestId ?? `consolidation-run-${uuidv7()}`,
119
+ contractVersion: contract.version,
120
+ model: opts.model,
121
+ startedAt: now(),
122
+ state: "pending",
123
+ cyclesDone: 0,
124
+ entriesSuperseded: 0,
125
+ foldBaseline: { superseded: openFold.superseded },
126
+ planIds: [],
127
+ usage: { calls: 0, promptTokens: 0, completionTokens: 0, retries: 0, truncations: 0 },
128
+ repairs: { hallucinatedMembers: 0, duplicateMembers: 0, nonIntegerMembers: 0, emptyGroups: 0, oversizeGroups: 0 },
129
+ writeFailures: [],
130
+ }));
131
+ if (resumed && run.planCache === undefined && (run.model !== opts.model || run.contractVersion !== contract.version)) {
132
+ run.model = opts.model;
133
+ run.contractVersion = contract.version;
134
+ }
135
+ const announceFailures = [];
136
+ const announce = (items) => {
137
+ try {
138
+ enqueueMemoryAnnouncement(controlDir, { kind: "gate", at: now(), items });
139
+ }
140
+ catch (err) {
141
+ announceFailures.push(err instanceof Error ? err.message : String(err));
142
+ }
143
+ };
144
+ const settle = (outcome, drive, stopDetail) => {
145
+ run.state = "settled";
146
+ run.outcome = outcome;
147
+ run.settledAt = now();
148
+ if (stopDetail !== undefined)
149
+ run.stopDetail = stopDetail;
150
+ run.residue = run.residue ?? drive?.stop?.fuseBlocked;
151
+ writeDriverRun(controlDir, run);
152
+ const notices = [];
153
+ if (outcome !== "converged") {
154
+ const notice = memoryConsolidationIncompleteNotice({ scope, runId: run.runId, stop: outcome, residueProducts: run.residue?.length ?? 0, cyclesDone: run.cyclesDone });
155
+ notices.push(notice);
156
+ announce([notice.message]);
157
+ }
158
+ return {
159
+ runId: run.runId,
160
+ scope,
161
+ resumed,
162
+ outcome,
163
+ converged: outcome === "converged",
164
+ ...(run.stopDetail !== undefined ? { stopDetail: run.stopDetail } : {}),
165
+ cycles: drive?.cycles ?? [],
166
+ cyclesDone: run.cyclesDone,
167
+ productsCommitted: drive?.productsCommitted ?? 0,
168
+ entriesSuperseded: drive?.entriesSuperseded ?? 0,
169
+ planIds: [...run.planIds],
170
+ model: run.model,
171
+ contractVersion: run.contractVersion,
172
+ usage: { ...run.usage },
173
+ repairs: { ...run.repairs },
174
+ writeFailures: [...run.writeFailures],
175
+ residue: run.residue ?? [],
176
+ ...(run.planArchive !== undefined ? { planArchive: run.planArchive } : {}),
177
+ notices,
178
+ ...(announceFailures.length > 0 ? { announceFailures: [...announceFailures] } : {}),
179
+ };
180
+ };
181
+ if (parkedSeat !== undefined) {
182
+ return settle("parked", undefined, parkedSeat);
183
+ }
184
+ let staleResidue = [];
185
+ const distill = async (candidates, snapshot, s) => {
186
+ if (run.planCache === undefined) {
187
+ let minted;
188
+ try {
189
+ minted = await mintLlmConsolidationPlan({
190
+ candidates,
191
+ chat: opts.chat,
192
+ model: opts.model,
193
+ ...(opts.baseUrl !== undefined ? { baseUrl: opts.baseUrl } : {}),
194
+ maxInputsPerProduct: screened.maxInputsPerProduct,
195
+ contract,
196
+ onProgress: opts.onProgress ?? null,
197
+ });
198
+ }
199
+ catch (err) {
200
+ if (codeOf(err) === "config.memory_consolidation_driver")
201
+ throw err;
202
+ throw new DriverMintFailure(`the model seat failed during the mint: ${err instanceof Error ? err.message : String(err)}`);
203
+ }
204
+ const transcript = minted.ok ? minted.plan.minting.transcript : minted.calls;
205
+ run.usage.calls += transcript.length;
206
+ for (const c of transcript) {
207
+ run.usage.promptTokens += c.usage?.promptTokens ?? 0;
208
+ run.usage.completionTokens += c.usage?.completionTokens ?? 0;
209
+ }
210
+ if (!minted.ok) {
211
+ throw new DriverMintFailure(minted.reason);
212
+ }
213
+ const plan = minted.plan;
214
+ run.usage.retries += plan.minting.parseRepairs.retries;
215
+ run.usage.truncations += plan.minting.parseRepairs.lengthCapped.length;
216
+ run.repairs = {
217
+ hallucinatedMembers: plan.minting.repairs.hallucinatedMembers,
218
+ duplicateMembers: plan.minting.repairs.duplicateMembers,
219
+ nonIntegerMembers: plan.minting.repairs.nonIntegerMembers,
220
+ emptyGroups: plan.minting.repairs.emptyGroups,
221
+ oversizeGroups: plan.minting.repairs.oversizeGroups.length,
222
+ };
223
+ run.writeFailures = plan.minting.writeFailures.map((w) => ({ key: w.key, members: w.members, reason: w.reason })) ?? [];
224
+ const dropped = plan.minting.repairs.hallucinatedMembers + plan.minting.repairs.duplicateMembers + plan.minting.repairs.nonIntegerMembers;
225
+ const claimed = plan.products.reduce((a, p) => a + p.inputIds.length, 0) + plan.minting.writeFailures.reduce((a, w) => a + w.members, 0);
226
+ const total = dropped + claimed;
227
+ const ratio = total === 0 ? (dropped > 0 ? 1 : 0) : dropped / total;
228
+ if (ratio > mintRepairBudget) {
229
+ throw new DriverMintFailure(`the mint needed structural repairs on ${dropped} of ${total} member claim(s) (${(ratio * 100).toFixed(1)}% > mintRepairBudget ${(mintRepairBudget * 100).toFixed(1)}%) — a plan repaired this much does not represent the model's intent, and committing it would fold entries the harness chose, not the model (hallucinated ${plan.minting.repairs.hallucinatedMembers}, duplicate ${plan.minting.repairs.duplicateMembers}, nonInteger ${plan.minting.repairs.nonIntegerMembers}; emptied groups ${plan.minting.repairs.emptyGroups}, oversize ${plan.minting.repairs.oversizeGroups.length})`);
230
+ }
231
+ run.planArchive = distillerPlanArchiveName(run.runId, run.attempt);
232
+ run.planCache = { maxInputsPerProduct: plan.maxInputsPerProduct, products: plan.products };
233
+ writeDriverRun(controlDir, run);
234
+ archiveDistillerPlan(controlDir, run.runId, plan, run.attempt);
235
+ }
236
+ staleResidue = (run.planCache?.products ?? []).flatMap((p) => {
237
+ const offered = new Map();
238
+ for (const c of candidates)
239
+ if (typeof c?.entry?.id === "string")
240
+ offered.set(c.entry.id, c.entry.rev);
241
+ const ids = p.inputIds ?? [];
242
+ const live = ids.filter((id) => offered.has(id));
243
+ if (live.length === 0)
244
+ return [];
245
+ const stale = p.inputRevs !== undefined && live.some((id) => p.inputRevs[id] !== undefined && offered.get(id) !== p.inputRevs[id]);
246
+ const vanished = live.length < ids.length;
247
+ const unanchored = p.inputRevs === undefined;
248
+ return stale || vanished || unanchored ? [{ name: p.name, targets: live.length }] : [];
249
+ });
250
+ const replayView = run.planCache !== undefined && run.planCache.products.some((p) => p.inputRevs === undefined)
251
+ ? { ...run.planCache, products: run.planCache.products.filter((p) => p.inputRevs !== undefined) }
252
+ : run.planCache;
253
+ return llmPlanDistiller(replayView)(candidates, snapshot, s);
254
+ };
255
+ let firstSnapshotOfNewRun = !resumed;
256
+ const takeSnapshot = async (_cycle) => {
257
+ writeDriverRun(controlDir, run);
258
+ let force = !firstSnapshotOfNewRun;
259
+ let throttleWaits = 0;
260
+ let busyWaits = 0;
261
+ for (;;) {
262
+ try {
263
+ const snap = await engine.snapshotConsolidationCandidates(scope, { full: true, ...(force ? { force: { requestId: run.requestId } } : {}) });
264
+ firstSnapshotOfNewRun = false;
265
+ return snap;
266
+ }
267
+ catch (err) {
268
+ const code = codeOf(err);
269
+ if (code === "memory.consolidation_gated") {
270
+ if (!force) {
271
+ force = true;
272
+ continue;
273
+ }
274
+ if (throttleWaits >= MAX_THROTTLE_WAITS_PER_SNAPSHOT)
275
+ throw err;
276
+ throttleWaits += 1;
277
+ const lastRunAt = engine.readConsolidationGate(scope)?.lastRunAt;
278
+ const until = (lastRunAt ?? now()) + throttleWindowMs;
279
+ run.nextEligibleAt = until;
280
+ writeDriverRun(controlDir, run);
281
+ const waitMs = Math.max(1, until - now());
282
+ await sleep(waitMs);
283
+ continue;
284
+ }
285
+ if (code === "memory.consolidation_busy") {
286
+ if (busyWaits >= MAX_BUSY_WAITS)
287
+ throw err;
288
+ busyWaits += 1;
289
+ const parked = await recoverSeat(engine, scope, { sleep, windowMs: throttleWindowMs });
290
+ if (parked !== undefined) {
291
+ const e = new Error(parked);
292
+ e[CONSOLIDATION_SNAPSHOT_PARKED] = true;
293
+ throw e;
294
+ }
295
+ continue;
296
+ }
297
+ throw err;
298
+ }
299
+ }
300
+ };
301
+ let drive;
302
+ try {
303
+ drive = await driveConsolidationToFixpoint(engine, scope, {
304
+ distill,
305
+ readFoldState: () => engine.readConsolidationFoldState(scope),
306
+ options: screened,
307
+ fuseCeilingOf: supersessionFuseCeiling,
308
+ requestIdPrefix: run.requestId,
309
+ maxCycles: opts.maxCycles ?? 64,
310
+ takeSnapshot,
311
+ onCycle: (row) => {
312
+ if (row.committed.products > 0 || row.committed.superseded > 0)
313
+ run.cyclesDone += 1;
314
+ run.entriesSuperseded = (run.entriesSuperseded ?? 0) + row.committed.superseded;
315
+ if (row.planId !== undefined)
316
+ run.planIds.push(row.planId);
317
+ run.nextEligibleAt = now() + throttleWindowMs;
318
+ writeDriverRun(controlDir, run);
319
+ },
320
+ });
321
+ }
322
+ catch (err) {
323
+ if (err instanceof DriverMintFailure) {
324
+ announce([`memory consolidation driver run ${run.runId} for scope ${inlineUntrusted(scope, 80)} FAILED before any write: ${inlineUntrusted(err.detail, 300)}`]);
325
+ return settle("driver_failed", undefined, err.detail);
326
+ }
327
+ throw err;
328
+ }
329
+ const writeResidue = run.writeFailures.map((w) => ({ name: w.key, targets: w.members }));
330
+ const driverResidue = [...staleResidue, ...writeResidue];
331
+ if (drive.converged) {
332
+ const plannedMembers = (run.planCache?.products ?? []).reduce((a, p) => a + (p.inputIds?.length ?? 0), 0);
333
+ const rowAccount = run.entriesSuperseded ?? 0;
334
+ const storeAccount = run.foldBaseline !== undefined ? Math.max(0, drive.foldState.after.superseded - run.foldBaseline.superseded) : rowAccount;
335
+ const supersededAttested = Math.max(rowAccount, storeAccount);
336
+ const residueTargets = driverResidue.reduce((a, r) => a + r.targets, 0);
337
+ const accounted = supersededAttested + residueTargets;
338
+ if (plannedMembers > accounted) {
339
+ run.residue = driverResidue;
340
+ return settle("driver_failed", drive, `the drive reached a fixpoint but the account does not balance: ${plannedMembers} planned member(s), ${supersededAttested} superseded attested for this run (row account ${rowAccount}, store delta ${run.foldBaseline !== undefined ? storeAccount : "unavailable — the row predates the store anchor"}), ${residueTargets} in named residue — ${plannedMembers - accounted} member(s) left the candidate set without a fold the store attests (deleted or excluded mid-run${run.foldBaseline !== undefined ? "" : ", or folded in a window whose account write did not survive"}); their entries stand (or revive with their exclusion), and a fresh run re-mints over the current library`);
341
+ }
342
+ if (driverResidue.length > 0) {
343
+ run.residue = driverResidue;
344
+ return settle("driver_failed", drive, `the drive reached the surviving plan's fixpoint, but the driver's own half left ${driverResidue.length} group(s) unfolded (${staleResidue.length} stale against edited inputs, ${writeResidue.length} writing-pass failure(s)) — their members stay as they were; a fresh run re-mints over the current bytes`);
345
+ }
346
+ return settle("converged", drive);
347
+ }
348
+ if (driverResidue.length > 0)
349
+ run.residue = [...(drive.stop?.fuseBlocked ?? []), ...driverResidue];
350
+ const stop = drive.stop;
351
+ if (stop?.fuseBlocked !== undefined)
352
+ return settle("fuse_residue", drive, stop.reason);
353
+ if (stop?.exhausted === true)
354
+ return settle("max_cycles", drive, stop.reason);
355
+ if (stop?.parked === true)
356
+ return settle("parked", drive, stop.reason);
357
+ return settle("refused", drive, stop?.reason ?? "the drive stopped without a stated reason");
358
+ }
359
+ async function recoverSeat(engine, scope, { sleep, windowMs }) {
360
+ await engine.reconcileConsolidation(scope);
361
+ let seat = engine.readConsolidationGate(scope)?.openPlanId;
362
+ if (seat === undefined)
363
+ return undefined;
364
+ const summary = engine.listConsolidationPlans().find((p) => p.planId === seat);
365
+ if (summary?.corrupt === true || summary?.state === "conflict") {
366
+ return `plan ${seat} holds the ${JSON.stringify(scope)} seat in a ${summary.corrupt === true ? "corrupt" : "conflict"} state — resolveConsolidationPlan(planId, "retry" | "discard") is the host valve; the run parks rather than stacking a second plan on the seat`;
367
+ }
368
+ await sleep(windowMs);
369
+ await engine.reconcileConsolidation(scope);
370
+ seat = engine.readConsolidationGate(scope)?.openPlanId;
371
+ if (seat === undefined)
372
+ return undefined;
373
+ const again = engine.listConsolidationPlans().find((p) => p.planId === seat);
374
+ if (again?.corrupt === true || again?.state === "conflict") {
375
+ return `plan ${seat} holds the ${JSON.stringify(scope)} seat in a ${again.corrupt === true ? "corrupt" : "conflict"} state — resolveConsolidationPlan(planId, "retry" | "discard") is the host valve; the run parks rather than stacking a second plan on the seat`;
376
+ }
377
+ return undefined;
378
+ }