agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
140 lines (139 loc) • 7.64 kB
TypeScript
/**
* Named memory strategies — what each one does, which TYPES accept it, and
* what the host must supply before it can run.
*
* `MEMORY_STRATEGIES` is a const of seven bare strings. A string is enough to
* WRITE `strategy: { kind: … }` and not nearly enough to OFFER the choice: a
* host rendering a strategy picker off that const offers seven options, and
* learns which of them this deployment can actually run by calling
* `defineMemory` and reading the exception. That is a selector that discovers
* its own capabilities by failing.
*
* So each strategy declares itself, and `listMemoryStrategies()` enumerates
* the declarations. The shape mirrors the influence exemplar in this same
* package (`listInfluenceStrategies()` → `{ name, description, requirements,
* scorer }`), with two deliberate differences:
*
* • the id is `kind`, not `name` — it is the value the caller writes into
* `strategy.kind`, so a picker's option value IS the id;
* • there is no `scorer`, because a memory strategy is not a function the
* host calls; what it needs instead is `types` — the memory TYPES that
* accept it, which is the other half of "can I offer this?" (`decay` on a
* SEMANTIC store is refused however good the host's credentials are).
*
* `requirements` is what a host must SUPPLY (an embedder, an LLM, a store
* that can search). Empty means the strategy runs anywhere, at $0.
*
* Pattern: declared capability descriptors + the two guards that enforce
* them — SHAPE first (`assertStrategyShape`: is this even a
* strategy?), REQUIREMENTS after (`assertStrategyRequirements`: can
* this deployment run it?).
* Role: memory/ layer-1, beside the const it describes.
* Emits: N/A — build-time only.
*
* @see ./define.types.ts for `MEMORY_STRATEGIES` and the strategy union
* @see ../influence-core/strategies.ts for the exemplar this follows
*/
import { type DefineMemoryOptions, type MemoryStrategyKind, type MemoryType } from './define.types.js';
/**
* What a strategy needs the host to supply before it can run. A picker greys
* out (or refuses to offer) strategies whose requirements it cannot meet.
*
* Three well-known values ship; the type stays open so a consumer's own
* strategy descriptor can name something else:
*
* - `'embedder'` — an `Embedder` that turns text into a vector.
* - `'vector-store'` — a store that implements `search()`. Not the same
* requirement as an embedder: the embedder makes the
* query vector, the store is what ranks against it,
* and a deployment can easily have one without the
* other (`RedisStore` is a full memory store with no
* `search()` at all).
* - `'llm'` — a chat provider the strategy calls on the host's
* behalf.
* - `'model'` — the model id that provider is called WITH. Its own
* requirement rather than part of `'llm'`, because a
* deployment can hold a provider and still have no
* answer for which model compression should run on —
* and a library that picked one for you would be
* picking your invoice (9.14.0).
*/
export type MemoryStrategyRequirement = 'embedder' | 'vector-store' | 'llm' | 'model' | (string & Record<never, never>);
/**
* A memory strategy, described: enough for a host to render it in a picker
* and know, before it offers the option, whether this deployment can run it.
*/
export interface MemoryStrategyInfo {
/** The value written as `strategy.kind` — a member of `MEMORY_STRATEGIES`. */
readonly kind: MemoryStrategyKind;
/** One-or-two-sentence plain description, current-truth caveats included. */
readonly description: string;
/** What the host must supply. Empty = runs anywhere, no dependency, $0. */
readonly requirements: readonly MemoryStrategyRequirement[];
/** The memory TYPES that accept this strategy. Any other pair is refused. */
readonly types: readonly MemoryType[];
}
/**
* Every memory strategy, described — cheapest first, in the order the docs
* teach them. Frozen: a host renders its picker straight off this.
*
* @example Offer only what this deployment can actually run
* ```ts
* import { listMemoryStrategies } from 'agentfootprint/memory';
*
* const available = new Set(embedder ? ['embedder', 'vector-store'] : []);
* const offerable = listMemoryStrategies().filter(
* (s) => s.types.includes('episodic') && s.requirements.every((r) => available.has(r)),
* );
* // → window, budget, decay, hybrid — the four that cost nothing to run.
* ```
*/
export declare function listMemoryStrategies(): readonly MemoryStrategyInfo[];
/**
* One strategy's description by `kind`, or `undefined` for a string that is
* not a strategy at all.
*/
export declare function memoryStrategyInfo(kind: string): MemoryStrategyInfo | undefined;
/**
* Refuse a strategy whose SHAPE is wrong, before anything reads into it.
*
* WHY it runs FIRST, ahead of both the pipeline dispatch and the
* requirements walk: those two read FIELDS off the strategy
* (`strategies[0]`, `s.embedder`, `for (const sub of strategy.strategies)`),
* and a field read off a shape that never had it is a `TypeError` with the
* library's internals in the text — `Cannot read properties of undefined
* (reading '0')` for `{ kind: 'hybrid', size: 5 }`, which names neither the
* option the caller got wrong nor the one they meant. A caller cannot act on
* that. This guard turns every such read into a refusal that names the
* field, shows the line that would have worked, and points at the catalogue.
*
* It checks SHAPE only — is this an object, does it carry a `kind`, does a
* `hybrid` carry the array that makes it a hybrid. It deliberately does NOT
* judge whether the kind is real or legal for the type: the dispatch refuses
* those and names the alternative for that type, which is the better message.
*
* @param strategy the strategy exactly as the caller wrote it — `unknown`,
* because the whole point is that it may not be a `Strategy`.
* @param site the call to name in the message, e.g. `defineMemory[chat]`.
*/
export declare function assertStrategyShape(strategy: unknown, site: string): void;
/**
* Refuse a config whose strategy declares a requirement the caller did not
* supply — by name, at BUILD, with the fix in the message.
*
* WHY it runs LAST, after the pipeline dispatch rather than before it: the
* dispatch's own refusals know more than this one does. `defineMemory`'s
* TOP_K arm knows about the `ranksBy: 'server-text'` exemption AND about the
* write half it takes away; the CAUSAL arm knows that exemption does not
* apply to it; the EXTRACT arm knows the `llm` matters only for
* `extractor: 'llm'`. Speaking first would replace those messages with a
* blunter one. This is the BACKSTOP: it says something only when nothing
* better already did, and its job is that no declared requirement can go
* unchecked — the failure it exists to prevent is a missing dependency
* surfacing as a `TypeError` from five frames inside a stage, halfway
* through a paid run.
*
* @param options the config as written by the caller.
* @param site the call to name in the message, e.g. `defineMemory[chat]`.
*/
export declare function assertStrategyRequirements(options: DefineMemoryOptions, site: string): void;