/** * routeRecorder — records the skill-graph route a run actually took. * * A passive observer that reconstructs, hop by hop, which skill the agent was in, * where it went next, and WHY — by COMPOSING already-shipped events (no engine * change): `agentfootprint.context.evaluated` (its `routing[]` carries via/from/ * label per active skill-graph injection) + `agentfootprint.skill.rejected` (an * out-of-reach read_skill) + `stream.tool_start` (the tool that drove a hop). * * Also folds in the GREY-AREA GOVERNORS (observability tier): it detects * oscillation (A→B→A→B within `pingPongWindow`) and a run of consecutive rejected * `read_skill` jumps (`maxRejectedRetries`), reported via `getTrips()`. These LABEL * the trace (`onTrip:'stay'` semantics) — the hard "always stops" guarantee remains * the agent's iteration cap; a runtime force-stop is a deferred follow-on. * * Pattern: CombinedRecorder (Convention 1 — single purpose: route evidence). Owns a * `SequenceStore`. Convention 4: resets on a new `runId`. * Role: Tier-3 /observe recorder — `Agent.create(...).recorder(routeRecorder())`. * Powers the lens, the "Why this skill?" panel, and paper route figures. */ import type { EmitEvent } from 'footprintjs'; interface RunBoundaryEvent { readonly traversalContext?: { readonly runId?: string; }; } /** How the graph arrived at a skill on a hop. */ export type RouteOutcome = 'entry' | 'route' | 'stay' | 'rejected'; /** One hop of the route — the skill the graph was in at one iteration + how. */ export interface RouteHop { /** runtimeStageId of the iteration (the SequenceStore key). */ readonly runtimeStageId: string; readonly iteration: number; /** The skill before this hop (undefined at cold start). */ readonly fromSkill?: string; /** The skill after this hop (undefined for a pure rejection). */ readonly toSkill?: string; readonly outcome: RouteOutcome; /** A human-readable reason for this hop (see `formatRouteHop`). */ readonly why: string; /** The route edge's caption, when one drove the hop. */ readonly edgeLabel?: string; /** The tool whose result drove the hop (most recent tool_start). */ readonly lastTool?: string; /** Rejection only — the skill the model tried to jump to. */ readonly requestedSkill?: string; /** Rejection only — the reachable set it was bounded to. */ readonly reachable?: readonly string[]; } /** A governor trip — the route is misbehaving. */ export type RouteTripKind = 'ping-pong' | 'rejected-cap'; export interface RouteTrip { readonly kind: RouteTripKind; readonly iteration: number; readonly skills: readonly string[]; readonly detail: string; } export interface RouteRecorderOptions { readonly id?: string; /** Window for oscillation detection (a [X,Y,X,Y] pattern trips). Default 4. */ readonly pingPongWindow?: number; /** Consecutive rejected read_skill jumps before a `rejected-cap` trip. Default 3. */ readonly maxRejectedRetries?: number; } export interface RouteRecorderHandle { readonly id: string; /** The distinct skill sequence the run moved through (the "route"). */ getPath(): readonly string[]; /** Every hop, in order. */ getHops(): readonly RouteHop[]; /** The rejected read_skill attempts (out-of-reach jumps). */ getRejections(): readonly RouteHop[]; /** Governor trips (oscillation / rejected-retry cap). */ getTrips(): readonly RouteTrip[]; clear(): void; onEmit(event: EmitEvent): void; onRunStart(event: RunBoundaryEvent): void; } /** A human-readable one-line reason for a hop. Exported (pure). */ export declare function formatRouteHop(hop: RouteHop): string; /** Build the route recorder. */ export declare function routeRecorder(options?: RouteRecorderOptions): RouteRecorderHandle; export {};