/** * Decision cache for the expensive Brain tiers. * * Autonomy workloads ask the same question over and over: a goal-completion * check every phase, a budget-extension question per subagent, a * monitor engagement per repeated signal. Each one used to be a fresh * council/LLM call even when nothing about the situation had changed, which * is the single largest avoidable source of Brain token spend. * * Scope is deliberately narrow — the cache exists to skip PROVIDER calls, not * to replace judgement: * * - Only decisions produced by the `council` / `llm` tiers are stored. A * deterministic tier is already free, so caching it would add lookup cost * and staleness for no gain. * - `ask_human` is never cached: it is a request for input, not a verdict. * - An observed FAILURE outcome for a cached decision evicts it. This is * what stops the cache from cementing a bad call: the ledger already * correlates outcomes, so a decision that demonstrably went wrong will * not be replayed. * - Entries expire on a TTL, so a stale world eventually re-asks anyway. * * The key includes everything that could change the right answer — question * shape, source, risk, fallback, and the exact option set — so two questions * that merely look similar do not share a verdict. * * @module brain-cache */ import type { EventBus } from '../kernel/events.js'; import type { BrainArbiter, BrainDecision, BrainDecisionRequest } from './brain.js'; export interface BrainDecisionCacheOptions { /** Master switch. Default false — caching a judgement is opt-in. */ enabled?: boolean | undefined; /** Entry lifetime in ms. Default 300_000 (5 min). */ ttlMs?: number | undefined; /** Maximum live entries; oldest evicted first. Default 200. */ maxEntries?: number | undefined; /** * Outcome feed. When a cached decision is later observed to have FAILED, * its entry is dropped so the mistake is not replayed. */ events?: EventBus | undefined; /** Override the clock for deterministic tests. */ now?: (() => number) | undefined; } export interface BrainCacheStats { hits: number; misses: number; stores: number; evictions: number; size: number; } /** * Build the cache key for a request. * * Reuses `brainDecisionKey` for the question so id/number-shaped tokens * collapse (the same question about two different subagents shares an entry), * then adds every other field that can legitimately change the answer. */ export declare function brainCacheKey(request: BrainDecisionRequest): string; export declare class BrainDecisionCache { private readonly opts; private readonly entries; private readonly keyByRequestId; private readonly unsubscribers; private stats; private readonly enabled; private readonly ttlMs; private readonly maxEntries; private readonly now; constructor(opts?: BrainDecisionCacheOptions); /** Subscribe to the outcome feed so failures evict their decision. */ start(): void; stop(): void; get(request: BrainDecisionRequest): BrainDecision | undefined; /** * Store a decision if it is worth caching. Returns true when stored. * Callers pass the tier that produced it — a deterministic tier is free to * recompute, and `ask_human` is a request for input rather than a verdict. */ set(request: BrainDecisionRequest, decision: BrainDecision, tier: string | undefined): boolean; snapshot(): BrainCacheStats; clear(): void; } export interface CachingBrainArbiterOptions { inner: BrainArbiter; cache: BrainDecisionCache; } /** * Wrap an arbiter so repeated identical questions replay a previous * provider-backed verdict instead of paying for it again. * * Position INSIDE the ledger guard: a guard denial must always be evaluated * against the live failure history, never served from a cache. */ export declare function createCachingBrainArbiter(opts: CachingBrainArbiterOptions): BrainArbiter; //# sourceMappingURL=brain-cache.d.ts.map