/** * Two-phase prefix-cache-break detector (design/31). Each turn it records a structural fingerprint of * the prefix (system prompt / per-tool schema / tool set / model namespace) and compares the turn's * `cacheRead` against the prior warm turn; on a confirmed drop it attributes the break to a root cause * (model switch / a specific tool's drift / tool-set change / system-prefix change / server-or-TTL). * * The strict superset of the task-end "low hit-rate" heuristic: it pinpoints the BREAK TURN and names * the cause, instead of only reporting an aggregate at the end. Provider-agnostic — the only provider * input is `cacheRead` (OpenAI `cached_tokens` / Anthropic `cache_read_input_tokens`, already normalized). * * v1 is task-scoped (compares turns within one run; system/tools are stable so the high-value per-tool * attribution mostly fires on a model switch or across runs). A per-session v2 would feed a persisted * prior snapshot — the `observe` contract is already shaped for that. */ export interface CacheBreakFinding { turn: number; cacheReadBefore: number; cacheReadAfter: number; cause: "model-switch" | "tool-schema" | "tool-set" | "system-prefix" | "server-or-ttl"; detail: string; } export interface ToolFingerprintInput { name: string; description: string; parameters: unknown; } /** * Project a tool list into the structural inputs the detector fingerprints. Shared by the initial * snapshot in `prepareTask` AND the per-materialization refresh in tool-disclosure (design/36): both * MUST derive the fingerprint the same way, or a deferred tool going placeholder→full schema would * be hashed inconsistently. Keep this the single source of "what counts as the cacheable tool prefix". */ export declare function toolsToFingerprintInputs(tools: ReadonlyArray<{ name: string; description: string; parameters: unknown; }>): ToolFingerprintInput[]; export declare class CacheBreakDetector { private prev; /** * Reset the baseline. **Call ONLY after a SUCCESSFUL compaction** (design/31 red line #11): a legit * prefix shrink isn't a break, but resetting when compaction did NOT happen opens a false-negative * window (a real break right after would be missed). */ notifyCompaction(): void; /** * Record this turn's fingerprint + compare cacheRead to the prior warm turn. Returns a finding on a * confirmed break. Hashes are computed here per call **by design**, taking the raw prefix rather than * pre-computed hashes. NOTE (design/36): the inputs are NOT necessarily stable within a run — deferred * tools materialize placeholder→full schema mid-task, so the caller refreshes the fingerprint at * materialization (via `toolsToFingerprintInputs`). A resulting `tool-schema`/`tool-set` finding on * that turn is EXPECTED and truthful (the cacheable prefix genuinely changed); the bug this guards * against is the opposite — a frozen fingerprint hiding that change (a false negative). */ observe(input: { turn: number; systemPrompt: string; tools: ToolFingerprintInput[]; modelKey: string; cacheRead: number; }): CacheBreakFinding | undefined; } //# sourceMappingURL=cache-break-detector.d.ts.map