agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
95 lines (94 loc) • 4.06 kB
TypeScript
/**
* EmbeddingCache — content-hash embedding cache (RFC-002 §3).
*
* Pattern: transparent Decorator — implements `Embedder` itself, so
* every influence-core function (and any other embedder
* consumer) takes a plain `Embedder` and the cache is pure
* injection: wrap once, thread the SAME instance through
* lint + margins + edge weights, and a description embedded
* for the catalog lint is free for the margin scorer.
* Role: `src/influence-core/` leaf. No agent/runtime imports.
*
* ## Bounded honesty (the library convention)
*
* The cache is BOUNDED — `maxEntries` caps memory (LRU eviction), and
* the eviction count is VISIBLE via `stats()`. A consumer seeing
* `evictions > 0` knows the cache is thrashing and can size it up;
* nothing silently degrades out of view.
*
* ## Keying
*
* Entries are keyed by `contentHash(text)` — a fast non-cryptographic
* 64-bit FNV-1a (length-qualified). NOT a security boundary: it
* deduplicates embedding calls, it does not authenticate content.
* Collisions are astronomically unlikely at cache scale (≤ maxEntries
* live keys) but would silently return the colliding text's vector —
* acceptable for a similarity proxy, never rely on it for exact-match
* semantics. One cache per embedder instance: keys do NOT encode the
* model, so sharing one cache across different embedders would mix
* vector spaces (the same reason `MemoryStore` rejects mixed
* dimensions).
*
* ## Concurrency
*
* In-flight requests coalesce: N concurrent `embed()` calls for the
* same text issue ONE inner call (single-flight). Rejections are NOT
* cached — a failed embedding retries on the next request.
*/
import type { Embedder, EmbedArgs, EmbedBatchArgs } from './types.js';
/**
* Fast, deterministic, browser-safe content hash (FNV-1a, two 32-bit
* lanes + length qualifier → "len-xxxxxxxxyyyyyyyy"). Non-cryptographic
* — cache keying only.
*/
export declare function contentHash(text: string): string;
export interface EmbeddingCacheOptions {
/**
* Maximum cached vectors. Oldest (least recently used) entries are
* evicted past the cap; evictions are counted in `stats()`.
* Default 1024 — a 30-tool catalog plus a long run's reasoning
* steps fit with a wide margin.
*/
readonly maxEntries?: number;
}
/** Visible cache health — the bounded-honesty surface. */
export interface EmbeddingCacheStats {
/** Vectors currently held. */
readonly size: number;
/** The configured cap. */
readonly maxEntries: number;
/** Served from cache (includes coalesced in-flight joins). */
readonly hits: number;
/** Forwarded to the inner embedder. */
readonly misses: number;
/** Entries dropped to respect `maxEntries`. >0 = consider sizing up. */
readonly evictions: number;
}
/**
* Wrap an embedder with a bounded, content-hash-keyed LRU cache.
* See module docs for keying, bounds, and concurrency semantics.
*/
export declare class EmbeddingCache implements Embedder {
readonly dimensions: number;
private readonly inner;
private readonly maxEntries;
/** LRU store — Map iteration order is recency (refreshed on hit). */
private readonly vectors;
/** Single-flight joins — promises live here until settled. */
private readonly inflight;
private hits;
private misses;
private evictions;
constructor(inner: Embedder, options?: EmbeddingCacheOptions);
embed(args: EmbedArgs): Promise<number[]>;
embedBatch(args: EmbedBatchArgs): Promise<number[][]>;
/** Visible cache health (bounded honesty — see module docs). */
stats(): EmbeddingCacheStats;
/** Drop all cached vectors. Stats counters are preserved. */
clear(): void;
private refresh;
private store;
private embedSequential;
}
/** Factory sugar — `embeddingCache(embedder)` reads like the built-ins. */
export declare function embeddingCache(inner: Embedder, options?: EmbeddingCacheOptions): EmbeddingCache;