AgentForgeAgentForge

Tutorial: Add Tools and Memory

Extend your agent with multiple tools, budget controls, and session memory.

This tutorial picks up where Build a CLI Agent left off. You'll add a second tool, wire up budget governance, and give the agent memory so it remembers past conversations.

What you'll build

A product assistant agent that:

  • Searches a product catalog
  • Checks order status
  • Remembers what you asked in previous runs
  • Stays within a cost budget

Step 1: Start from the previous project

If you haven't done the first tutorial, go through Build a CLI Agent first. Or start fresh:

mkdir product-agent && cd product-agent
npm init -y
npm install @ahzan-agentforge/core zod
npm install -D typescript @types/node

Same tsconfig.json and package.json setup as before (add "type": "module" and "start": "npx tsx src/main.ts").

Step 2: Define multiple tools

Create src/tools.ts:

import { defineTool } from '@ahzan-agentforge/core';
import { z } from 'zod';

// Simulated product database
const products = [
  { id: 'KB-001', name: 'Wireless Keyboard', price: 49.99, stock: 23 },
  { id: 'MS-002', name: 'Ergonomic Mouse', price: 34.99, stock: 15 },
  { id: 'HD-003', name: 'USB-C Hub', price: 29.99, stock: 0 },
  { id: 'MN-004', name: '4K Monitor', price: 399.99, stock: 8 },
  { id: 'WC-005', name: 'HD Webcam', price: 59.99, stock: 42 },
];

// Simulated order database
const orders: Record<string, { status: string; items: string[]; total: number }> = {
  'ORD-100': { status: 'delivered', items: ['Wireless Keyboard'], total: 49.99 },
  'ORD-101': { status: 'shipped', items: ['Ergonomic Mouse', 'USB-C Hub'], total: 64.98 },
  'ORD-102': { status: 'processing', items: ['4K Monitor'], total: 399.99 },
};

export const searchProducts = defineTool({
  name: 'search_products',
  description: 'Search the product catalog by keyword. Returns matching products with price and stock.',
  input: z.object({
    query: z.string().describe('Search keyword'),
  }),
  output: z.object({
    results: z.array(z.object({
      id: z.string(),
      name: z.string(),
      price: z.number(),
      stock: z.number(),
    })),
    total: z.number(),
  }),
  execute: async ({ query }) => {
    const q = query.toLowerCase();
    const results = products.filter(
      p => p.name.toLowerCase().includes(q) || p.id.toLowerCase().includes(q)
    );
    return { results, total: results.length };
  },
});

export const checkOrder = defineTool({
  name: 'check_order',
  description: 'Check the status of an order by order ID.',
  input: z.object({
    orderId: z.string().describe('Order ID (e.g. ORD-100)'),
  }),
  output: z.object({
    orderId: z.string(),
    status: z.string(),
    items: z.array(z.string()),
    total: z.number(),
  }),
  execute: async ({ orderId }) => {
    const order = orders[orderId.toUpperCase()];
    if (!order) {
      return { orderId, status: 'not_found', items: [], total: 0 };
    }
    return { orderId, ...order };
  },
});

Step 3: Add budget governance

Budget governance caps how much a single run can cost. If the agent exceeds the limit, AgentForge stops the run before it racks up charges.

Create src/main.ts:

import { defineAgent, createLLM, InMemoryMemoryStore } from '@ahzan-agentforge/core';
import { searchProducts, checkOrder } from './tools.js';

// --- LLM ---

const llm = createLLM({
  provider: 'anthropic',
  model: 'claude-sonnet-4-20250514',
});

// --- Memory ---
// InMemoryMemoryStore persists within the process.
// For cross-session persistence, swap with PgVectorMemoryStore.

const memoryStore = new InMemoryMemoryStore();

// --- Agent ---

const agent = defineAgent({
  name: 'product-assistant',
  description: 'Helps customers search products and check orders',
  tools: [searchProducts, checkOrder],
  llm,
  systemPrompt: `You are a product assistant for an electronics store.
You can search the catalog and check order statuses.
Be concise and helpful. If a product is out of stock, say so clearly.
If you remember previous interactions with the customer, reference them naturally.`,
  maxSteps: 10,
  budget: {
    maxCostUsd: 0.05,  // 5 cents per run — keeps costs predictable
    maxTokens: 4000,
  },
  memory: {
    store: memoryStore,
    config: {
      provider: 'in-memory',
      namespace: 'product-assistant',
      autoCapture: true,
      maxResults: 5,
    },
  },
});

// --- Run ---

const task = process.argv[2] ?? 'Do you have any keyboards in stock?';

console.log(`\nTask: ${task}\n`);

const result = await agent.run({ task });

console.log(`Status: ${result.status}`);
console.log(`Steps:  ${result.trace.steps.length}`);
console.log(`Tokens: ${result.trace.summary.totalTokens}`);
console.log(`Output: ${result.output}\n`);

// Show tool calls
for (const step of result.trace.steps) {
  if (step.type === 'tool_call') {
    console.log(`  [tool] ${step.toolName} → ${JSON.stringify(step.toolOutput)}`);
  }
}

// Show budget usage
if (result.trace.summary.estimatedCostUsd !== undefined) {
  console.log(`\n  Cost: $${result.trace.summary.estimatedCostUsd.toFixed(4)}`);
}

Step 4: Run it

npm start "Do you have any keyboards?"

Expected output:

Task: Do you have any keyboards?

Status: completed
Steps:  2
Tokens: 847
Output: Yes! We have the Wireless Keyboard (KB-001) for $49.99 with 23 in stock.

  [tool] search_products → {"results":[{"id":"KB-001","name":"Wireless Keyboard","price":49.99,"stock":23}],"total":1}

  Cost: $0.0031

Try more queries:

npm start "What's the status of order ORD-101?"
npm start "Is the USB-C Hub available?"
npm start "Show me monitors under 500 dollars"

Step 5: See budget enforcement

If you set an unrealistically low budget:

budget: {
  maxCostUsd: 0.001,   // 0.1 cents — too low for most tasks
  maxTokens: 100,
},

The agent will stop early:

Status: failed
Output: null

  Error: BUDGET_EXCEEDED — Token limit of 100 exceeded

This is the framework protecting you. Reset it to a reasonable value and the agent works normally.

How the pieces fit together

┌─────────────────────────────────────────────┐
│                 agent.run()                  │
│                                              │
│  1. Check memory for past context            │
│  2. Send task + tools + memories to LLM      │
│  3. LLM says: call search_products           │
│  4. Budget check — are we within limits?     │
│  5. Validate input → run tool → validate out │
│  6. Send result back to LLM                  │
│  7. LLM says: here's the answer (text)       │
│  8. Auto-capture memories for next run        │
│  9. Record trace, finalize                   │
└─────────────────────────────────────────────┘

Every step passes through budget checks. Every tool call gets Zod validation on both input and output. Memories are captured automatically if autoCapture is enabled.

Next steps