Classes

AgentBuilder

Class: AgentBuilder

Defined in: src/core/agent/AgentBuilder.ts:45

Fluent builder. tool() accepts any Tool<TArgs, TResult> and registers it by its schema.name. Duplicate names throw at build time.

Constructors

Constructor

new AgentBuilder(opts): AgentBuilder

Defined in: src/core/agent/AgentBuilder.ts:152

Parameters

opts

AgentOptions

Returns

AgentBuilder

Methods

appName()

appName(name): this

Defined in: src/core/agent/AgentBuilder.ts:285

Set the agent's display name — substituted as {{appName}} in commentary + thinking templates. Same place to brand a tenant ("Acme Bot"), distinguish multi-agent roles ("Triage" vs "Reviewer"), or localize ("Asistente"). Default: 'Chatbot'.

Parameters

name

string

Returns

this


build()

build(): Agent

Defined in: src/core/agent/AgentBuilder.ts:804

Returns

Agent


commentaryTemplates()

commentaryTemplates(templates): this

Defined in: src/core/agent/AgentBuilder.ts:300

Override agentfootprint's bundled commentary templates. Spread on top of defaultCommentaryTemplates; missing keys fall back. Same Record<string, string> shape with {{vars}} substitution as the bundled defaults — see defaultCommentaryTemplates for the full key list.

Use cases: i18n ('agent.turn_start': 'El usuario...'), brand voice ("You: {{userPrompt}}"), per-tenant customization.

Parameters

templates

Readonly<Record<string, string>>

Returns

this


fact()

fact(injection): this

Defined in: src/core/agent/AgentBuilder.ts:432

Register a Fact — developer-supplied data the LLM should see. User profile, env info, computed summary, current time, … Distinct from Skills (LLM-activated guidance) and Steering (always-on rules) in INTENT — the engine treats them all alike.

Parameters

injection

Injection

Returns

this


injection()

injection(injection): this

Defined in: src/core/agent/AgentBuilder.ts:328

Register any Injection. Use this for power-user / custom flavors; for built-in flavors use the typed sugar (.skill, .steering, .instruction, .fact).

Parameters

injection

Injection

Returns

this


instruction()

instruction(injection): this

Defined in: src/core/agent/AgentBuilder.ts:410

Register an Instruction — rule-based system-prompt guidance. Predicate runs each iteration. Use for context-dependent rules including the "Dynamic ReAct" on-tool-return pattern.

Parameters

injection

Injection

Returns

this


instructions()

instructions(injections): this

Defined in: src/core/agent/AgentBuilder.ts:421

Bulk-register many instructions at once. Convenience for consumer code that organizes its instruction set in a flat array (const instructions = [outputFormat, dataRouting, ...]). Each element is registered via .instruction() so duplicate-id checks still fire per-entry.

Parameters

injections

readonly Injection[]

Returns

this


maxIterations()

maxIterations(n): this

Defined in: src/core/agent/AgentBuilder.ts:256

Override the ReAct iteration cap set via Agent.create({ maxIterations }). Convenience for builder-style code that prefers fluent setters over constructor opts. Last call wins.

