AutoGen Tutorial (TypeScript): adding memory to agents for advanced developers
This tutorial shows how to give an AutoGen TypeScript agent persistent memory using a simple SQLite-backed store and retrieval layer. You need this when the agent must remember prior customer context, policy details, or case history across multiple turns instead of treating every request as a fresh conversation.
What You'll Need
- •Node.js 20+
- •A TypeScript project with
"type": "module"enabled - •
@autogenai/autogeninstalled - •
openaiinstalled - •
better-sqlite3installed for local persistence - •An OpenAI API key in
OPENAI_API_KEY - •Basic familiarity with AutoGen agents, models, and tool calling
Install the packages:
npm install @autogenai/autogen openai better-sqlite3
npm install -D typescript tsx @types/better-sqlite3
Step-by-Step
- •Start by creating a tiny memory store. This is not “agent memory” inside the model; it is application memory that you control, persist, and retrieve before each run. For banking and insurance workflows, that separation matters because you need auditability and deterministic storage.
// memory.ts
import Database from "better-sqlite3";
export type MemoryItem = {
userId: string;
text: string;
createdAt: number;
};
const db = new Database("memory.db");
db.exec(`
CREATE TABLE IF NOT EXISTS memories (
id INTEGER PRIMARY KEY AUTOINCREMENT,
userId TEXT NOT NULL,
text TEXT NOT NULL,
createdAt INTEGER NOT NULL
)
`);
export function saveMemory(item: MemoryItem) {
const stmt = db.prepare(
"INSERT INTO memories (userId, text, createdAt) VALUES (?, ?, ?)"
);
stmt.run(item.userId, item.text, item.createdAt);
}
export function searchMemory(userId: string, query: string) {
const stmt = db.prepare(
"SELECT text FROM memories WHERE userId = ? AND text LIKE ? ORDER BY createdAt DESC LIMIT 5"
);
return stmt.all(userId, `%${query}%`) as { text: string }[];
}
- •Next, create an AutoGen agent that can read from memory before answering and write to memory after it responds. The pattern here is simple: retrieve relevant context, inject it into the prompt, then persist a compact summary of what happened.
// agent.ts
import OpenAI from "openai";
import { AssistantAgent } from "@autogenai/autogen";
import { saveMemory, searchMemory } from "./memory.js";
const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
export async function runWithMemory(userId: string, message: string) {
const memories = searchMemory(userId, message).map((m) => `- ${m.text}`).join("\n");
const agent = new AssistantAgent({
name: "memory_agent",
modelClient: client,
systemMessage:
"You are a helpful assistant for regulated workflows. Use provided memory when relevant.",
});
const result = await agent.run({
messages: [
{
role: "user",
content:
`Relevant memory:\n${memories || "- none"}\n\nUser request:\n${message}`,
},
],
});
const reply = result.messages.at(-1)?.content ?? "";
saveMemory({
userId,
text: `User asked: ${message} | Assistant replied: ${reply}`,
createdAt: Date.now(),
});
return reply;
}
- •Add a small entry point so you can test repeated interactions for the same user. The important part is that the second call should see the first call’s stored context and use it when responding.
// index.ts
import "dotenv/config";
import { runWithMemory } from "./agent.js";
async function main() {
const userId = "customer-1001";
const first = await runWithMemory(
userId,
"My policy number is POL-88421 and I prefer email updates."
);
console.log("First reply:", first);
const second = await runWithMemory(
userId,
"What contact preference did I mention earlier?"
);
console.log("Second reply:", second);
}
main().catch(console.error);
- •If you want better retrieval than
LIKE, add a summarization step before storing memory. In production systems, keep raw transcripts separate from compressed facts so your prompts stay small and your recall stays targeted.
// summarize.ts
export function summarizeForMemory(userText: string, assistantText: string) {
return [
`User preference or fact`,
`User said: ${userText}`,
`Assistant said: ${assistantText}`,
].join(" | ");
}
Then use it in your write path:
import { summarizeForMemory } from "./summarize.js";
// inside runWithMemory after reply is computed
saveMemory({
userId,
text: summarizeForMemory(message, reply),
createdAt: Date.now(),
});
- •Run the app with TypeScript execution and confirm the agent uses earlier context. If you are building for support or underwriting flows, this is where you would replace the toy lookup with embeddings plus metadata filters.
npx tsx index.ts
Testing It
Run the script twice for the same userId and inspect the second response. If memory is wired correctly, the agent should answer using the prior preference or fact instead of guessing.
Check that memory.db gets created and rows are inserted after each turn. Then change the userId and confirm the agent no longer sees previous customer state.
If you want to validate retrieval quality, ask follow-up questions that depend on stored facts rather than generic chat history. In regulated environments, also verify that only approved fields are persisted.
Next Steps
- •Replace SQLite text search with embeddings-based retrieval using a vector store
- •Add TTL rules so stale customer facts expire automatically
- •Store structured memory fields like
policyNumber,preferredChannel, andriskTierinstead of plain text
Keep learning
- •The complete AI Agents Roadmap — my full 8-step breakdown
- •Free: The AI Agent Starter Kit — PDF checklist + starter code
- •Work with me — I build AI for banks and insurance companies
By Cyprian Aarons, AI Consultant at Topiax.
Want the complete 8-step roadmap?
Grab the free AI Agent Starter Kit — architecture templates, compliance checklists, and a 7-email deep-dive course.
Get the Starter Kit