/** * The "the engine ignored you" detector (2026-09-19, from a live session). * * Evidence: one `team` session spent 66 tool calls / 25 LLM steps / 2.8 M * input tokens on a three-term lookup. Calls #5 and #6 asked bing for * `"舞萌DX" "你好世界"` and `"舞萌DX" "我的世界"` — two different queries — and * got byte-identical 10-hit lists about the Chinese character 舞 (dictionary * entries, dance videos). Nothing in the reply said so, so the agent kept * paying for the collapse: 20 more calls hand-building SERP URLs, then a * browser session, to work out what one honest signal would have told it in * the first minute — that the engine had dropped its qualifiers. * * Two independent signals, both cheap and deterministic: * - IDENTICAL hit set under a DIFFERENT query → the engine ignored the * query (a collapse), not "the topic is thin"; * - ZERO query-token overlap across every hit → the answer is unrelated to * what was asked. * * Either one is reported as a DIRECTIVE (what to do next: split the concept, * switch engine, or hand the whole lookups to a dispatched researcher), not * as a shrug. The repetition counter is the circuit-breaker the failing * session never had. */ /** Stable fingerprint of a result set: the hosts+paths that came back, not * their order (a re-rank of the same pages is the same answer). */ export declare function hitSignature(hits: ReadonlyArray<{ url?: string; }>): string; /** Does ANY hit actually mention the query? CJK is bigram-indexed (the same * 口径 as the RRF relevance score) so "舞萌DX" matches a hit about 舞萌DX, * while a page about the single character 舞 does not match. */ export declare function hitsShareAnyQueryTerm(hits: ReadonlyArray<{ title?: string; snippet?: string; url?: string; }>, terms: readonly string[]): boolean; export interface DupeVerdict { /** how many times this SHAPE has now come back in this process */ repeats: number; collapse: boolean; irrelevant: boolean; /** the directive appended to the tool output, "" when nothing is wrong */ note: string; } /** * An engine that has answered a DIFFERENT question with the SAME result set this * many times is not listening, and asking it again costs a round and a fetch to * produce bytes we already have. A live session proved the advisory version of * this rule does not work: the guard reported `collapse` at repeats 2,3,4,5,6 and * the model kept going (≈50 tm_search calls, 424K input tokens). So past the * limit the engine is refused outright — the only form of escalation that cannot * be ignored is not making the call. */ export declare const DUPE_BLOCK_AFTER = 3; /** Per-process ledger of (engine → last signature + query). Deliberately * in-memory: it is a conversation-level observation, and a restart of the * plugin legitimately starts a fresh judgement (the agent is asking afresh). */ export declare function createDupeGuard(limit?: number): { observe: (engine: string, query: string, hits: ReadonlyArray<{ url?: string; title?: string; snippet?: string; }>, terms: readonly string[]) => DupeVerdict; /** ask BEFORE spending a fetch: a stalled engine is refused without calling it */ stalled: (engine: string) => { blocked: boolean; stalls: number; }; seen: () => number; }; //# sourceMappingURL=dupe-guard.d.ts.map