import type { AgentMessage } from "../internal/harness.js"; import { type MemorySelector, encodeSurfacedKey } from "./memory-recall.js"; import type { MemoryStore } from "./memory.js"; /** * design/86 §3 缺口 A — DYNAMIC memory re-recall seam (CC `processSessionStartHooks('compact')` parity). * * PROBLEM (design/86 §3): our memory is a one-shot snapshot taken at task start (`prepareTask` → * `selectAndComposeMemory`/`Layered`, injected into the STABLE system prefix). A long task that runs many * turns and compacts loses the *fine detail* of its early turns; we never re-select more-relevant memory for * the conversation as it has evolved. CC re-evaluates relevant memories after every compaction. * * THIS SEAM (default OFF, data-gated like Seam C): after a REAL within-task compaction, re-run the EXISTING * selector + `selectAndComposeMemory`/`Layered` against the CURRENT conversation context (recent messages), * de-dup against everything already surfaced (`priorSurfacedIds`/`priorSurfacedKeys`, the same machinery the * initial recall uses), and inject any NEW relevant notes as a TAIL ATTACHMENT user message — NEVER touching * the stable system prefix (touching the prefix would break the 90–98% prefix-cache the whole stable-prefix * design rests on). * * Security (design/86 §3 ⑤): the bodies re-recalled here come back through the SAME read API * (`listStructuredNotes`/`getByIds`) and the SAME compose path (`selectAndComposeMemory`/`Layered`, which wrap * in `RECALL_CAVEAT` + ``) the INITIAL recall uses. Every note in the store has * already passed the WRITE-side secret gate (`guardedMemoryStore` → `enforceStructuredNoteSecretGate`); this * path adds NO new write inlet and NO new read inlet — it reuses the audited initial-recall pipeline verbatim, * so it cannot surface anything the initial recall couldn't, and it cannot leak a secret the write gate * already rejected. The de-dup also guarantees an already-shown body is never re-injected. */ /** Trust-injected configuration for the dynamic re-recall seam (lives on `RunnerDeps`, never `TaskSpec` — * it is part of the `memoryRecall` function-seam family, off by default). * * @deprecated design/138 S4 — the dynamic re-recall seam served ONLY the retired legacy memoryStore * injection path; prepareTask never wires it anymore (the runtask hook is dead-by-input). Removal is the * next major. */ export interface DynamicRecallConfig { /** Master switch. Absent/false ⇒ no dynamic re-recall (byte-identical to current behavior). */ enabled?: boolean; /** How many of the most-recent conversation messages form the re-recall query context. Default 6. */ recentMessageWindow?: number; /** Max chars of recent-message text fed to the selector as the query objective. Default 4000. */ maxQueryChars?: number; } export declare const DEFAULT_RECENT_MESSAGE_WINDOW = 6; export declare const DEFAULT_MAX_QUERY_CHARS = 4000; /** * Build the re-recall QUERY string from the recent conversation messages. Pure + deterministic. Takes the * last `window` messages, extracts their text, and joins/caps to `maxChars` (keeping the TAIL — the most * recent text — when over the cap, since the latest turns are the most relevant query signal). Returns "" * when there is no usable text (caller then skips re-recall: nothing to query on). */ export declare function buildRecallQuery(messages: readonly AgentMessage[], window?: number, maxChars?: number): string; /** What a single dynamic re-recall attempt produced. `block` present ⇒ the caller injects it as a tail * user message; `surfaced*` are the NEW ids/keys to fold into the run's durable surfaced set. A no-new / * degrade / disabled outcome yields `block: undefined` and empty surfaced sets (caller injects nothing). */ export interface DynamicRecallOutcome { block: string | undefined; /** Single-scope: note ids whose bodies were newly injected this attempt. */ surfacedIds: readonly string[]; /** Multi-scope (layered): `(scope,id)` composite keys whose bodies were newly injected this attempt. */ surfacedKeys: readonly string[]; } /** Inputs the Runner threads into one dynamic re-recall attempt. Mirrors the initial-recall call sites in * `prepare-task.ts` (single-scope vs layered), so the two paths share the exact same selector pipeline. */ export interface DynamicRecallInput { store: MemoryStore; /** The scope list (1 ⇒ single-scope path; >1 ⇒ layered path). Same `scopes` as the initial recall. */ scopes: readonly string[]; selector: MemorySelector; /** Query objective derived from the CURRENT conversation (see {@link buildRecallQuery}). */ query: string; recentTools?: string[]; maxSelected?: number; maxLinked?: number; timeoutMs?: number; signal?: AbortSignal; nowMs?: number; /** Single-scope durable de-dup: note ids already surfaced (initial recall + prior re-recalls). */ priorSurfacedIds?: ReadonlySet; /** Multi-scope durable de-dup: `(scope,id)` keys already surfaced. */ priorSurfacedKeys?: ReadonlySet; } /** * Run ONE dynamic re-recall against the current conversation. Reuses `selectAndComposeMemory` (single-scope) * or `selectAndComposeLayeredMemory` (multi-scope) — the SAME functions the initial recall uses — so the * dedup, the manifest, the fence, the caveat, and the degrade/no-new branches are all identical. * * Crucially, on a DEGRADE the dynamic seam injects NOTHING (returns `block: undefined`). This differs from * the INITIAL recall, which degrades to inject-all: at task start, degrade-to-inject-all is the safe floor * (never start a task with NO memory). MID-TASK, the stable prefix ALREADY holds the initial inject — so a * degrade here means "couldn't pick anything NEW relevant", and re-dumping the whole scope as a tail * attachment would (a) duplicate the prefix's memory and (b) explode the tail / break the cache. So the * dynamic seam treats degrade exactly like no-new: inject nothing, surface nothing. (This is the security- * and cost-correct choice: the seam can only ADD relevant tail notes, never re-dump or fail open.) * * @deprecated design/138 S4 — the dynamic re-recall seam served ONLY the retired legacy memoryStore * injection path; prepareTask never wires it anymore (the runtask hook is dead-by-input). Removal is the * next major. */ export declare function runDynamicRecall(input: DynamicRecallInput): Promise; /** Re-export so the wiring + tests have one import site for the composite key encoder. */ export { encodeSurfacedKey }; //# sourceMappingURL=dynamic-recall.d.ts.map