Haystack Tutorial (TypeScript): persisting agent state for intermediate developers
By Cyprian AaronsUpdated 2026-04-21
haystackpersisting-agent-state-for-intermediate-developerstypescript
This tutorial shows you how to persist agent state in Haystack using TypeScript so a conversation can survive process restarts, retries, and multi-step workflows. You need this when your agent is doing real work: collecting user details, waiting for approvals, or carrying context across multiple tool calls without losing memory.
What You'll Need
- •Node.js 18+ and npm
- •A TypeScript project with
ts-nodeor a build step - •Haystack JS/TS packages installed:
- •
@haystack-ai/core - •
@haystack-ai/agents
- •
- •An OpenAI API key set as
OPENAI_API_KEY - •A writable local folder for saving state
- •Basic familiarity with Haystack agents, tools, and message passing
Step-by-Step
- •Start by creating a small project with the dependencies you need. The important part here is that we’ll use a file-backed store so state survives between runs.
npm init -y
npm install @haystack-ai/core @haystack-ai/agents dotenv
npm install -D typescript ts-node @types/node
- •Add a simple TypeScript config and environment file. Keep the compiler target modern enough for async/await and native ESM-style imports.
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"outDir": "dist"
},
"include": ["src/**/*.ts"]
}
OPENAI_API_KEY=your_openai_key_here
- •Create a persistent state store on disk. The pattern is simple: load state at startup, update it after each interaction, then write it back before exit.
// src/state-store.ts
import { readFile, writeFile } from "node:fs/promises";
export type AgentState = {
sessionId: string;
turnCount: number;
notes: string[];
};
const defaultState = (sessionId: string): AgentState => ({
sessionId,
turnCount: 0,
notes: [],
});
export async function loadState(path: string, sessionId: string): Promise<AgentState> {
try {
const raw = await readFile(path, "utf8");
return JSON.parse(raw) as AgentState;
} catch {
return defaultState(sessionId);
}
}
export async function saveState(path: string, state: AgentState): Promise<void> {
await writeFile(path, JSON.stringify(state, null, 2), "utf8");
}
- •Build the agent runner and persist state after each turn. This example keeps the agent’s working memory in a plain object so you can store business-relevant fields like case IDs, flags, or extracted entities.
// src/index.ts
import "dotenv/config";
import { loadState, saveState } from "./state-store.js";
const STATE_PATH = "./agent-state.json";
const SESSION_ID = "claims-intake-001";
async function main() {
const state = await loadState(STATE_PATH, SESSION_ID);
state.turnCount += 1;
state.notes.push(`Turn ${state.turnCount}: collected user request`);
console.log("Loaded state:", state);
// Replace this with your Haystack agent call.
// Example shape only; keep your own model/tool wiring here.
const reply = `Session ${state.sessionId} is on turn ${state.turnCount}`;
console.log("Agent reply:", reply);
await saveState(STATE_PATH, state);
}
main().catch((err) => {
console.error(err);
process.exit(1);
});
- •If you want the persistence layer to sit next to an actual Haystack agent run, keep the same pattern around your invocation. The key is that the persisted object should contain stable workflow data, not raw transient chat logs only.
// src/run-agent.ts
import "dotenv/config";
import { loadState, saveState } from "./state-store.js";
type ConversationState = {
sessionId: string;
customerName?: string;
claimType?: string;
};
async function runTurn(input: string) {
const path = "./conversation-state.json";
const sessionId = "customer-42";
const state = (await loadState(path, sessionId)) as ConversationState & { turnCount?: number };
if (input.toLowerCase().includes("name is")) {
state.customerName = input.split("name is")[1]?.trim();
}
if (input.toLowerCase().includes("claim")) {
state.claimType = input.trim();
}
console.log("Before agent call:", state);
// Call your Haystack agent here and pass `state` into the system/tool context.
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