Module 8: Memory and Persistence
Introduction: Why Agents Need Memory
Overview
Your AI Research Assistant is robust. It has retry with exponential backoff, parallel search across multiple sources, merge with deduplication, and graceful degradation when a source fails. You built it in Module 7 and it holds up in the real world.
But it's amnesiac. Every run starts from zero.
Close the terminal, open it again, and your agent has no idea that five minutes ago it was halfway through a research task on RAG. The user comes back tomorrow and asks "can you go deeper on what you found yesterday?" — the agent doesn't know what they're talking about. A research run that spent 5 minutes processing 3 of 5 sources gets interrupted by a system crash — all that work is gone.
This isn't an edge case. It's what happens to any stateless system in production. And it's exactly what separates a demo from a product you can put in front of real users.
Why it matters: Without memory and persistence, Modules 9-12 aren't possible. Human-in-the-loop (M9) requires the agent to save its state so it can pause and wait for approval. Multi-agent (M10) requires agents to share context. Production (M12) requires that nothing gets lost. This module is the technical foundation for everything that follows.
Where are we in the guide?
This is Module 8 of LangChain & LangGraph: From Chains to Agents. It's the first module of Block 3 (Advanced LangGraph).
Block 1: LangChain Core (Modules 1-4) ✅ Done
Block 2: LangGraph Fundamentals (Modules 5-7) ✅ Done
Block 3: Advanced LangGraph (Modules 8-10) ← YOU ARE HERE (Module 8)
Block 4: Production (Modules 11-12)
Your progress:
Block 1 — LangChain Core ✅ Done
│
│ Module 1: Models and Providers ✅
│ Module 2: Tools and Tool Calling ✅
│ Module 3: Agents (create_agent) ✅
│ Module 4: Middleware and Customization ✅
│
▼
Block 2 — LangGraph Fundamentals ✅ Done
│
│ Module 5: Introduction to LangGraph ✅
│ Module 6: Functional API ✅
│ Module 7: Advanced Flows ✅
│
▼
Block 3 — Advanced LangGraph
│
│ Module 8: Memory and Persistence ← YOU ARE HERE
│ Module 9: Human-in-the-Loop 🔒 Next
│ Module 10: Multi-Agent Systems 🔒
│
▼
Block 4 — Production 🔒
│
│ Module 11: Deep Agents 🔒
│ Module 12: LangSmith and Production 🔒
Block 2 gave you the construction tools: StateGraph, Functional API, retry, branching, error handling. Block 3 turns them into enterprise capabilities: persistence (this module), human oversight (M9), and multi-agent collaboration (M10).
The bridge from Module 7
What you already have
Your Research Agent v2 is a solid system:
- ✅ Retry with exponential backoff when APIs fail
- ✅ Parallel search across 3 sources at once
- ✅ Merge with result deduplication
- ✅ Graceful degradation: if one source fails, the report gets built from the ones that answered
- ✅ Structured logging for traceability
What it's missing
Run this sequence in your head:
Scenario 1 — Crash mid-process:
Your agent has spent 5 minutes researching "state of AI in healthcare 2025"
It processed 3 of 5 sources successfully
The machine reboots (OS update, crash, accidental close)
Without persistence: you start from zero. 5 minutes + API costs, gone.
With persistence: you resume from source 4. 30 seconds.
Scenario 2 — The user comes back tomorrow:
Today: "Research RAG trends in 2025"
Tomorrow: "Can you go deeper on what you found yesterday about hybrid search?"
Without memory: "I have no information about previous research."
With memory: "Yesterday I found 3 papers on hybrid search. Let me dig into them."
Scenario 3 — Multiple users:
User A researches AI in healthcare. User B researches AI in finance.
Both use the same deployed agent.
Without isolation: the conversations bleed into each other.
With thread_id: each user gets their own isolated context.
Those three problems — crash recovery, memory across sessions, and multi-user — are exactly what this module solves.
The two kinds of memory (don't mix them up)
This distinction is fundamental. Confusing short-term and long-term memory is the most common mistake when working with stateful agents. They're completely different concepts with different implementations.
Short-term memory: the current conversation
Short-term memory is the message history within the current session. It's what lets the agent hold context between conversation turns:
Turn 1 — User: "What is RAG?"
Turn 1 — Agent: "RAG is Retrieval-Augmented Generation, a pattern that..."
Turn 2 — User: "Give me an example?"
↑ Without short-term memory, the agent doesn't know what "an example" refers to
↑ With short-term memory, it knows "an example" means RAG
Characteristics:
- ✅ Lives inside a single session (one conversation)
- ✅ Includes: user messages, agent responses, tool calls, tool results
- ✅ Managed with
MessagesStateandthread_id - ✅ Has a practical ceiling: the model's context window (128K tokens on GPT-4.1)
- ❌ Lost when the session ends (unless you use checkpointing)
Long-term memory: what survives across sessions
Long-term memory is information that survives across different conversations. It isn't messages — it's structured data the agent accumulates and looks up:
Session 1 (Monday):
User researches "AI in healthcare"
Agent stores: preference for academic sources, interest in medical diagnosis
Session 2 (Wednesday):
User: "Research something new"
Agent queries long-term memory:
→ "This user prefers academic sources"
→ "Their previous topics include AI in healthcare, especially diagnosis"
→ Adapts the search automatically
Characteristics:
- ✅ Persists across different sessions (days, weeks)
- ✅ Includes: user preferences, accumulated knowledge, interest profiles
- ✅ Managed with
InMemoryStoreor external databases - ✅ Not limited by the context window — it lives outside the model
- ❌ Requires explicit design: what to store, when to read it, how to update it
Episodic memory: specific past interactions
Episodic memory is a subset of long-term memory that refers to specific interactions the agent can point back to:
User: "Do you remember what you found last time about RAG?"
Agent queries episodic memory:
→ Session from March 3: researched RAG, found 5 papers, main conclusion was...
→ "Yes, last time I found that hybrid search beats dense retrieval by about 15%..."
It isn't a separate system — it's how you use long-term memory to recall past events.
Comparison table
| Aspect | Short-term | Long-term | Episodic |
|---|---|---|---|
| Scope | One session | Every session | Specific sessions |
| Content | Messages, tool calls | Preferences, profiles | Past events |
| Limit | Context window | External storage | External storage |
| Implementation | MessagesState | InMemoryStore / DB | Subset of long-term |
| Lost if... | The session ends | The store gets wiped | The store gets wiped |
| Example | "You just asked about RAG" | "You prefer academic sources" | "On Monday you researched healthcare" |
Stateless vs stateful: why it matters
Stateless agent (what you have now)
result = agent.invoke({"query": "What is RAG?"})
# The agent answers, and forgets everything immediately.
result = agent.invoke({"query": "Give me more detail"})
# "More detail about what? I have no prior context."
Every invoke() is an island. There's no link between calls. It's like talking to someone with anterograde amnesia: every interaction starts from scratch.
Stateful agent (what you'll build)
config = {"configurable": {"thread_id": "user_123"}}
result = agent.invoke({"query": "What is RAG?"}, config)
# The agent answers AND stores the conversation in thread "user_123"
result = agent.invoke({"query": "Give me more detail"}, config)
# The agent reads the history of thread "user_123"
# It knows "more detail" refers to RAG
# It answers with context
The thread_id is the key. Each thread is an independent conversation with its own history. Same agent, different threads, different contexts. That's how multi-user works.
The persistence stack
LangGraph has a deliberate design for persistence: you start simple for development, and you move to production by changing one line.
MemorySaver: development and testing
from langgraph.checkpoint.memory import MemorySaver
checkpointer = MemorySaver()
graph = builder.compile(checkpointer=checkpointer)
- ✅ Zero config: no database, no Docker
- ✅ Perfect for development: fast tests, iteration, debugging
- ❌ In-memory: if the process restarts, everything is gone
- ❌ Single-process: doesn't work with multiple workers
Use MemorySaver for: prototyping, testing, local development, tutorials.
PostgresSaver: production
from langgraph.checkpoint.postgres import PostgresSaver
checkpointer = PostgresSaver.from_conn_string("postgresql://user:pass@host:5432/db")
graph = builder.compile(checkpointer=checkpointer)
- ✅ Durable: survives process restarts, crashes, deployments
- ✅ Multi-process: several workers can read and write
- ✅ Scalable: PostgreSQL is battle-tested for production
- ❌ Requires a PostgreSQL instance
Use PostgresSaver for: production, staging, any environment where durability matters.
The migration is trivial
Look at the change:
# Development
checkpointer = MemorySaver()
# Production (the ONLY line that changes)
checkpointer = PostgresSaver.from_conn_string(os.getenv("DATABASE_URL"))
# The rest of the code is IDENTICAL
graph = builder.compile(checkpointer=checkpointer)
One line. The rest of your code — nodes, edges, state, logic — doesn't change at all. That's what a well-designed abstraction looks like: the checkpointer is a swappable backend.
Module map
| # | Capsule | What you'll learn | Type |
|---|---|---|---|
| 01 | Introduction (this one) | Why agents need memory, kinds of memory, the persistence stack | Intro |
| 02 | Short-term memory: conversation history | MessagesState, thread_id, message trimming, summarization | Technique |
| 03 | Checkpointing: MemorySaver | Automatic checkpoints, inspecting states, thread management | Technique |
| 04 | Durable execution and crash recovery | Simulating crashes, resuming from a checkpoint, idempotency | Technique |
| 05 | Time-travel debugging | Navigating state history, replay, debugging decisions | Technique |
| 06 | Long-term memory with Store | InMemoryStore, namespaces, storing and querying data across sessions | Technique |
| 07 | PostgresSaver: persistence in production | PostgreSQL setup, migrating from MemorySaver, multi-worker | Technique |
| 08 | Project: Research Agent with memory | Research Agent v3: checkpointing + long-term memory + multi-user | Project |
The learning flow
You start with short-term memory (capsule 02) — the most immediate concept: how to keep context between conversation turns. Then you learn checkpointing (capsule 03) — the mechanism that saves the agent's state at every step. With checkpointing, you implement crash recovery (capsule 04) — the agent survives interruptions and resumes where it left off. Next, you use those checkpoints for time-travel debugging (capsule 05) — you walk the state history and understand why the agent made each decision. You add long-term memory (capsule 06) — information that persists across sessions. You migrate to PostgresSaver (capsule 07) — real persistence for production. Finally, you put it all together in Research Agent v3 (capsule 08).
The progression is: conversation → checkpoints → durability → debugging → persistent memory → production → project.
Connection with the project
Research Agent v3: the agent that remembers
Your Research Agent takes a big step forward in this module:
v1 (Module 6): Works, but fragile
↓
v2 (Module 7): Robust (retry, branching, error handling)
↓
v3 (This module): Persistent (checkpointing, memory, multi-user)
│
│ + Checkpointing: research gets saved step by step
│ + Crash recovery: if it's interrupted, it resumes where it stopped
│ + Long-term memory: remembers preferences across sessions
│ + Thread management: multi-user with isolated context
│
▼
v4 (Module 9): + Human approvals before expensive actions
The proof is concrete: close the terminal, open a new one, run your agent with the same thread_id — and it remembers the previous research. That moment, when you see the agent "know" what you were researching an hour ago, is when this module clicks.
Connection with Module 9: Human-in-the-Loop
Persistence is a technical prerequisite for human-in-the-loop. The reason is direct:
Without persistence:
Agent: "I'm about to run an expensive search across 5 APIs"
System: interrupt() — pause for human approval
??? The agent lost its state. It doesn't know where it was.
With persistence:
Agent: "I'm about to run an expensive search across 5 APIs"
System: interrupt() — pause for human approval
State saved in a checkpoint with thread_id
... 10 minutes later ...
Human: "Approved"
System: resume from the checkpoint — the agent continues exactly where it paused
Without checkpointing, interrupt() makes no sense — the agent can't pause and resume if it has no persistent state. That's why this module comes before M9.
What this module does NOT cover
- ❌ Vector stores and RAG — Storing embeddings for semantic search is a different topic. This module is about the agent's state (conversations, checkpoints, preferences), not external knowledge bases.
- ❌ Knowledge databases — We're not building a retrieval system. Long-term memory here means preferences and profiles, not indexed documents.
- ❌ Redis as a backend — We mention it as an option, but we focus on MemorySaver (dev) and PostgresSaver (prod). Redis is useful for caching and ephemeral sessions.
- ❌ Human-in-the-loop — We mention that persistence enables it, but implementing interrupts and approvals belongs to Module 9.
- ❌ Multi-agent memory sharing — How several agents share memory is covered in Module 10.
Technical setup
Prerequisites
- ✅ Module 7 done — you have a Research Agent v2 with retry, branching, and error handling
- ✅ Python 3.11+ installed
- ✅ At least one API key from a provider (OpenAI recommended)
Installation
If you finished Module 7, you already have the main dependencies. Add the checkpointing package for PostgreSQL (you'll use it in capsule 07):
pip install langgraph langchain-openai python-dotenv langgraph-checkpoint-postgres
Check that the import works:
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import MessagesState
print("MemorySaver available")
print("MessagesState available")
# Expected output:
# MemorySaver available
# MessagesState available
Environment variables
Your .env from Module 7 still works:
# .env
OPENAI_API_KEY=sk-...
# For capsule 07 (PostgresSaver), you'll add:
# DATABASE_URL=postgresql://user:password@localhost:5432/langgraph_db
Quick check: an agent with memory
Run this script to verify that checkpointing works:
from langgraph.graph import StateGraph, MessagesState, START, END
from langgraph.checkpoint.memory import MemorySaver
def echo(state: MessagesState) -> dict:
last_message = state["messages"][-1].content
return {"messages": [{"role": "assistant", "content": f"Echo: {last_message}"}]}
builder = StateGraph(MessagesState)
builder.add_node("echo", echo)
builder.add_edge(START, "echo")
builder.add_edge("echo", END)
checkpointer = MemorySaver()
graph = builder.compile(checkpointer=checkpointer)
config = {"configurable": {"thread_id": "test_001"}}
result1 = graph.invoke({"messages": [{"role": "user", "content": "Hello"}]}, config)
print(f"Turn 1: {result1['messages'][-1].content}")
result2 = graph.invoke({"messages": [{"role": "user", "content": "Do you remember what I said?"}]}, config)
print(f"Turn 2: {result2['messages'][-1].content}")
print(f"Messages in history: {len(result2['messages'])}")
# Expected output:
# Turn 1: Echo: Hello
# Turn 2: Echo: Do you remember what I said?
# Messages in history: 4
If you see 4 messages in the history (2 from the user + 2 from the agent), checkpointing is working. The agent accumulates messages across invocations thanks to the thread_id.
Signs of success
By the end of this module, you'll know you got it right if:
- ✅ Your agent holds a conversation with context: you can refer back to earlier messages and the agent follows
- ✅ You can close the terminal, open a new one, and the agent remembers the previous conversation (with PostgresSaver)
- ✅ You simulate a crash mid-research and the agent resumes exactly where it stopped
- ✅ You can walk the agent's state history and see the state it had at each step (time-travel)
- ✅ The agent remembers user preferences across different sessions (long-term memory)
- ✅ Two different users share the same agent with completely isolated contexts (thread_id)
Self-assessment
If you can answer these questions, you're on the right track:
- What's the difference between short-term and long-term memory in an agent?
- Why isn't MemorySaver suitable for production?
- What happens if you don't implement message trimming in a long conversation?
- How does checkpointing let an agent pause and resume?
- What is a
thread_idand why is it necessary for multi-user?
Summary
- Your Research Agent v2 is robust, but amnesiac: every run starts from zero. Without persistence, crashes destroy work, users get no continuity, and there's no multi-user isolation
- Short-term memory is the current conversation's history — messages, tool calls, results. Managed with
MessagesStateandthread_id - Long-term memory is information that persists across sessions — preferences, profiles, accumulated knowledge. Managed with
InMemoryStoreor databases - Episodic memory is a subset of long-term: specific past interactions the agent can refer back to
- MemorySaver = development and testing (in-memory, zero config). PostgresSaver = production (durable, multi-process). The migration is one line of code
- Persistence is a prerequisite for human-in-the-loop (M9): the agent needs saved state to pause and resume
- This module turns the Research Agent from a prototype that "works" into a system that remembers — the difference between a demo and a product
Further reading
- LangGraph — Persistence — Official docs on LangGraph's persistence system: checkpointers, threads, state
- LangGraph — Memory — Short-term and long-term memory concepts in LangGraph
- How to add memory to chatbots — Practical guide to adding conversation history
- How to add cross-thread memory — Implementing long-term memory that persists across threads
- LangGraph Checkpoint PostgreSQL — PostgresSaver setup for production
- Designing AI Agents with Memory — LangChain Blog — Article on memory patterns for agents
Module 8 — LangChain & LangGraph: From Chains to Agents
Next capsule: Short-term Memory: Conversation History — you'll learn how MessagesState manages conversation history, how thread_id isolates context per user, and why message trimming is mandatory in production.