How to Fix 'memory not persisting in production' in CrewAI (TypeScript)
If your CrewAI memory works locally but resets in production, the issue is usually not “memory” itself. It’s almost always one of three things: you’re creating a new agent/crew per request, your storage backend is ephemeral, or your production runtime is killing process state between invocations.
In CrewAI TypeScript, this shows up as missing conversation history, empty short-term memory, or agents that behave like they’ve never seen the user before. The common symptom is: it works in a long-lived dev process, then fails once deployed to serverless, containers, or horizontally scaled workers.
The Most Common Cause
The #1 cause is recreating the Crew/Agent/Memory objects on every request and expecting in-process memory to survive.
That works in local dev because the Node process stays alive. In production, especially on serverless platforms or autoscaled containers, each request may hit a fresh instance.
Broken vs fixed pattern
| Broken pattern | Fixed pattern |
|---|---|
| Memory lives only inside the request handler | Memory is backed by persistent storage and reused |
New Crew created per request | Shared memory store injected into crew setup |
| Works locally, resets in prod | Persists across requests and restarts |
// ❌ Broken: memory is recreated every request and stored only in-process
import { Crew, Agent } from "@crewai/typescript";
export async function POST(req: Request) {
const agent = new Agent({
name: "SupportAgent",
role: "Customer support",
goal: "Help the user",
});
const crew = new Crew({
agents: [agent],
memory: true,
});
const result = await crew.kickoff({
inputs: { message: "Remember my account number is 1234" },
});
return Response.json(result);
}
// ✅ Fixed: use a persistent memory provider and reuse configuration
import { Crew, Agent } from "@crewai/typescript";
import { SqliteMemoryStore } from "./memory/SqliteMemoryStore";
const memoryStore = new SqliteMemoryStore({
path: process.env.MEMORY_DB_PATH ?? "./data/crewai.sqlite",
});
const agent = new Agent({
name: "SupportAgent",
role: "Customer support",
goal: "Help the user",
});
const crew = new Crew({
agents: [agent],
memory: {
enabled: true,
store: memoryStore,
},
});
export async function POST(req: Request) {
const result = await crew.kickoff({
inputs: { message: "Remember my account number is 1234" },
sessionId: "user-abc-123",
});
return Response.json(result);
}
The key difference is not just memory: true. You need a real backing store and a stable session identifier. Without both, CrewAI can’t associate future turns with prior state.
Other Possible Causes
1. Missing sessionId or user-scoped key
If you don’t pass a stable identifier, memory may be created but never reloaded for the same user.
// ❌ No stable identity
await crew.kickoff({
inputs: { message: "My policy number is P-9911" },
});
// ✅ Stable identity per user/session
await crew.kickoff({
inputs: { message: "My policy number is P-9911" },
sessionId: `user:${user.id}`,
});
If you’re using anonymous chat sessions, generate one UUID per browser session and persist it client-side.
2. Ephemeral filesystem in production
A lot of teams point memory at ./tmp or /var/task, then deploy to Lambda, Vercel, or containers with writable layers that disappear on restart.
// ❌ Bad on serverless
const store = new SqliteMemoryStore({ path: "./tmp/memory.db" });
// ✅ Use durable storage
const store = new PostgresMemoryStore({
connectionString: process.env.DATABASE_URL!,
});
If you must use SQLite locally, treat it as dev-only. In production, use Postgres, Redis with persistence, or another durable backend supported by your stack.
3. Multiple replicas without shared storage
If pod A writes memory and pod B serves the next request, B won’t see A’s state unless both use the same store.
# ❌ Each replica has local disk only
replicas: 3
volumeMounts:
- name: local-tmp
mountPath: /app/data
Fix it by using a shared database or managed cache:
# ✅ Shared external store
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: app-secrets
key: database-url
This matters even if your code looks correct. Horizontal scaling breaks any design that assumes node-local state.
4. Memory disabled by config override
Sometimes the code enables memory, but environment config disables it in prod.
const crew = new Crew({
agents,
memory: process.env.ENABLE_MEMORY === "true",
});
If ENABLE_MEMORY is missing in production, you get falsey behavior and no persistence.
Use explicit defaults:
const enableMemory = process.env.ENABLE_MEMORY !== "false";
Also check for deployment-time overrides in Helm charts, .env.production, or CI secrets injection.
How to Debug It
- •
Log the session ID on every request
- •If it changes unexpectedly, you found the bug.
- •You want one stable key per conversation or customer.
- •
Log the resolved memory backend
- •Print whether you are using SQLite, Postgres, Redis, or in-memory storage.
- •If it says “in-memory” in prod, that’s your problem.
- •
Check whether the process survives between requests
- •Add a boot timestamp and instance ID.
- •If they change every call, your runtime is stateless and cannot hold local memory.
- •
Inspect stored rows/documents directly
- •Query your DB after a conversation turn.
- •If nothing was written, your write path is broken.
- •If data exists but isn’t read back, your lookup key or session mapping is wrong.
A quick sanity check:
console.log({
instanceId,
sessionId,
memoryBackend: process.env.MEMORY_BACKEND,
});
If sessionId changes per request or memoryBackend points to local disk in production, stop debugging CrewAI internals and fix infrastructure first.
Prevention
- •
Use a persistent store from day one.
- •SQLite is fine for local development.
- •Postgres/Redis/etc. should back production memory.
- •
Treat
sessionIdas part of your API contract.- •Generate it once per user conversation.
- •Pass it through every kickoff call.
- •
Add an integration test that restarts the app between two turns.
- •First turn writes memory.
- •Second turn reads it back.
- •If that test fails, don’t ship it.
The real fix for “memory not persisting in production” is usually boring infrastructure work, not prompt tuning. Once you stop relying on process-local state and start binding conversations to durable storage plus stable IDs, CrewAI behaves predictably in TypeScript.
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