How to add memory to LangGraph Agents

How to add memory to LangGraph Agents

Himanshu Sangshetti • May 20, 2026

LangGraph is built around state. Each node in the graph reads the state, mutates it, and passes it on. Checkpointers persist that state. Threads scope it. The architecture is honest about what it is: a runtime for stateful agent workflows.

The gap is that the state and the threads are bound to the graph, not to the user. When the same user returns three weeks later in a new thread, their preferences and history start over. That is what an external memory layer is for, and Mem0's LangGraph integration is the most direct path through it.

What LangGraph is

LangGraph is a low-level orchestration framework and runtime for building long-running, stateful agents. It is built by the LangChain team and does not require LangChain itself. The two are usually paired (LangGraph for the flow, LangChain for the LLM and tool primitives) but the LangGraph runtime stands on its own.

The core abstractions are small. A StateGraph defines the agent's workflow. Nodes are Python functions that read state and return updates. Edges connect nodes. State is a TypedDict (or MessagesState for chat-shaped flows) that flows through the graph as nodes mutate it. Two special nodes, START and END, define the execution boundaries.

The framework is aimed at complex agent orchestration where the flow itself matters: branching, conditional handoffs, tool loops, human-in-the-loop checkpoints, and multi-agent coordination. For simpler one-shot agents LangChain ships prebuilt architectures. LangGraph is where the work goes when the agent's decision graph has multiple paths.

How LangGraph handles memory natively

LangGraph splits persistence into two layers. The split matters for what each one is and is not built for.

Checkpointers persist graph state. After every super-step, the runtime serializes a snapshot (channel values, next nodes to execute, metadata, timestamps, task info) and writes it through a BaseCheckpointSaver implementation. The shipped backends cover the usual range:

Threads scope checkpoints. A thread is a unique ID that ties a sequence of checkpoints together. Pass thread_id in the configurable portion of the config on every call and the runtime resumes from the latest checkpoint for that thread. New thread, new state.

Store is the second persistence hook, separate from checkpointers. The Store interface persists data across threads as namespaced tuples and supports semantic search via an embedding model. Where checkpointers hold the in-flight state of one workflow, the store holds long-lived information the application wants any future thread to see.

Both abstractions are well-designed primitives. They are also primitives, not products. The application owns what gets put into them.

Where LangGraph's built-in memory stops

The native persistence layer is well-shaped for one specific job: durable graph state for stateful, long-running agents. Outside that shape, the gap shows.

Mem0 is the integration LangGraph users reach for when the gap above is real. The wiring is shorter than the list of limits.

How to add Mem0 to LangGraph

The full setup is documented at docs.mem0.ai/integrations/langgraph. Install pulls LangGraph alongside the Mem0 client:

pip install langgraph langchain-openai mem0ai python-dotenv

The integration uses the standard MemoryClient and plugs into a normal StateGraph. The state carries the messages and the user identifier the memory layer scopes on:

from typing import Annotated, TypedDict, List

from langgraph.graph import StateGraph, START
from langgraph.graph.message import add_messages
from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
from mem0 import MemoryClient

llm = ChatOpenAI(model="gpt-5-mini")
mem0 = MemoryClient()

class State(TypedDict):
    messages: Annotated[List[HumanMessage | AIMessage], add_messages]
    mem0_user_id: str

def chatbot(state: State):
    messages = state["messages"]
    user_id = state["mem0_user_id"]
    memories = mem0.search(messages[-1].content, filters={"user_id": user_id})
    context = "Relevant information from previous conversations:\n"

for memory in memories["results"]:
        context += f"- {memory['memory']}\n"

system_message = SystemMessage(content=(
        "You are a helpful assistant. Use the context to personalize your responses.\n"
        + context
    ))

response = llm.invoke([system_message] + messages)

mem0.add(
        [{"role": "user", "content": messages[-1].content},
        {"role": "assistant", "content": response.content}],
        user_id=user_id,
    )

return {"messages": [response]}

Graph wiring is the usual LangGraph pattern:

graph = StateGraph(State)
graph.add_node("chatbot", chatbot)
graph.add_edge(START, "chatbot")
compiled_graph = graph.compile()

Three things change once the chatbot node owns its own memory:

The same pattern composes cleanly with multi-node graphs. A retrieval node can read memories before a planner node decides which tool to call. A reflection node can write a derived fact after an action runs. The state graph stays the place where flow lives. The memory layer stays the place where durable facts live.

Final Note

LangGraph gives every node a place to read and write state. The native persistence layer keeps that state alive across super-steps and threads. What it does not do is decide what is worth keeping past the run or scope facts to the user rather than the workflow. One Mem0 integration, a couple of node-level calls, and the graph picks up persistence keyed to who the user is rather than which run they came in on.

Mem0 is an intelligent, open-source memory layer designed for LLMs and AI agents to provide long-term, personalized, and context-aware interactions across sessions.