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/nodeSame 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.0031Try 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 exceededThis 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
- Test and Debug — write tests for this agent without calling a real LLM
- Budget governance — fine-grained cost models and warnings
- Memory — PgVector for persistent cross-session memory
- Autonomy policy — control which tools need human approval