LlamaIndex Tutorial (TypeScript): persisting agent state for advanced developers
This tutorial shows how to persist LlamaIndex agent state in TypeScript so your agent can survive process restarts, keep conversation context, and resume tool usage without rebuilding memory from scratch. You need this when you’re running agents in production, especially behind job queues, API servers, or worker processes that can restart at any time.
What You'll Need
- •Node.js 18+
- •A TypeScript project with
tsconfig.json - •An OpenAI API key
- •These packages:
- •
llamaindex - •
dotenv - •
typescript - •
tsxorts-nodefor running TypeScript directly
- •
- •A writable local directory for persisted state
Install the dependencies:
npm install llamaindex dotenv
npm install -D typescript tsx @types/node
Step-by-Step
- •Create a small project setup and load your API key from
.env. Keep the config simple; the important part is that the same runtime can be restarted and still read the persisted files.
import "dotenv/config";
import { OpenAI } from "llamaindex";
if (!process.env.OPENAI_API_KEY) {
throw new Error("OPENAI_API_KEY is missing");
}
const llm = new OpenAI({
model: "gpt-4o-mini",
apiKey: process.env.OPENAI_API_KEY,
});
console.log("LLM ready:", llm.model);
- •Build an agent with memory and a tool. For persistence, the agent’s conversational state needs to be serializable, so keep tools deterministic and avoid storing ephemeral runtime objects inside closures.
import {
OpenAIAgent,
ChatMemoryBuffer,
FunctionTool,
} from "llamaindex";
const getPolicyStatus = FunctionTool.from(
async ({ policyNumber }: { policyNumber: string }) => {
return `Policy ${policyNumber} is active and paid through next month`;
},
{
name: "get_policy_status",
description: "Look up policy status by policy number",
parameters: {
type: "object",
properties: {
policyNumber: { type: "string" },
},
required: ["policyNumber"],
},
}
);
const memory = ChatMemoryBuffer.fromDefaults({
tokenLimit: 4000,
});
const agent = new OpenAIAgent({
tools: [getPolicyStatus],
llm,
memory,
});
- •Persist the memory to disk after each interaction. In practice, this is the part people skip; they keep state only in RAM and lose it on restart. Here we serialize the chat buffer to a JSON file that can be restored later.
import { promises as fs } from "node:fs";
import path from "node:path";
const STATE_DIR = path.join(process.cwd(), ".agent-state");
const STATE_FILE = path.join(STATE_DIR, "memory.json");
async function saveMemory() {
await fs.mkdir(STATE_DIR, { recursive: true });
await fs.writeFile(STATE_FILE, JSON.stringify(memory.toDict(), null, 2), "utf8");
}
async function loadMemory() {
try {
const raw = await fs.readFile(STATE_FILE, "utf8");
const data = JSON.parse(raw);
return ChatMemoryBuffer.fromDict(data);
} catch {
return ChatMemoryBuffer.fromDefaults({ tokenLimit: 4000 });
}
}
- •Restore persisted state before creating the agent. This makes the agent resume with prior messages instead of starting from a blank conversation every time your process boots.
async function main() {
const restoredMemory = await loadMemory();
const persistedAgent = new OpenAIAgent({
tools: [getPolicyStatus],
llm,
memory: restoredMemory,
});
const response1 = await persistedAgent.chat({
message: "Check policy A12345 for me.",
});
console.log("First response:", response1.response);
}
main();
- •Run multiple turns and save after each one. This is where persistence matters most: if your worker crashes after turn one, turn two still starts from the last saved state instead of losing context.
async function chatAndPersist(message: string) {
const result = await agent.chat({ message });
await saveMemory();
return result.response;
}
async function demo() {
console.log(await chatAndPersist("Check policy A12345 for me."));
console.log(await chatAndPersist("Now summarize that in one sentence."));
}
demo().catch((err) => {
console.error(err);
process.exit(1);
});
Testing It
Run the script once and confirm it creates .agent-state/memory.json. Then run it again with a follow-up prompt like “What was I asking about earlier?” and verify the agent still has context from the previous session.
If you want a stricter test, delete only the process but keep the state file intact. The second run should behave like a resumed session, not a fresh conversation.
Also inspect the saved JSON directly. You should see serialized chat messages rather than an empty object or transient runtime references.
Next Steps
- •Move persistence from local disk to Redis or Postgres for multi-instance deployments.
- •Add per-user state keys so each customer gets isolated memory.
- •Persist not just chat history, but also tool outputs and workflow checkpoints for long-running agents.
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