UNPKG

agentfootprint

Version:

The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.

106 lines • 5.07 kB
/** * lazyTraceToolpack — the toolpack with LATE-BOUND artifacts. * * `traceToolpack(artifacts)` is a factory over FROZEN artifacts: it * precomputes an index and even bakes step-id enums into tool schemas. * That is right for the dedicated debugger (the run is already complete * when you build it) — and wrong for `.selfExplain()`, where the tools * are defined at Agent BUILD time but must read the agent's own *previous * completed run*, which changes every turn. * * This wrapper splits the two lifetimes: * * - SCHEMAS are built once from an empty template (artifact-independent — * no id enums, generic descriptions; the same shape `traceToolpack` * itself produces past the enum cap), so the tools can be mounted on * a skill before any run exists. * - EXECUTION resolves `resolve()` per call, builds the REAL toolpack * over the resolved artifacts (memoized by snapshot identity — one * index per completed run, not per call), and delegates through * `callTraceTool` so arg validation matches the eager path. * * When `resolve()` returns undefined (no completed run yet), every tool * answers with an honest, model-visible message instead of throwing — * the same #9 philosophy as the toolpack's unknown-id corrections. */ import { callTraceTool, traceToolpack } from './traceToolpack.js'; import { NO_COMPLETED_RUN_MESSAGE } from '../lib/traceToolNames.js'; // Declared in `traceToolNames.ts` (beside the reserved names) so the delegate // tool can answer "no run yet" without loading this module; re-exported here // because it is this pack's answer. export { NO_COMPLETED_RUN_MESSAGE }; /** * Model-visible answer when a tool MOUNTED on the catalog has no evidence * to serve for this particular run. * * The lazy pack's shape is fixed at Agent BUILD time — the tool catalog and * the builder's name reservation both need to know the list before any run * exists — while an artifact bag's OPTIONAL parts (the narrative, the event * tail) are decided per run. So a tool can be on the catalog with nothing * behind it, and the honest move is to say which switch turns it back on * rather than to answer emptily. */ export function missingArtifactMessage(toolName) { const part = toolName === 'read_narrative' ? 'narrative' : 'events'; return (`${toolName} has nothing to read for this run — the captured evidence carries no ` + `${part}. This agent was built with selfExplain({ include: { ${part}: false } }), or the ` + `artifacts were assembled without it. The other trace tools are unaffected.`); } /** * Minimal empty snapshot for building artifact-independent template * schemas. The cast is safe because the toolpack's CONSTRUCTION reads * only `commitLog` (`?? []`) and `executionTree` (visit() guards * undefined) — `sharedState`/`commitValues` are read only inside * execute paths, and the wrapper below shadows every template execute. */ const EMPTY_SNAPSHOT = { commitLog: [], executionTree: undefined, sharedState: {}, commitValues: 'full', }; /** * Build the toolpack with late-bound artifacts. * * The template is built over an artifact bag that declares EVERY optional * part (an empty narrative, an empty event tail), so the lazy pack mounts * the same tool list every time. That fixed list is what the Agent * builder's name reservation and the tool catalog are both keyed on — a * pack whose shape depended on a run that has not happened yet could not * be reserved at build time. When a resolved run turns out not to carry * one of those parts, the tool answers with * {@link missingArtifactMessage} instead of quietly disappearing. */ export function lazyTraceToolpack(resolve, options) { const template = traceToolpack({ snapshot: EMPTY_SNAPSHOT, narrative: [], events: [] }, options); // One real toolpack per completed run: memoized by snapshot identity. // Retention note: the memo pins at most ONE prior snapshot (whatever a // trace tool last executed against) until the next call rebinds it. let memoSnapshot; let memoCause; let memoTools; const realTools = (artifacts) => { if (memoTools === undefined || memoSnapshot !== artifacts.snapshot || memoCause !== artifacts.cause) { memoSnapshot = artifacts.snapshot; memoCause = artifacts.cause; memoTools = traceToolpack(artifacts, options); } return memoTools; }; return template.map((tool) => ({ ...tool, execute: async (args, ctx) => { const artifacts = resolve(); if (!artifacts) return NO_COMPLETED_RUN_MESSAGE; const real = realTools(artifacts); if (!real.some((candidate) => candidate.schema.name === tool.schema.name)) { return missingArtifactMessage(tool.schema.name); } return callTraceTool(real, tool.schema.name, args, ctx); }, })); } //# sourceMappingURL=lazyToolpack.js.map