/** * Duplicate-call dedup cache (agentic-loop mitigation). * * A model stuck in a loop re-issues the same read call with identical * arguments. Read tools are served from a short-TTL cache on repeat — the * model gets the identical answer instantly (with a note) instead of * re-running DB/bridge queries. Stateful tools are excluded: repeated * identical calls are legitimate there (build polling, write retries after * fixes, git state checks). */ export declare const DEDUP_TTL_MS = 60000; /** * Tools that CHANGE what a subsequent read would return. * * Excluding a write from the cache is not enough — the danger is the READ that * follows it. `get_object_info(include:"xml")` on an entity, three * `d365fo_file(action="modify")` writes, then the same read again: the second * read matched the cached key and served the 2399-byte pre-write body while * disk held 2738 bytes with both ranges, under a note telling the agent to trust * it. An agent verifying its own write is told the write did not happen. Seen in * 2 of the 4 eval runs on 2026-08-23 (L2-form-control-removal-lifecycle, * L2-entity-query-range-roundtrip). * * This is deliberately an EPOCH, not per-object invalidation: a write to one * object changes reads of others (extensions, references, search hits, the * symbol index), so scoping the blast radius would be a guess. Bumping a * counter costs nothing and is provably right. The loop it exists to break — * the same read re-issued seconds apart with no write between — is unaffected. */ export declare const MUTATING_TOOLS: Set; export declare function currentWriteEpoch(): number; export declare function bumpWriteEpoch(): number; /** Tools whose repeated identical calls are legitimate — never dedup, never loop-hint. */ export declare const DEDUP_EXCLUDED_TOOLS: Set; export declare function dedupKey(toolName: string, args: unknown): string; export declare function getDedupedResult(key: string): any | undefined; /** * `epoch` is the write epoch captured when the call STARTED. A read that began * before a write and finished after it computed a pre-write answer, so storing * it under the current (bumped) epoch would re-introduce the very staleness this * guards against — which is why the caller passes it rather than letting it * default. The default (the epoch as of now) only holds when the caller knows no * write can have raced, i.e. tests and synchronous callers. */ export declare function storeDedupResult(key: string, result: any, epoch?: number): void; /** Test/maintenance helper. Resets the epoch too, so a cleared cache is a clean slate. */ export declare function clearDedupCache(): void; export declare function getInFlight(key: string): Promise | undefined; export declare function registerInFlight(key: string): { resolve: (r: any) => void; reject: (e: any) => void; }; export declare function clearInFlight(key: string): void; export declare function clearAllInFlight(): void; /** Append a note to the first text item of a result (shallow clone). */ export declare function appendNote(result: any, note: string): any; //# sourceMappingURL=callDedup.d.ts.map