agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
113 lines • 5.79 kB
TypeScript
/**
* 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<MemoryState>) => string;
}
export declare function loadRelevant(config: LoadRelevantConfig): (scope: TypedScope<MemoryState>) => Promise<void>;
/** 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