LangGraph Memory
Memory in LangGraph is the mechanism that allows an agent graph to persist its execution state and resume exactly where it left off—across restarts, failures, or intentional pauses. It transforms a single-shot LLM chain into a long-running, production-grade agent system. This article focuses strictly on implementation: what gets persisted, how checkpoints work, how to store state externally, and how to design robust memory patterns for real-world workflows.
What Is Memory in LangGraph
Memory is state persistence + execution resumability. When a LangGraph agent runs, it moves through nodes, modifies state, and may call tools or prompt a human. Without memory, every run starts from scratch. With memory, the agent can:
- Survive a process crash and pick up from the last safe point
- Pause for human approval and resume days later
- Remember conversation context across multiple user turns within a session
- Reuse intermediate results in long-running, multi‑step processes
Crucially, LangGraph’s memory is not simply the LLM’s chat window. It is the full serialised state of the graph’s execution, stored in a checkpointer. You can choose where and how that state lives—in memory, on disk, in a database—and the graph runtime automatically saves and restores it.
Why Memory Matters
Production agent systems rarely complete in one shot. Memory directly enables:
| Requirement | How memory helps |
|---|---|
| Long‑running workflows | A multi‑hour research agent can be paused and resumed without losing intermediate findings |
| Failure recovery | If a tool call fails or the process crashes, the graph replays from the last checkpoint, not from the start |
| Human‑in‑the‑loop pauses | The agent stops, sends a request for approval, waits indefinitely, then continues exactly where it was |
| Multi‑turn user sessions | A chatbot remembers everything said so far, even after the underlying compute instance is recycled |
| Production reliability | Operations teams can inspect, rollback, and replay specific states if something goes wrong |
Consider a customer onboarding agent that needs to call 5 external APIs over 20 minutes. Halfway through, the third API returns a “try again later”. A memory‑aware agent simply checkpoints, waits, and resumes the remaining steps; a stateless one would have to start over, duplicating side‑effects and wasting time.
Core Memory Mechanisms in LangGraph
Three primitives work together to provide memory:
- State – a shared data structure that flows through the graph (the “runtime memory”)
- Checkpointer – the component that saves snapshots of this state after every step (or at strategic points)
- Resume – a mechanism that lets you re‑enter the graph with a specific
thread_idand continue execution from the latest saved state
State Persistence
State is a typed dictionary (or Pydantic model) that you define. All nodes read and write to it. The checkpointer serialises that state at every super‑step (after a node finishes and before the next node runs). If the graph stops for any reason, the state is already safe.
Checkpoints
A checkpoint is a full copy of the graph’s state at a given point in time, plus enough metadata to resume. LangGraph’s default runtime creates checkpoints automatically—you don’t need to manually call a save function. The checkpointer records:
- The current state (values)
- The ID of the last executed node
- The next nodes to execute
- Pending interrupts (e.g., waiting for human input)
- The entire execution config (
thread_id, etc.)
Resume Execution
To resume, you call the same graph with the same thread_id (and optionally the same checkpoint_id if you want a specific version). The graph loads the last checkpoint and continues from the next nodes.
In code, that’s:
config = {"configurable": {"thread_id": "user-session-123"}}
# First run (stops at interrupt)
graph.invoke({"messages": [HumanMessage("Book a flight")]}, config)
# Later, resume (the graph knows it was waiting for approval)
graph.invoke(None, config)
Types of Memory in LangGraph
Memory isn’t a monolith. You’ll use different storage backends and strategies depending on the lifespan and scope of the data.
| Type | Lifespan | Storage example | Typical use |
|---|---|---|---|
| Short‑Term Memory | Single graph run | In‑memory checkpointer | Prototyping, scripts, testing |
| Long‑Term Memory | Across runs, persistent | SQLite, Postgres, cloud DB | Production agents, crash recovery |
| Session Memory | Tied to a user session (thread_id) | Same as long‑term, but scoped | Multi‑turn conversations, customer support |
LangGraph itself does not directly manage vector‑store “agent memory” (e.g., facts learned over time). However, you can easily integrate any vector database as a node that fetches relevant memories and stores them in the graph’s state—effectively giving your agent long‑term, searchable memory.
State vs Memory
A frequent source of confusion.
- State is the current runtime data structure that nodes read and update. It lives inside the graph execution and is ephemeral unless persisted.
- Memory is the persistence of that state across invocations and the ability to resume. Memory is implemented by the checkpointer.
Think of it like a video game: state is where the player is, their inventory, and the enemies on screen. Memory is the save file that lets you turn off the console and resume tomorrow. In LangGraph, the save file is the checkpoint.
class AgentState(TypedDict):
messages: Annotated[list, add_messages] # state field
tool_output: str # state field
approved: bool # state field
# This entire dict becomes the checkpoint when persisted
Checkpointing Model
Checkpoints are the backbone of LangGraph’s memory system. Understanding exactly when and how they are created is essential for building reliable agents.
When Checkpoints are Created
LangGraph creates a checkpoint after every node execution. This is an append‑only log. Each checkpoint references its parent, forming a chain. You don’t pay a performance penalty for manual saves—it’s automatic.
What Data is Stored
A checkpoint contains:
- State snapshot – all fields of the state dictionary
- Metadata – source node, step number, timestamp
- Pending sends – any nodes that need to be executed next
- Interrupts – if the graph paused for human input, the interrupt value is saved
How Recovery Works
When you invoke a graph with a thread_id that already has a checkpoint, the graph:
- Loads the latest checkpoint.
- Examines the “next nodes” from that checkpoint.
- Resumes execution from those nodes as if the pause never happened.
- (Optionally) replays the exact tool calls if you’re using a deterministic tool or have recorded the output.
State Persistence Strategies
LangGraph’s checkpointer is abstracted behind a simple interface, allowing you to choose a storage backend that matches your requirements.
| Strategy | Backend | Use case |
|---|---|---|
| In‑Memory | MemorySaver() | Development, prototyping, single‑session scripts |
| File‑Based | SQLite via SqliteSaver | Lightweight production, single‑process deployments |
| Database | Postgres (PostgresSaver) | High‑availability, concurrent access, cloud deployment |
| External Store | Custom implementation (e.g., S3 + DynamoDB) | Specialised enterprise needs, auditing requirements |
Choosing a backend is a one‑line configuration change:
from langgraph.checkpoint.sqlite import SqliteSaver
# SQLite (file)
checkpointer = SqliteSaver.from_conn_string("state.db")
# In-memory
from langgraph.checkpoint.memory import MemorySaver
checkpointer = MemorySaver()
# Postgres
from langgraph.checkpoint.postgres import PostgresSaver
checkpointer = PostgresSaver.from_conn_string("postgresql://...")
graph = builder.compile(checkpointer=checkpointer)
All backends support the same interface: the graph code never changes. For production, a database is recommended because it survives process restarts and can be accessed by multiple replicas (if you design your application to route the same thread_id to the same worker).
Human-in-the-Loop Memory
One of the most powerful patterns enabled by memory is human‑in‑the‑loop (HITL). The graph can pause, send a request to a human reviewer, and wait—potentially for hours or days.
LangGraph’s built‑in interrupt function pauses execution and stores the reason in the checkpoint. To resume, you supply the human’s decision.
Example workflow:
User input → Intent classification node → [Checkpoint] → Interrupt for review
→ Human approves / edits → Resume → Execute tool → Final response
Code sketch:
from langgraph.types import interrupt
def human_approval_node(state: State):
# This pauses the graph and saves the interrupt state
decision = interrupt({"question": "Is this action OK?", "draft": state["draft"]})
# After resume, `decision` contains the human's input
state["approved"] = decision.get("approved", False)
return state
Invocation:
config = {"configurable": {"thread_id": "ticket-987"}}
# First run: the graph will pause at the interrupt
graph.invoke({"draft": "..."}, config)
# ... time passes, human reviews ...
# Resume with the human's answer
graph.invoke(Command(resume={"approved": True}), config)
The checkpointer holds the state during the entire pause. If the server restarts, the thread can still be resumed as long as the same persistent backend is used.
Memory Design Patterns
Several well‑tested patterns emerge when you combine state, checkpoints, and resumption. Choose based on the required durability and complexity.
1. Stateless Execution Pattern
- Description: Graph runs from start to finish, no checkpointer used.
- When to use: Simple, short‑lived tasks where failure means restarting is acceptable (e.g., a daily summariser that can be re‑run).
- Memory used: None.
2. Stateful Workflow Pattern
- Description: Full checkpointing enabled; graph may pause for human input or wait for external events.
- When to use: Human‑in‑the‑loop, long‑running approvals, any workflow that spans multiple minutes/hours.
- Memory used: Persistent checkpointer (SQLite/Postgres).
- Example: Loan approval process.
3. Persistent Agent Pattern
- Description: A long‑lived agent that maintains state across many user turns within a session (
thread_id). The checkpointer persists the entire conversation history. - When to use: Customer service chatbots, personal assistants that remember context over days.
- Memory used: Database‑backed checkpointer, plus optional vector store for knowledge retrieval.
- Example: A support bot that remembers a user’s issue across multiple chats.
4. Event‑Driven Memory Pattern
- Description: The graph runs in response to events (e.g., a webhook), resumes an existing thread, and updates the state before pausing again.
- When to use: Microservices that orchestrate agentic steps triggered by external signals (new email, payment confirmation).
- Memory used: Checkpointer with external store, and often an event queue that includes the
thread_id. - Example: A sales agent that triggers follow‑up tasks when a lead opens an email.
Each pattern builds on the same core checkpoint mechanism. The key design decision is the lifecycle of a thread_id and whether it is reused across many events or tied to a single workflow.
Memory in Production Systems
When moving to production, memory goes from “nice to have” to “critical infrastructure”. Implementation details matter.
- Reliability: Use a transactional database for the checkpointer. Postgres guarantees that a checkpoint is fully written before the graph considers the step done, eliminating partial saves.
- Crash recovery: If a node crashes, the graph state is lost only for the current node. When you restart, LangGraph replays from the last checkpoint, re‑running the failed node. Idempotency of tool calls becomes important here.
- Scalability: The checkpointer must handle concurrent reads and writes. Route requests with the same
thread_idto the same worker (session affinity) to avoid race conditions. Otherwise, a distributed database can serve multiple consumers, but you must be careful with state consistency—LangGraph’s checkpoints are designed to be append‑only, reducing conflicts. - Consistency: Checkpoints are immutable once written (except for the current frontier). This means you can inspect any past state. Store additional metadata like
run_idto trace lineage. - Observability: Log the
thread_idand checkpoint IDs alongside your existing tracing. Many teams integrate LangGraph with LangSmith or a custom logger to visualise checkpoint chains and debug resumes.
A simple production configuration:
from langgraph.checkpoint.postgres import PostgresSaver
import psycopg_pool
pool = psycopg_pool.ConnectionPool(conninfo="...", min_size=2, max_size=10)
checkpointer = PostgresSaver(pool)
# Ensure tables exist
checkpointer.setup()
graph = builder.compile(checkpointer=checkpointer)
Common Mistakes
Even experienced developers can misuse memory. Here are the pitfalls and their consequences.
- Storing too much state: Large checkpoint payloads slow down serialisation and bloat the database. Each checkpoint is a full snapshot; if your state contains unneeded artefacts, the storage cost multiplies quickly.
- Not separating transient vs persistent data: Temporary data (e.g., raw tool call payloads) mixed with essential session state makes checkpoints unnecessarily large and complicates resumption logic. Only persist what you need to rebuild the next step.
- Ignoring checkpoint overhead: With the default “after every node” checkpointer, a 10‑node graph creates 10 checkpoints per run. That’s fine for workflows with few steps, but for graphs with many rapid nodes, consider batching or using a lighter store during development.
- Overusing memory for simple tasks: If a workflow is fully deterministic and can be restarted safely, a checkpointer adds complexity without value. Avoid enabling memory “just because”.
- Using in‑memory checkpointers in production: A single process restart wipes all ongoing conversations. Always use a persistent backend when users or business processes depend on continuity.
- Assuming
thread_iduniqueness without a naming convention: A poorly chosenthread_id(like a simple counter) can collide. Use universally unique IDs (UUIDs) and, ideally, a meaningful prefix (e.g.,user-xyz-session-abc).
Consequences: bloated databases, slow resume times, lost user sessions, and hard‑to‑debug “stuck” graphs.
Best Practices
Adopt these habits to keep your memory layer clean and robust.
- Keep state minimal – Only persist what the next node absolutely needs. Compute transient data inside nodes and don’t store it unless required for resumption.
- Persist only necessary fields – Use a Pydantic model with explicit fields. Avoid storing raw LLM responses if you only need a structured result.
- Use checkpoints strategically – LangGraph’s default is per‑node, but you can also add manual
send_checkpointcalls if you need to snapshot at a specific point. Rarely needed; the default is fine. - Separate memory layers – Use the checkpointer for workflow state; use a separate vector store or database for factual agent memory (e.g., user preferences). Don’t stuff everything into the graph state.
- Use structured state schemas – TypedDict or Pydantic models. They make it clear what gets persisted and improve IDE support.
- Monitor memory growth – Set up alerts on the size of your checkpointer table. Archive old threads if they are no longer needed.
- Test resumption – Write integration tests that stop and resume a graph, then assert the final state is correct.
Memory and Other LangGraph Components
Memory is not an isolated feature; it’s woven into the fabric of the graph.
- State – Memory persists the state. Every state update triggers a checkpoint.
- Nodes – Nodes execute business logic. The checkpointer records which node was last and which is next.
- Edges – Conditional edges decide the next node. The checkpointer captures the decision so that a resume continues along the same path.
- Workflows – Entire workflows (e.g., “collect data → verify → approve → execute”) become durable because each step is checkpointed.
- Human‑in‑the‑loop –
interruptfunctions rely on memory to save the pending request and the point of suspension. - Production systems – Memory is the bridge between a single
invoke()call and a long‑running, fault‑tolerant service.
Everything that makes an agent durable ties back to the checkpointer.
Practical Example: Customer Support Agent with Memory
Let’s build a realistic support agent that uses memory to handle pauses and multi‑turn conversations.
Workflow:
- User sends a request.
- Intent node classifies the request.
- Tool node fetches account data or knowledge base articles.
- Checkpoint – graph pauses if confidence is low or if the action is sensitive.
- Human review – the agent waits for approval.
- Resume – the approved action is executed.
- Final response is sent.
Step 1: Define state
from typing import TypedDict, Annotated
from langgraph.graph.message import add_messages
from langchain_core.messages import BaseMessage
class SupportState(TypedDict):
messages: Annotated[list[BaseMessage], add_messages]
intent: str
ticket_data: dict
needs_approval: bool
approved: bool
final_response: str
Step 2: Build nodes
def intent_node(state: SupportState):
# Use LLM to classify intent
state["intent"] = "refund_request" # simplified
state["needs_approval"] = True if state["intent"] in ["refund_request"] else False
return state
def tool_node(state: SupportState):
# Fetch customer data
state["ticket_data"] = {"order_id": "123", "amount": 99.99}
return state
def human_review_node(state: SupportState):
# This node will only be reached if needs_approval is True
decision = interrupt({
"question": "Approve refund?",
"details": state["ticket_data"]
})
state["approved"] = decision.get("approved", False)
return state
def action_node(state: SupportState):
if state["approved"]:
# execute refund
state["final_response"] = "Refund processed."
else:
state["final_response"] = "Refund denied."
return state
Step 3: Create the graph
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import MemorySaver
builder = StateGraph(SupportState)
builder.add_node("intent", intent_node)
builder.add_node("tools", tool_node)
builder.add_node("human_review", human_review_node)
builder.add_node("action", action_node)
builder.add_edge(START, "intent")
builder.add_edge("intent", "tools")
builder.add_conditional_edges("tools", lambda s: "human_review" if s["needs_approval"] else "action")
builder.add_edge("human_review", "action")
builder.add_edge("action", END)
# Use in-memory for local demo; swap for Postgres in production
memory = MemorySaver()
graph = builder.compile(checkpointer=memory)
Step 4: Execute and resume
config = {"configurable": {"thread_id": "support-session-001"}}
# First call: the graph runs until the interrupt
graph.invoke({"messages": [HumanMessage("I want a refund")]}, config)
# The graph is now paused at human_review_node
# ... human approves later (maybe from a different process)
from langgraph.types import Command
graph.invoke(Command(resume={"approved": True}), config)
# The final state now has final_response = "Refund processed."
Memory used here:
- The checkpointer saves state after
intent,tools, and then pauses. - The
thread_idties the two invocations together. - The resume picks up exactly at the
human_review_node, with the interrupt value replaced by the human’s decision.
FAQ
1. What is memory in LangGraph?
Memory is the ability to persist and later restore the exact execution state of an agent graph, enabling long‑running workflows, human‑in‑the‑loop pauses, and crash recovery.
2. How is memory different from state?
State is the active data structure during a run. Memory is the persisted snapshot of that state (a checkpoint) that survives across runs.
3. What are checkpoints?
Checkpoints are point‑in‑time snapshots of the entire graph state, automatically saved after each node. They include enough information to resume execution seamlessly.
4. Can LangGraph resume execution after a crash?
Yes. As long as a persistent checkpointer is used (e.g., Postgres), the graph can be restarted with the same thread_id and will continue from the last successful node.
5. Where is memory stored?
Memory is stored in the checkpointer backend you configure. Options include in‑memory (MemorySaver), SQLite, Postgres, or custom implementations.
6. Is memory persistent by default?
No. By default, no checkpointer is attached, so the graph is stateless between runs. You must explicitly provide a checkpointer to enable persistence.
7. How do I implement long‑running agents?
Attach a persistent checkpointer (e.g., SqliteSaver or PostgresSaver) to your compiled graph, and always use the same thread_id for a given workflow session. The agent can then run for hours or days, surviving pauses.
8. Does LangGraph handle LLM memory (chat history) separately?
The message history is stored as part of the graph state (if you include a messages key). When you resume, the full conversation is available. For long‑term factual memory, you would integrate an external vector store.
9. What happens if I try to resume a graph that already finished?
If the graph has no pending “next nodes”, the checkpointer will have recorded an END marker. Invoking it again with the same thread_id will do nothing (or may raise an exception). You can start a new thread_id or reset the state.
10. Can multiple users share the same checkpointer?
Yes, as long as each user or session uses a unique thread_id. The checkpointer segregates data by thread ID, so a shared Postgres instance works perfectly.
11. What’s the difference between thread_id and checkpoint_id?
thread_id identifies a single coherent conversation or workflow. checkpoint_id identifies a specific version within that thread’s history. You normally only need the thread_id; resuming always loads the latest checkpoint.
12. Can I manually save a checkpoint?
LangGraph saves automatically. You generally do not need manual saves. If you absolutely must force a checkpoint, you can call send_checkpoint from inside a node, but that is an advanced use case.
13. Does memory affect performance?
Each checkpoint serialises the entire state, which has a cost. For graphs with very large state (e.g., thousands of messages), this can be noticeable. Use only necessary fields and consider a fast serialisation backend (e.g., msgpack via custom saver).
14. How do I monitor memory usage?
Query the checkpointer’s underlying storage (e.g., number of rows in the checkpoints table). In Postgres, you can set up a cron job to archive or delete old threads after a TTL.
15. Is memory compatible with streaming and async?
Yes. The checkpointer works with both sync and async graph invocations. Streaming outputs are unaffected; the state is captured after the full step completes.
16. Can I run a graph without memory and add it later?
Yes, you can start with no checkpointer for quick prototyping and add one when you need persistence. The graph code remains identical; only the compile step changes.
Conclusion
LangGraph memory is not an afterthought—it is the fundamental building block that makes agents durable, interruptible, and production‑ready. By separating state persistence into a pluggable checkpointer, the framework gives you precise control over durability without complicating your business logic.
Key takeaways:
- Memory = state persistence + resumability. It’s powered by automatic checkpoints.
- Checkpoints capture the entire graph state after every node, enabling seamless crash recovery and human‑in‑the‑loop pauses.
- State persistence strategies range from in‑memory for development to Postgres for high‑availability production.
- Design patterns like the Stateful Workflow and Persistent Agent Pattern help you apply memory appropriately.
- Production memory demands careful attention to storage choice, consistency, and observability.
Mastering memory turns your LangGraph prototypes into reliable, long‑running services. Continue deepening your expertise:
- LangGraph Core Concepts – understand the graph execution model that underlies memory.
- Tool Calling in LangGraph – integrate tools that need checkpoint‑aware idempotency.
- LangGraph Workflows – design complete multi‑step workflows with pauses and resumptions.
- Human‑in‑the‑Loop Patterns – explore advanced interrupt and approval designs.
- Taking LangGraph to Production – deployment strategies, scaling, and database configuration.
Start small with MemorySaver, then graduate to a persistent backend—your agents will thank you when they wake up exactly where they left off.