/**
 * J1 anchoring detector (recall-recurrence) — pure module.
 *
 * Implements two detection rules from ROADMAP-RESEARCH.md L546:
 *   R1 query_repeat: same queryHash within recentRepeatWindow returned
 *     same topMemoryId (caller is re-asking the same question).
 *   R2 memory_dominance: same topMemoryId across >= minDominance distinct
 *     queryHashes (memory acts as a fixed-point anchor regardless of what
 *     the agent asks).
 *
 * Per the plan v3 architectural decision: each pipeline (api.recall via
 * HTTP, cmdRecall, MCP hippo_recall) owns its OWN ring buffer Map keyed
 * by (tenant, session). No cross-pipeline sharing (the typical multi-
 * process deployment makes IPC ring-sharing impractical; per-pipeline
 * is correct because each pipeline has its own top-1 ranking anyway).
 *
 * Plan: docs/plans/2026-05-26-j1-anchoring-detector.md.
 * Composes with J3.2: AnchoringHint + PlanningFallacyHint are independent
 * signals; both can fire on the same recall.
 */
export type AnchoringReason = 'query_repeat' | 'memory_dominance';
export interface AnchoringHint {
    reason: AnchoringReason;
    /** The memory ID that is anchoring the agent's reasoning. */
    memoryId: string;
    /** For 'memory_dominance': how many distinct queries in the recent window
     *  had this memory as their top-1 result. Always >= 3 when emitted. */
    queryCount?: number;
    /** Human-readable summary surfaced to the agent. */
    summary: string;
    /** Discriminator for hint origin; reserved for future variants. */
    source: 'j1-recurrence';
}
export interface RecallHistoryEntry {
    /** Hash of the queryText that produced this entry (see hashQueryText). */
    queryHash: number;
    /** Top-1 memory id this recall surfaced; null if zero results. */
    topMemoryId: string | null;
    /** ISO-8601 timestamp; advisory, not used by R1/R2 logic. */
    ts: string;
    /** Memory id of the AnchoringHint that fired on this recall, if any.
     *  Used by R1/R2 cooldown logic to prevent re-emitting the same hint on
     *  consecutive recalls within the dominance window. Caller-written
     *  AFTER detectAnchoring returns; reads next time detectAnchoring runs. */
    anchoredOn?: string;
}
export type RecallHistorySnapshot = readonly RecallHistoryEntry[];
export interface DetectAnchoringOpts {
    /** R2 threshold: number of distinct queryHashes that must have returned
     *  the same topMemoryId. Default 3. */
    minDominance?: number;
    /** R1 window: how many recent history entries to scan for query repeat.
     *  Default 5. */
    recentRepeatWindow?: number;
    /** Cooldown: if the immediately-prior fire (per `anchoredOn`) was for
     *  the same topMemoryId within this many history entries, suppress.
     *  Default 3. */
    cooldown?: number;
}
/**
 * Normalize + hash a query text into a 32-bit integer.
 * Lowercase → strip non-alphanumeric → split → drop empty + short tokens →
 * sort tokens → join → FNV-1a 32-bit.
 *
 * Token sort + dedup means semantically-equivalent queries with reordered
 * words collide intentionally (the roadmap's "semantically-distinct" v1
 * uses textual normalization; embedding-based distinctness is J1-v2).
 *
 * Deterministic across processes; stable across Node + V8 versions.
 */
export declare function hashQueryText(query: string): number;
/**
 * Detect anchoring patterns in the recall history against the current
 * recall's (queryHash, topMemoryId).
 *
 * Rule precedence: R2 (memory_dominance) wins on tie. When both R1 and R2
 * fire on the same recall, return only the R2 hint — R2's signal is the
 * cognitively stronger one (a memory dominating multiple DIFFERENT queries
 * is a fixed-point anchor; R1 alone is just a literal re-ask).
 *
 * Cooldown: if the immediately-prior recall fired a hint on the SAME
 * topMemoryId within `cooldown=3` history entries, suppress. Prevents
 * spam when the agent repeatedly recalls within the dominance window.
 * Cooldown is per-memory, not per-rule: if R2 fired on M (cooldown
 * engaged for M), and the next recall has top=N + repeated query →
 * R1 fires on N (different memory, not in cooldown).
 *
 * @returns AnchoringHint when a pattern fires; null otherwise.
 */
export declare function detectAnchoring(history: RecallHistorySnapshot, currentQueryHash: number, currentTopMemoryId: string | null, opts?: DetectAnchoringOpts): AnchoringHint | null;
/**
 * Bounded FIFO ring of RecallHistoryEntry. Newest entries pushed via
 * append; oldest evicted when the ring is full. The class is intentionally
 * a thin wrapper around an array so snapshotRing returns a readonly view
 * without copying on the hot path.
 */
export declare class RingBuffer {
    private entries;
    append(entry: RecallHistoryEntry): void;
    snapshot(): RecallHistorySnapshot;
    size(): number;
}
/**
 * Build the (tenant, session) key for a per-session ring Map. Uses a NUL
 * (`\x00`) byte as delimiter because tenant ids and session ids are
 * validated elsewhere to reject NUL chars — guarantees collision-free
 * concatenation regardless of what `:` or other delimiters might appear
 * inside either field (notably API-key-derived subjects can contain `:`).
 */
export declare function buildSessionKey(tenantId: string, sessionId: string): string;
/**
 * Get-or-create a RingBuffer for a session key. Caps total tracked keys
 * at `maxSessions` (default 1000) with LRU eviction — when the cap is
 * hit, deletes the oldest-inserted key before inserting the new one.
 * Map iteration order preserves insertion order per ECMA-262 spec, so
 * "oldest" = first key returned by Map.prototype.keys().
 */
export declare function getOrCreateRing(map: Map<string, RingBuffer>, key: string, maxSessions?: number): RingBuffer;
/**
 * Append a recall to a ring. The `anchoredOn` argument carries the
 * memoryId of the AnchoringHint that fired on THIS recall (or undefined
 * if no hint). detectAnchoring reads it next time for cooldown gating.
 */
export declare function appendRecall(ring: RingBuffer, queryHash: number, topMemoryId: string | null, anchoredOn?: string): void;
/** Snapshot a ring as a readonly RecallHistorySnapshot. */
export declare function snapshotRing(ring: RingBuffer): RecallHistorySnapshot;
//# sourceMappingURL=recall-history.d.ts.map