Throws if n is not a positive integer or exceeds the hard cap (clampIterations's upper bound).

Parameters

n

number

Returns

this


memory()

memory(definition): this

Defined in: src/core/agent/AgentBuilder.ts:459

Register a Memory subsystem — load/persist conversation context, facts, narrative beats, or causal snapshots across runs.

The MemoryDefinition is produced by defineMemory({ type, strategy, store }). Multiple memories layer cleanly via per-id scope keys (memoryInjection_${id}):

Agent.create({ provider })
  .memory(defineMemory({ id: 'short', type: MEMORY_TYPES.EPISODIC,
                         strategy: { kind: MEMORY_STRATEGIES.WINDOW, size: 10 },
                         store }))
  .memory(defineMemory({ id: 'facts', type: MEMORY_TYPES.SEMANTIC,
                         strategy: { kind: MEMORY_STRATEGIES.EXTRACT,
                                     extractor: 'pattern' }, store }))
  .build();

The READ subflow runs at the configured timing (default MEMORY_TIMING.TURN_START) and writes its formatted output to the memoryInjection_${id} scope key for the slot subflows to consume.

Parameters

definition

MemoryDefinition

Returns

this


outputFallback()

outputFallback<T>(options): this

Defined in: src/core/agent/AgentBuilder.ts:577

3-tier degradation for output-schema validation failures. Pairs with .outputSchema() — calling .outputFallback() without an outputSchema first throws (the fallback has nothing to validate).

Three tiers:

  1. Primary — LLM emitted schema-valid JSON. Caller gets it.
  2. FallbackOutputSchemaError thrown. The async fallback(error, raw) runs; its return is re-validated.
  3. Canned — static safety-net value. NEVER throws when set.

canned is validated against the schema at builder time — fail-fast on misconfig (a canned that doesn't validate would defeat the fail-open guarantee).

Two typed events fire on tier transitions for observability:

  • agentfootprint.resilience.output_fallback_triggered
  • agentfootprint.resilience.output_canned_used

Type Parameters

T

T

Parameters

options

OutputFallbackOptions<T>

Returns

this

Example

import { z } from 'zod';
const Refund = z.object({ amount: z.number(), reason: z.string() });

const agent = Agent.create({...})
  .outputSchema(Refund)
  .outputFallback({
    fallback: async (err, raw) => ({ amount: 0, reason: 'manual review' }),
    canned:   { amount: 0, reason: 'unable to process' },
  })
  .build();

outputSchema()

outputSchema<T>(parser, opts?): this

Defined in: src/core/agent/AgentBuilder.ts:522

Declarative terminal contract. The agent's final answer must be JSON matching parser. Auto-injects a system-prompt instruction telling the LLM the shape, and exposes agent.runTyped() / agent.parseOutput() for parse + validate at the call site.

The parser is duck-typed: any object with a parse(unknown): T method works (Zod, Valibot, ArkType, hand-written). The optional description field on the parser drives the auto-generated instruction; consumers can also override via opts.instruction.

Throws if called more than once on the same builder (avoids silent override surprises).

Type Parameters

T

T

Parameters

parser

OutputSchemaParser<T>

Validation strategy that throws on shape failure.

opts?

OutputSchemaOptions

Optional { name, instruction } to customize.

Returns

this

Example

import { z } from 'zod';
  const Output = z.object({
    status: z.enum(['ok', 'err']),
    items: z.array(z.string()),
  }).describe('A status enum + an array of strings.');

  const agent = Agent.create({...})
    .outputSchema(Output)
    .build();

  const typed = await agent.runTyped({ message: '...' });
  typed.status; // narrowed to 'ok' | 'err'

rag()

rag(definition): this

Defined in: src/core/agent/AgentBuilder.ts:487

Register a RAG retriever — semantic search over a vector-indexed corpus. Identical plumbing to .memory() (RAG resolves to a MemoryDefinition produced by defineRAG()); this alias exists so the consumer's intent reads clearly:

agent
  .memory(shortTermConversation)   // remembers what the USER said
  .rag(productDocs)                // retrieves what the CORPUS says
  .build();

Both end up as memory subflows, but the alias separates "user conversation memory" from "document corpus retrieval" in code intent, ids, and Lens chips.

Parameters

definition

MemoryDefinition

Returns

this


recorder()

recorder(rec): this

Defined in: src/core/agent/AgentBuilder.ts:274

Attach a footprintjs CombinedRecorder to the built Agent. Wired via agent.attach(rec) immediately after construction, so the recorder sees every event from the very first run.

Equivalent to calling agent.attach(rec) post-build; the builder method is a convenience for codebases that prefer fully-fluent agent assembly. Multiple recorders are supported (each gets its own attach() call).

Parameters

rec

CombinedRecorder

Returns

this


reliability()

reliability(config): this

Defined in: src/core/agent/AgentBuilder.ts:653

Wire rules-based reliability around every CallLLM execution. The framework wraps the LLM call in a retry/fallback/fail-fast loop driven by preCheck and postDecide rules.

Decision verbs the rules can emit (see ReliabilityDecision for the full list):

continue — pre-check OK, proceed to the call • ok — post-call OK, commit and return • retry — re-call same provider (bumps attempt) • retry-other — advance to next provider in providers[]fallback — invoke config.fallback(req, lastError)fail-fast — throw ReliabilityFailFastError at agent.run()

Streaming + reliability semantics — first-chunk arbitration: Pre-first-chunk failures (connection/headers/breaker-open) honor the full rule set (retry, retry-other, fallback, fail-fast). Post-first-chunk failures (mid-stream) honor only ok and fail-fast; rules wanting retry/retry-other/fallback are escalated to fail-fast with kind 'mid-stream-not-retryable'. This matches LangChain's RunnableWithFallbacks pattern and the prevailing industry default — see the streaming + reliability design memo for the full discussion.

Throws if called more than once on the same builder.

Parameters

config

ReliabilityConfig

Returns

this

Example

import { Agent } from 'agentfootprint';
  import { ReliabilityFailFastError } from 'agentfootprint/reliability';

  const agent = Agent.create({ provider, model: 'mock' })
    .system('Triage support tickets.')
    .reliability({
      postDecide: [
        { when: (s) => s.errorKind === '5xx-transient' && s.attempt < 3,
          then: 'retry', kind: 'transient-retry' },
        { when: (s) => s.error !== undefined,
          then: 'fail-fast', kind: 'unrecoverable' },
      ],
      circuitBreaker: { failureThreshold: 3 },
    })
    .build();

  try {
    await agent.run({ message: 'help' });
  } catch (e) {
    if (e instanceof ReliabilityFailFastError) {
      console.log(e.kind, e.reason);
    }
  }

selfExplain()

selfExplain(opts?): this

Defined in: src/core/agent/AgentBuilder.ts:785

Let this agent answer why-questions about its OWN previous completed turn, from its recorded trace. Mounts one skill: day to day the tool catalog carries only the skill's activation row; when the user asks "why did you…", the LLM activates it and that iteration alone gets the trace tools (inline mode) or a single explain_run tool that runs a nested trace debugger on a cheaper model (delegate mode).

Evidence binds LATE — always to the previous COMPLETED run, never the in-flight one — and includes control edges (a per-run control-dependence recorder is attached automatically).

Parameters

opts?

SelfExplainOptions = {}

Returns

this

Examples

Agent.create({ provider, model })
    .system('You are a refunds assistant.')
    .tool(lookupOrder)
    .selfExplain()                                 // inline, zero config
    .build();
.selfExplain({ delegate: { provider: anthropic(), model: 'claude-haiku-4-5' } })

skill()

skill(injection): this

Defined in: src/core/agent/AgentBuilder.ts:341

Register a Skill — LLM-activated, system-prompt + tools. Auto-attaches the read_skill activation tool to the agent. Skill stays active for the rest of the turn once activated.

Parameters

injection

Injection

Returns

this


skillGraph()

skillGraph(graph): this

Defined in: src/core/agent/AgentBuilder.ts:375

Mount a declarative skill graph (proposal 002) — each skill carries a graph-derived trigger (entry → always/rule, deterministic route → rule / on-tool-return), so dynamic token-efficient loading becomes declared and drawable. Pure sugar over .injection()graph.toMermaid() renders the topology.

Parameters

graph
nextSkill

(ctx) => string | undefined

reachableSkills?

(currentSkillId?) => readonly string[]

scoreEntries?

(ctx, signal?) => Promise<EntryScoring>

skills

readonly Injection[]

Returns

this

Example

const graph = skillGraph()
    .entry(triage)
    .route(triage, sfp, { when: (r) => r.toolName === 'get_counters' && JSON.parse(r.result).crc > 0 })
    .build();
  Agent.create({ provider }).skillGraph(graph).build();

skills()

skills(registry): this

Defined in: src/core/agent/AgentBuilder.ts:356

Bulk-register every Skill in a SkillRegistry. Use for shared skill catalogs across multiple Agents — register skills once on the registry; attach the same registry to every consumer Agent.

Parameters

registry
list

Returns

this

Example

const registry = new SkillRegistry();
  registry.register(billingSkill).register(refundSkill);
  const supportAgent = Agent.create({ provider }).skills(registry).build();
  const escalationAgent = Agent.create({ provider }).skills(registry).build();

steering()

steering(injection): this

Defined in: src/core/agent/AgentBuilder.ts:401

Register a Steering doc — always-on system-prompt rule. Use for invariant guidance: output format, persona, safety policies.

Parameters

injection

Injection

Returns

this


system()

system(prompt, options?): this

Defined in: src/core/agent/AgentBuilder.ts:176

Set the base system prompt.

Parameters

prompt

string

The system prompt text. Stable per-turn.

options?

Optional config. cache controls how the CacheDecision subflow treats this prompt block:

  • 'always' (default) — cache the base prompt as a stable prefix anchor. Highest cache-hit rate; recommended for production agents whose system prompt rarely changes.
  • 'never' — skip caching. Use if the prompt contains volatile content (timestamps, per-request user IDs).
  • 'while-active' — semantically equivalent to 'always' for the base prompt (it's always active by definition).
  • { until } — conditional invalidation (e.g., flush after iter 5).
cache?

CachePolicy

Returns

this


thinking()

thinking(opts): this

Defined in: src/core/agent/AgentBuilder.ts:746

v2.14+ — REQUEST-side thinking activation. Tells the provider to emit reasoning blocks alongside its response.

What this does: every LLM call carries LLMRequest.thinking = { budget }. The AnthropicProvider translates to thinking: { type: 'enabled', budget_tokens: N } on the wire. The model spends up to budget reasoning tokens before producing the visible response.

Distinct from .thinkingHandler():

  • .thinking({ budget }) = ASK the model to think (request side)
  • .thinkingHandler(h) = NORMALIZE the response (response side)

Most consumers want both; auto-wired handler covers the response side automatically when .thinking() is set on a thinking-capable provider. Setting .thinking() without .thinkingHandler(null) is the typical happy path.

Provider compatibility:

  • Anthropic: requires claude-sonnet-4-5 / opus-4-5 (or newer). Older models reject with HTTP 400.
  • OpenAI: ignores. o1/o3 reasoning is selected at the model id level; this field is a no-op for OpenAIProvider.

Budget guidance: Anthropic recommends 1024-32000 reasoning tokens. budget MUST be less than the request's max_tokens (defaults to 4096 in AnthropicProvider — bump via the request maxTokens if budget > ~3000).

Calling twice throws — same shape as .reliability() / .outputSchema().

Parameters

opts
budget

number

Returns

this

Example

Agent.create({ provider: anthropic({...}), model: 'claude-sonnet-4-5' })
    .system('You are a careful reasoning agent.')
    .thinking({ budget: 5000 })   // ask Anthropic to think
    .build();

thinkingHandler()

thinkingHandler(handler): this

Defined in: src/core/agent/AgentBuilder.ts:697

Wire a thinking handler (v2.14+). Three usage patterns:

• OMITTED (default) — framework auto-wires by provider.name via findThinkingHandler from the registry. Most consumers using a shipped provider get thinking support for free.

• EXPLICIT handler — override the auto-wire. For custom providers or for swapping in a custom Anthropic/OpenAI handler with different normalization (e.g. redacting blocks before they land).

• EXPLICIT null — opt out entirely. The thinking subflow is NOT mounted even if the provider would auto-match. Use when you want to skip thinking parsing for this agent (cost / latency / UX reasons).

Calling twice throws — same shape as .reliability() / .outputSchema() to enforce single-source intent.

Parameters

handler

ThinkingHandler | null

Returns

this

Examples

// Default — auto-wire AnthropicThinkingHandler for anthropic provider
  Agent.create({ provider: anthropic({...}), model: '...' }).build();
// Custom handler that redacts thinking content
  Agent.create({...}).thinkingHandler(myRedactingHandler).build();
// Opt out of thinking parsing entirely
  Agent.create({ provider: anthropic({...}), model: '...' })
    .thinkingHandler(null)
    .build();

thinkingTemplates()

thinkingTemplates(templates): this

Defined in: src/core/agent/AgentBuilder.ts:312

Override agentfootprint's bundled thinking templates. Same contract shape as commentary; different vocabulary — first-person status the chat bubble shows mid-call. Per-tool overrides go via tool.<toolName> keys (e.g., 'tool.weather': 'Looking up the weather…'). See defaultStatusTemplates for the full key list.

Parameters

templates

Readonly<Record<string, string>>

Returns

this


tool()

tool<TArgs, TResult>(tool): this

Defined in: src/core/agent/AgentBuilder.ts:184

Type Parameters

TArgs

TArgs

TResult

TResult

Parameters

tool

Tool<TArgs, TResult>

Returns

this


toolProvider()

toolProvider(provider): this

Defined in: src/core/agent/AgentBuilder.ts:238

Wire a chainable ToolProvider (from agentfootprint/tool-providers) as the agent's per-iteration tool source.

The provider is consulted EVERY iteration via provider.list(ctx) with ctx = { iteration, activeSkillId, identity }. Tools the provider emits flow into the Tools slot alongside any static tools registered via .tool() / .tools(). The tool-call dispatcher also consults the provider so dynamic chains (gatedTools, skillScopedTools) dispatch correctly when their visible-set changes mid-turn.

Throws if called more than once on the same builder (avoids silent override surprises).

Parameters

provider

ToolProvider

Returns

this

Example

Permission-gated baseline
  import { gatedTools, staticTools } from 'agentfootprint/tool-providers';
  import { PermissionPolicy } from 'agentfootprint/security';

  const policy = PermissionPolicy.fromRoles({
    readonly: ['lookup', 'list_skills', 'read_skill'],
    admin:    ['lookup', 'list_skills', 'read_skill', 'delete'],
  }, 'readonly');

  const provider = gatedTools(
    staticTools(allTools),
    (toolName) => policy.isAllowed(toolName),
  );

  const agent = Agent.create({ provider: llm, model })
    .system('You answer.')
    .toolProvider(provider)
    .build();

tools()

tools(tools): this

Defined in: src/core/agent/AgentBuilder.ts:199

Register many tools at once. Convenience for tool sources that return a list (e.g., await mcpClient(...).tools()). Each tool is registered via .tool() so duplicate-name validation still fires per-entry.

Parameters

tools

readonly Tool<Record<string, unknown>, unknown>[]

Returns

this

On this page