@sema-agent/core 5.57.0 → 5.59.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.
- package/CHANGELOG.md +96 -0
- package/dist/agents/cascade.d.ts +1 -1
- package/dist/agents/cumulative-stats.d.ts +1 -1
- package/dist/agents/observer.d.ts +2 -2
- package/dist/agents/peer-admission.d.ts +1 -1
- package/dist/agents/retain-ledger.d.ts +2 -2
- package/dist/agents/roster-store.d.ts +8 -8
- package/dist/agents/send-message-tool.d.ts +2 -2
- package/dist/agents/subagent-steps.d.ts +1 -1
- package/dist/agents/subagent.d.ts +13 -13
- package/dist/agents/team.d.ts +5 -5
- package/dist/agents/tool-filter.d.ts +2 -2
- package/dist/agents/verify.d.ts +1 -1
- package/dist/bench/metrics.d.ts +35 -35
- package/dist/brain/degrading.d.ts +1 -1
- package/dist/brain/errors.d.ts +3 -3
- package/dist/brain/reasoning.d.ts +2 -2
- package/dist/brain/repetition.d.ts +1 -1
- package/dist/brain/route-adjudicator.d.ts +8 -1
- package/dist/brain/route-adjudicator.js +8 -1
- package/dist/brain/status-sink.d.ts +2 -2
- package/dist/brain/stream-shared.d.ts +1 -1
- package/dist/config/catalog.d.ts +5 -5
- package/dist/core/arg-summary.d.ts +4 -4
- package/dist/core/ask-class.d.ts +2 -2
- package/dist/core/ask-question.d.ts +1 -1
- package/dist/core/auto-compaction.d.ts +15 -15
- package/dist/core/auto-mode.d.ts +5 -5
- package/dist/core/background-agent-store.d.ts +20 -20
- package/dist/core/background-shell.d.ts +4 -4
- package/dist/core/checkpoint-store.d.ts +35 -27
- package/dist/core/context-edit.d.ts +1 -1
- package/dist/core/context-guard.d.ts +1 -1
- package/dist/core/exec-output-tail.d.ts +6 -6
- package/dist/core/file-snapshot-store.d.ts +8 -8
- package/dist/core/git-worktree-env.d.ts +3 -3
- package/dist/core/governance-codes.d.ts +37 -10
- package/dist/core/governance-codes.js +54 -1
- package/dist/core/hooks.d.ts +73 -33
- package/dist/core/hooks.js +87 -25
- package/dist/core/image-downsample.d.ts +1 -1
- package/dist/core/locked-config.d.ts +1 -1
- package/dist/core/lsp.d.ts +1 -1
- package/dist/core/mailbox-store.d.ts +1 -1
- package/dist/core/mcp.d.ts +3 -3
- package/dist/core/memory-engine/consolidation-driver.d.ts +211 -0
- package/dist/core/memory-engine/consolidation-driver.js +427 -0
- package/dist/core/memory-engine/consolidation.d.ts +119 -3
- package/dist/core/memory-engine/consolidation.js +22 -1
- package/dist/core/memory-engine/data-plane.d.ts +1 -1
- package/dist/core/memory-engine/distiller.d.ts +550 -0
- package/dist/core/memory-engine/distiller.js +598 -0
- package/dist/core/memory-engine/dual-root.d.ts +1 -1
- package/dist/core/memory-engine/engine.d.ts +143 -10
- package/dist/core/memory-engine/engine.js +149 -23
- package/dist/core/memory-engine/file-backend.d.ts +14 -2
- package/dist/core/memory-engine/file-backend.js +3 -0
- package/dist/core/memory-engine/index.d.ts +6 -3
- package/dist/core/memory-engine/index.js +4 -2
- package/dist/core/memory-engine/layout.js +20 -6
- package/dist/core/memory-engine/origin-clearance.d.ts +1 -1
- package/dist/core/memory-engine/scope-contract.d.ts +4 -4
- package/dist/core/memory-engine/sync-client.d.ts +16 -16
- package/dist/core/memory-engine/sync.d.ts +4 -4
- package/dist/core/memory-engine/types.d.ts +17 -0
- package/dist/core/memory-recall.d.ts +1 -1
- package/dist/core/memory.d.ts +2 -2
- package/dist/core/permission-rule-consent.d.ts +185 -36
- package/dist/core/permission-rule-consent.js +219 -44
- package/dist/core/permission-rule-model.d.ts +235 -31
- package/dist/core/permission-rule-model.js +105 -35
- package/dist/core/permission-rules.d.ts +9 -9
- package/dist/core/remote-env.d.ts +8 -8
- package/dist/core/roles.d.ts +3 -3
- package/dist/core/roles.js +1 -0
- package/dist/core/runner/assemble-result.d.ts +2 -2
- package/dist/core/runner/compaction-call-options.d.ts +3 -3
- package/dist/core/runner/memory-consolidation-driver.d.ts +49 -0
- package/dist/core/runner/memory-consolidation-driver.js +60 -0
- package/dist/core/runner/memory-consolidation.d.ts +1 -1
- package/dist/core/runner/prepare-config-doors.d.ts +3 -3
- package/dist/core/runner/prepare-task.d.ts +21 -21
- package/dist/core/runner/prepare-task.js +24 -17
- package/dist/core/runner/prepare-workspace-restore.d.ts +2 -2
- package/dist/core/runner/runtask.d.ts +11 -11
- package/dist/core/runner/runtask.js +4 -4
- package/dist/core/runner/session-rule-policy.d.ts +1 -1
- package/dist/core/runner/teardown-bounded.d.ts +1 -1
- package/dist/core/runner/tool-disclosure.d.ts +2 -2
- package/dist/core/runner/turn-attachments.d.ts +11 -11
- package/dist/core/scheduler.d.ts +5 -5
- package/dist/core/secret-env.d.ts +1 -1
- package/dist/core/sensitive-path-policy.d.ts +1 -1
- package/dist/core/session-policy-store.d.ts +2 -2
- package/dist/core/session-reconcile.d.ts +2 -2
- package/dist/core/session-store.d.ts +3 -3
- package/dist/core/session.d.ts +1 -1
- package/dist/core/shutdown-debug.d.ts +2 -2
- package/dist/core/side-query.d.ts +2 -2
- package/dist/core/spec-contract.d.ts +1 -1
- package/dist/core/store-contracts/contract-harness.d.ts +2 -2
- package/dist/core/store-contracts/contract-kit-version.d.ts +2 -2
- package/dist/core/store-contracts/mailbox-store-contract.d.ts +1 -1
- package/dist/core/store-contracts/mailbox-store-contract.js +1 -1
- package/dist/core/task-notification.d.ts +5 -5
- package/dist/core/task-registry-agent.d.ts +12 -12
- package/dist/core/task-registry-monitor.d.ts +1 -1
- package/dist/core/task-registry-shared.d.ts +41 -41
- package/dist/core/task-registry.d.ts +12 -12
- package/dist/core/tool-detach.d.ts +2 -2
- package/dist/core/tool-errors.d.ts +3 -3
- package/dist/core/tool-policy.d.ts +113 -28
- package/dist/core/tool-policy.js +80 -1
- package/dist/core/tool-result-budget.d.ts +1 -1
- package/dist/core/tool-result-store.d.ts +2 -2
- package/dist/core/tools.d.ts +1 -1
- package/dist/core/trace.d.ts +26 -23
- package/dist/core/types.d.ts +164 -86
- package/dist/core/types.js +4 -3
- package/dist/core/untrusted-egress.d.ts +1 -1
- package/dist/core/untrusted-text.d.ts +7 -7
- package/dist/core/wiring-manifest.d.ts +5 -5
- package/dist/core/workflow-journal-store.d.ts +14 -14
- package/dist/core/workflow-run-store-contract.d.ts +1 -1
- package/dist/core/workflow-run-store-contract.js +1 -1
- package/dist/core/workflow-run-store.d.ts +4 -4
- package/dist/engine/compaction/compaction.d.ts +3 -3
- package/dist/engine/compaction/utils.d.ts +2 -2
- package/dist/engine/execution-env/kill-tree.d.ts +1 -1
- package/dist/engine/execution-env/node-execution-env.d.ts +8 -8
- package/dist/engine/harness/agent-harness.d.ts +6 -6
- package/dist/engine/harness/messages.d.ts +1 -1
- package/dist/engine/harness/types.d.ts +10 -10
- package/dist/engine/llm/types.d.ts +14 -14
- package/dist/engine/loop/agent-loop.d.ts +3 -3
- package/dist/engine/loop/types.d.ts +4 -4
- package/dist/engine/lsp/node-lsp-manager.d.ts +2 -2
- package/dist/engine/session/import-validate.d.ts +1 -1
- package/dist/engine/session/log-digest.d.ts +1 -1
- package/dist/engine/session/memory-repo.d.ts +2 -2
- package/dist/engine/session/session.d.ts +4 -4
- package/dist/fixtures/index.d.ts +4 -4
- package/dist/index.d.ts +6 -5
- package/dist/index.js +5 -4
- package/dist/orchestration/goal.d.ts +1 -1
- package/dist/orchestration/run-spec.d.ts +1 -1
- package/dist/orchestration/run-workflow-tool.d.ts +12 -12
- package/dist/orchestration/workflow-governance.d.ts +4 -4
- package/dist/orchestration/workflow-observe.d.ts +1 -1
- package/dist/orchestration/workflow-script-runner.d.ts +1 -1
- package/dist/orchestration/workflow-script-store.d.ts +9 -9
- package/dist/orchestration/workflow-size-guideline.d.ts +1 -1
- package/dist/orchestration/workflow-types.d.ts +5 -5
- package/dist/orchestration/workflow.d.ts +10 -10
- package/dist/prompt-assembly/artifact-store.d.ts +1 -1
- package/dist/prompt-assembly/artifact.d.ts +1 -1
- package/dist/prompt-assembly/assemble.d.ts +1 -1
- package/dist/prompt-assembly/composer.d.ts +2 -2
- package/dist/prompt-assembly/epoch.d.ts +2 -2
- package/dist/prompt-assembly/event-registry.d.ts +1 -1
- package/dist/prompt-assembly/explain.d.ts +3 -3
- package/dist/prompt-assembly/tool-catalog.d.ts +1 -1
- package/dist/prompt-assembly/turn-snapshot.d.ts +4 -4
- package/dist/prompt-assembly/types.d.ts +12 -12
- package/dist/prompts/coordinator.d.ts +1 -1
- package/dist/prompts/default.d.ts +8 -8
- package/dist/prompts/simple-sections.d.ts +3 -3
- package/dist/prompts/supervisor.d.ts +2 -2
- package/dist/scenarios/full-body.d.ts +3 -3
- package/dist/scenarios/scenario-registry.d.ts +1 -1
- package/dist/stores/cc/sidecar-transcript.d.ts +3 -3
- package/dist/stores/file/fs-atomic.d.ts +2 -2
- package/dist/stores/file/index.d.ts +1 -1
- package/dist/stores/file/session-store.d.ts +2 -2
- package/dist/stores/file/workflow-journal-store.d.ts +4 -4
- package/dist/tools/fs/bash-readonly-classifier.d.ts +1 -1
- package/dist/tools/fs/encoding.d.ts +4 -4
- package/dist/tools/fs/fs-bash.d.ts +3 -3
- package/dist/tools/fs/fs-pdf.d.ts +1 -1
- package/dist/tools/fs/fs-shared.d.ts +6 -6
- package/dist/tools/fs/index.d.ts +2 -2
- package/dist/tools/fs/notebook.d.ts +1 -1
- package/dist/tools/fs/pdf.d.ts +1 -1
- package/dist/tools/fs/read-deny.d.ts +1 -1
- package/dist/tools/fs/safety.d.ts +9 -9
- package/dist/tools/fs/search.d.ts +2 -2
- package/dist/tools/monitor.d.ts +3 -3
- package/dist/tools/task-list.d.ts +2 -2
- package/dist/tools/web.d.ts +4 -4
- package/dist/tools/worktree.d.ts +5 -5
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +1826 -3
|
@@ -0,0 +1,211 @@
|
|
|
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 ConsolidationPlanFoldEvidence, 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
|
+
* (`readConsolidationPlanFoldEvidence` joined in design/376 slice ③, #436: the conservation
|
|
119
|
+
* wall's terminal witness is member-level, so the face is REQUIRED — an engine without it could
|
|
120
|
+
* only offer the store-delta counts an interleaved fold can numerically offset.) */
|
|
121
|
+
export interface ConsolidationDriverEngine extends ConsolidationDriveEngine {
|
|
122
|
+
readConsolidationGate(scope: string): ConsolidationGateRow | undefined;
|
|
123
|
+
listConsolidationPlans(): ConsolidationPlanSummary[];
|
|
124
|
+
readConsolidationPlanFoldEvidence(planId: string): ConsolidationPlanFoldEvidence;
|
|
125
|
+
readConsolidationFoldState(scope: string): Promise<ConsolidationFoldState>;
|
|
126
|
+
readonly controlPlaneRoot: string;
|
|
127
|
+
}
|
|
128
|
+
export interface RunMemoryConsolidationOptions {
|
|
129
|
+
/** The explicit model seat (design/339 §1.1: the ENGINE holds none). Build one with
|
|
130
|
+
* `openAiCompatChatSeat` or hand any JSON-only completion function. */
|
|
131
|
+
chat: DistillerChatFn;
|
|
132
|
+
/** The explicit model id, recorded into the run and the archive (alias spellings refuse). */
|
|
133
|
+
model: string;
|
|
134
|
+
/** Audit locator recorded into the archive when the seat is endpoint-shaped. */
|
|
135
|
+
baseUrl?: string;
|
|
136
|
+
/** The frozen prompt contract; default = the product v1 (dl-3 byte-identical). A different
|
|
137
|
+
* version here is a deployment's explicit measured choice. */
|
|
138
|
+
contract?: LlmDistillerContract;
|
|
139
|
+
/** The PROTOCOL knobs — pass the same object the engine was constructed with
|
|
140
|
+
* (`MemoryEngineOptions.consolidation`); the driver derives its throttle window and per-plan
|
|
141
|
+
* caps from it. Defaults = the factory defaults. */
|
|
142
|
+
consolidation?: MemoryConsolidationOptions;
|
|
143
|
+
/** Abort the run after this many cycles in THIS call (the run row stays pending only on crash;
|
|
144
|
+
* a reached bound settles `max_cycles`). Positive integer; default 64. */
|
|
145
|
+
maxCycles?: number;
|
|
146
|
+
/** R2 — the structural-repair budget over the mint's member references: the ratio of member
|
|
147
|
+
* claims the sanitiser had to DROP (hallucinated + duplicate + non-integer) to all member
|
|
148
|
+
* claims the model made. Above this, the plan does not represent the model's intent and the
|
|
149
|
+
* run aborts with zero writes rather than committing a harness-repaired fold. In [0, 1];
|
|
150
|
+
* default 0.10. (Empty groups are the same drops counted at group grain; oversize groups are
|
|
151
|
+
* not repairs — they ride on, named, and the schedule reports them infeasible. Format repairs
|
|
152
|
+
* and retries are disclosed separately and bounded by the contract's own retry count.) */
|
|
153
|
+
mintRepairBudget?: number;
|
|
154
|
+
/** The run's force credential; defaults to a minted id. A RESUMED run keeps its original. */
|
|
155
|
+
requestId?: string;
|
|
156
|
+
/** Host seams for the throttle waits (tests and hosts with their own clocks). */
|
|
157
|
+
now?: () => number;
|
|
158
|
+
sleep?: (ms: number) => Promise<void>;
|
|
159
|
+
onProgress?: Parameters<typeof mintLlmConsolidationPlan>[0]["onProgress"];
|
|
160
|
+
}
|
|
161
|
+
export interface ConsolidationRunReceipt {
|
|
162
|
+
runId: string;
|
|
163
|
+
scope: string;
|
|
164
|
+
/** Whether this call CONTINUED a pending run (no new mint) instead of starting one. */
|
|
165
|
+
resumed: boolean;
|
|
166
|
+
outcome: ConsolidationRunStopReason;
|
|
167
|
+
converged: boolean;
|
|
168
|
+
stopDetail?: string;
|
|
169
|
+
/** This call's cycles (store-delta accounted); `cyclesDone` is the run total across resumes. */
|
|
170
|
+
cycles: ConsolidationDriveCycleRow[];
|
|
171
|
+
cyclesDone: number;
|
|
172
|
+
productsCommitted: number;
|
|
173
|
+
entriesSuperseded: number;
|
|
174
|
+
planIds: string[];
|
|
175
|
+
model: string;
|
|
176
|
+
contractVersion: string;
|
|
177
|
+
usage: ConsolidationDriverRunRow["usage"];
|
|
178
|
+
repairs: ConsolidationDriverRunRow["repairs"];
|
|
179
|
+
writeFailures: ConsolidationDriverRunRow["writeFailures"];
|
|
180
|
+
residue: Array<{
|
|
181
|
+
name: string;
|
|
182
|
+
targets: number;
|
|
183
|
+
}>;
|
|
184
|
+
planArchive?: string;
|
|
185
|
+
/** Advisory notices minted for this run (the `_incomplete` disclosure when not converged). */
|
|
186
|
+
notices: EngineNotice[];
|
|
187
|
+
/** A failed announcement enqueue is disclosed, never allowed to fail the settled run. */
|
|
188
|
+
announceFailures?: string[];
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Run (or RESUME) one scope's consolidation as a persistent multi-cycle job. See the module note
|
|
192
|
+
* for the shape; the per-refusal behavior implements the design/376 §1.2 failure table:
|
|
193
|
+
*
|
|
194
|
+
* - `_disabled` / a corrupt gate / an unreadable governance account BEFORE any commit: the coded
|
|
195
|
+
* error is rethrown (a configuration/store fault the host must repair — a pending run row, if
|
|
196
|
+
* one exists, stays resumable after the repair);
|
|
197
|
+
* - `_gated` (time/session gate): retried under the run's force requestId — the run-level force
|
|
198
|
+
* the design names; the FORCED audit line is engine-deduplicated to once per run;
|
|
199
|
+
* - `_gated` (hard throttle): the driver WAITS the window out (`min(minRunIntervalMs, 60s)`) and
|
|
200
|
+
* continues — a throttled cycle is scheduling, not a terminal;
|
|
201
|
+
* - `_busy`: never a blind snapshot retry (the seat refuses snapshots too — a blind loop spins
|
|
202
|
+
* forever): `reconcileConsolidation` first; a conflict/corrupt plan parks the run for the host
|
|
203
|
+
* valve; a live competing worker is waited out briefly, then reported;
|
|
204
|
+
* - model failures / a mint over the repair budget: the run settles `driver_failed` with ZERO
|
|
205
|
+
* writes — the mint sits between snapshot and commit, so no seat was taken, `lastRunAt` never
|
|
206
|
+
* moved and the time gate is not burned (the failure is free to retry);
|
|
207
|
+
* - a conflict plan mid-run parks (`parked`); fuse residue and the cycle bound settle
|
|
208
|
+
* `fuse_residue` / `max_cycles` with the residue named — the next run's FULL snapshot
|
|
209
|
+
* re-distills the shrunken library (cross-run = re-mint; in-run = plan cache).
|
|
210
|
+
*/
|
|
211
|
+
export declare function runMemoryConsolidationDriver(engine: ConsolidationDriverEngine, scope: string, opts: RunMemoryConsolidationOptions): Promise<ConsolidationRunReceipt>;
|
|
@@ -0,0 +1,427 @@
|
|
|
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
|
+
let staleResidueMemberIds = new Set();
|
|
186
|
+
const distill = async (candidates, snapshot, s) => {
|
|
187
|
+
if (run.planCache === undefined) {
|
|
188
|
+
let minted;
|
|
189
|
+
try {
|
|
190
|
+
minted = await mintLlmConsolidationPlan({
|
|
191
|
+
candidates,
|
|
192
|
+
chat: opts.chat,
|
|
193
|
+
model: opts.model,
|
|
194
|
+
...(opts.baseUrl !== undefined ? { baseUrl: opts.baseUrl } : {}),
|
|
195
|
+
maxInputsPerProduct: screened.maxInputsPerProduct,
|
|
196
|
+
contract,
|
|
197
|
+
onProgress: opts.onProgress ?? null,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
catch (err) {
|
|
201
|
+
if (codeOf(err) === "config.memory_consolidation_driver")
|
|
202
|
+
throw err;
|
|
203
|
+
throw new DriverMintFailure(`the model seat failed during the mint: ${err instanceof Error ? err.message : String(err)}`);
|
|
204
|
+
}
|
|
205
|
+
const transcript = minted.ok ? minted.plan.minting.transcript : minted.calls;
|
|
206
|
+
run.usage.calls += transcript.length;
|
|
207
|
+
for (const c of transcript) {
|
|
208
|
+
run.usage.promptTokens += c.usage?.promptTokens ?? 0;
|
|
209
|
+
run.usage.completionTokens += c.usage?.completionTokens ?? 0;
|
|
210
|
+
}
|
|
211
|
+
if (!minted.ok) {
|
|
212
|
+
throw new DriverMintFailure(minted.reason);
|
|
213
|
+
}
|
|
214
|
+
const plan = minted.plan;
|
|
215
|
+
run.usage.retries += plan.minting.parseRepairs.retries;
|
|
216
|
+
run.usage.truncations += plan.minting.parseRepairs.lengthCapped.length;
|
|
217
|
+
run.repairs = {
|
|
218
|
+
hallucinatedMembers: plan.minting.repairs.hallucinatedMembers,
|
|
219
|
+
duplicateMembers: plan.minting.repairs.duplicateMembers,
|
|
220
|
+
nonIntegerMembers: plan.minting.repairs.nonIntegerMembers,
|
|
221
|
+
emptyGroups: plan.minting.repairs.emptyGroups,
|
|
222
|
+
oversizeGroups: plan.minting.repairs.oversizeGroups.length,
|
|
223
|
+
};
|
|
224
|
+
run.writeFailures = plan.minting.writeFailures.map((w) => ({ key: w.key, members: w.members, reason: w.reason })) ?? [];
|
|
225
|
+
const dropped = plan.minting.repairs.hallucinatedMembers + plan.minting.repairs.duplicateMembers + plan.minting.repairs.nonIntegerMembers;
|
|
226
|
+
const claimed = plan.products.reduce((a, p) => a + p.inputIds.length, 0) + plan.minting.writeFailures.reduce((a, w) => a + w.members, 0);
|
|
227
|
+
const total = dropped + claimed;
|
|
228
|
+
const ratio = total === 0 ? (dropped > 0 ? 1 : 0) : dropped / total;
|
|
229
|
+
if (ratio > mintRepairBudget) {
|
|
230
|
+
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})`);
|
|
231
|
+
}
|
|
232
|
+
run.planArchive = distillerPlanArchiveName(run.runId, run.attempt);
|
|
233
|
+
run.planCache = { maxInputsPerProduct: plan.maxInputsPerProduct, products: plan.products };
|
|
234
|
+
writeDriverRun(controlDir, run);
|
|
235
|
+
archiveDistillerPlan(controlDir, run.runId, plan, run.attempt);
|
|
236
|
+
}
|
|
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
|
+
staleResidueMemberIds = new Set();
|
|
242
|
+
staleResidue = (run.planCache?.products ?? []).flatMap((p) => {
|
|
243
|
+
const ids = p.inputIds ?? [];
|
|
244
|
+
const live = ids.filter((id) => offered.has(id));
|
|
245
|
+
if (live.length === 0)
|
|
246
|
+
return [];
|
|
247
|
+
const stale = p.inputRevs !== undefined && live.some((id) => p.inputRevs[id] !== undefined && offered.get(id) !== p.inputRevs[id]);
|
|
248
|
+
const vanished = live.length < ids.length;
|
|
249
|
+
const unanchored = p.inputRevs === undefined;
|
|
250
|
+
if (!(stale || vanished || unanchored))
|
|
251
|
+
return [];
|
|
252
|
+
for (const id of live)
|
|
253
|
+
staleResidueMemberIds.add(id);
|
|
254
|
+
return [{ name: p.name, targets: live.length }];
|
|
255
|
+
});
|
|
256
|
+
const replayView = run.planCache !== undefined && run.planCache.products.some((p) => p.inputRevs === undefined)
|
|
257
|
+
? { ...run.planCache, products: run.planCache.products.filter((p) => p.inputRevs !== undefined) }
|
|
258
|
+
: run.planCache;
|
|
259
|
+
return llmPlanDistiller(replayView)(candidates, snapshot, s);
|
|
260
|
+
};
|
|
261
|
+
let firstSnapshotOfNewRun = !resumed;
|
|
262
|
+
const takeSnapshot = async (_cycle) => {
|
|
263
|
+
writeDriverRun(controlDir, run);
|
|
264
|
+
let force = !firstSnapshotOfNewRun;
|
|
265
|
+
let throttleWaits = 0;
|
|
266
|
+
let busyWaits = 0;
|
|
267
|
+
for (;;) {
|
|
268
|
+
try {
|
|
269
|
+
const snap = await engine.snapshotConsolidationCandidates(scope, { full: true, ...(force ? { force: { requestId: run.requestId } } : {}) });
|
|
270
|
+
firstSnapshotOfNewRun = false;
|
|
271
|
+
return snap;
|
|
272
|
+
}
|
|
273
|
+
catch (err) {
|
|
274
|
+
const code = codeOf(err);
|
|
275
|
+
if (code === "memory.consolidation_gated") {
|
|
276
|
+
if (!force) {
|
|
277
|
+
force = true;
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
if (throttleWaits >= MAX_THROTTLE_WAITS_PER_SNAPSHOT)
|
|
281
|
+
throw err;
|
|
282
|
+
throttleWaits += 1;
|
|
283
|
+
const lastRunAt = engine.readConsolidationGate(scope)?.lastRunAt;
|
|
284
|
+
const until = (lastRunAt ?? now()) + throttleWindowMs;
|
|
285
|
+
run.nextEligibleAt = until;
|
|
286
|
+
writeDriverRun(controlDir, run);
|
|
287
|
+
const waitMs = Math.max(1, until - now());
|
|
288
|
+
await sleep(waitMs);
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
if (code === "memory.consolidation_busy") {
|
|
292
|
+
if (busyWaits >= MAX_BUSY_WAITS)
|
|
293
|
+
throw err;
|
|
294
|
+
busyWaits += 1;
|
|
295
|
+
const parked = await recoverSeat(engine, scope, { sleep, windowMs: throttleWindowMs });
|
|
296
|
+
if (parked !== undefined) {
|
|
297
|
+
const e = new Error(parked);
|
|
298
|
+
e[CONSOLIDATION_SNAPSHOT_PARKED] = true;
|
|
299
|
+
throw e;
|
|
300
|
+
}
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
throw err;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
let drive;
|
|
308
|
+
try {
|
|
309
|
+
drive = await driveConsolidationToFixpoint(engine, scope, {
|
|
310
|
+
distill,
|
|
311
|
+
readFoldState: () => engine.readConsolidationFoldState(scope),
|
|
312
|
+
options: screened,
|
|
313
|
+
fuseCeilingOf: supersessionFuseCeiling,
|
|
314
|
+
requestIdPrefix: run.requestId,
|
|
315
|
+
maxCycles: opts.maxCycles ?? 64,
|
|
316
|
+
takeSnapshot,
|
|
317
|
+
onCycle: (row) => {
|
|
318
|
+
if (row.committed.products > 0 || row.committed.superseded > 0)
|
|
319
|
+
run.cyclesDone += 1;
|
|
320
|
+
run.entriesSuperseded = (run.entriesSuperseded ?? 0) + row.committed.superseded;
|
|
321
|
+
if (row.planId !== undefined)
|
|
322
|
+
run.planIds.push(row.planId);
|
|
323
|
+
run.nextEligibleAt = now() + throttleWindowMs;
|
|
324
|
+
writeDriverRun(controlDir, run);
|
|
325
|
+
},
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
catch (err) {
|
|
329
|
+
if (err instanceof DriverMintFailure) {
|
|
330
|
+
announce([`memory consolidation driver run ${run.runId} for scope ${inlineUntrusted(scope, 80)} FAILED before any write: ${inlineUntrusted(err.detail, 300)}`]);
|
|
331
|
+
return settle("driver_failed", undefined, err.detail);
|
|
332
|
+
}
|
|
333
|
+
throw err;
|
|
334
|
+
}
|
|
335
|
+
const writeResidue = run.writeFailures.map((w) => ({ name: w.key, targets: w.members }));
|
|
336
|
+
const driverResidue = [...staleResidue, ...writeResidue];
|
|
337
|
+
if (drive.converged) {
|
|
338
|
+
const plannedIds = [...new Set((run.planCache?.products ?? []).flatMap((p) => p.inputIds ?? []))];
|
|
339
|
+
const plannedMembers = (run.planCache?.products ?? []).reduce((a, p) => a + (p.inputIds?.length ?? 0), 0);
|
|
340
|
+
const rowAccount = run.entriesSuperseded ?? 0;
|
|
341
|
+
const storeAccount = run.foldBaseline !== undefined ? Math.max(0, drive.foldState.after.superseded - run.foldBaseline.superseded) : rowAccount;
|
|
342
|
+
const supersededAttested = Math.max(rowAccount, storeAccount);
|
|
343
|
+
const residueTargets = driverResidue.reduce((a, r) => a + r.targets, 0);
|
|
344
|
+
const accounted = supersededAttested + residueTargets;
|
|
345
|
+
if (plannedMembers > accounted) {
|
|
346
|
+
run.residue = driverResidue;
|
|
347
|
+
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`);
|
|
348
|
+
}
|
|
349
|
+
if (plannedIds.length > 0) {
|
|
350
|
+
const evidence = collectRunFoldEvidence(engine, scope, run.requestId);
|
|
351
|
+
if (evidence.skipped.length > 0) {
|
|
352
|
+
const named = evidence.skipped.slice(0, 5).map((s) => `${s.planId} (${s.state})`).join(", ");
|
|
353
|
+
announce([
|
|
354
|
+
`memory consolidation conservation wall for scope ${inlineUntrusted(scope, 80)}: ${evidence.skipped.length} same-scope plan file(s) answered unreadable and were SKIPPED as foreign to run ${run.runId} — they attest nothing for this run and need out-of-band repair: ${named}${evidence.skipped.length > 5 ? ", …" : ""}`,
|
|
355
|
+
]);
|
|
356
|
+
}
|
|
357
|
+
if (evidence.unreadable !== undefined) {
|
|
358
|
+
run.residue = driverResidue;
|
|
359
|
+
return settle("driver_failed", drive, `the drive reached a fixpoint but the member-level fold evidence is incomplete: plan ${evidence.unreadable} — one of THIS run's own cycles — answered ${evidence.unreadableState} (control-plane damage on run-owned evidence, not a transient race); a converged label needs attestable members; the library stands as committed and a fresh run re-mints over the current bytes`);
|
|
360
|
+
}
|
|
361
|
+
const missing = plannedIds.filter((id) => !evidence.targets.has(id) && !staleResidueMemberIds.has(id));
|
|
362
|
+
if (missing.length > 0) {
|
|
363
|
+
run.residue = driverResidue;
|
|
364
|
+
return settle("driver_failed", drive, `the drive reached a fixpoint but ${missing.length} of ${plannedIds.length} planned member(s) left the candidate set without a fold this run's plans attest (deleted, excluded, or superseded by another writer mid-run; e.g. ${missing.slice(0, 5).join(", ")}) — a store-delta count can be numerically offset by an unrelated interleaved fold, so member identity is the final judge; the entries stand (or revive with their exclusion) and a fresh run re-mints over the current library`);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
if (driverResidue.length > 0) {
|
|
368
|
+
run.residue = driverResidue;
|
|
369
|
+
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`);
|
|
370
|
+
}
|
|
371
|
+
return settle("converged", drive);
|
|
372
|
+
}
|
|
373
|
+
if (driverResidue.length > 0)
|
|
374
|
+
run.residue = [...(drive.stop?.fuseBlocked ?? []), ...driverResidue];
|
|
375
|
+
const stop = drive.stop;
|
|
376
|
+
if (stop?.fuseBlocked !== undefined)
|
|
377
|
+
return settle("fuse_residue", drive, stop.reason);
|
|
378
|
+
if (stop?.exhausted === true)
|
|
379
|
+
return settle("max_cycles", drive, stop.reason);
|
|
380
|
+
if (stop?.parked === true)
|
|
381
|
+
return settle("parked", drive, stop.reason);
|
|
382
|
+
return settle("refused", drive, stop?.reason ?? "the drive stopped without a stated reason");
|
|
383
|
+
}
|
|
384
|
+
function collectRunFoldEvidence(engine, scope, requestId) {
|
|
385
|
+
const cycleId = new RegExp(`^${requestId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}-c\\d+$`);
|
|
386
|
+
const targets = new Set();
|
|
387
|
+
const skipped = [];
|
|
388
|
+
for (const summary of engine.listConsolidationPlans()) {
|
|
389
|
+
if (summary.corrupt === true)
|
|
390
|
+
continue;
|
|
391
|
+
if (summary.scope !== scope)
|
|
392
|
+
continue;
|
|
393
|
+
const ev = engine.readConsolidationPlanFoldEvidence(summary.planId);
|
|
394
|
+
if (ev.state !== "ok") {
|
|
395
|
+
if (ev.state === "corrupt" && ev.requestId !== undefined && cycleId.test(ev.requestId)) {
|
|
396
|
+
return { targets, unreadable: summary.planId, unreadableState: ev.state, skipped };
|
|
397
|
+
}
|
|
398
|
+
skipped.push({ planId: summary.planId, state: ev.state });
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
if (ev.scope !== scope || !cycleId.test(ev.requestId))
|
|
402
|
+
continue;
|
|
403
|
+
for (const t of ev.supersededTargets)
|
|
404
|
+
targets.add(t);
|
|
405
|
+
}
|
|
406
|
+
return { targets, skipped };
|
|
407
|
+
}
|
|
408
|
+
async function recoverSeat(engine, scope, { sleep, windowMs }) {
|
|
409
|
+
await engine.reconcileConsolidation(scope);
|
|
410
|
+
let seat = engine.readConsolidationGate(scope)?.openPlanId;
|
|
411
|
+
if (seat === undefined)
|
|
412
|
+
return undefined;
|
|
413
|
+
const summary = engine.listConsolidationPlans().find((p) => p.planId === seat);
|
|
414
|
+
if (summary?.corrupt === true || summary?.state === "conflict") {
|
|
415
|
+
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`;
|
|
416
|
+
}
|
|
417
|
+
await sleep(windowMs);
|
|
418
|
+
await engine.reconcileConsolidation(scope);
|
|
419
|
+
seat = engine.readConsolidationGate(scope)?.openPlanId;
|
|
420
|
+
if (seat === undefined)
|
|
421
|
+
return undefined;
|
|
422
|
+
const again = engine.listConsolidationPlans().find((p) => p.planId === seat);
|
|
423
|
+
if (again?.corrupt === true || again?.state === "conflict") {
|
|
424
|
+
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`;
|
|
425
|
+
}
|
|
426
|
+
return undefined;
|
|
427
|
+
}
|