How to Fix 'memory not persisting' in LangChain (TypeScript)
If your LangChain TypeScript app says memory is “not persisting,” it usually means the chain is being recreated every request, or the memory object is not actually attached to the runnable you think it is. In practice, this shows up when chat history disappears between turns, even though BufferMemory, ConversationBufferMemory, or a message history store is configured.
The usual pattern is simple: the code works for a single call, then every next call behaves like a brand-new conversation. That’s almost always a lifecycle or wiring problem, not a LangChain bug.
The Most Common Cause
The #1 cause is creating memory inside the request handler or inside the function that runs on every call. That gives you a fresh memory instance each time, so nothing persists.
Here’s the broken pattern and the fixed pattern side by side:
| Broken | Fixed |
|---|---|
| Memory created per request | Memory created once and reused |
| Chain rebuilt every call | Chain instance reused |
| History resets on every invocation | History accumulates across turns |
// ❌ Broken: memory is recreated on every request
import { ChatOpenAI } from "@langchain/openai";
import { ConversationChain } from "langchain/chains";
import { BufferMemory } from "langchain/memory";
export async function handler(req: Request) {
const llm = new ChatOpenAI({ model: "gpt-4o-mini" });
const memory = new BufferMemory({
returnMessages: true,
memoryKey: "history",
});
const chain = new ConversationChain({
llm,
memory,
});
const result = await chain.invoke({ input: "My name is Sam" });
return Response.json(result);
}
// ✅ Fixed: create once, reuse across requests
import { ChatOpenAI } from "@langchain/openai";
import { ConversationChain } from "langchain/chains";
import { BufferMemory } from "langchain/memory";
const llm = new ChatOpenAI({ model: "gpt-4o-mini" });
const memory = new BufferMemory({
returnMessages: true,
memoryKey: "history",
});
const chain = new ConversationChain({
llm,
memory,
});
export async function handler(req: Request) {
const body = await req.json();
const result = await chain.invoke({ input: body.message });
return Response.json(result);
}
If you’re in serverless, this matters even more. A cold start can still reset module state, so if you need persistence across invocations, use an external chat history store instead of in-memory state.
Other Possible Causes
1. Wrong memoryKey or prompt variable mismatch
If your prompt expects chat_history but your memory writes to history, LangChain won’t inject the messages where you expect.
// ❌ Broken
const memory = new BufferMemory({
memoryKey: "history",
});
const prompt = ChatPromptTemplate.fromMessages([
["system", "Use the chat history below:\n{chat_history}"],
]);
// ✅ Fixed
const memory = new BufferMemory({
memoryKey: "chat_history",
});
const prompt = ChatPromptTemplate.fromMessages([
["system", "Use the chat history below:\n{chat_history}"],
]);
2. Using stateless chains with no message history adapter
Some LCEL runnables do not persist anything unless you explicitly wire in RunnableWithMessageHistory. If you only call .invoke() on a plain chain, there is no storage layer.
// ❌ Broken
const result = await chain.invoke({ input: "Remember my name" });
// ✅ Fixed with message history
import { RunnableWithMessageHistory } from "@langchain/core/runnables";
import { InMemoryChatMessageHistory } from "@langchain/core/chat_history";
const store = new Map<string, InMemoryChatMessageHistory>();
function getHistory(sessionId: string) {
if (!store.has(sessionId)) {
store.set(sessionId, new InMemoryChatMessageHistory());
}
return store.get(sessionId)!;
}
const withHistory = new RunnableWithMessageHistory({
runnable: chain,
getMessageHistory: getHistory,
inputMessagesKey: "input",
historyMessagesKey: "history",
});
3. New session ID on every request
If you use RunnableWithMessageHistory but generate a fresh session ID each time, persistence will look broken because every call gets its own isolated thread.
// ❌ Broken
await withHistory.invoke(
{ input: "What is my name?" },
{ configurable: { sessionId: crypto.randomUUID() } }
);
// ✅ Fixed
await withHistory.invoke(
{ input: "What is my name?" },
{ configurable: { sessionId: userId } }
);
4. Misusing deprecated classes or wrong package imports
LangChain TypeScript has changed over time. Mixing older langchain/* imports with newer @langchain/core patterns can produce confusing behavior where memory compiles but doesn’t behave as expected.
// ❌ Risky mix of old and new patterns
import { ConversationBufferMemory } from "langchain/memory";
import { RunnableWithMessageHistory } from "@langchain/core/runnables";
// ✅ Prefer one consistent stack per project version
import { InMemoryChatMessageHistory } from "@langchain/core/chat_history";
import { RunnableWithMessageHistory } from "@langchain/core/runnables";
How to Debug It
- •
Print the session identifier
- •If you’re using message history, log
sessionId. - •If it changes between requests, that’s your bug.
- •If you’re using message history, log
- •
Inspect the prompt variables
- •Confirm your prompt uses the same key as your memory.
- •Check for mismatches like
history,chat_history, ormessages.
- •
Log stored messages before and after invoke
- •For
InMemoryChatMessageHistory, dump.getMessages(). - •If it stays empty, your history adapter isn’t wired correctly.
- •For
- •
Check where objects are instantiated
- •If
new BufferMemory()ornew ConversationChain()appears inside a route handler, move it out. - •For serverless apps, decide whether you need process-local state or external persistence.
- •If
A useful sanity check is to reproduce manually:
await withHistory.invoke(
{ input: "My name is Sam" },
{ configurable: { sessionId: "user-123" } }
);
const second = await withHistory.invoke(
{ input: "What is my name?" },
{ configurable: { sessionId: "user-123" } }
);
If that still forgets Sam, your history wiring is wrong. If it works locally but fails in production, you’re probably hitting cold starts or ephemeral runtime state.
Prevention
- •
Keep one clear persistence strategy:
- •process-local memory for demos and single-instance apps
- •external stores like Redis/Postgres for production sessions
- •
Standardize your keys:
- •pick one
memoryKey - •use the same key in prompts and runnable config
- •pick one
- •
Tie conversation state to a stable user/session ID:
- •never use random UUIDs unless you want a brand-new conversation every turn
The main takeaway is simple: LangChain does not magically persist context for you. If memory resets, check object lifetime first, then key names, then session identity, then whether you’re using an actual storage-backed message history layer.
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