Miscellaneous

Miscellaneous

How to Build a Production AI Agent with LangGraph and Mem0

How to Build a Production AI Agent with LangGraph and Mem0

LangGraph has become a common orchestration layer for building multi-step agents. It helps AI engineers define stateful workflows, control tool calls, and manage error handling using node graphs.

However, most LangGraph examples remain essentially stateless. The agent sees only the current input and ephemeral context inside the graph. Once the session ends or the conversation window resets, the agent forgets everything. This is usually fine for demos, but it fails in production where users expect continuity.

Persistent memory is the missing layer. The agent needs a way to remember user preferences, past decisions, and long-running tasks across sessions and channels. Mem0 is designed to fill that gap while fitting naturally into LangGraph’s node model.

This post walks through a working LangGraph agent that starts stateless, then becomes memory-aware by integrating Mem0 at the retrieval and output nodes.

👉Get a free API key at app.mem0.ai to follow along (free tier, no credit card, includes all the add() and search() calls shown below).

The Core Memory Problem in Production Agents

In production, agents must behave more like software systems than chatbots. Users return days later, switch devices, and expect agents to remember what happened before.

Common memory problems include:

  • Loss of user preferences between sessions, such as tone, format, or tools

  • Repeated questions where users must restate the context each time

  • Broken workflows when agents forget prior steps or commitments

  • Inconsistent behavior when multiple channels share a user

LangGraph handles in-graph state, but not persistent state across runs. The typical pattern is to store conversation history in a database or vector store, then manually fetch and include it in the prompt. This quickly becomes complex, especially when multiple agents or services share memory.

Mem0 tackles this by providing an opinionated memory layer that is:

  • Session-aware, associating memories with user IDs and metadata

  • Model agnostic, working with different LLMs and frameworks

  • Query-oriented, returning relevant memories instead of raw logs

  • Simple to integrate, via a small client library and HTTP API

This aligns well with LangGraph nodes, where memory retrieval and update can be modeled as separate steps.

LangGraph Agent Architecture in Practice


Shows how a basic LangGraph agent passes state through nodes and where memory nodes fit naturally in the flow.

A typical LangGraph agent looks like a state machine. There is a graph definition that specifies:

  • A set of nodes, each representing a function or tool

  • Edges that define possible transitions between nodes

  • A state object passed through nodes during execution

For a conversational agent, a simple graph may have:

  • A router node that decides the next step

  • A llm node that calls the language model

  • A tool node that calls external APIs

Memory integration fits naturally into this pattern as:

  • A memory_retrieval node that enriches the state before the LLM call

  • A memory_update node that writes new memories after the LLM output

Mem0’s role is to back these two nodes with persistent storage and search. The graph remains the orchestration layer, while Mem0 becomes the memory backbone.

Stateless LangGraph Agent Baseline

Before introducing Mem0, it is useful to define a minimal LangGraph agent that has no persistent memory. The following example uses a simple conversational agent:

from typing import TypedDict, List
from langgraph.graph import StateGraph, END
from langchain_openai import ChatOpenAI

# Basic conversation state
class ConversationState(TypedDict):
    messages: List[dict]

llm = ChatOpenAI(model="gpt-4o-mini")

def llm_node(state: ConversationState) -> ConversationState:
    # Call the LLM with current messages only
    response = llm.invoke(state["messages"])
    return {
        "messages": state["messages"] + [{"role": "assistant", "content": response.content}]
    }

# Build a minimal graph
graph = StateGraph(ConversationState)

graph.add_node("llm", llm_node)
graph.set_entry_point("llm")
graph.add_edge("llm", END)

app = graph.compile()

if __name__ == "__main__":
    # Turn 1
    state = {"messages": [{"role": "user", "content": "Hi, my name is Alice."}]}
    result = app.invoke(state)
    print("Turn 1:", result["messages"][-1]["content"])

    # Turn 2
    state2 = {"messages": [
        {"role": "user", "content": "Hi, my name is Alice."},  # must repeat
        {"role": "assistant", "content": result["messages"][-1]["content"]},
        {"role": "user", "content": "What is my name?"}
    ]}
    result2 = app.invoke(state2)
    print("Turn 2:", result2["messages"][-1]["content"])
