/** * loadRelevant — read-side stage that embeds a query and fetches the * most similar entries from a vector-capable `MemoryStore`. * * Reads from scope: `identity`, `messages` (or `newMessages`, or custom queryFrom) * Writes to scope: `loaded` (MemoryEntry[], ordered best-first — narrowed * by `pickByBudget` downstream) * `retrieved` (RetrievalEvidence — what was considered, * what was admitted, and why about each one) * * Query derivation: * Default: the last user message. That's the natural "what is the user * asking about?" signal. Override with `queryFrom` for custom retrieval. * * Empty behavior: * No query text → no search → `loaded = []`. Downstream `pickByBudget` * picks nothing and the formatter emits nothing — safe, and now * RECORDED: the evidence still lands, saying the query was empty. * * Feature detection: * Throws at stage build time if the store doesn't implement `search()`. * Fail-loud — a semantic pipeline configured against a non-vector store * is a config bug, not a runtime condition. * * ─── Why the threshold moved out of the store (8.8.0) ────────────────── * * Until 8.8.0 the quality floor was passed to `store.search({ minScore })`, * so every candidate that failed it was filtered INSIDE the store and never * came back. The consequence was not a small one: a retrieval that injected * nothing left no trace of what it nearly injected, and "why did the agent * not read that passage" had no answer anywhere in the recording. * * The floor is now applied here, over a pool of `k + rejectWindow`. **This * does not change which entries are admitted, ever.** Proof: `search` * returns score-descending, and the pool is at least `k`. Either * * (a) every entry in the pool clears the floor — then the admitted set is * the first `k` of them, which is exactly what `{ minScore, k }` would * have returned; or * (b) some entry at position p fails the floor — then every entry after p * fails it too, so the pool already contains EVERY entry in the * namespace that clears the floor, and admitting all of them (capped * at `k`) is again exactly what `{ minScore, k }` would have returned. * * `rejectWindow` therefore only controls how many near-misses we can SHOW. * It cannot change what the model sees. * * ─── The size bound (8.19.0) ─────────────────────────────────────────── * * `k` is a COUNT bound. It says how many passages may reach the prompt and * nothing about how much text that is — which is how ten ordinary headings * became eleven thousand characters against a 4000-character slot budget, * with nothing but defaults on either side. `maxChars` is the bound on the * other axis: a character budget spent across the admitted passages in rank * order, tail dropped, every drop named `'over-char-budget'` in the record. * Unset by default, so a retriever that does not ask for it behaves exactly * as it did before. */ import type { TypedScope } from 'footprintjs'; import type { MemoryStore } from '../store/index.js'; import type { MemoryState } from '../stages/index.js'; import type { RetrievalStrategy } from '../retrieval/types.js'; import type { Embedder } from './types.js'; export interface LoadRelevantConfig { /** The vector-capable store. Must implement `search()`. */ readonly store: MemoryStore; /** * Embedder used to turn the query text into a vector. * * Optional since 9.3.0, for a store that declares `ranksBy: 'server-text'`: * it takes the question as words (`SearchOptions.text`) and ranks it on the * backend's side, so no vector is ever produced, nothing is billed for one, * and no `agentfootprint.embedding.generated` is emitted — the recording * says an embedding happened only when one did. */ readonly embedder?: Embedder; /** * Identifier for the embedder. When set, the search filters entries * to those produced by the same embedder (prevents cross-model * similarity pollution). */ readonly embedderId?: string; /** Top-k to retrieve. Default 20 — picker will narrow further by budget. */ readonly k?: number; /** Minimum cosine score [-1, 1] to consider a match. Default: none. */ readonly minScore?: number; /** * A character budget for the admitted passages, spent in RANK order * (8.19.0). Default: none — `k` is the only bound, exactly as before. * * Counts passage characters, not rendered prompt bytes. See * {@link RetrievalEvidence.maxChars}. */ readonly maxChars?: number; /** Filter results by tier. */ readonly tiers?: ReadonlyArray<'hot' | 'warm' | 'cold'>; /** * The rule that decides which candidates reach the prompt. Defaults to * `topK({ k, threshold: minScore })` — i.e. `k` and `minScore` above are * the shorthand, and this is the same rule spelled out. Pass a strategy * to replace the rule entirely. */ readonly retrieval?: RetrievalStrategy; /** * Extract the query text from scope. Default: the last user message. * Override for custom retrieval signals. */ readonly queryFrom?: (scope: TypedScope) => string; } export declare function loadRelevant(config: LoadRelevantConfig): (scope: TypedScope) => Promise; /** Test seam — the once-per-process warning is per PROCESS, which tests must be able to reset. */ export declare function __resetEmptyCorpusWarnings(): void; //# sourceMappingURL=loadRelevant.d.ts.map