How to Fix 'memory not persisting during development' in LangChain (TypeScript)
When LangChain memory “doesn’t persist” during development, it usually means your chain or agent is being recreated on every request, so the in-memory state gets wiped before the next turn. In TypeScript, this shows up most often when you’re testing in a serverless route, hot-reloading dev server, or creating a new BufferMemory / RunnableWithMessageHistory instance inside the handler.
The key point: LangChain memory is only persistent for as long as the process and object instance stay alive. If you rebuild the chain on every request, you are not persisting anything.
The Most Common Cause
The #1 cause is creating memory inside the request handler instead of keeping it in a long-lived scope.
With BufferMemory, ConversationBufferMemory, or RunnableWithMessageHistory, the memory object must survive across calls. If you instantiate it per request, you’ll see behavior like:
- •the model answers as if it has no prior context
- •chat history is always empty on the second message
- •logs show no actual error, just missing state
Broken vs fixed pattern
| Broken pattern | Fixed pattern |
|---|---|
| Memory and chain created inside the route handler | Memory and store created outside the handler |
| New instance per request | Reused instance per session/process |
| History resets on every call | History persists while process stays warm |
// ❌ Broken: memory is recreated on every request
import { BufferMemory } from "langchain/memory";
import { ConversationChain } from "langchain/chains";
import { ChatOpenAI } from "@langchain/openai";
export async function POST(req: Request) {
const { message } = await req.json();
const llm = new ChatOpenAI({ model: "gpt-4o-mini" });
const memory = new BufferMemory({
memoryKey: "history",
returnMessages: true,
});
const chain = new ConversationChain({
llm,
memory,
});
const result = await chain.invoke({ input: message });
return Response.json(result);
}
// ✅ Fixed: keep reusable state outside the handler
import { BufferMemory } from "langchain/memory";
import { ConversationChain } from "langchain/chains";
import { ChatOpenAI } from "@langchain/openai";
const llm = new ChatOpenAI({ model: "gpt-4o-mini" });
const memory = new BufferMemory({
memoryKey: "history",
returnMessages: true,
});
const chain = new ConversationChain({
llm,
memory,
});
export async function POST(req: Request) {
const { message } = await req.json();
const result = await chain.invoke({ input: message });
return Response.json(result);
}
If you’re using RunnableWithMessageHistory, the same rule applies. The history factory must return a stable store keyed by session ID, not a fresh array each time.
// ✅ Correct session-based history
import { RunnableWithMessageHistory } from "@langchain/core/runnables";
import { ChatMessageHistory } from "@langchain/community/stores/message/in_memory";
const store = new Map<string, ChatMessageHistory>();
function getHistory(sessionId: string) {
if (!store.has(sessionId)) {
store.set(sessionId, new ChatMessageHistory());
}
return store.get(sessionId)!;
}
Other Possible Causes
1) Wrong memoryKey or prompt variable name
LangChain will not magically map your prompt variables. If your prompt expects {chat_history} but your memory uses "history", the chain won’t inject prior messages.
// ❌ Mismatch
const memory = new BufferMemory({ memoryKey: "history" });
const prompt = ChatPromptTemplate.fromMessages([
["system", "Use this chat history: {chat_history}"],
]);
// ✅ Match names exactly
const memory = new BufferMemory({ memoryKey: "chat_history" });
const prompt = ChatPromptTemplate.fromMessages([
["system", "Use this chat history: {chat_history}"],
]);
2) You are using stateless deployments during development
If you run on Vercel serverless functions, Next.js route handlers with cold starts, or any ephemeral container, process memory is not durable. You may never hit the same instance twice.
// This works only while the same process stays warm.
const memory = new BufferMemory();
For dev/testing, use a real backing store:
- •Redis
- •Postgres
- •Upstash Redis
- •DynamoDB
3) You forgot to pass session IDs into message history
RunnableWithMessageHistory depends on a consistent sessionId. If every request generates a new ID, every turn becomes a brand-new conversation.
// ❌ New session every time
configurable: {
sessionId: crypto.randomUUID(),
}
// ✅ Stable session per user/conversation
configurable: {
sessionId: userId,
}
4) You are mixing old and new LangChain APIs
A common migration bug is combining ConversationChain patterns with newer runnable-based code. The result is usually silent failure or empty history because the wrong variables are being passed.
Typical symptom:
- •
ValueError: Missing value for input variable 'history' - •or prompts that never receive prior messages
Make sure your chain type matches your memory strategy:
- •old-style chains:
BufferMemory,ConversationBufferMemory - •runnable style:
RunnableWithMessageHistory
How to Debug It
- •
Log whether your objects are recreated
- •Add logs where you create
memory,chain, and any history store. - •If they print on every request, that’s your problem.
- •Add logs where you create
- •
Inspect prompt variables
- •Print the final prompt input keys before invoking.
- •Confirm that your prompt expects the same key as your memory uses.
- •Watch for errors like:
- •
Missing value for input variable 'history' - •
Input to ChatPromptTemplate is missing variables
- •
- •
Check whether you have stable session identity
- •Log
sessionIdfor each request. - •If it changes between turns, persistence will fail even if everything else is correct.
- •Log
- •
Test with a single long-lived Node process
- •Run locally in plain Node first.
- •If it works there but fails in Next.js/Vercel/serverless, you’re hitting process isolation or cold starts.
Prevention
- •Keep conversational state out of request-local scope.
- •Use explicit session IDs and a real persistence layer for anything beyond local dev.
- •Match
memoryKey, prompt variables, and chain type exactly; don’t rely on defaults.
If you want a reliable mental model, use this rule:
In-memory LangChain memory is not storage. It is just object state in one running process.
If that object gets recreated, your “persisted” chat history disappears by design.
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