from typing import TypedDict, List
from langgraph.graph import StateGraph, END
from langchain_openai import ChatOpenAI

# Basic conversation state
class ConversationState(TypedDict):
    messages: List[dict]

llm = ChatOpenAI(model="gpt-4o-mini")

def llm_node(state: ConversationState) -> ConversationState:
    # Call the LLM with current messages only
    response = llm.invoke(state["messages"])
    return {
        "messages": state["messages"] + [{"role": "assistant", "content": response.content}]
    }

# Build a minimal graph
graph = StateGraph(ConversationState)

graph.add_node("llm", llm_node)
graph.set_entry_point("llm")
graph.add_edge("llm", END)

app = graph.compile()

if __name__ == "__main__":
    # Turn 1
    state = {"messages": [{"role": "user", "content": "Hi, my name is Alice."}]}
    result = app.invoke(state)
    print("Turn 1:", result["messages"][-1]["content"])

    # Turn 2
    state2 = {"messages": [
        {"role": "user", "content": "Hi, my name is Alice."},  # must repeat
        {"role": "assistant", "content": result["messages"][-1]["content"]},
        {"role": "user", "content": "What is my name?"}
    ]}
    result2 = app.invoke(state2)
    print("Turn 2:", result2["messages"][-1]["content"])
from typing import TypedDict, List
from langgraph.graph import StateGraph, END
from langchain_openai import ChatOpenAI

# Basic conversation state
class ConversationState(TypedDict):
    messages: List[dict]

llm = ChatOpenAI(model="gpt-4o-mini")

def llm_node(state: ConversationState) -> ConversationState:
    # Call the LLM with current messages only
    response = llm.invoke(state["messages"])
    return {
        "messages": state["messages"] + [{"role": "assistant", "content": response.content}]
    }

# Build a minimal graph
graph = StateGraph(ConversationState)

graph.add_node("llm", llm_node)
graph.set_entry_point("llm")
graph.add_edge("llm", END)

app = graph.compile()

if __name__ == "__main__":
    # Turn 1
    state = {"messages": [{"role": "user", "content": "Hi, my name is Alice."}]}
    result = app.invoke(state)
    print("Turn 1:", result["messages"][-1]["content"])

    # Turn 2
    state2 = {"messages": [
        {"role": "user", "content": "Hi, my name is Alice."},  # must repeat
        {"role": "assistant", "content": result["messages"][-1]["content"]},
        {"role": "user", "content": "What is my name?"}
    ]}
    result2 = app.invoke(state2)
    print("Turn 2:", result2["messages"][-1]["content"])

This agent has no memory beyond the messages in the current invocation. If the developer does not replay the full history, the model will forget earlier details. Even with history replay, nothing persists across sessions or services.

Production systems need more than replayed chat logs. They need structured, queryable memory that survives across runs. This is where Mem0 enters.

Introducing Mem0 as a Dedicated Memory Layer

Mem0 provides a simple API for:

  • add to store new memories

  • search to retrieve relevant memories

  • update and delete for maintenance

  • Scoping by user IDs or custom namespaces

The key difference from generic vector stores is that Mem0 treats memory as a first-class concept. It focuses on user-centric information and context rather than raw embeddings.

To integrate with LangGraph, two questions must be answered:

  1. When should the agent store a memory with mem0_client.add?

  2. When should the agent retrieve memory with mem0_client.search?

A common production pattern is:

  • At the retrieval node, query Mem0 for relevant memories based on the latest user message and known user ID.

  • At the output node, extract durable facts from the conversation and store them as memories.

The following sections implement this pattern in a working LangGraph graph.

Wiring Mem0 Into LangGraph Nodes

First, instantiate a Mem0 client. The Python SDK handles network calls to the Mem0 API or self-hosted deployment.

👉Wanna give it a try? Get a Mem0 API Key and try it yourself.

import os
from mem0 import MemoryClient

