TechByteByByte

Agent State and Memory: Clearing Up the Terminology

Untangle messages, conversation history, short-term state, and persistent memory — genuinely different concepts that get blurred together — and see exactly which one to use for which real need.

#LangChain#Agents#Memory#State

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, ToolMessage objects 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 growing messages list 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_id is how real conversation history persists across separate calls, with each thread_id keeping 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