How to Fix 'memory not persisting during development' in LangGraph (Python)
When LangGraph memory “doesn’t persist during development,” it usually means your graph is running with a fresh checkpointer state on every request, or you’re not passing the same thread_id between invocations. The symptom shows up fast: chat history disappears, state resets after reloads, or a workflow works once and then acts stateless on the next run.
In practice, this is almost always a wiring problem, not a LangGraph bug. The graph is fine; the persistence layer, config, or runtime boundary is wrong.
The Most Common Cause
The #1 cause is creating a new checkpointer or using a new thread ID every time you invoke the graph.
If you use MemorySaver but instantiate it inside the request handler, the memory lives only for that one process call. Same result if you forget to pass configurable.thread_id, because LangGraph has no stable key to load prior state.
Broken vs fixed
| Broken pattern | Fixed pattern |
|---|---|
New MemorySaver() created per request | Single shared checkpointer instance |
Missing thread_id | Stable thread_id passed on every call |
# BROKEN: memory resets because checkpointer is recreated
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import StateGraph, MessagesState
def handle_request(user_input: str):
checkpointer = MemorySaver() # new instance every request
builder = StateGraph(MessagesState)
# ... define nodes/edges ...
graph = builder.compile(checkpointer=checkpointer)
return graph.invoke(
{"messages": [{"role": "user", "content": user_input}]}
# no thread_id
)
# FIXED: reuse the same checkpointer and pass a stable thread_id
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import StateGraph, MessagesState
checkpointer = MemorySaver()
builder = StateGraph(MessagesState)
# ... define nodes/edges ...
graph = builder.compile(checkpointer=checkpointer)
def handle_request(user_input: str, session_id: str):
return graph.invoke(
{"messages": [{"role": "user", "content": user_input}]},
config={"configurable": {"thread_id": session_id}},
)
If you are using MessagesState, this matters even more because conversation history is stored in checkpoints keyed by that thread. No stable thread key means no replayable state.
Other Possible Causes
1) You compiled without a checkpointer
A compiled graph without a checkpointer will behave statelessly.
# BROKEN
graph = builder.compile()
# FIXED
from langgraph.checkpoint.memory import MemorySaver
checkpointer = MemorySaver()
graph = builder.compile(checkpointer=checkpointer)
2) You are using an in-memory saver in a multi-process setup
MemorySaver only persists inside the current Python process. If you run multiple workers, reload the server, or restart the app, state disappears.
# BROKEN for multi-process / dev server reloads
from langgraph.checkpoint.memory import MemorySaver
checkpointer = MemorySaver()
Use a durable backend for anything beyond local single-process testing:
# FIXED: use a persistent backend such as Postgres
from langgraph.checkpoint.postgres import PostgresSaver
checkpointer = PostgresSaver.from_conn_string(DB_URL)
3) Your thread_id changes between requests
If your frontend generates a new UUID on every message, LangGraph sees each turn as a separate conversation.
# BROKEN: new thread per call
config={"configurable": {"thread_id": str(uuid.uuid4())}}
# FIXED: reuse session/user/thread identifier
config={"configurable": {"thread_id": f"user:{user_id}:chat:{chat_id}"}}
4) You are invoking with the wrong API shape
A common mistake is passing config in the wrong place or mutating state outside the graph.
# BROKEN: config missing or misplaced
graph.invoke({"messages": messages}, {"thread_id": "abc"})
# FIXED: use configurable.thread_id exactly where LangGraph expects it
graph.invoke(
{"messages": messages},
config={"configurable": {"thread_id": "abc"}},
)
Also watch for custom code that overwrites state instead of merging it. If your node returns only partial data and your reducer is wrong, it can look like persistence failed when really your state schema is dropping fields.
How to Debug It
- •
Check whether checkpoints are being created
- •Add logging around compile time and invocation.
- •Confirm you are compiling once, not inside the request handler.
- •If using
MemorySaver, remember it dies with the process.
- •
Print the exact config being sent
- •Verify
configurable.thread_idis present on every invoke. - •Confirm it stays constant across turns for the same conversation.
- •Verify
- •
Inspect whether your app restarts between calls
- •FastAPI with auto-reload, serverless functions, and notebook cells often create false “persistence” failures.
- •If restarting clears memory, that’s expected with
MemorySaver.
- •
Test with a minimal repro
- •Remove tools, model calls, and UI code.
- •Keep only:
- •one graph
- •one shared checkpointer
- •one stable thread ID
- •If persistence works there, your bug is in app wiring.
Prevention
- •Compile graphs once at startup and reuse the compiled
StateGraph. - •Use stable conversation IDs from your auth/session layer, not random UUIDs per request.
- •Use
MemorySaveronly for local single-process development; switch to Postgres or another durable store when you need persistence across restarts or workers. - •Treat
config={"configurable":{"thread_id": ...}}as mandatory for any stateful LangGraph workflow.
If you want one rule to remember: LangGraph memory persists by checkpoint key, not by magic. Keep the checkpointer alive and keep the thread_id stable.
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