# Configure Mem0
MEM0_API_KEY = os.getenv("MEM0_API_KEY")

mem0_client = MemoryClient(api_key=MEM0_API_KEY)
import os
from mem0 import MemoryClient

# Configure Mem0
MEM0_API_KEY = os.getenv("MEM0_API_KEY")

mem0_client = MemoryClient(api_key=MEM0_API_KEY)
import os
from mem0 import MemoryClient

# Configure Mem0
MEM0_API_KEY = os.getenv("MEM0_API_KEY")

mem0_client = MemoryClient(api_key=MEM0_API_KEY)

Next, extend the LangGraph state to include fields for user ID and retrieved memories:

from typing import TypedDict, List, Optional

class ConversationState(TypedDict):
    user_id: str
    messages: List[dict]
    memories: Optional[List[dict]]  # results from mem0_client.search
from typing import TypedDict, List, Optional

class ConversationState(TypedDict):
    user_id: str
    messages: List[dict]
    memories: Optional[List[dict]]  # results from mem0_client.search
from typing import TypedDict, List, Optional

class ConversationState(TypedDict):
    user_id: str
    messages: List[dict]
    memories: Optional[List[dict]]  # results from mem0_client.search

Define a memory_retrieval node that uses Mem0 search:

def memory_retrieval_node(state: ConversationState) -> ConversationState:
    user_id = state.get("user_id")
    latest_user_message = next(
        (m["content"] for m in reversed(state["messages"]) if m["role"] == "user"),
        None,
    )

    if not user_id or not latest_user_message:
        return {**state, "memories": []}

    # Search Mem0 for relevant memories for this user
    search_results = mem0_client.search(
        user_id=user_id,
        query=latest_user_message,
        top_k=5,
    )

    # Store raw results in state, can be structured or simplified later
    return {**state, "memories": search_results}
def memory_retrieval_node(state: ConversationState) -> ConversationState:
    user_id = state.get("user_id")
    latest_user_message = next(
        (m["content"] for m in reversed(state["messages"]) if m["role"] == "user"),
        None,
    )

    if not user_id or not latest_user_message:
        return {**state, "memories": []}

    # Search Mem0 for relevant memories for this user
    search_results = mem0_client.search(
        user_id=user_id,
        query=latest_user_message,
        top_k=5,
    )

    # Store raw results in state, can be structured or simplified later
    return {**state, "memories": search_results}
def memory_retrieval_node(state: ConversationState) -> ConversationState:
    user_id = state.get("user_id")
    latest_user_message = next(
        (m["content"] for m in reversed(state["messages"]) if m["role"] == "user"),
        None,
    )

    if not user_id or not latest_user_message:
        return {**state, "memories": []}

    # Search Mem0 for relevant memories for this user
    search_results = mem0_client.search(
        user_id=user_id,
        query=latest_user_message,
        top_k=5,
    )

    # Store raw results in state, can be structured or simplified later
    return {**state, "memories": search_results}

Then, adapt the LLM node so that it includes retrieved memories in the prompt:

def llm_with_memory_node(state: ConversationState) -> ConversationState:
    system_prompt_parts = [
        "You are a helpful assistant.",
        "Use the following long-term memories about the user when relevant.",
    ]

    # Attach memories as context
    formatted_memories = []
    for mem in state.get("memories") or []:
        # Assuming Mem0 returns { 'content': '...', 'id': '...' , ...}
        formatted_memories.append(f"- {mem.get('content')}")

    if formatted_memories:
        system_prompt_parts.append("Known memories:")
        system_prompt_parts.extend(formatted_memories)

    system_prompt = "\n".join(system_prompt_parts)

    # Prepend system message with memories to current messages
    messages = [{"role": "system", "content": system_prompt}] + state["messages"]

    response = llm.invoke(messages)

    return {
        **state,
        "messages": state["messages"] + [
            {"role": "assistant", "content": response.content}
        ],
    }
