export type RememberOptions = { /** * - LLM provider implementing `generate(messages, tools, opts)`. */ provider: Provider; /** * - The Store socket (or a `Memory` wrapper). remember writes through * `.store(content, metadata)` ONLY — no other verb, no backend assumptions. */ store: Pick; /** * - Optional definition of what counts as durable for this task; appended to the * distiller prompt to steer it (the A3 contract idea, reused on the write side). */ contract?: string | undefined; /** * - Merged into every stored fact's metadata. `kind` is always `'fact'` * (NOT overridable — `remember` writes facts; `fact` is litectx's canonical durable kind and harmless to other * stores, so the stored label always matches the `facts` counter). Add ANY OTHER metadata here (tags, or litectx * `format`/`scope` to differentiate facts). NEVER carries ctx — ctx rides in its own option, never persisted. */ metadata?: Record | undefined; /** * - The run ctx. If it carries * the loop-lent `recordMemoryOp`, each fact write counts against `result.metrics.memory.facts`. */ ctx?: ({ recordMemoryOp?: (kind: string) => void; } & Record) | undefined; /** * - Budget hook; * each distill pass forwards usage so a bareguard gate keeps counting (mirror of Evaluator). */ onLlmResult?: ((payload: { usage: any; model: string | null; kind: "remember"; }) => any) | undefined; /** * - Override the distiller system prompt (defaults to the F5-validated one). */ prompt?: string | undefined; }; export type RememberOutcome = { /** * - The facts written, in order — deduplicated within this call by exact string * (so `facts.length` is the count stored). Cross-run / semantic dedup is the store's/consumer's job. */ facts: string[]; /** * - How many non-empty spans were processed. */ spans: number; }; export type Provider = import("../types").Provider; export type Store = import("../types").Store; /** * @typedef {object} RememberOptions * @property {Provider} provider - LLM provider implementing `generate(messages, tools, opts)`. * @property {Pick} store - The Store socket (or a `Memory` wrapper). remember writes through * `.store(content, metadata)` ONLY — no other verb, no backend assumptions. * @property {string} [contract] - Optional definition of what counts as durable for this task; appended to the * distiller prompt to steer it (the A3 contract idea, reused on the write side). * @property {Record} [metadata] - Merged into every stored fact's metadata. `kind` is always `'fact'` * (NOT overridable — `remember` writes facts; `fact` is litectx's canonical durable kind and harmless to other * stores, so the stored label always matches the `facts` counter). Add ANY OTHER metadata here (tags, or litectx * `format`/`scope` to differentiate facts). NEVER carries ctx — ctx rides in its own option, never persisted. * @property {{ recordMemoryOp?: (kind: string) => void } & Record} [ctx] - The run ctx. If it carries * the loop-lent `recordMemoryOp`, each fact write counts against `result.metrics.memory.facts`. * @property {(payload: { usage: any, model: string|null, kind: 'remember' }) => any} [onLlmResult] - Budget hook; * each distill pass forwards usage so a bareguard gate keeps counting (mirror of Evaluator). * @property {string} [prompt] - Override the distiller system prompt (defaults to the F5-validated one). */ /** * @typedef {object} RememberOutcome * @property {string[]} facts - The facts written, in order — deduplicated within this call by exact string * (so `facts.length` is the count stored). Cross-run / semantic dedup is the store's/consumer's job. * @property {number} spans - How many non-empty spans were processed. */ /** * Distill durable facts from harvested spans and persist them through the Store socket. * * @param {Array} spans - The harvested spans (stash's feedstock). * Each is a transcript chunk — a raw string, or an object with `content`/`text`. Empty/blank spans are skipped. * @param {RememberOptions} options * @returns {Promise} * @when you want to distill durable facts from harvested transcript spans and persist them through a Store socket (the consolidation pass) * @fails skips empty spans and never fabricates; a provider HaltError propagates clean. Each pass forwards usage via onLlmResult; a fact counts once via ctx.recordMemoryOp. * @example * const { facts } = await remember(spans, { provider, store }); * console.log(`consolidated ${facts.length} durable facts`); */ export function remember(spans: Array, options?: RememberOptions): Promise; /** * remember — the consolidation pass (eval-assist F5). The "future glue" the PRD parked: it turns the spans * `stash` harvested out of the live transcript into durable facts, written back through the GENERIC four-verb * `Store` socket (`store`/`search`/`get`/`delete`). Backend-agnostic by construction — works with the * JsonFileStore, SQLite, litectx, or a custom store — so it carries NO litectx coupling (the reason the * read-litectx's-promotion-count alternative was rejected: it reaches past the socket into one backend). * * One cheap LLM pass per span distills durable signal (decisions, config, identifiers, stable preferences) * and DROPS ephemeral chatter, superseded values, and unanswered questions. The distiller prompt below is the * one validated live on the real Anthropic wire in `poc/f5-remember-distill.mjs` (faithfulness + correction + * discrimination, all falsifiable). A wrong distiller poisons Memory silently, so that POC must stay green. * * SECURITY — memory-poisoning surface: facts are model output distilled over UNTRUSTED transcript content and * written to durable memory. The grounding prompt refuses a direct "record this fact" injection (validated in the * F5 POC + integration test), but treat recalled facts as untrusted CONTEXT, never as authority — and never feed * a recalled fact into a privileged action without the same gate you'd apply to any model output. * * Composes AROUND a Loop (like Evaluator/refine), never inside `loop.js`. Optional, flagged-and-deletable. * Budget visibility: each distill pass forwards `usage` to `opts.onLlmResult` (mirror of Evaluator) so a wired * bareguard gate keeps counting. A `HaltError` from the provider propagates clean. Metering: each fact write * counts against `result.metrics.memory.facts` via the loop-lent `ctx.recordMemoryOp('facts')` hook — the * honest producer that field waited for. `facts` is DISJOINT from `stored`: remember writes via `store.store` * WITHOUT threading ctx, so it never also trips the generic-write counter. */ /** @typedef {import('../types').Provider} Provider */ /** @typedef {import('../types').Store} Store */ export const DISTILL_PROMPT: string;