agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
73 lines (72 loc) • 3.69 kB
TypeScript
/**
* Agent validators — pure helper functions extracted from Agent.ts.
*
* These run at Agent construction time (eagerly, so misconfiguration
* fails fast at `.build()`) and during stage execution (safeStringify
* for tool-result formatting).
*
* Pure functions, no class state — extracted for readability and
* isolated testability. The Agent class imports + invokes these in
* its constructor and stage handlers.
*/
import type { MemoryDefinition } from '../../memory/define.types.js';
import type { Injection } from '../../injection-engine/types.js';
import type { ToolRegistryEntry } from '../../tools/tools.js';
/**
* Validate that every memory definition has a unique id. Each memory
* writes to its own scope key (`memoryInjection_${id}`); duplicates
* silently overwrite, leading to data loss that's hard to debug.
*
* Throws on collision so `Agent.build()` fails fast at construction.
*/
export declare function validateMemoryIdUniqueness(memories: readonly MemoryDefinition[]): void;
/**
* Validate `maxIterations`. The historical silent clamp to 50 existed because
* footprintjs's recursive traversal hit its depth wall around iteration 71 —
* footprintjs 9.0.0's trampoline removed that wall (loops run on a flat
* stack), so the cap is gone. The lower bound (1) still prevents a
* 0-iteration agent; large values are the consumer's COST choice (each
* iteration is an LLM call) — a dev-mode warning flags budgets above 100.
* The Agent passes matching headroom to the engine's own loop-iteration
* limit so it can never fire below the agent's budget.
*/
export declare function clampIterations(n: number): number;
/**
* Validate tool-name uniqueness across `.tool()`-registered tools +
* every Skill's `inject.tools[]`. The LLM dispatches by `tool.schema.name`
* (the wire format), so any collision silently shadows execution.
*
* Called eagerly in the Agent constructor so `Agent.build()` throws
* immediately, not on first `run()`.
*
* `read_skill` is reserved when ≥1 Skill is registered — collisions
* with consumer tools throw.
*/
export declare function validateToolNameUniqueness(registry: readonly ToolRegistryEntry[], injections: readonly Injection[]): void;
/**
* What a tool that returned nothing puts in the conversation.
*
* Self-describing on purpose: the model reads this string as the tool's
* result, and it has to be able to tell "this tool does not report a value"
* apart from "this tool returned the word undefined". `''` would say neither.
*/
export declare const NO_TOOL_VALUE = "(this tool returned no value)";
/**
* JSON.stringify with circular-ref protection. Tool results are untrusted —
* a hostile/buggy tool returning a cyclic object must not crash the run.
* Falls back to '[unstringifiable: <reason>]' so the LLM still sees that
* the tool ran and produced something unusable.
*
* **Total, since 8.18.0.** `JSON.stringify` returns `undefined` — not a
* string — for `undefined`, a function and a symbol, so the old body's
* `: string` was a promise it could not keep. An `async execute()` that did
* its work and forgot to `return` produced a `role: 'tool'` message with
* `content: undefined`, which crashed the next turn's messages slot with an
* anonymous `TypeError` five frames inside the engine. A tool is allowed to
* do something and report nothing; the conversation just has to say so.
*
* The truth is unaffected: `agentfootprint.stream.tool_end` still carries
* the tool's REAL return value, `undefined` included (every serializing sink
* renders an Error in it by the same wire rule).
*/
export declare function safeStringify(value: unknown): string;