def llm_with_memory_node(state: ConversationState) -> ConversationState:
    system_prompt_parts = [
        "You are a helpful assistant.",
        "Use the following long-term memories about the user when relevant.",
    ]

    # Attach memories as context
    formatted_memories = []
    for mem in state.get("memories") or []:
        # Assuming Mem0 returns { 'content': '...', 'id': '...' , ...}
        formatted_memories.append(f"- {mem.get('content')}")

    if formatted_memories:
        system_prompt_parts.append("Known memories:")
        system_prompt_parts.extend(formatted_memories)

    system_prompt = "\n".join(system_prompt_parts)

    # Prepend system message with memories to current messages
    messages = [{"role": "system", "content": system_prompt}] + state["messages"]

    response = llm.invoke(messages)

    return {
        **state,
        "messages": state["messages"] + [
            {"role": "assistant", "content": response.content}
        ],
    }
def llm_with_memory_node(state: ConversationState) -> ConversationState:
    system_prompt_parts = [
        "You are a helpful assistant.",
        "Use the following long-term memories about the user when relevant.",
    ]

    # Attach memories as context
    formatted_memories = []
    for mem in state.get("memories") or []:
        # Assuming Mem0 returns { 'content': '...', 'id': '...' , ...}
        formatted_memories.append(f"- {mem.get('content')}")

    if formatted_memories:
        system_prompt_parts.append("Known memories:")
        system_prompt_parts.extend(formatted_memories)

    system_prompt = "\n".join(system_prompt_parts)

    # Prepend system message with memories to current messages
    messages = [{"role": "system", "content": system_prompt}] + state["messages"]

    response = llm.invoke(messages)

    return {
        **state,
        "messages": state["messages"] + [
            {"role": "assistant", "content": response.content}
        ],
    }

Finally, implement a memory_update node that writes durable facts to Mem0 after the assistant responds:

def memory_update_node(state: ConversationState) -> ConversationState:
    user_id = state.get("user_id")
    if not user_id:
        return state

    # A simple pattern: extract candidate memories from the last assistant message
    last_assistant_message = next(
        (m["content"] for m in reversed(state["messages"]) if m["role"] == "assistant"),
        None,
    )
    last_user_message = next(
        (m["content"] for m in reversed(state["messages"]) if m["role"] == "user"),
        None,
    )

    if not last_assistant_message or not last_user_message:
        return state

    # For production, a separate LLM can classify what should become memory.
    # Here, store the user message as a preference or fact.
    mem0_client.add(
        user_id=user_id,
        text=last_user_message,
        metadata={"source": "langgraph_agent"},
    )

    return state
def memory_update_node(state: ConversationState) -> ConversationState:
    user_id = state.get("user_id")
    if not user_id:
        return state

    # A simple pattern: extract candidate memories from the last assistant message
    last_assistant_message = next(
        (m["content"] for m in reversed(state["messages"]) if m["role"] == "assistant"),
        None,
    )
    last_user_message = next(
        (m["content"] for m in reversed(state["messages"]) if m["role"] == "user"),
        None,
    )

    if not last_assistant_message or not last_user_message:
        return state

    # For production, a separate LLM can classify what should become memory.
    # Here, store the user message as a preference or fact.
    mem0_client.add(
        user_id=user_id,
        text=last_user_message,
        metadata={"source": "langgraph_agent"},
    )

    return state
def memory_update_node(state: ConversationState) -> ConversationState:
    user_id = state.get("user_id")
    if not user_id:
        return state

    # A simple pattern: extract candidate memories from the last assistant message
    last_assistant_message = next(
        (m["content"] for m in reversed(state["messages"]) if m["role"] == "assistant"),
        None,
    )
    last_user_message = next(
        (m["content"] for m in reversed(state["messages"]) if m["role"] == "user"),
        None,
    )

    if not last_assistant_message or not last_user_message:
        return state

    # For production, a separate LLM can classify what should become memory.
    # Here, store the user message as a preference or fact.
    mem0_client.add(
        user_id=user_id,
        text=last_user_message,
        metadata={"source": "langgraph_agent"},
    )

    return state

The graph now has three functional nodes: memory retrieval, LLM with memory, and memory update.

Full Working LangGraph Agent With Mem0


