agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
79 lines (78 loc) • 3.86 kB
TypeScript
/**
* buildAgentMessageApiChart — the Agent (ReAct) form of the messageAPI
* merge-tree, as ONE FLAT main chart (no inner LLM-call sub-box).
*
* The whole ReAct cycle lives directly in the single Agent chart:
*
* Context (ROOT selector — inits + picks which context slots to engineer)
* ├─ system-prompt ┐
* ├─ messages ─────┼─→ messageAPI → Call-LLM
* └─ tools ────────┘
* → Route (decider) → [ ToolCalls (execute) → loop ] / Final (response)
* loopTo(Context)
*
* WHY flat (the user's call): the entire agent — context engineering, the LLM
* call, routing, tool execution, the loop, and the final response — is ONE
* visible flowchart in ONE Agent box. No nested LLM-call box: Lens wraps the
* whole chart in the Agent main-box and renders the slots as pills. This is
* simpler than the composed (sf-llm-call subflow) shape and avoids box-in-box
* nesting entirely.
*
* The three context slots are DIRECT children of Context; all converge at
* messageAPI (which assembles system-prompt + messages); Call-LLM then sends
* the assembled payload plus the tool schemas. Route decides tool-calls (loop)
* vs final (terminate). The same chart serves Static and Dynamic agents — only
* which slots the Context selector lights per iteration differs.
*/
import type { FlowChart } from 'footprintjs';
import type { LLMProvider, LLMToolSchema } from '../../provider-contract/types.js';
export interface AgentMessageApiChartDeps {
readonly provider: LLMProvider;
readonly model: string;
readonly systemPrompt: string;
readonly tools: readonly LLMToolSchema[];
readonly maxIterations?: number;
readonly structureRecorders?: readonly import('footprintjs').StructureRecorder[];
/**
* The id of the run this chart is about to make (9.91.0) — supply it and
* Call-LLM mints a receipt on every turn of the loop, the fingerprint of
* what the model was handed, committed at each call for `receiptAt` and
* `servedAt` to read. The twin of `MessageApiChartDeps.getRunId`, and for
* the same reason: this is a chart BUILDER handed to an executor the caller
* owns, so the salt has to come from whoever starts the run.
*
* OMIT IT AND NO RECEIPT IS MINTED — never an unsalted one. The rule and its
* reason live in `messageApiReceipt.ts`; `servedAt` declares the absence and
* rebuilds the view as it always did.
*
* @example (internal — `buildAgentMessageApiChart` is not on the package's
* public surface; the type is, so a caller composing this chart inside the
* library reads the contract here)
* ```ts
* const runId = `run-${Date.now()}`;
* const chart = buildAgentMessageApiChart({ ...deps, getRunId: () => runId });
* await new FlowChartExecutor(chart).run({ input: { message: 'hi' } });
* ```
*/
readonly getRunId?: () => string | undefined;
}
/**
* Build the Agent merge-tree chart as one flat ReAct flowchart.
*
* **Cancellation.** Every model call reads its signal from the run's `env`.
* For an abort or a deadline to cancel the provider's request, pass the signal
* there too: `env: { signal }`, and for a deadline `AbortSignal.timeout(ms)`. A
* bare `FlowChartExecutor` hands its own `signal` and `timeoutMs` to the
* traversal only. That stops the chart, but a call already in flight runs to
* completion, and you are billed for it.
*
* ```ts
* const controller = new AbortController();
* await new FlowChartExecutor(chart).run({
* input: { message: 'hi' },
* signal: controller.signal, // stops the traversal
* env: { signal: controller.signal }, // cancels the model call in flight
* });
* ```
*/
export declare function buildAgentMessageApiChart(deps: AgentMessageApiChartDeps): FlowChart;