/** * trajectory — the per-loop trajectory assembler (proposal 005). * * Slices a recorded ReAct run's commit log into ordered LoopFrames — one per * iteration — so the two-score localizer (L2), the recall scorer (L3), and the * backtracking debugger (L4) read the same per-loop substrate instead of one * flattened bag. PURE + read-only (NOT a recorder, Convention 1): the loop-level * peer of `causalChain` / `commitValueAt` / `stepOutputText`. * * Pieces: * - `bucketByAnchors` — the domain-agnostic HEAD-range partition (pure, total); * - `findLoopHeads` — the flat-chart loop-head detector (one head per injection-engine entry); * - `assembleTrajectory` — the agent-flavored projection (call-llm pointer + intermediate * text + live contextSources via findLastWriter/commitValueAt). * * Handles BOTH chart shapes: * - FLAT (`buildAgentChart`, default `reactMode: 'dynamic'`): `call-llm` is a parent-level * stage; frames are bucketed over the RUN commit log by injection-engine loop heads. * - GROUPED (`buildDynamicAgentChart`, `reactMode: 'dynamic-grouped'`): the LLM turn runs in * an `sf-llm-call` subflow whose inner commits live in `subflowResults['sf-llm-call#k']`, * retained PER-ITERATION by footprintjs subflow-commit-visibility (≥ d458898). Each loop is * projected PER-SCOPE over its own inner commit log — no cross-scope merge, so the slice * primitives run correctly over the isolated log. Such frames carry `subflowScope`. */ import type { CommitBundle } from 'footprintjs/advanced'; import type { UntrackedSource } from 'footprintjs/trace'; import type { EvidenceInput } from '../influence-core/index.js'; import { type ContextBugArtifacts, type HonestyFlag } from './types.js'; /** One source the loop's `call-llm` read, traced back to its live writer for THAT loop. */ export interface ContextSource { /** The state key call-llm#k read (e.g. 'systemPromptInjections'). */ readonly key: string; /** runtimeStageId of the live writer (findLastWriter); undefined when never committed before. */ readonly writerId: string | undefined; /** The writer's commitLog ARRAY position — NOT the optional CommitBundle.idx. */ readonly writerArrayIdx: number | undefined; /** Materialized live value (commitValueAt); undefined under the pre-run-initial blind spot. */ readonly value: unknown; /** The bridge handed to scorers: { id, text, ancestorTexts }. */ readonly evidence: EvidenceInput; } /** One ReAct iteration — bounded by the loop HEAD, pointing at the call-llm inside it. */ export interface LoopFrame { /** Anchor ordinal 0,1,2… DERIVED from the commit log (NOT TraversalContext.loopIteration). */ readonly loopIndex: number; /** The call-llm#k runtimeStageId — the LLM-step pointer WITHIN the frame. */ readonly llmCallId: string | undefined; /** call-llm#k's commitLog array index — the EXCLUSIVE beforeIdx for findLastWriter. */ readonly llmCallArrayIdx: number | undefined; /** The loop-HEAD commit's array index — the body's lower bound. */ readonly headArrayIdx: number; /** Every runtimeStageId in [head[k], head[k+1]) — the full multi-stage body of round k, in commit order. */ readonly bodyIds: readonly string[]; /** stepOutputText over the call-llm commit (assistant content + tool-call intents). */ readonly intermediateText: string | undefined; /** One per key call-llm#k read. */ readonly contextSources: readonly ContextSource[]; /** * WALK-ONLY (proposal 008): the proximate tool result for this loop — the most recent * `lastToolResult` committed before this loop's call-llm, with the PRODUCING loop's tool-calls * stage as its `writerId` (the cross-loop provenance edge L4's `walkToRoot` descends along). NOT * in `contextSources` — L3's narrow never scores it. Absent on loop 0 / grouped frames. */ readonly proximateToolSource?: ProximateToolSource; /** * GROUPED chart only: the `sf-llm-call` mount runtimeStageId this frame was projected from. * When set, ALL array indices on this frame (`headArrayIdx`, `llmCallArrayIdx`, * `bodyIds`, each `contextSource.writerArrayIdx`) are relative to that subflow's OWN inner * commit log (`subflowResults[subflowScope].treeContext.history`), NOT the run commit log. * Absent for FLAT-chart frames (indices are run-commit-log relative). */ readonly subflowScope?: string; /** Pass-through of the call-llm bundle's untrackedSources ('args'|'env'|'silent'). */ readonly incompleteSources?: ReadonlyArray; /** True when incompleteSources is non-empty — "this step read untracked; slice may be * incomplete here". NOT a model-internalized claim (that is undetectable — see Trajectory). */ readonly untrackedReadsPresent: boolean; } /** The proximate tool result a loop's decision was conditioned on — L4's cross-loop hop edge (proposal 008). */ export interface ProximateToolSource { /** The committed `{ toolName, result }` (redaction-scrubbed, via commitValueAt). */ readonly value: unknown; /** The producing loop's tool-calls stage runtimeStageId — resolves to an EARLIER frame. */ readonly writerId: string | undefined; /** Honesty: the call-llm read `history`, NOT this key — an INFERRED proximate, not a direct read. */ readonly proximate: true; } /** The run input, re-injected as a synthetic node — a PROXY (args is untracked), never a recorded edge. */ export interface SyntheticQuestionNode { readonly text: string; readonly incompleteSources: readonly ['args']; readonly injected: true; } export interface Trajectory { readonly frames: readonly LoopFrame[]; /** Commits BEFORE the first head (seed, memory-read) — run setup, not a loop body. */ readonly prelude: readonly string[]; /** Only populated when the contrastive path is wired (proposal 005 L2 note). */ readonly question?: SyntheticQuestionNode; /** Degrade-never-throw. STANDING caveat on EVERY trajectory: contextSources show only * sources re-committed to tracked state; context the model retained internally (carried * in its own reasoning, never re-committed) leaves no read→write edge and is NOT here. */ readonly honestyFlags: readonly HonestyFlag[]; /** Set only when maxFrames cut the run. */ readonly truncated?: { readonly byFrames: boolean; }; } /** One frame's raw partition: the head's array index + the runtimeStageIds in its half-open range. */ export interface AnchorBucket { readonly headArrayIdx: number; readonly bodyIds: readonly string[]; } /** * Partition a commit log by a list of HEAD runtimeStageIds (taken as data — no agent * knowledge). Each frame is the half-open range `[head[k], head[k+1])`; commits before * the first head are the `prelude`. TOTAL: every commit lands in exactly one frame OR * the prelude. Heads not found in the log are ignored; ordering follows the log, not the * input list (an out-of-order or duplicate head list cannot reorder/duplicate commits). * * A head is anchored at the FIRST commit bearing its runtimeStageId — a single stage * execution can flush MORE THAN ONE commit bundle under one runtimeStageId (parallel * fork merges, multi-flush stages), and those repeats stay INSIDE the frame they open * rather than each spawning a spurious one-commit frame. */ export declare function bucketByAnchors(commitLog: readonly CommitBundle[], headRuntimeStageIds: readonly string[]): { frames: AnchorBucket[]; prelude: string[]; }; /** * The flat-chart loop heads: the FIRST commit of each injection-engine ENTRY (one per * ReAct iteration, since the loop is branch-sourced back to the injection engine). A head * is a commit that is in the injection engine while the previous commit was not — so a * multi-commit injection-engine body yields exactly one head per loop. * * Returns the runtimeStageIds to feed `bucketByAnchors`. Empty when the run never enters * the injection engine (e.g. the grouped chart, where the loop lives in sf-llm-call — * the caller degrades with an honesty flag). */ export declare function findLoopHeads(commitLog: readonly CommitBundle[]): string[]; export interface AssembleTrajectoryOptions { /** Chars of each source value / intermediate text embedded. Default 2000. */ readonly maxTextChars?: number; /** Keep only the first N frames (honesty-flagged via `truncated`). */ readonly maxFrames?: number; } /** * Slice a recorded agent run into a {@link Trajectory} — one {@link LoopFrame} per ReAct * iteration, each carrying its `call-llm` pointer, the call's output text, and the live * {@link ContextSource}s that fed it (traced via `findLastWriter` + `commitValueAt` from the * SAME commit log — zero new capture). * * Takes the SAME `ContextBugArtifacts` bag the localizer takes — adopter call is just * `assembleTrajectory(artifacts)`. * * Handles BOTH chart shapes: * - FLAT (`reactMode: 'dynamic'`, default): `call-llm` is a parent-level stage; frames are * bucketed over the run commit log by the `sf-injection-engine` loop heads. * - GROUPED (`reactMode: 'dynamic-grouped'`): the LLM turn runs in an `sf-llm-call` subflow; * each loop is projected PER-SCOPE over its own inner commit log (retained per-iteration by * footprintjs subflow-commit-visibility). Such frames carry `subflowScope` and their array * indices are inner-log-relative. * * Standing caveat on every result: contextSources show only sources re-committed to tracked * state; context the model retained internally (carried in its own reasoning, never * re-committed) leaves no read→write edge and is NOT represented. */ export declare function assembleTrajectory(artifacts: ContextBugArtifacts, opts?: AssembleTrajectoryOptions): Trajectory; //# sourceMappingURL=trajectory.d.ts.map