Visualizes the full production LangGraph flow where Mem0 powers dedicated retrieval and update nodes around the LLM.

Putting everything together yields a complete LangGraph agent that uses Mem0 to remember the user across turns and sessions.

import os
from typing import TypedDict, List, Optional

from langgraph.graph import StateGraph, END
from langchain_openai import ChatOpenAI
from mem0 import MemoryClient

# Configure LLM and Mem0
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
MEM0_API_KEY = os.getenv("MEM0_API_KEY")

llm = ChatOpenAI(model="gpt-4o-mini", api_key=OPENAI_API_KEY)
mem0_client = MemoryClient(api_key=MEM0_API_KEY)

class ConversationState(TypedDict):
    user_id: str
    messages: List[dict]
    memories: Optional[List[dict]]

def memory_retrieval_node(state: ConversationState) -> ConversationState:
    user_id = state.get("user_id")
    latest_user_message = next(
        (m["content"] for m in reversed(state["messages"]) if m["role"] == "user"),
        None,
    )

    if not user_id or not latest_user_message:
        return {**state, "memories": []}

    search_results = mem0_client.search(
        user_id=user_id,
        query=latest_user_message,
        top_k=5,
    )

    return {**state, "memories": search_results}

def llm_with_memory_node(state: ConversationState) -> ConversationState:
    system_prompt_parts = [
        "You are a helpful assistant.",
        "Use the following long-term memories about the user when relevant.",
    ]

    formatted_memories = []
    for mem in state.get("memories") or []:
        formatted_memories.append(f"- {mem.get('content')}")

    if formatted_memories:
        system_prompt_parts.append("Known memories:")
        system_prompt_parts.extend(formatted_memories)

    system_prompt = "\n".join(system_prompt_parts)

    messages = [{"role": "system", "content": system_prompt}] + state["messages"]

    response = llm.invoke(messages)

    return {
        **state,
        "messages": state["messages"] + [
            {"role": "assistant", "content": response.content}
        ],
    }

def memory_update_node(state: ConversationState) -> ConversationState:
    user_id = state.get("user_id")
    if not user_id:
        return state

    last_user_message = next(
        (m["content"] for m in reversed(state["messages"]) if m["role"] == "user"),
        None,
    )

    if not last_user_message:
        return state

    # Store user message as candidate memory
    mem0_client.add(
        user_id=user_id,
        text=last_user_message,
        metadata={"source": "langgraph_agent"},
    )

    return state

# Build LangGraph
graph = StateGraph(ConversationState)

graph.add_node("memory_retrieval", memory_retrieval_node)
graph.add_node("llm", llm_with_memory_node)
graph.add_node("memory_update", memory_update_node)

graph.set_entry_point("memory_retrieval")
graph.add_edge("memory_retrieval", "llm")
graph.add_edge("llm", "memory_update")
graph.add_edge("memory_update", END)

app = graph.compile()

if __name__ == "__main__":
    user_id = "user-123"

    # Turn 1: introduce a preference
    state1 = {
        "user_id": user_id,
        "messages": [{"role": "user", "content": "Hi, my name is Alice. I prefer concise bullet points."}],
        "memories": [],
    }
    result1 = app.invoke(state1)
    print("Turn 1:", result1["messages"][-1]["content"])

    # Turn 2: new session, agent should remember preferences via Mem0
    state2 = {
        "user_id": user_id,
        "messages": [{"role": "user", "content": "Can you summarize the benefits of LangGraph?"}],
        "memories": [],
    }
    result2 = app.invoke(state2)
    print("Turn 2:", result2["messages"][-1]["content"])

    # Turn 3: ask about identity
    state3 = {
        "user_id": user_id,
        "messages": [{"role": "user", "content": "What is my name and how do I like responses formatted?"}],
        "memories": [],
    }
    result3 = app.invoke(state3)
    print("Turn 3:", result3["messages"][-1]["content"])
import os
from typing import TypedDict, List, Optional

from langgraph.graph import StateGraph, END
from langchain_openai import ChatOpenAI
from mem0 import MemoryClient

