An AI agent without persistent memory forgets everything the moment your process dies. Every restart is a blank slate – no context, no conversation history, no learned preferences. That’s fine for toy demos. In production, your agent needs to pick up exactly where it left off.
LangGraph (the agent runtime from the LangChain team) solves this with checkpointers – pluggable backends that save your agent’s full state after every step. You compile your graph with a checkpointer, pass a thread_id at runtime, and the framework handles serialization, retrieval, and state restoration automatically.
Here’s how to move from volatile in-memory state to durable, production-grade persistence.
The In-Memory Baseline
Before adding persistence, make sure your agent works with MemorySaver. This stores state in a Python dictionary – fast, but gone when the process exits.
| |
The thread_id is the session key. Same thread, same memory. Different thread, fresh context. This pattern stays identical no matter which backend you swap in – the only thing that changes is the checkpointer.
Persist to SQLite
SQLite is the fastest path to persistence that survives restarts. No server to run, no connection strings to configure – just a file on disk.
| |
| |
Kill the process. Restart it. Run the same code with the same thread_id, and the agent still knows you prefer dark mode. The checkpoint tables get created automatically in the SQLite file.
If you need async support (which you will in any FastAPI or async web app), use AsyncSqliteSaver:
| |
SQLite works well for single-instance deployments, local development, and prototypes. Once you need multiple app instances hitting the same memory store, move to PostgreSQL.
Persist to PostgreSQL
PostgreSQL is the production choice. It handles concurrent writes from multiple agent instances, supports proper transactions, and you probably already run it in your stack.
| |
| |
The .setup() call is critical on first run – it creates the required tables in your database. Skip it and you’ll get a UndefinedTable error:
| |
For async applications, use AsyncPostgresSaver with the same connection string:
| |
When manually creating psycopg connections instead of using from_conn_string, you must set autocommit=True and use row_factory=dict_row:
| |
Miss autocommit=True and .setup() silently fails to commit the checkpoint tables. Your agent will appear to work until the first restart, when it finds an empty database.
Add Cross-Thread Memory with LangGraph Store
Checkpointers give you per-thread memory – each thread_id gets its own isolated conversation history. But what if you want your agent to remember facts across different threads? A user mentions their timezone in one conversation, and you want the agent to know it in every future conversation.
That’s what the LangGraph InMemoryStore (and its persistent variants) is for. It’s a key-value store that lives outside the checkpoint system.
| |
The store uses a namespace hierarchy to organize data. Your agent can read and write to it using the store parameter that LangGraph injects into tool functions and node functions:
| |
This store persists across threads. A user can set their timezone in thread A, start a new conversation in thread B, and the agent can still retrieve it.
Memory Strategy Comparison
Not every agent needs the same memory backend. Here’s when to use what:
| Backend | Best For | Limits |
|---|---|---|
MemorySaver | Development, testing, short-lived scripts | Lost on restart |
SqliteSaver | Single-instance apps, local tools, prototypes | Single-writer only |
PostgresSaver | Production multi-instance deployments | Requires a running Postgres server |
InMemoryStore | Cross-thread facts, user profiles | Lost on restart (pair with persistent store) |
For production, the typical pattern is PostgresSaver for checkpointing (per-thread conversation history) combined with a persistent store for cross-thread knowledge.
Common Errors and Fixes
ModuleNotFoundError: No module named 'langgraph.checkpoint.sqlite'
The checkpoint backends are separate packages. Install the one you need:
| |
InvalidUpdateError: Expected dict, got list
Your state schema is missing a reducer. If a state key holds a list (like messages), annotate it:
| |
psycopg.errors.UndefinedTable: relation "checkpoints" does not exist
Call checkpointer.setup() before invoking your agent. This creates the required tables.
Checkpoint data growing too large
Every state key gets serialized at every step. Don’t store raw API responses, large files, or full document contents in state. Extract what you need into concise fields and let the raw data live elsewhere.
Legacy Memory Classes
If you’re reading older tutorials, you’ll encounter ConversationBufferMemory, ConversationSummaryMemory, and VectorStoreRetrieverMemory from langchain.memory. These were deprecated in LangChain v0.3.1. They still work (removal is planned for v1.0), but all new code should use LangGraph checkpointers instead.
The migration is straightforward: remove the memory object, add a checkpointer, and pass thread_id in your config. The checkpointer handles everything the old memory classes did, plus it gives you time travel, fault tolerance, and human-in-the-loop patterns for free.
Related Guides
- How to Add Human-in-the-Loop Approval to AI Agents
- How to Test and Debug AI Agents with LangSmith Tracing
- How to Build Agents with LangGraph
- How to Build a Research Agent with LangGraph and Tavily
- How to Build a Data Analysis Agent with Code Execution
- How to Build Autonomous Agents with the OpenAI Assistants API
- How to Build an MCP Server for AI Agents with Python
- How to Build a Memory-Augmented Agent with Vector Search
- How to Build a GitHub Issue Triage Agent with LLMs and the GitHub API
- How to Build a Retrieval Agent with Tool Calling and Reranking