/** * walk-to-root — L4: the influence-guided backtracking debugger (proposal 007). * * `walkToRoot` walks BACKWARD from the symptom across loops to find the ROOT context source of a * DECISION bug (root ≠ proximate). Per hop: NARROW with per-loop influence (L3) → HOP along * `writerId` provenance to the loop that produced the culprit → ISOLATE with run-wide ablation (L2). * Pure orchestration over shipped tiers — no new scorer, no new ablation. * * Home: `context-bisect` (consumes Trajectory + shortlist + the localizer's ablation). * * HONESTY (the proxy CAN misdirect the hop — folded in from review): * - The per-hop narrow is a correlational PROXY (FA-dominated) — it points at a neighborhood and * CANNOT separate a planted instruction from an innocent same-topic sibling. Three guards: (1) BEAM * not top-1 (keep siblings in play); (2) ablation is the ONLY discriminator (a wrong-branch hop that * doesn't flip is never `root`); (3) the narrow reorders, never drops. Remaining caveat: if the narrow * never surfaces the true root into the beam, ablation never tests it and the walk stops shallow — a * recall blind spot, amplified in a single chain. * - `root` is CAUSAL (ablation-only): set only when run-wide ablation flips on a stable baseline. * Without a `rerun`, the walk is correlational and `root` is absent. * - FLAT charts only for the cross-loop hop — grouped loop frames are scope-isolated (degraded + flagged). * - Three first-class honest stops (never silent): `unseparated-siblings`, `overdetermined-or-incomplete`, * `untracked-origin`. * * VALIDATION (the descent edge is now populated on real agents — proposal 008): `assembleTrajectory` * surfaces each loop's proximate `lastToolResult` on the WALK-ONLY `LoopFrame.proximateToolSource` * field (writer = the PRODUCING loop's tool-calls stage — a cross-loop edge). `writtenByOf` reads it, * so on a real flat agent with tool calls the cross-loop DESCENT fires (proven at the component level: * the enrichment populates the edge on a real trajectory + the algorithm descends on it). It is * WALK-ONLY — not in `contextSources` — so L3's narrow + its measured recall are untouched. * END-TO-END VALIDATED (ctxbug/harness/eval-l4-walk.mjs): on a REAL agentfootprint misdirect agent * with a realistic embedder (bge) for the narrow and a REAL causal ablation (rebuild the agent WITHOUT * the planted fact → the outcome flips), the walk BURIES the plant at the symptom, DESCENDS via the * proximate tool edge to the wrong-decision loop, and ablation convicts `root = the planted * instruction` — where flat single-trigger localize does not. FLAT only — grouped's run-level * `lastToolResult` lives outside the per-scope inner logs (deferred, degrade-flagged below). */ import type { Embedder } from '../influence-core/index.js'; import { type SuspectClassifier } from './localize.js'; import type { Trajectory } from './trajectory.js'; import type { AblationRerun, AblationVerdict, ContextBugArtifacts, HonestyFlag, SuspectKind } from './types.js'; /** One honest stop reason when the walk cannot cleanly descend/convict. */ export type RootCauseNote = 'unseparated-siblings' | 'overdetermined-or-incomplete' | 'untracked-origin'; /** One hop of the symptom→root walk. */ export interface RootCauseHop { /** The loop this hop examined. */ readonly loopIndex: number; /** The narrowed culprit at this hop (joins a localizer Suspect 1:1). */ readonly suspectId: string; readonly kind: SuspectKind; /** The narrow is text-similarity — a PROXY, never causal. */ readonly narrowedBy: 'text-similarity'; /** The causal convict — present only when a `rerun` ablated this hop's suspect. */ readonly verdict?: AblationVerdict; /** The provenance writer this hop's culprit came from (runtimeStageId). */ readonly writtenBy?: string; /** The loopIndex the walk descended to next (the writer's frame); absent if it stopped here. */ readonly cameFrom?: number; /** A first-class honest stop, when applicable. */ readonly note?: RootCauseNote; } export interface RootCausePath { /** Symptom → … in walk order. */ readonly hops: readonly RootCauseHop[]; /** The DEEPEST ablation-convicted hop (CAUSAL). Absent without a flip / without a rerun. */ readonly root?: RootCauseHop; readonly honestyFlags: readonly HonestyFlag[]; readonly truncated?: { readonly byHops: boolean; readonly byAblations: boolean; }; } export interface WalkToRootOptions { readonly embedder: Embedder; /** The convict tier. Without it the walk is correlational and `root` is absent. */ readonly rerun?: AblationRerun; /** Top-k writers ablated per hop (NOT top-1 — the proxy can't separate same-topic siblings). Default 2. */ readonly beamK?: number; /** Forwarded to the per-loop narrow. */ readonly recencyDecay?: number; readonly k?: number; /** Walk-depth budget. Default 8. */ readonly maxHops?: number; /** Total ablation-probe budget across the whole walk. Default 24. */ readonly maxAblations?: number; readonly classifier?: SuspectClassifier; readonly signal?: AbortSignal; } /** * Resolve every `writerId` to the index of the frame whose `bodyIds` contains it (NOT via * `parseRuntimeStageId` — that yields executionIndex, not loopIndex). A writer not in any frame * (run prelude / root-seeded) maps to `undefined`. Invariant: each id lands in exactly one frame. */ export declare function buildWriterFrameIndex(trajectory: Trajectory): Map; /** * Walk backward from the symptom to the root context source — from a recorded run. * Thin wrapper: `assembleTrajectory` then {@link walkTrajectory}. See module docs for the honesty model. */ export declare function walkToRoot(artifacts: ContextBugArtifacts, opts: WalkToRootOptions): Promise; /** * The walk itself, over an already-assembled {@link Trajectory} (composable + directly testable). * FLAT charts only for the cross-loop hop; grouped charts degrade (within-loop) with a flag. */ export declare function walkTrajectory(trajectory: Trajectory, opts: WalkToRootOptions): Promise;