# Configure LLM and Mem0
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
MEM0_API_KEY = os.getenv("MEM0_API_KEY")

llm = ChatOpenAI(model="gpt-4o-mini", api_key=OPENAI_API_KEY)
mem0_client = MemoryClient(api_key=MEM0_API_KEY)

class ConversationState(TypedDict):
    user_id: str
    messages: List[dict]
    memories: Optional[List[dict]]

def memory_retrieval_node(state: ConversationState) -> ConversationState:
    user_id = state.get("user_id")
    latest_user_message = next(
        (m["content"] for m in reversed(state["messages"]) if m["role"] == "user"),
        None,
    )

    if not user_id or not latest_user_message:
        return {**state, "memories": []}

    search_results = mem0_client.search(
        user_id=user_id,
        query=latest_user_message,
        top_k=5,
    )

    return {**state, "memories": search_results}

def llm_with_memory_node(state: ConversationState) -> ConversationState:
    system_prompt_parts = [
        "You are a helpful assistant.",
        "Use the following long-term memories about the user when relevant.",
    ]

    formatted_memories = []
    for mem in state.get("memories") or []:
        formatted_memories.append(f"- {mem.get('content')}")

    if formatted_memories:
        system_prompt_parts.append("Known memories:")
        system_prompt_parts.extend(formatted_memories)

    system_prompt = "\n".join(system_prompt_parts)

    messages = [{"role": "system", "content": system_prompt}] + state["messages"]

    response = llm.invoke(messages)

    return {
        **state,
        "messages": state["messages"] + [
            {"role": "assistant", "content": response.content}
        ],
    }

def memory_update_node(state: ConversationState) -> ConversationState:
    user_id = state.get("user_id")
    if not user_id:
        return state

    last_user_message = next(
        (m["content"] for m in reversed(state["messages"]) if m["role"] == "user"),
        None,
    )

    if not last_user_message:
        return state

    # Store user message as candidate memory
    mem0_client.add(
        user_id=user_id,
        text=last_user_message,
        metadata={"source": "langgraph_agent"},
    )

    return state

# Build LangGraph
graph = StateGraph(ConversationState)

graph.add_node("memory_retrieval", memory_retrieval_node)
graph.add_node("llm", llm_with_memory_node)
graph.add_node("memory_update", memory_update_node)

graph.set_entry_point("memory_retrieval")
graph.add_edge("memory_retrieval", "llm")
graph.add_edge("llm", "memory_update")
graph.add_edge("memory_update", END)

app = graph.compile()

if __name__ == "__main__":
    user_id = "user-123"

    # Turn 1: introduce a preference
    state1 = {
        "user_id": user_id,
        "messages": [{"role": "user", "content": "Hi, my name is Alice. I prefer concise bullet points."}],
        "memories": [],
    }
    result1 = app.invoke(state1)
    print("Turn 1:", result1["messages"][-1]["content"])

    # Turn 2: new session, agent should remember preferences via Mem0
    state2 = {
        "user_id": user_id,
        "messages": [{"role": "user", "content": "Can you summarize the benefits of LangGraph?"}],
        "memories": [],
    }
    result2 = app.invoke(state2)
    print("Turn 2:", result2["messages"][-1]["content"])

    # Turn 3: ask about identity
    state3 = {
        "user_id": user_id,
        "messages": [{"role": "user", "content": "What is my name and how do I like responses formatted?"}],
        "memories": [],
    }
    result3 = app.invoke(state3)
    print("Turn 3:", result3["messages"][-1]["content"])
import os
from typing import TypedDict, List, Optional

from langgraph.graph import StateGraph, END
from langchain_openai import ChatOpenAI
from mem0 import MemoryClient

# Configure LLM and Mem0
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
MEM0_API_KEY = os.getenv("MEM0_API_KEY")

llm = ChatOpenAI(model="gpt-4o-mini", api_key=OPENAI_API_KEY)
mem0_client = MemoryClient(api_key=MEM0_API_KEY)

class ConversationState(TypedDict):
    user_id: str
    messages: List[dict]
    memories: Optional[List[dict]]

