/** * loop-recall — L3: the per-loop RECALL shortlist (proposal 006, the validated subset of 003). * * A per-loop influence scorer that RESCUES early-entered culprits into the top-k shortlist so * ablation has the right candidates to convict — a **recall booster that narrows before ablation, * NOT a #1 ranker** (H2 measured the #1-ranker as a loss; recall as a win: top-3 10/10 vs 9/10). * * Home: `context-bisect` (NOT influence-core — it consumes the `Trajectory` + `defaultSuspectClassifier`, * and influence-core is a leaf; context-bisect already depends on influence-core). Sibling to the * `contrastive` opt-in scorer pattern: a separate scorer, not a mutation of `scoreInfluence`. * * Mechanism (per loop, in order): * 1. Reuse `defaultSuspectClassifier` on the frame's contextSources → the SAME suspect identity * the localizer emits (injectionId / toolName) — so a candidate joins 1:1 with a `Suspect`. * 2. Score each suspect's content vs the loop's OWN output (`intermediateText`, the per-loop * anchor — NOT the final answer) via `scoreInfluence`. This per-loop signal is the win over * plain final-answer influence: it catches a source in the loop it actually fed. * 3. Recency-weighted SUM: `score(s) = Σ_N recencyDecay^(lastLoop − N) · perLoop_N(s)`. Recent * loops weigh more; rank descending for recall, return top-k. * * MECHANISM NOTE (the gate earned this): proposal 006 first proposed a FORWARD eligibility sum. * The recall@k gate measured it against the H2 prototype and it FAILED (forward up-weights early * loops → top-3 4/10). The H2 winner was a BACKWARD recency-weighted sum (top-3 10/10) — so that is * what ships. Measure-before-promote in action. * * HONESTY (must-read — folded in from review): * - **Correlational only.** Every `recallScore` is an embedding-geometry PROXY, never "because". * The causal claim stays in ablation (`localizeContextBug`). * - **FA-dominated, not four-signal.** Per-loop sources carry `ancestorTexts: []`, so `adaptWeights` * zeroes AVG+PERSIST (DEPTH constant) — the per-loop score is in practice cosine-to-the-loop-output. * - **Gate-validated mechanism.** The shipped recency sum reproduces H2 (top-3 10/10 vs plain 9/10) * at the default recencyDecay; the gate FAILS promotion otherwise. * - **Thin, single-setup.** The recall win is 10/10 vs 9/10 (n=10, one fixture, one embedder/model). */ import type { Embedder, InfluenceWeights } from '../influence-core/index.js'; import { type SuspectClassifier } from './localize.js'; import type { Trajectory } from './trajectory.js'; import type { HonestyFlag, SuspectKind } from './types.js'; /** * Default recency decay for the per-loop aggregation. A loop N's per-suspect score is weighted by * `recencyDecay^(lastLoop − N)` — recent loops weigh more. H2 swept `{0.5, 0.7, 0.9, 1.0}`; the * recall@k gate (running THIS shipped scorer) confirmed `0.5` and `0.7` reproduce top-3 10/10 vs * plain 9/10, while `≥0.9` does not — so the validated band is `[0.5, 0.7]` and the default is the * lowest measured winner, `0.5`. (We do NOT ship an unmeasured value.) `1` = uniform; `0` = last-loop-only. */ export declare const DEFAULT_RECENCY_DECAY = 0.5; /** One recall-shortlist candidate — joins 1:1 with a localizer `Suspect` via `suspectId`. */ export interface LoopCandidate { /** The suspect identity the default classifier emits — `injectionId` / `toolName`. The join key. */ readonly suspectId: string; /** Suspect kind (injection / memory / tool) — from the classifier. */ readonly kind: SuspectKind; /** Normalized recall score in [0, 1] (= eligibility / max across candidates) — the display/threshold headline. */ readonly recallScore: number; /** RAW backward recency-weighted sum (pre-normalization) — `Σ_N recencyDecay^(lastLoop−N)·perLoop_N`, the actual computed proxy magnitude. (Field name kept for back-compat; the shipped mechanism is the BACKWARD sum, not the original forward-eligibility proposal — see module doc.) */ readonly eligibility: number; /** First loop index this suspect fed — why recall rescues early-entered culprits. */ readonly enteredLoop: number; /** Per-loop track: the clamped per-loop score in each loop the suspect appeared. */ readonly perLoop: readonly { readonly loopIndex: number; readonly recallScore: number; }[]; /** True if a loop it fed read untracked sources, or the trajectory was frame-truncated. */ readonly incomplete: boolean; } /** The recall shortlist — ranked recall-first; `candidates` is already top-`k`. */ export interface LoopRecallShortlist { readonly candidates: readonly LoopCandidate[]; readonly k: number; readonly recencyDecay: number; /** Standing caveats passed through from the trajectory (literal — review must-fix #4). */ readonly honestyFlags: readonly HonestyFlag[]; } export interface ShortlistEarlyCulpritsOptions { /** Embedder; wrap in an `EmbeddingCache` (scoring is O(frames × suspects)). */ readonly embedder: Embedder; /** Recency decay; default {@link DEFAULT_RECENCY_DECAY}. Loop N weighted `recencyDecay^(lastLoop−N)` * (1 = uniform, 0 = last-loop-only). Validated band `[0.5, 0.7]`. */ readonly recencyDecay?: number; /** Shortlist size. Default 5. recall@k is the headline metric. */ readonly k?: number; /** Override the suspect classifier (default `defaultSuspectClassifier`). */ readonly classifier?: SuspectClassifier; /** Composite weights forwarded to `scoreInfluence`. */ readonly weights?: InfluenceWeights; readonly signal?: AbortSignal; } /** * Score a recorded agent run into a per-loop RECALL shortlist (L3). Works on BOTH flat and grouped * trajectories (it reads each frame's contextSources + intermediateText regardless of shape). * * Returns the top-`k` candidates ranked recall-first, each joinable 1:1 with a localizer `Suspect` * via `suspectId`. Feed it to `localizeContextBug({ shortlist })` to REORDER suspects before the * ablation budget is spent — narrow (recall), then convict (causal). */ export declare function shortlistEarlyCulprits(trajectory: Trajectory, opts: ShortlistEarlyCulpritsOptions): Promise; //# sourceMappingURL=loop-recall.d.ts.map