/** * selfExplain — the IN-CONVERSATION door over the agent's own trace. * * `.selfExplain()` on the builder mounts ONE skill plus ONE scoped tool * provider. Day to day the tool catalog carries only the skill's * activation row — the trace tools are NOT in the skill (skill `tools` * land in the static registry, exposed every iteration); they ride a * `skillScopedTools` provider gated on the skill's activation, composed * with whatever provider the consumer already set. When the user asks a * why-question the LLM activates the skill, and the NEXT iteration's * catalog gains the trace tools, bound to the agent's own PREVIOUS * COMPLETED run. * * The two pieces here: * * 1. `SelfExplainBinding` — the late-binding seam, a plain CombinedRecorder * attached like any consumer recorder (zero engine changes): * * - capture at `onRunEnd`/`onRunFailed`: the just-finished run's * snapshot becomes the explainable evidence (a FAILED run is * still a completed trace — "why did you fail?" works); * - rotate at `onRunStart`: a FRESH ControlDepRecorder per run. The * retired instance never sees the new run's events, so its live * `asLookup()` survives Convention-4's runId reset — the captured * control edges stay valid for the whole next turn. * * B13 safety lives here: `Agent.run()` reassigns its executor at run * START, so resolving artifacts mid-run through `getLastSnapshot()` * would expose the IN-FLIGHT run. Capturing only at terminal flush * means the binding can never serve anything but a completed run. * * 2. `buildSelfExplainSkill` — the skill in two modes: * * - INLINE (default): the skill unlocks the trace tools — every name * in `TRACE_TOOL_NAMES` (11 today) — in the main agent's own loop * (same model). The count is deliberately not restated anywhere * that could fall behind the pack. * - DELEGATE: the skill unlocks ONE tool — `explain_run(question)` — * whose execute runs a nested `traceDebugAgent` on the consumer's * chosen (cheaper) provider/model and returns its evidence-cited * answer. The main conversation pays for one tool call; the * trace-walking loop happens at the delegate's price. Loaded via * dynamic import so the builder never statically pulls Agent * through this module (no core ↔ lib cycle). */ import { type CombinedRecorder, type RuntimeSnapshot } from 'footprintjs'; import type { Injection } from '../injection-engine/types.js'; import type { AgentOptions } from '../../core/agent/types.js'; import type { Unsubscribe } from '../../events/dispatcher.js'; import type { AgentfootprintEvent } from '../../events/registry.js'; import type { ToolProvider } from '../../tool-providers/types.js'; import type { InnerRunLookup } from './innerRunRecords.js'; import type { TraceToolpackArtifacts, TraceToolpackOptions } from './types.js'; /** * How much of a turn's evidence the binding keeps. * * Both default to TRUE, which is the point: the tools that read them * (`read_narrative`, `inspect_tool_call`) are on the catalog either way, * and a tool that answers "⚠ no evidence" by default is a tool that * teaches the model not to call it. Turn one off when the cost matters * more than the answer — a very long-running turn, or a run whose * narrative would repeat what the structured tools already say. */ export interface SelfExplainInclude { /** The run's plain-English story → the `read_narrative` tool. Default true. */ readonly narrative?: boolean; /** * A bounded tail of the run's typed events → tool-call timings and * outcomes in `inspect_tool_call`, and the Context Integrity findings * `find_context_errors` reads. Default true. Off means no wildcard event * subscription is made at all, not a subscription that is ignored — and * `find_context_errors` then reports the evidence channel as ABSENT * rather than reporting a run with no context errors. */ readonly events?: boolean; } /** Consumer surface for `.selfExplain()` on the Agent builder. */ export interface SelfExplainOptions { /** Appended to the recommended skill body (ours stays; yours adds). */ readonly instruction?: string; /** * Answer why-questions on a SEPARATE (typically cheaper) model: the * skill unlocks one `explain_run` tool that runs a nested * `traceDebugAgent` and returns its evidence-cited answer. */ readonly delegate?: { readonly provider: AgentOptions['provider']; readonly model: string; readonly maxIterations?: number; }; /** Skill id (activation key for `read_skill`). Default 'self-explain'. */ readonly id?: string; /** Bounding dials forwarded to the toolpack. */ readonly toolpack?: TraceToolpackOptions; /** Which optional parts of a turn's evidence to capture. Both default true. */ readonly include?: SelfExplainInclude; /** * Cap on retained events per turn (only with `include.events`). Default * 2,000 — enough for a long tool-using turn, small enough that a server * holding one binding per agent does not grow without limit. A tail that * dropped events says so in `inspect_tool_call`. */ readonly maxEvents?: number; } /** Default per-turn event cap for the self-explain binding. */ export declare const SELF_EXPLAIN_MAX_EVENTS = 2000; /** * What the binding reads a completed turn's evidence FROM. * * One object rather than three wiring calls, on purpose: the * `BoundaryRecorder` lesson in this codebase is that a seam needing three * separate connections gets two of them in some integrations, and the * missing third fails silently. Here the only caller is `AgentBuilder`, * and it hands over all of it at once. */ export interface SelfExplainSource { /** The just-finished run's snapshot — `agent.getLastSnapshot()`. */ getSnapshot(): RuntimeSnapshot | undefined; /** The run's narrative entries — `agent.getLastNarrativeEntries()`. */ getNarrative?(): readonly { readonly text: string; }[]; /** The typed event stream — `agent.on('*', …)`. */ on?(type: '*', listener: (event: AgentfootprintEvent) => void): Unsubscribe; /** * The records TOOLS kept of their own runs — present when the agent * mounts a `flowchartAsTool({ keepRecord: true })`. Unlike the other * three, this is NOT captured at the terminal flush: the store is a live, * bounded object owned by the tool, and the binding holds the lookup so * `inspect_tool_run` reads whatever the tool has filed. Rotating it per * turn would throw away the previous turn's inner runs at exactly the * moment the follow-up question arrives. */ getInnerRuns?(): InnerRunLookup | undefined; } /** * The late-binding seam. Create one per built Agent, attach * `binding.recorder()` via `agent.attach()`, and point `bindTo()` at the * agent's `getLastSnapshot`. `artifacts` then always answers with the * previous COMPLETED run — never the in-flight one. */ export declare class SelfExplainBinding { private readonly include; private readonly maxEvents; private source; private ctrl; private tail; private captured; constructor(include?: SelfExplainInclude, maxEvents?: number); private get wantsNarrative(); private get wantsEvents(); /** * Point the binding at the agent it explains. * * Accepts the bare `getSnapshot` function it has always accepted (the * evidence a completed run leaves behind on its own), or the full * source, which adds the two parts a snapshot does NOT carry: the * narrative, and the typed event stream. */ bindTo(source: SelfExplainSource | (() => RuntimeSnapshot | undefined)): void; /** Evidence of the previous completed run, or undefined before the first. */ get artifacts(): TraceToolpackArtifacts | undefined; /** The recorder to attach — forwards flow events to the per-run ctrl. */ recorder(): CombinedRecorder; } /** The default skill id — the activation key the LLM passes to read_skill. */ export declare const SELF_EXPLAIN_SKILL_ID = "self-explain"; /** * The skill `.selfExplain()` mounts — methodology body ONLY. The trace * tools deliberately do NOT ride the skill: skill `tools` land in the * static registry (exposed every iteration); catalog gating is the * ToolProvider's job — see {@link buildSelfExplainToolProvider}. */ export declare function buildSelfExplainSkill(options: SelfExplainOptions): Injection; /** * The gated tool delivery — `skillScopedTools` (the shipped primitive) * scoped to the skill's id, composed with the consumer's own provider * when they set one. The iteration after activation, `ctx.activeSkillId` * matches and the catalog gains the trace tools (inline) or the single * `explain_run` tool (delegate). */ export declare function buildSelfExplainToolProvider(binding: SelfExplainBinding, options: SelfExplainOptions, existing?: ToolProvider): ToolProvider;