def memory_retrieval_node(state: ConversationState) -> ConversationState:
    user_id = state.get("user_id")
    latest_user_message = next(
        (m["content"] for m in reversed(state["messages"]) if m["role"] == "user"),
        None,
    )

    if not user_id or not latest_user_message:
        return {**state, "memories": []}

    search_results = mem0_client.search(
        user_id=user_id,
        query=latest_user_message,
        top_k=5,
    )

    return {**state, "memories": search_results}

def llm_with_memory_node(state: ConversationState) -> ConversationState:
    system_prompt_parts = [
        "You are a helpful assistant.",
        "Use the following long-term memories about the user when relevant.",
    ]

    formatted_memories = []
    for mem in state.get("memories") or []:
        formatted_memories.append(f"- {mem.get('content')}")

    if formatted_memories:
        system_prompt_parts.append("Known memories:")
        system_prompt_parts.extend(formatted_memories)

    system_prompt = "\n".join(system_prompt_parts)

    messages = [{"role": "system", "content": system_prompt}] + state["messages"]

    response = llm.invoke(messages)

    return {
        **state,
        "messages": state["messages"] + [
            {"role": "assistant", "content": response.content}
        ],
    }

def memory_update_node(state: ConversationState) -> ConversationState:
    user_id = state.get("user_id")
    if not user_id:
        return state

    last_user_message = next(
        (m["content"] for m in reversed(state["messages"]) if m["role"] == "user"),
        None,
    )

    if not last_user_message:
        return state

    # Store user message as candidate memory
    mem0_client.add(
        user_id=user_id,
        text=last_user_message,
        metadata={"source": "langgraph_agent"},
    )

    return state

# Build LangGraph
graph = StateGraph(ConversationState)

graph.add_node("memory_retrieval", memory_retrieval_node)
graph.add_node("llm", llm_with_memory_node)
graph.add_node("memory_update", memory_update_node)

graph.set_entry_point("memory_retrieval")
graph.add_edge("memory_retrieval", "llm")
graph.add_edge("llm", "memory_update")
graph.add_edge("memory_update", END)

app = graph.compile()

if __name__ == "__main__":
    user_id = "user-123"

    # Turn 1: introduce a preference
    state1 = {
        "user_id": user_id,
        "messages": [{"role": "user", "content": "Hi, my name is Alice. I prefer concise bullet points."}],
        "memories": [],
    }
    result1 = app.invoke(state1)
    print("Turn 1:", result1["messages"][-1]["content"])

    # Turn 2: new session, agent should remember preferences via Mem0
    state2 = {
        "user_id": user_id,
        "messages": [{"role": "user", "content": "Can you summarize the benefits of LangGraph?"}],
        "memories": [],
    }
    result2 = app.invoke(state2)
    print("Turn 2:", result2["messages"][-1]["content"])

    # Turn 3: ask about identity
    state3 = {
        "user_id": user_id,
        "messages": [{"role": "user", "content": "What is my name and how do I like responses formatted?"}],
        "memories": [],
    }
    result3 = app.invoke(state3)
    print("Turn 3:", result3["messages"][-1]["content"])

On the first turn, the agent stores the user’s name and preference. On subsequent turns, the memory_retrieval_node fetches relevant memories from Mem0. The system prompt includes those memories so the LLM can respond consistently.

Across sessions, the agent remembers the user without relying on chat history replay or database queries scattered through the codebase.

Before And After State: Stateless vs Memory Aware


Compares a stateless LangGraph agent with one backed by Mem0 to make the before and after behavior clear.

The difference between a stateless agent and a Mem0-backed agent is visible in the behavior across turns.

Aspect

Stateless LangGraph agent

LangGraph agent with Mem0

User identity persistence

Lost unless manually replayed in history

Stored as memory keyed by user_id

Preferences across sessions

Must be restated every time

Retrieved via mem0_client.search

Prompt construction

Only current messages

Current messages plus enriched memories

Storage model

Raw chat logs or none

Structured long-term memory

Multi-channel consistency

Each channel is isolated without extra work

Shared memory across channels

