Every agent you’ve built so far has had a real limitation you may not have noticed: call agent.invoke(...) twice, in two separate Python calls, and the second one has no idea the first one ever happened. This module explains exactly why, and untangles a set of words — messages, history, state, memory — that get used almost interchangeably online, despite meaning genuinely different things.
Proving the limitation exists
from langchain.chat_models import init_chat_model
from langchain.agents import create_agent
agent = create_agent(model=init_chat_model("openai:gpt-4o-mini"), tools=[])
agent.invoke({"messages": [{"role": "user", "content": "My name is Kenji."}]})
result = agent.invoke({"messages": [{"role": "user", "content": "What's my name?"}]})
print(result["messages"][-1].content)
Run this, and the agent almost certainly won’t know the answer. This isn’t a bug — it’s working exactly as designed. Each .invoke() call is a completely fresh, independent run, with no connection to any previous one, unless you explicitly provide that connection yourself.
The real vocabulary, defined precisely
- Messages are the individual
HumanMessage,AIMessage,ToolMessageobjects from Module 6 — the raw building blocks. - Conversation history is a specific, ordered list of messages representing one particular conversation so far — what you built by hand in Module 6’s
ask()function. - Short-term state is what an agent tracks within a single
.invoke()call, across its internal loop rounds — the growingmessageslist from Module 14 that lets a multi-step agent remember its own earlier tool results during one request. - Persistent memory is conversation history that survives between separate
.invoke()calls — the piece missing from the example above, and the actual subject of this module.
Example 1: the real fix — a checkpointer
LangChain agents can persist conversation history across calls using a checkpointer — a component that saves and restores state, keyed by a conversation identifier called a thread_id.
from langchain.chat_models import init_chat_model
from langchain.agents import create_agent
from langgraph.checkpoint.memory import InMemorySaver
checkpointer = InMemorySaver()
agent = create_agent(model=init_chat_model("openai:gpt-4o-mini"), tools=[], checkpointer=checkpointer)
config = {"configurable": {"thread_id": "conversation-1"}}
agent.invoke({"messages": [{"role": "user", "content": "My name is Kenji."}]}, config=config)
result = agent.invoke({"messages": [{"role": "user", "content": "What's my name?"}]}, config=config)
print(result["messages"][-1].content)
Run this, and the agent correctly remembers. The thread_id is the actual key: every call using "conversation-1" shares the same, persisted history. InMemorySaver keeps this in your program’s memory — genuinely fine for local development and testing, but gone the moment your program stops running.
Example 2: two separate conversations, staying genuinely separate
config_a = {"configurable": {"thread_id": "user-a"}}
config_b = {"configurable": {"thread_id": "user-b"}}
agent.invoke({"messages": [{"role": "user", "content": "My favorite color is blue."}]}, config=config_a)
agent.invoke({"messages": [{"role": "user", "content": "My favorite color is green."}]}, config=config_b)
result_a = agent.invoke({"messages": [{"role": "user", "content": "What's my favorite color?"}]}, config=config_a)
result_b = agent.invoke({"messages": [{"role": "user", "content": "What's my favorite color?"}]}, config=config_b)
print("User A:", result_a["messages"][-1].content)
print("User B:", result_b["messages"][-1].content)
Each thread_id genuinely isolates its own history. This is exactly how a real, multi-user application keeps different users’ conversations from bleeding into each other — one thread_id per real user or session, not one shared agent memory for everyone.
Why persistent memory has a real, honest cost worth understanding
Recall the Token Usage concerns from earlier in this glossary of concepts — every message ever added to a thread_id’s history gets resent, in full, on every subsequent call, exactly like the growing message list you built by hand back in Module 6. A conversation that’s been going for an hour carries real, accumulated token cost on every single new message, not just the newest one. This is a genuine, practical reason real applications eventually need to think about trimming or summarizing old history — a concern we’ll return to properly once we cover middleware in the next module.
Common mistakes worth avoiding
Forgetting checkpointer entirely and being confused when an agent “forgets everything.” Recall the very first example in this module — without an explicit checkpointer and matching thread_id, every .invoke() call is genuinely independent, by design, not by accident.
Reusing the same thread_id across genuinely different users. Recall Example 2 — if two different real users’ requests accidentally share one thread_id, their conversations merge, and one user’s private information can leak directly into another’s conversation. Generate a distinct thread_id per real user or session, deliberately.
Using InMemorySaver in anything meant to survive a restart. It’s genuinely fine for development, but every stored conversation disappears the moment your program stops. Real, deployed applications need a persistent checkpointer backed by an actual database — a topic covered more fully in the Production Best Practices module later in this course.
What you should take away from this module
- Messages, conversation history, short-term state, and persistent memory are four genuinely distinct concepts, not interchangeable words for the same thing.
- Without a checkpointer, every
agent.invoke()call is completely independent — this is the default, correct behavior, not a bug. - A checkpointer plus a
thread_idis how real conversation history persists across separate calls, with eachthread_idkeeping its own conversation genuinely isolated. - Persistent memory carries a real, accumulating token cost, since the full history resends on every call.
Where this goes next
The next module covers Middleware — one of the most important, current LangChain concepts, and directly relevant to the token-cost concern just raised. You’ll learn how to customize what happens before and after every step of an agent’s loop, including trimming or summarizing growing history, without rewriting the loop itself.
- Author
- TechByteByByte Editorial Team
- Reviewed by
- TechByteByByte Admin
- Published
- Last reviewed