import type { MemoryStore } from "./memory.js"; import { type ConsolidationLLM, type ConsolidationStats } from "./runner/memory-consolidation.js"; /** * design/84 Seam B (M6+M10) — **cursor-incremental scope consolidation**. A thin function over the pure * {@link runMemoryConsolidation}: it reads the scope's persisted cursor, feeds ONLY the notes appended * AFTER it (the incremental batch) into one reconcile pass, then — IFF the whole pass succeeds — advances * the cursor to the high-water mark it observed. This is the externally-triggerable periodic path (a * deployment's timer / N-session gate fires it); the TRIGGER and any cross-process LOCK live OUTSIDE this * function (the deployment shell's exec/persist axes — see {@link ConsolidateScopeOptions.acquire}). * * **Cursor marker semantics (no-miss / no-dup):** the cursor is an OPAQUE ordering marker the STORE defines * (this core never parses it). In the three reference stores it is a note **id** — `uuidv7`, which is * lexicographically time-sortable, so "after the cursor" is a string `>` comparison and the high-water mark * is the lexicographic MAX. Each pass: * - reads `cursor = getConsolidationCursor(scope)` (undefined ⇒ whole scope pending), * - lists the scope's note headers (stable store ordering) and keeps those with `id > cursor` AND not * already `consolidationGenerated` (a prior pass's own output — excluded so it is never re-merged), * - reconciles that incremental batch via `runMemoryConsolidation` (which itself re-excludes * `consolidationGenerated` + this-batch ids from each note's candidate set), * - advances the cursor to the high-water mark of the fed batch ∪ `stats.addedIds`, but capped STRICTLY * BELOW the smallest note this pass FAILED to process (`stats.failedIds`) — a contiguous successful-prefix * marker, not the plain max (the MAJOR1 BLOCKER: a single max marker cannot keep a sub-max failed note * pending; see {@link cappedHighWater}). * No-dup: a note with `id <= cursor` is never re-fed. No-miss: a note appended after the advance, or one at/ * above a failure floor this pass, keeps `id > cursor` (uuidv7 monotonic) and is picked up by a later pass. A * pass's own ADDs carry `consolidationGenerated:true` AND (when below the failure floor) are folded into the * high-water mark, so they are excluded twice over. * * **Whole-pass advance (decision 3):** the cursor advances (up to the success boundary) ONLY when the pass * returns (no throw). A pass that THROWS (LLM hard failure) does NOT advance at all — the batch is fully * retried next time. A pass that returns with per-note failures advances only past the contiguous successful * prefix below the smallest failure (fail-open: at worst a near-duplicate survives an extra pass, never data * loss; consistent with the memory-store contract). * * **No-op safety (the BLOCKER §2 closes):** when the store lacks the cursor pair * ({@link supportsPeriodicConsolidation} false) this is a NO-OP that calls `onWarn` — it NEVER degrades into a * full re-consolidation of the whole scope (which would re-merge already-consolidated notes forever). It is * also a no-op when the store can't consolidate at all ({@link supportsConsolidation} false) or the manifest * surface ({@link MemoryStore.listStructuredNotes}) is absent. */ export interface ConsolidateScopeDeps { store: MemoryStore; llm: ConsolidationLLM; /** Routes diagnostics (skipped decisions, no-op-because-unsupported, lock-busy) — same sink as the Runner's * `onError(phase:"memory")`. Never throws back into this function. */ onWarn?: (err: unknown) => void; /** * design/84 Seam B (TOC profile) — OPTIONAL cross-process consolidation lock. When provided, * {@link consolidateScope} acquires it for `scope` before the pass and releases it after; a `release` of * `undefined` (lock busy) makes the pass a NO-OP (another process is already consolidating this scope). * The implementation (writeThenLink + stale-PID prune) lives in the deployment shell (`stores/file`), NOT * core — core only DEFINES the injection point (the constitutional persist/exec-axis split). */ acquire?: (scope: string) => Promise<(() => void) | undefined> | (() => void) | undefined; } /** Tuning for one {@link consolidateScope} pass (mirrors the inline consolidation settings; all optional). */ export interface ConsolidateScopeOptions { band?: { lo: number; hi: number; }; searchLimit?: number; /** Cap on notes fed into ONE pass (token bound). The cursor advances only past the contiguous SUCCESSFUL * prefix of the FED batch (the first `maxNotes` pending) — never past the un-fed tail beyond the cap, and * never past a note this pass FAILED to process — so a backlog larger than `maxNotes`, and any failed note, * keep `id > cursor` and drain over successive passes without ever re-feeding the notes already handled. */ maxNotes?: number; } /** * Run ONE cursor-incremental consolidation pass for `scope` (design/84 Seam B). Returns the pass stats * (or `undefined` when it was a no-op: unsupported store, empty incremental batch, or lock busy). Never * throws for a no-op reason; a hard LLM failure inside `runMemoryConsolidation` propagates (the caller's * fire-and-forget envelope catches it) and the cursor is left UN-advanced (the batch retries next pass). */ export declare function consolidateScope(scope: string, deps: ConsolidateScopeDeps, opts?: ConsolidateScopeOptions): Promise; /** * design/84 Seam B (切片 4) — advance ONLY the cursor after an INLINE (task-end) consolidation pass has * already run, so a later periodic {@link consolidateScope} starts from the inline-processed high-water mark * (it does NOT re-consolidate the notes the inline pass just handled). The inline path consolidates the * notes the model saved THIS task (the newest, highest-id notes); advancing the cursor over the PROCESSED * ids ∪ addedIds makes inline and periodic share ONE cursor. * * **MAJOR1 BLOCKER:** the advance caps STRICTLY BELOW the smallest FAILED note (`failedIds`). A single * max-marker cursor cannot keep a sub-max failed note pending, so a low-id note whose handling failed must * stop the cursor below it even when a higher-id sibling succeeded — otherwise the failed note (id <= the * plain max) would lose `id > cursor` and be exiled from consolidation forever. Pass the pass's * `stats.consolidatedIds` (processed), `stats.addedIds`, and `stats.failedIds`. * * No-op when the store lacks the cursor pair (back-compat: an old store keeps the pure-inline behavior). * Never throws — a cursor-advance failure routes to `onWarn` (the inline notes are already durable; the * worst case is the next periodic pass re-sees them, which `runMemoryConsolidation` handles idempotently). */ export declare function advanceCursorAfterInline(store: MemoryStore, scope: string, seenNoteIds: ReadonlyArray, addedIds: ReadonlyArray, failedIds?: ReadonlyArray, onWarn?: (err: unknown) => void): Promise; //# sourceMappingURL=consolidate-scope.d.ts.map