Integration footprint

Single LLM node

Two extra nodes for retrieval and update

Control over what is stored

Ad hoc duplication in logs

Memory-specific filtering and metadata

In production, this shift has a material impact on user experience and system complexity. Mem0 becomes the single shared memory service behind multiple LangGraph apps and agents.

Where This Pattern Stops

The retrieval and update nodes shown above cover the core memory problem, but they are not a universal solution for all agent patterns.

Some limitations of the pattern include:

  • Memory selection is simplistic, storing entire user turns as memories. Production systems benefit from a classifier that decides what should be stored and how it should be structured.

  • All memories are treated equally. Certain memories, such as identities or long-term preferences, may need special handling and conflict resolution.

  • Retrieval uses a basic query based on the latest user message. Complex workflows may require different queries per node or per task, for example, planning nodes versus execution nodes.

  • The pattern assumes a single user ID per graph run. Multi-actor graphs, such as team agents, need distinct memory scopes and sometimes shared collaborative memory.

  • Context window limits still apply. Even with Mem0, the agent must select a small subset of relevant memories to keep prompts manageable and cost-efficient.

Mem0 solves the persistent memory layer and retrieval interface, but engineers still need to design memory schemas, selection strategies, and governance policies that match their domain.

How Mem0 Fits Into Production LangGraph Systems

In larger systems, Mem0 sits alongside LangGraph as a foundational service:

  • LangGraph orchestrates control flow, tools, and state transitions.

  • Mem0 provides long-term memory for users, tasks, and entities.

  • The LLM remains stateless, receiving context synthesized by LangGraph nodes.

Common production patterns where this integration is useful include:

  • Customer support agents who remember previous tickets, preferences, and resolutions.

  • Developer assistants that track repositories, projects, and user-specific configurations.

  • Multi-step workflows where commitments and partial outputs need to persist across days.

  • Cross-channel experiences where memory must be shared across web, mobile, and other platforms.

Mem0’s API maps cleanly onto LangGraph nodes. search is a retrieval node, add is an output node, and update or delete can be maintenance nodes. Different graphs can share the same Mem0 instance, which simplifies memory management across services.

Frequently Asked Questions

Q. What problem does Mem0 solve for LangGraph agents?

Mem0 provides persistent, queryable memory for agents that otherwise operate on transient state. For LangGraph, it turns a stateless graph into a system that can remember users, preferences, and facts across sessions and channels.

Q. How does Mem0 integrate with LangGraph in practice?

Mem0 is integrated as two types of nodes: a retrieval node that calls mem0_client.search before the LLM invocation and an update node that calls mem0_client.add after the assistant responds. The LangGraph state carries the user ID and retrieved memories so the LLM can use them in its prompt.

Q. When should an engineer store new memories with Mem0?

New memories should be stored when the conversation contains durable information that will matter later, such as user identities, preferences, or decisions. Many teams use a separate classifier or small LLM call to decide which parts of a conversation become memory rather than storing every message.

Q. Why not use plain chat history instead of Mem0?

Plain chat history grows without structure and becomes expensive to replay for every turn. It is also difficult to query for specific facts across channels or agents. Mem0 focuses on structured long-term memory, provides search and metadata, and avoids tight coupling between history and memory.

Q. How does this pattern behave across multiple sessions or devices?

As long as the same user ID is provided in the LangGraph state, Mem0 can retrieve memories regardless of session or device. Different LangGraph graphs or services can share the same Mem0 memory for that user, which creates consistent behavior across surfaces.

Q. What is the impact of Mem0 on latency and cost?

Mem0 adds a memory retrieval and update call to the agent workflow, which adds network overhead, but it can reduce token usage by avoiding full history replay and keeping prompts focused on relevant memories. Engineers can tune top_k and memory selection strategies to balance latency, cost, and recall quality.

Further Reading

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.

Get your free API Key here: app.mem0.ai or self-host mem0 from our open source github repository.

GET TLDR from:

Summarize

Website/Footer

Summarize

Website/Footer

Summarize

Website/Footer

Summarize

Website/Footer