AutoGen Tutorial (TypeScript): persisting agent state for advanced developers
This tutorial shows you how to persist AutoGen agent state in TypeScript so conversations, memory, and tool-driven workflows survive process restarts. You need this when your agent is part of a real app: a support workflow, claims triage, or any system where losing context on deploy is not acceptable.
What You'll Need
- •Node.js 20+
- •A TypeScript project with
ts-nodeor a build step - •
@autogenai/autogeninstalled - •
dotenvfor loading API keys - •An OpenAI API key in
OPENAI_API_KEY - •A writable local folder for persisted state
Install the packages:
npm install @autogenai/autogen dotenv
npm install -D typescript ts-node @types/node
Step-by-Step
- •Create a small persistence layer first. The point is not to serialize the whole runtime; it’s to persist the minimum state you need to reconstruct the agent after restart.
import fs from "node:fs/promises";
import path from "node:path";
const STATE_DIR = path.resolve(".agent-state");
export async function saveState(sessionId: string, state: unknown) {
await fs.mkdir(STATE_DIR, { recursive: true });
await fs.writeFile(
path.join(STATE_DIR, `${sessionId}.json`),
JSON.stringify(state, null, 2),
"utf-8"
);
}
export async function loadState<T>(sessionId: string): Promise<T | null> {
try {
const raw = await fs.readFile(path.join(STATE_DIR, `${sessionId}.json`), "utf-8");
return JSON.parse(raw) as T;
} catch {
return null;
}
}
- •Define the state shape you want to persist. For advanced use cases, keep it explicit: conversation history, user profile facts, and any workflow checkpoints.
export type AgentState = {
sessionId: string;
messages: Array<{ role: "user" | "assistant"; content: string }>;
facts: Record<string, string>;
};
export const createEmptyState = (sessionId: string): AgentState => ({
sessionId,
messages: [],
facts: {},
});
- •Build an agent runner that restores state before every turn and writes it back after the turn completes. This example uses AutoGen’s TypeScript client with a simple assistant agent pattern.
import "dotenv/config";
import { AssistantAgent } from "@autogenai/autogen";
import { loadState, saveState, createEmptyState, type AgentState } from "./state.js";
const client = new AssistantAgent({
name: "claims_assistant",
modelClientOptions: {
apiKey: process.env.OPENAI_API_KEY!,
model: "gpt-4o-mini",
},
});
export async function runTurn(sessionId: string, userInput: string) {
const saved = await loadState<AgentState>(sessionId);
const state = saved ?? createEmptyState(sessionId);
state.messages.push({ role: "user", content: userInput });
const response = await client.run({
messages: state.messages.map((m) => ({ role: m.role, content: m.content })),
});
const assistantText = response.messages.at(-1)?.content ?? "";
state.messages.push({ role: "assistant", content: assistantText });
await saveState(sessionId, state);
return { state, assistantText };
}
- •Add durable checkpoints around tool outputs or extracted facts. In production, this is usually more valuable than persisting raw chat text because it lets you resume workflows deterministically.
import { runTurn } from "./agent.js";
import { loadState, saveState } from "./state.js";
async function main() {
const sessionId = "case-123";
const first = await runTurn(sessionId, "My policy number is POL-7781.");
console.log(first.assistantText);
const current = await loadState<any>(sessionId);
current.facts.policyNumber = "POL-7781";
await saveState(sessionId, current);
const second = await runTurn(sessionId, "Can you continue from my policy number?");
console.log(second.assistantText);
}
main().catch(console.error);
- •If you want restart safety across multiple processes or containers, move the same JSON blob into Redis or Postgres instead of local disk. The interface stays the same; only
saveStateandloadStatechange.
type StateStore = {
save(sessionId: string, value: AgentState): Promise<void>;
load(sessionId: string): Promise<AgentState | null>;
};
export async function resumeSession(store: StateStore, sessionId: string) {
const state = (await store.load(sessionId)) ?? createEmptyState(sessionId);
state.messages.push({ role: "user", content: "Resume my case." });
await store.save(sessionId, state);
}
Testing It
Run the script twice with the same sessionId. On the first run, the agent should answer and write .agent-state/case-123.json; on the second run, it should load that file and continue with prior context instead of starting fresh.
Inspect the saved JSON directly to confirm both messages and facts are present. If you’re using Redis or Postgres later, test by killing the process between turns and verifying that the next process instance resumes from persisted data.
A good smoke test is to add a temporary log before and after loadState. You want to see “restored existing state” on turn two and “created empty state” only on turn one.
Next Steps
- •Persist tool call traces separately from conversation text so you can replay workflows safely.
- •Add schema validation with
zodbefore saving or loading agent state. - •Move from file-based persistence to Redis or Postgres once you have concurrent sessions or multiple app instances.
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