LangChain memory is what lets an agent remember earlier messages and facts. In LangChain 1.x it comes in two types: short-term memory, the thread’s message history saved by a checkpointer and keyed by thread_id, and long-term memory, JSON documents in a LangGraph store shared across threads. The old langchain.memory classes such as ConversationBufferMemory are deprecated.
A memory bug usually shows up one of two ways. In the first, the bot forgets the order number the user gave it one message ago, because each request starts from a blank prompt. In the second, memory is on and prompt tokens per turn keep climbing until a long conversation dies with a context-length error. Both come from the same design decision made badly. The sections below cover which classes are deprecated, what replaced them, how an agent reads and writes its memory, and which policy keeps token growth flat.
ConversationBufferMemory is deprecated: what replaces it
ConversationBufferMemory is deprecated since version 0.3.1, as recorded in the legacy class source. For LangChain 1.x agents, move conversation history into agent state and pass a LangGraph checkpointer to create_agent. Supply a thread_id on each invocation so the next turn reads the same conversation. The short-term memory guide documents this pattern.
The class was not deleted at 1.0. The v0.3 decorator set removal="1.0.0", but the class moved with other legacy code into the separate langchain-classic package. In the current langchain-classic source, the decorator keeps since="0.3.1", pushes removal to "2.0.0" and names langchain.agents.create_agent as the alternative. The package’s memory module still exports the window, token-buffer, summary-buffer, entity and vector-store memory classes too. PyPI listed langchain-classic 1.0.8, released June 10, 2026, requiring Python 3.10 or later (checked October 2026).
The replacement depends on what the old memory object was doing:
| Legacy need | LangChain 1.x mapping |
|---|---|
| Keep the conversation transcript | Message state plus a checkpointer, scoped to a thread |
| Keep only recent turns or a token budget | Trim messages before the model call, or remove messages from state |
| Summarize older conversation turns | SummarizationMiddleware with a recent-message tail |
| Recall facts across separate conversations | A LangGraph store, passed through store= to create_agent |
| Retrieve saved facts by similarity | A store configured with an embedding index and an explicit search step |
The long-term memory guide covers namespaces, keys and reading or writing stored facts through tools. A checkpointer does not automatically extract cross-thread facts, and a store does not automatically put them in the prompt. Add the read and write logic for the behavior you need. Persistence alone also does not trim the transcript; choose a context-management policy separately.
A migration off the deprecated class usually runs in this order:
- Confirm the environment. The v1 migration guide states that all LangChain packages now require Python 3.10 or higher.
- If legacy code must keep running in the meantime,
pip install langchain-classicand change imports fromlangchain.memorytolangchain_classic.memory. The same guide lists legacy chains such asConversationChainamong the things that moved there. - Replace the chain-plus-memory pair with
create_agent(..., checkpointer=...)and pass athread_idinconfigon every call. - Map the old class’s strategy onto trimming, summarization or a store using the table above.
- Swap
InMemorySaverfor a database-backed saver and run its one-timesetup()before the first request.
The legacy classes and what each one did
A tutorial that imports from langchain.memory describes the pre-1.0 API. The v0.3 migration guide explains the move from legacy memory implementations to LangGraph persistence. Treat the old class names below as descriptions of behavior to preserve during migration, and use the current memory guides for the replacement APIs.
Each one still names a strategy you will re-implement:
- ConversationBufferMemory: the whole transcript, verbatim, every turn. Token cost grows linearly with conversation length.
- ConversationBufferWindowMemory: the last
nturns only. Flat cost, hard amnesia beyond the window. - ConversationTokenBufferMemory: the most recent messages that fit inside a token budget. Same idea, budgeted in tokens instead of turns.
- ConversationSummaryMemory: a running summary written by the model, replacing the transcript.
- ConversationSummaryBufferMemory: recent messages verbatim plus a summary of everything older, under one token limit.
- VectorStoreRetrieverMemory: past exchanges embedded and retrieved by similarity to the current input.
- ConversationEntityMemory: structured facts about named entities extracted as the conversation runs.
One exception from the guide: “If you have been using RunnableWithMessageHistory or BaseChatMessageHistory, you do not need to make any changes.”
The two memory types that matter now
Current LangChain collapses the list above into two scopes, defined in the LangGraph memory concepts page.
Short-term memory is thread-scoped. It is the message history of one conversation, held in the agent’s state and persisted by a checkpointer after every step, keyed by a thread_id. Passing the same thread_id resumes the conversation. This is what ConversationBufferMemory did, except that persistence also buys fault tolerance, human-in-the-loop pauses and time travel over checkpoints.
Long-term memory is cross-thread. It is a store of JSON documents organized by namespace and key, where the docs describe a namespace as working “like a folder” and a key “like a file name”. Namespaces usually carry a user or org ID, so a fact learned on Monday’s thread is available on Friday’s. The docs borrow three categories from psychology: semantic (facts about a user), episodic (past agent actions), and procedural (the instructions the agent runs under, such as its system prompt).
The Generative Agents paper describes recording experiences, synthesizing reflections and retrieving memories to plan behavior. It provides background for separating stored experiences from the context selected for a particular response.
Wiring up short-term memory
From the short-term memory guide, the minimum is a checkpointer on the agent and a thread_id in the config:
from langchain.agents import create_agent
from langgraph.checkpoint.memory import InMemorySaver
agent = create_agent(
model="anthropic:claude-sonnet-4-6",
tools=[],
checkpointer=InMemorySaver(),
)
config = {"configurable": {"thread_id": "support-4471"}}
agent.invoke({"messages": [{"role": "user", "content": "My order is 88213."}]}, config)
agent.invoke({"messages": [{"role": "user", "content": "Where is it?"}]}, config)
InMemorySaver is for development. The docs are direct about production: “use a checkpointer backed by a database”, with PostgresSaver from langgraph-checkpoint-postgres as the reference option and MongoDB, Redis and Oracle savers listed on the add-memory page.
Initialization is backend-specific. For PostgresSaver, install langgraph-checkpoint-postgres with "psycopg[binary]", build the saver with PostgresSaver.from_conn_string(DB_URI) and call checkpointer.setup() before first use, as shown in the short-term memory guide. The add-memory page shows the same first-run checkpointer.setup() call for the Redis and Oracle savers; for any other backend, follow that package’s own initialization instructions.
| Backend | Checkpointer (short-term) | Store (long-term) | Package |
|---|---|---|---|
| In-process | InMemorySaver | InMemoryStore | ships with langgraph |
| Postgres | PostgresSaver, AsyncPostgresSaver | PostgresStore, AsyncPostgresStore | langgraph-checkpoint-postgres |
| Redis | RedisSaver, AsyncRedisSaver | RedisStore, AsyncRedisStore | langgraph-checkpoint-redis |
| MongoDB | MongoDBSaver, AsyncMongoDBSaver | no store documented | langgraph-checkpoint-mongodb |
| Oracle | OracleSaver, AsyncOracleSaver | OracleStore, AsyncOracleStore | langgraph-oracledb |
The table follows the add-memory page; the async variants use asetup() for the same one-time migration.
LangChain agent memory: what the agent can read and write
Agent memory in LangChain 1.x is the agent’s state rather than a separate memory object. create_agent keeps messages in an AgentState, the checkpointer saves that state per thread, and tools reach it through a ToolRuntime parameter that the model never sees. Extend the state when the agent must remember more than the transcript.
The short-term memory guide subclasses AgentState and passes it as state_schema:
from langchain.agents import create_agent, AgentState
from langchain.tools import tool, ToolRuntime
from langgraph.checkpoint.memory import InMemorySaver
class CustomState(AgentState):
user_id: str
@tool
def get_user_info(runtime: ToolRuntime) -> str:
"""Look up user info."""
user_id = runtime.state["user_id"]
return "Known customer" if user_id == "user_123" else "Unknown user"
agent = create_agent(
model="anthropic:claude-sonnet-4-6",
tools=[get_user_info],
state_schema=CustomState,
checkpointer=InMemorySaver(),
)
Custom fields such as user_id can be supplied in the invoke input alongside messages, and the checkpointer persists them with the rest of the thread. The agent has four ways to touch memory:
- Read thread state in a tool:
runtime.state["..."], as above. - Write thread state from a tool: return a
Commandwith anupdate, which can include aToolMessagefor the conversation. - Rewrite state before each model call: a function decorated with
@before_modelfromlangchain.agents.middlewarereturns a state update. The guide’s example keeps the first message plus the most recent few. - Read and write cross-thread memory: the long-term memory guide passes a store as
store=and aContextdataclass ascontext_schema=. A tool then callsruntime.store.get(("users",), runtime.context.user_id)orruntime.store.put(...)with the same namespace and key.
Keep the identity used for namespaces in context, not in something the model writes. A tool that takes the user ID as a model-generated argument lets one user’s thread read or overwrite another user’s memories.
LangChain memory management: keeping the thread inside the context window
A checkpointer alone gives you ConversationBufferMemory semantics: everything, forever. The metric to watch is prompt tokens per turn against turn number. With a plain buffer that line is a diagonal, and it only ends one way. Three tools flatten it, mapping onto the old window, token-buffer and summary classes.
| Policy | API | What changes in storage | Extra cost |
|---|---|---|---|
| Trim per call | trim_messages | Nothing; the model sees a subset | None |
| Rewrite history | @before_model returning RemoveMessage(id=REMOVE_ALL_MESSAGES) plus kept messages | Stored messages replaced | None |
| Delete specific messages | RemoveMessage(id=...) | Those messages gone for good | None |
| Summarize | SummarizationMiddleware(trigger=..., keep=...) | Older messages folded into a summary | One model call per firing |
trim_messages from langchain_core is the window and token buffer in one function. Its signature takes a max_tokens budget, a strategy of "first" or "last", a token_counter (a model, a callable, or "approximate"), and boundary controls so a tool result is never cut away from the call that produced it:
from langchain_core.messages import trim_messages
trimmed = trim_messages(
state["messages"],
max_tokens=3000,
strategy="last",
token_counter=model,
start_on="human",
include_system=True,
allow_partial=False,
)
RemoveMessage deletes messages from state permanently, and RemoveMessage(id=REMOVE_ALL_MESSAGES) clears a thread. Trimming is per-call; removal changes what the checkpointer stores. Removal relies on a state key with the add_messages reducer, which the default AgentState provides, and the guide warns that what remains must still be a valid history for your provider: some require it to start with a user message, and every tool call needs its matching tool result.
SummarizationMiddleware is ConversationSummaryBufferMemory reborn. It fires when the history crosses a trigger and folds older messages into a summary while keeping a recent tail verbatim:
from langchain.agents.middleware import SummarizationMiddleware
agent = create_agent(
model="anthropic:claude-sonnet-4-6",
tools=[],
checkpointer=checkpointer,
middleware=[
SummarizationMiddleware(
model="openai:gpt-5.4-mini",
trigger=("tokens", 4000),
keep=("messages", 20),
)
],
)
The docs state the trade-off in one line: trimming or removing messages means “you may lose information”. Summarization loses less, at the price of a model call each time it fires. Point the summarizer at a cheaper model than the agent’s; how to reduce LangChain token costs covers the other levers on the same bill.
Managing memory also means inspecting and deleting what is stored. The add-memory page documents get_state(config) for a thread’s latest snapshot (add a checkpoint_id to the config for an older one), get_state_history(config) for every snapshot newest first, and checkpointer.delete_thread(thread_id) to drop all checkpoints for a thread. That last call is the one to wire into account deletion and retention jobs.
Wiring up long-term memory
The long-term memory guide exposes the store to tools through runtime.store, with get and put on a namespace. Add an embedding index and search becomes the replacement for VectorStoreRetrieverMemory:
from langchain.embeddings import init_embeddings
from langgraph.store.memory import InMemoryStore
store = InMemoryStore(
index={"embed": init_embeddings("openai:text-embedding-3-small"), "dims": 1536}
)
namespace = ("user-1042", "memories")
store.put(namespace, "pref-units", {"data": "Prefers metric units and short answers."})
hits = store.search(namespace, query="how should measurements be formatted?", limit=3)
Swap InMemoryStore for PostgresStore in production. The remaining choice is when to write. The concepts page distinguishes writing “in the hot path”, where the agent saves memories during the turn and the user pays the latency, from writing “in the background”, where a separate job extracts memories later and must be triggered carefully so it does not run on a half-finished thread.
The persistence layer is shared by create_agent and custom graphs; LangChain vs LangGraph explains when to configure an agent or define the graph directly.
What you’ll see
Plot prompt tokens per turn by thread and the memory type is legible from the shape. Buffer: a diagonal that ends in a context-length exception. Window or trim: a ramp that goes flat at the budget, plus tickets about the bot “forgetting” something from twelve turns back. Summary: a sawtooth that drops each time the middleware fires, with the risk that a summary silently rewrites a fact. Instrument at the span level rather than guessing from the bill; the OpenTelemetry GenAI conventions define per-call input and output token attributes to plot.
Caveats
thread_idmust be unique per conversation, not per user. Reusing a user ID as the thread ID merges every session that user ever had into one state.InMemorySaverandInMemoryStorevanish on restart: fine on a laptop, silent data loss in a rescheduled container.- The store is an injection surface. Anything a tool writes into a namespace is replayed into prompts on later threads, so a poisoned document retrieved once can persist as a “fact”. The patterns in indirect prompt injection in RAG pipelines apply to memory writes too.
- Summaries drift. Each pass is a lossy rewrite of the last one, and numbers, names and negations go first.
- Embedding
dimsmust match the model you pass toindex; a mismatch fails at write time, not at configuration time. - None of this is a single “memory”. The MemGPT paper frames the context window as fast main memory and external storage as a separate tier. In a LangChain application, decide separately what to persist and what to include in the next model call.
For how memory sits alongside tools and control flow, see LangChain building blocks: chains, tools and agent control.
If history is present but the agent repeats a step or mishandles a tool response, follow the diagnostic guide to why LangChain agents loop, skip tools, or fail to parse.
Related across the network
- LlamaIndex vs LangChain: Which to Use for RAG — llamaindexhub.com
- CVE Roundup: AI/ML Infrastructure Vulnerabilities — Q1 2026 — ai-alert.org
- Agent Tool-Use Exfiltration: When Indirect Injection Does Damage — aisec.blog
- Measuring Prompt-Injection Robustness in Tool-Using Agents — aisecbench.com
- AI Sec Weekly: Friday, May 15, 2026 — aisecweekly.com
FAQ
can i still use conversationbuffermemory in langchain 1.x?
Yes, if you install langchain-classic and import it from langchain_classic.memory. The class still raises a deprecation warning, and that package schedules its removal for version 2.0.0 with create_agent named as the alternative. create_agent takes a checkpointer, not a memory object, so treat the class as a bridge for legacy chains during migration.
what is the difference between a checkpointer and a store in langchain?
A checkpointer saves one thread’s state after every step, keyed by thread_id, so the same conversation can resume. A store holds JSON documents under namespaces and keys that any thread can read, which is how a preference learned in one conversation reaches the next. An agent can use both: pass them as checkpointer= and store= to create_agent.
does langchain memory persist after a restart?
Only with a database-backed saver. InMemorySaver and InMemoryStore keep everything in process memory, so a restart or a rescheduled container wipes every thread. For persistence, use PostgresSaver and PostgresStore from langgraph-checkpoint-postgres, or the Redis or Oracle equivalents, and run the one-time setup() call before the first request reaches the agent.
how do i clear conversation memory in a langchain agent?
Return RemoveMessage(id=REMOVE_ALL_MESSAGES) from a node or middleware to wipe the messages in a thread’s state, or call checkpointer.delete_thread(thread_id) to drop every checkpoint stored for that thread. Starting a new thread_id also gives a clean history without deleting anything, which keeps the old conversation available for audit or time travel.
is langchain memory the same as langgraph memory?
In 1.x, yes. create_agent runs on LangGraph, so its short-term memory is a LangGraph checkpointer and its long-term memory is a LangGraph store. The same savers and stores work in a hand-written StateGraph. Only the pre-1.0 langchain.memory classes are a separate system, and they now ship in the langchain-classic package.