Course Content
LangChain Mastery
7 sections · 109 lessons
How do you implement a LangChain agent with memory?
What you need to know
Why an agent needs memory
A model call is stateless: the model sees only what is in this request. If a customer says "Where is my order?" and then "Cancel it", the second message means nothing unless the first turn — and the order ID the tool returned — is sent again. Memory is the code that stores past turns and puts them back in the next request.
Two kinds of memory
| Short-term (thread) memory | Long-term memory | |
|---|---|---|
| Holds | This conversation's messages and tool results | Facts across conversations: preferences, profile |
| LangChain 1.x piece | Checkpointer + thread_id | Store + a namespace such as ("users", user_id) |
| Example | "Cancel it" resolves to ORD-1042 | "This user prefers Hindi replies" |
The code
1from langchain.agents import create_agent2from langchain.agents.middleware import SummarizationMiddleware3from langgraph.checkpoint.postgres import PostgresSaver # pip install langgraph-checkpoint-postgres45DB_URI = "postgresql://app:***@db:5432/support"67with PostgresSaver.from_conn_string(DB_URI) as checkpointer:8 checkpointer.setup() # creates tables, run once9 agent = create_agent(10 model, tools=[get_order, cancel_order],11 system_prompt="You are a support agent for an online store.",12 checkpointer=checkpointer,13 middleware=[SummarizationMiddleware(model=settings.small_model,14 trigger=("tokens", 4000),15 keep=("messages", 20))],16 )17 cfg = {"configurable": {"thread_id": "user-881:chat-17"}}18 agent.invoke({"messages": [{"role": "user", "content": "Where is ORD-1042?"}]}, cfg)19 agent.invoke({"messages": [{"role": "user", "content": "Cancel it."}]}, cfg)On the second call you send only the new message. The graph loads the saved state for that thread_id, appends the new message, and runs. The model sees the earlier tool result and knows "it" is ORD-1042.
SummarizationMiddleware replaces older messages with a summary once the history passes 4,000 tokens, keeping the last 20 messages as they are. (The trigger and keep names are from recent 1.x releases; the first 1.0 release used max_tokens_before_summary and messages_to_keep.)
Why checkpointers beat the old approach
- They save tool calls and results, not just the text turns. The old memory classes often lost intermediate steps.
- They save after every step, so a crash mid-run can resume.
- They enable human-in-the-loop: a run can pause for approval and continue later from the checkpoint.
The legacy pattern — a prompt with MessagesPlaceholder("chat_history") and RunnableWithMessageHistory wrapped around an AgentExecutor — needed a separate history store and did not persist the loop's own state. RunnableWithMessageHistory has been deprecated since langchain-core 1.3.3, and its warning points to LangGraph persistence.
Keying threads safely
Build the thread_id from your authenticated user and conversation, for example f"{user_id}:{conversation_id}". Never accept it from the request body unchecked, or one user can read another's conversation.
A real-life example
A help-centre support bot for a food-delivery app used InMemorySaver in its first release. It worked in testing, but in production the app ran on 6 pods behind a load balancer: a user's second message often landed on a different pod, which had no memory of the first, and the bot asked "Which order?" again. Pod restarts during deploys also wiped every conversation.
The team switched to PostgresSaver. Any pod can now load any thread, deploys no longer reset chats, and "which order?" repeats dropped to nearly zero. Because conversations with long order histories grew past 10,000 tokens, they added SummarizationMiddleware, which kept the average prompt under 5,000 tokens.
Follow-up questions to expect
- "What is a
thread_id?" — The key for one conversation's saved state. Same ID continues the conversation; a new ID starts fresh. - "Where does user-level memory go?" — In a LangGraph store (
InMemoryStore,PostgresStore) passed asstore=, read and written by tools throughToolRuntime. - "How do you clear a conversation?" — Delete the thread with
checkpointer.delete_thread(thread_id), or start a newthread_id.