Get Started

Quick Start

Build your first agent in 5 minutes — and add memory in 5 more.

What is an AI agent?

An LLM that can take actions, not just generate text. It calls tools, reads the results, decides what to do next, and loops until done. agentfootprint makes every step of that loop inspectable — what landed in which slot, which tool ran with what args, why a decision branched the way it did.

User question → LLM thinks → calls a tool → reads result → thinks again → responds

Install

npm install agentfootprint

Your first agent

import { Agent, defineTool } from 'agentfootprint'
import { mock } from 'agentfootprint/providers';

const weather = defineTool({
  name: 'weather',
  description: 'Get current weather for a city.',
  inputSchema: {
    type: 'object',
    properties: { city: { type: 'string' } },
    required: ['city'],
  },
  execute: async (args) => `${(args as { city: string }).city}: 72°F, sunny`,
});

const agent = Agent.create({
  provider: mock({ reply: 'Paris is 72°F and sunny today.' }),
  model: 'mock',
  maxIterations: 5,
})
  .system('You answer weather questions using the `weather` tool.')
  .tool(weather)
  .build();

const result = await agent.run({ message: 'Weather in Paris?' });
console.log(result);
// → "Paris is 72°F and sunny today."

The mock provider returns a scripted reply — perfect for $0 testing. Swap to anthropic / openai / bedrock / ollama for real LLMs — the only change is the provider: line. The real-LLM factories ship from a separate subpath so the main bundle stays free of vendor-SDK peer deps:

import { anthropic } from 'agentfootprint/providers';
// ...
provider: anthropic({ apiKey: process.env.ANTHROPIC_API_KEY! }),

Add memory in 5 more lines

agentfootprint ships a single defineMemory({ type, strategy, store }) factory. Pick a TYPE (what shape you keep), a STRATEGY (how you fit it in), and a STORE.

import { Agent } from 'agentfootprint'
import { defineMemory, MEMORY_TYPES, MEMORY_STRATEGIES, InMemoryStore } from 'agentfootprint/memory'
import { mock } from 'agentfootprint/providers';

const memory = defineMemory({
  id: 'short-term',
  type: MEMORY_TYPES.EPISODIC,
  strategy: { kind: MEMORY_STRATEGIES.WINDOW, size: 10 }, // last 10 turns
  store: new InMemoryStore(),
});

const agent = Agent.create({ provider: mock({ reply: 'Hi Alice!' }), model: 'mock' })
  .memory(memory)
  .build();

// Pass an `identity` so memory accumulates per conversation.
const id = { conversationId: 'alice-session' };
await agent.run({ message: 'My name is Alice', identity: id });
const result = await agent.run({ message: 'What did I just say?', identity: id });
// → memory subflow loaded the prior turn; the agent answered from context

Read memory examples for all 7 strategies (window / budget / summarize / topK / extract / causal / hybrid).

See what happened

// Structured execution trace — every stage, every decision,
// every tool call, every memory injection. Each entry has a
// human-readable `.text` plus a `runtimeStageId` for time-travel.
for (const entry of agent.getLastNarrativeEntries()) {
  console.log(entry.text);
}

Or attach typed event listeners:

agent.on('agentfootprint.context.injected', (e) => {
  console.log(`[${e.payload.source}] landed in ${e.payload.slot} slot`);
});
agent.on('agentfootprint.stream.tool_start', (e) => {
  console.log(`→ tool ${e.payload.toolName}(${JSON.stringify(e.payload.args)})`);
});

Every event is typed: 59 events across 16 domains (context, agent, stream, tools, skill, permission, eval, cost, memory, …).

The run, as a flowchart

This is the actual trace of the weather agent above — generated by running it (scripted mock, $0, offline) and rendering the footprintjs snapshot it produced. Click a node to inspect what happened there; the LLM turns are foldable groups with their system / messages / tools slots inside.

Loading the run…

The picture isn't drawn by hand or by a separate tracing pipeline — the agent run is a footprintjs flowchart, and the renderer (footprint-explainable-ui) draws that snapshot directly.

Give a skill a procedure (steps)

When a skill's playbook is really a sequence — look up the order, confirm the duplicate, then refund — declare it as data and the framework walks it for you: each iteration offers only the current step's tool (banner-led: [Step 2 of 6 — confirm the duplicate charge]), a skip_step tool puts any decline on the record, and a premature stop gets one teaching nudge. Your other tools and read_skill stay available throughout — a declared order, not a cage.

const refundProcedure = defineSkill({  id: 'refund',  description: 'Handles refunds end to end, by declared procedure.',  body: 'Follow the refund procedure. Every step says why it exists.',  tools: [    t('find_order', 'order #4021: $42.10, card ending 4242'),    t('check_history', 'one charge on 2026-08-01, one duplicate on 2026-08-02'),    t('verify_identity', 'identity verified'),    approveRefund,    t('issue_refund', 'refund of $42.10 issued'),    t('file_receipt', 'receipt R-991 filed'),  ] as never,  steps: [    { tool: 'find_order', note: 'find the order before touching money' },    { tool: 'check_history', note: 'confirm the duplicate charge' },    { tool: 'verify_identity', note: 'verify the caller owns the card' },    { tool: 'approve_refund', note: 'a person approves before money moves' },    { tool: 'issue_refund', note: 'refund the duplicate charge only' },    { tool: 'file_receipt', note: 'file the receipt for audit' },  ],  // 'advance' (the default, spelled out): a recorded skip moves on.  onSkip: 'advance',});

Every move is a typed event — skill.step_advanced, skill.step_skipped, skill.steps_unfinished — and a step whose tool asks a human pauses the run and advances on resume. Full walkthrough in Skills → Steps as data; runnable end-to-end in examples/context-engineering/16-skill-steps.ts.

Next steps

Run any of the examples end-to-end with npm run example examples/<path>.ts inside a clone of the repo. Every example is also an end-to-end test in CI.

On this page