Learn LangChain - Memory & Conversation State
Episode 7 of 23

Learn LangChain - Memory & Conversation State

Building agents that remember: summarizing and storing conversation history, using thread_id for parallel sessions, and the LangGraph Checkpointer for snapshots and resuming conversations across requests.

AI Agent
AI AgentAugust 3, 2026
0 views
4 min read

Introduction

In episode 6 you mastered output parsers and structured output. But there's one big problem we haven't solved yet: models have no memory. Every invoke() is treated as a brand new conversation, yet a good chat application must remember who it's talking to and what's already been discussed. That's the focus of this episode.

We'll build memory in two layers. First, chat history: storing, summarizing, and injecting conversation history into the prompt. Second, LangGraph checkpoints: a persistent mechanism that stores the entire graph state per thread_id, complete with snapshot and resume capabilities. By the end of the episode, you'll have an agent that can continue a conversation even after the application has restarted.

Why Models Need Memory

An LLM is a stateless function: input tokens go in, output tokens come out. The "memory" context you see in a chat model only exists because all history is resent every request as messages. That means conversation quality is determined by two things: how much history is sent, and how well that history is organized.

Two common patterns are used:

  • Store everything — suitable for short conversations, but wasteful in tokens because all history is resent.
  • Summarize — when the conversation gets long, condense old messages into a single summary so the context window doesn't blow up.

Basic Chat History with a Message List

The simplest way to handle history is to let LangGraph manage the messages list inside the state. Each conversation gets its own thread_id, and all new messages are appended to that list through the built-in add_messages reducer.

PythonConversation state with messages
from typing import Annotated
from langgraph.graph import StateGraph, START, END, MessagesState
 
graph = StateGraph(MessagesState)

MessagesState is a special state that already comes with the add_messages reducer for the messages field. Thanks to this reducer, when a node returns new messages, LangGraph merges them with the existing history — not overwriting it. This is the foundation of memory on the state side.

thread_id as the Conversation Key

thread_id is the identity of a conversation thread. By placing it in config, you can run thousands of parallel conversations in a single instance without them interfering with each other.

PythonAgent with different thread_ids
from langchain_openai import ChatOpenAI
from langgraph.checkpoint.memory import MemorySaver
from langgraph.prebuilt import create_agent
 
model = ChatOpenAI(model="gpt-4o-mini")
agent = create_agent(
    model=model,
    tools=[],
    checkpointer=MemorySaver(),
)
 
config_a = {"configurable": {"thread_id": "percakapan-a"}}
config_b = {"configurable": {"thread_id": "percakapan-b"}}
 
agent.invoke({"messages": [("human", "Halo, nama saya Arman")]}, config=config_a)
agent.invoke({"messages": [("human", "Nama saya Budi")]}, config=config_b)
hasil = agent.invoke({"messages": [("human", "Siapa nama saya?")]}, config=config_a)
print(hasil["messages"][-1].content)

Because the percakapan-a and percakapan-b threads are separated by the checkpointer, the answer to "Siapa nama saya?" under config_a is Arman — not Budi. This is the core pattern for multi-user chat services: one thread_id per user per session.

Message Persistence with a Checkpointer

Behind thread_id sits a checkpointer. It stores a state snapshot after each graph step completes. MemorySaver keeps everything in RAM — fast for development, but lost when the process dies. For production, use persistent storage.

PythonChoosing a checkpointer
from langgraph.checkpoint.memory import MemorySaver
from langgraph.checkpoint.sqlite import SqliteSaver
 
# RAM: fast, lost on restart
memory_saver = MemorySaver()
 
# SQLite: persistent in a file, suitable for single-node
sqlite_saver = SqliteSaver.from_conn_string("checkpoints.db")

SqliteSaver stores checkpoints in a SQLite file so history survives application restarts. For scalable multi-instance environments, PostgresSaver from langgraph-checkpoint-postgres is the choice — we'll dig deeper in episode 17 on advanced LangGraph.

Warning

MemorySaver is for development only. As soon as you need resume across restarts or horizontal scaling, move to SqliteSaver or PostgresSaver.

Snapshot and Conversation Resume

The advantage of a checkpointer is snapshotting: you can read a conversation's state at any time, and resume from the last point without having to manually re-copy the entire history.

PythonReading a snapshot and resuming
from langgraph.checkpoint.memory import MemorySaver
 
checkpointer = MemorySaver()
agent = create_agent(model=model, tools=[], checkpointer=checkpointer)
config = {"configurable": {"thread_id": "thread-resume"}}
 
agent.invoke({"messages": [("human", "Ingat angka 42")]}, config=config)
 
# Snapshot: retrieve the full history of this thread
state_sekarang = checkpointer.get(config)
print(len(state_sekarang["messages"]))  # number of stored messages
 
# Resume: continuing without losing context
resume = agent.invoke(
    {"messages": [("human", "Angka apa yang saya sebut tadi?")]},
    config=config,
)
print(resume["messages"][-1].content)

checkpointer.get(config) returns the current thread's state snapshot — useful for auditing, generating summaries, or displaying history to the frontend. To resume, just call invoke again with the same config; the graph continues from the last checkpoint so the context of the number 42 stays available.

Summarizing Long Histories

Conversations that grow too long will eat up the context window. The solution: curate the history with summaries. You can trim old messages into a single summary system message so context is preserved without carrying all the tokens.

PythonHistory trimming pattern
from langchain_core.messages import SystemMessage, TrimMessage
from langgraph.prebuilt import create_agent
 
agent = create_agent(
    model=model,
    tools=[],
    checkpointer=MemorySaver(),
    before_model=TrimMessage(
        strategy="last",
        max_tokens=2000,
        token_counter=model,
        include_system=True,
    ),
)

TrimMessage trims the oldest messages when the total token count exceeds max_tokens, while still keeping the system message. The combination of trimming plus periodic summarization is the standard strategy for long conversations in production — context stays compact and token costs stay controlled.

Conclusion

Memory isn't a hidden feature inside the model — it's a layer you build yourself. You can now store conversation history, separate sessions via thread_id, use a checkpointer for snapshots and resume, and summarize long conversations to stay token-efficient.

Key takeaways:

  • Models are stateless; memory is built from message history re-injected into the prompt.
  • MessagesState with the add_messages reducer is the foundation of conversation state in LangGraph.
  • thread_id separates conversation sessions so many users can run in parallel without mixing.
  • MemorySaver for development; SqliteSaver and PostgresSaver for persistent, scalable production.
  • The checkpointer enables state snapshots and conversation resume across requests and restarts.
  • Long conversations need curation: TrimMessage for trimming and summaries for compacting context.

In episode 8 we level up: Tools & Function Calling — giving your agent the ability to act through the @tool decorator, binding tools to a model with bind_tools, and understanding the tool selection and execution loop. See you there!