> ## Documentation Index
> Fetch the complete documentation index at: https://docs.remem.online/llms.txt
> Use this file to discover all available pages before exploring further.

# LangGraph Agent with Persistent Memory Using Remem

> Integrate Remem with LangGraph to give your agent cross-session memory that persists across sessions, deployments, and LLM switches — in under 10 minutes.

LangGraph is excellent at managing state within a single session, but that state disappears the moment the session ends. Remem fills that gap by giving your LangGraph agent a persistent memory layer that survives restarts, redeployments, and even swapping out the underlying LLM. The integration adds just two nodes to your existing graph.

## Overview

LangGraph agents are stateful by design — but that state resets every session. Remem adds cross-session memory that persists across sessions, deployments, and LLM switches.

The pattern is simple: add two nodes to your graph.

```
load_memory → your_agent → save_memory
```

`load_memory` runs before your agent responds, fetching relevant memories and injecting them into state. `save_memory` runs after, storing what the user just said. Your agent node itself needs no changes — it just reads `memory_context` from state.

## Prerequisites

Install the required packages:

```bash theme={null}
pip install remem-py langgraph langchain-openai python-dotenv
```

Create a `.env` file with your API keys:

```bash theme={null}
REMEM_API_KEY=rm_live_xxx
OPENAI_API_KEY=sk-xxx
```

## The Pattern

The three-node structure keeps memory concerns completely separate from your agent logic:

| Node          | Runs         | What it does                                                  |
| ------------- | ------------ | ------------------------------------------------------------- |
| `load_memory` | Before agent | Fetches relevant memories, writes `memory_context` to state   |
| `agent`       | Main logic   | Reads `memory_context` from state, calls LLM, appends reply   |
| `save_memory` | After agent  | Reads last human message from state, calls `remem.remember()` |

## Steps

<Steps>
  <Step title="Define AgentState">
    Add `memory_context` to your `TypedDict`. This field carries memories from `load_memory` into the `agent` node without any direct coupling between the two.

    ```python theme={null}
    from typing import TypedDict, Annotated
    from langgraph.graph.message import add_messages

    class AgentState(TypedDict):
        messages:       Annotated[list, add_messages]
        user_id:        str
        memory_context: str   # injected by load_memory, used by agent
    ```
  </Step>

  <Step title="Create the load_memory node">
    This node runs first. It fetches memories relevant to the user's latest message using `AsyncRememClient.recall()` and writes them into state as a formatted string.

    ```python theme={null}
    from remem import AsyncRememClient

    remem = AsyncRememClient(api_key=os.getenv("REMEM_API_KEY"))
    AGENT_ID = "langgraph_agent"

    async def load_memory(state: AgentState) -> AgentState:
        last_message = state["messages"][-1].content

        memories = await remem.recall(
            query=last_message,
            user_id=state["user_id"],
            agent_id=AGENT_ID,
            top_k=5,
            min_score=0.0,
        )

        if memories:
            context = "\n".join(f"- {m.content}" for m in memories)
        else:
            context = "No previous memories for this user."

        return {**state, "memory_context": context}
    ```
  </Step>

  <Step title="Create the agent node">
    The agent node reads `memory_context` from state and injects it into the system prompt. No changes needed to your core agent logic — memory is just another string in the prompt.

    ```python theme={null}
    from langchain_openai import ChatOpenAI
    from langchain_core.messages import SystemMessage

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

    async def agent(state: AgentState) -> AgentState:
        system = SystemMessage(content=f"""You are a helpful assistant.

    What you already know about this user:
    {state['memory_context']}

    Use this context to give personalised responses.
    Never ask for information you already know.""")

        response = await llm.ainvoke([system] + state["messages"])

        return {**state, "messages": [response]}
    ```
  </Step>

  <Step title="Create the save_memory node">
    After the agent responds, save the user's last message as an episodic memory. This node always returns state unchanged — its only job is the side effect of writing to Remem.

    ```python theme={null}
    from langchain_core.messages import HumanMessage

    async def save_memory(state: AgentState) -> AgentState:
        human_messages = [
            m for m in state["messages"]
            if isinstance(m, HumanMessage)
        ]

        if human_messages:
            await remem.remember(
                content=human_messages[-1].content,
                user_id=state["user_id"],
                agent_id=AGENT_ID,
                memory_type="episodic",
            )

        return state
    ```
  </Step>

  <Step title="Wire the graph">
    Connect the three nodes in order, set `load_memory` as the entry point, and compile:

    ```python theme={null}
    from langgraph.graph import StateGraph, END

    builder = StateGraph(AgentState)

    builder.add_node("load_memory", load_memory)
    builder.add_node("agent",       agent)
    builder.add_node("save_memory", save_memory)

    builder.set_entry_point("load_memory")
    builder.add_edge("load_memory", "agent")
    builder.add_edge("agent",       "save_memory")
    builder.add_edge("save_memory", END)

    graph = builder.compile()
    ```
  </Step>

  <Step title="Run the graph">
    Invoke the graph with a `HumanMessage`, a `user_id`, and an empty `memory_context`. LangGraph fills `memory_context` during execution via the `load_memory` node.

    ```python theme={null}
    import asyncio
    from langchain_core.messages import HumanMessage

    async def chat(user_id: str, message: str) -> str:
        result = await graph.ainvoke({
            "messages":       [HumanMessage(content=message)],
            "user_id":        user_id,
            "memory_context": "",
        })
        return result["messages"][-1].content

    asyncio.run(chat("user_victor", "Hi, I'm Victor. I build AI agents in Lagos."))
    ```
  </Step>
</Steps>

## Complete Example

Here is the full working implementation in a single file:

```python theme={null}
import os
from typing import TypedDict, Annotated
from dotenv import load_dotenv

from langgraph.graph import StateGraph, END
from langgraph.graph.message import add_messages
from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage, HumanMessage
from remem import AsyncRememClient

load_dotenv()

remem = AsyncRememClient(api_key=os.getenv("REMEM_API_KEY"))
llm   = ChatOpenAI(model="gpt-4o-mini")

AGENT_ID = "langgraph_agent"


# ── State ─────────────────────────────────────────────────────────

class AgentState(TypedDict):
    messages:       Annotated[list, add_messages]
    user_id:        str
    memory_context: str   # injected by load_memory, used by agent


# ── Nodes ─────────────────────────────────────────────────────────

async def load_memory(state: AgentState) -> AgentState:
    """
    Runs before the agent responds.
    Fetches memories relevant to the user's latest message
    and injects them into state as memory_context.
    """
    last_message = state["messages"][-1].content

    memories = await remem.recall(
        query=last_message,
        user_id=state["user_id"],
        agent_id=AGENT_ID,
        top_k=5,
        min_score=0.0,
    )

    if memories:
        context = "\n".join(f"- {m.content}" for m in memories)
    else:
        context = "No previous memories for this user."

    return {**state, "memory_context": context}


async def agent(state: AgentState) -> AgentState:
    """
    The main agent node.
    Uses memory_context from load_memory in its system prompt.
    """
    system = SystemMessage(content=f"""You are a helpful assistant.

What you already know about this user:
{state['memory_context']}

Use this context to give personalised responses.
Never ask for information you already know.""")

    response = await llm.ainvoke([system] + state["messages"])

    return {**state, "messages": [response]}


async def save_memory(state: AgentState) -> AgentState:
    """
    Runs after the agent responds.
    Saves the user's latest message as a memory.
    """
    human_messages = [
        m for m in state["messages"]
        if isinstance(m, HumanMessage)
    ]

    if human_messages:
        await remem.remember(
            content=human_messages[-1].content,
            user_id=state["user_id"],
            agent_id=AGENT_ID,
            memory_type="episodic",
        )

    return state


# ── Graph ─────────────────────────────────────────────────────────

builder = StateGraph(AgentState)

builder.add_node("load_memory", load_memory)
builder.add_node("agent",       agent)
builder.add_node("save_memory", save_memory)

builder.set_entry_point("load_memory")
builder.add_edge("load_memory", "agent")
builder.add_edge("agent",       "save_memory")
builder.add_edge("save_memory", END)

graph = builder.compile()


# ── Run ───────────────────────────────────────────────────────────

async def chat(user_id: str, message: str) -> str:
    result = await graph.ainvoke({
        "messages":       [HumanMessage(content=message)],
        "user_id":        user_id,
        "memory_context": "",
    })
    return result["messages"][-1].content


# Test across sessions
import asyncio

async def main():
    USER = "user_victor"

    # Session 1
    print(await chat(USER, "Hi, I'm Victor. I build AI agents in Lagos."))
    print(await chat(USER, "I prefer short technical answers."))

    # Session 2 — agent remembers Victor
    print(await chat(USER, "What do you know about me?"))
    # "You're Victor, you build AI agents in Lagos,
    #  and you prefer short technical answers."

asyncio.run(main())
```

## `context()` vs `recall()` in LangGraph

Both methods retrieve memories, but they serve different purposes in a LangGraph workflow:

|                  | `recall()`                           | `context()`                        |
| ---------------- | ------------------------------------ | ---------------------------------- |
| **Requires**     | A query string                       | Only `user_id` + `agent_id`        |
| **Ranking**      | Semantic similarity to query         | Importance + recency               |
| **Best for**     | When user has already sent a message | Session start before first message |
| **Use in graph** | `load_memory` node (mid-session)     | Session initialisation node        |

Use this pattern to handle both cases in `load_memory`:

```python theme={null}
async def load_memory(state: AgentState) -> AgentState:
    """
    Use context() when starting a fresh session with no message yet.
    Use recall() when the user has already sent a message.
    """
    messages = state["messages"]

    if messages:
        # User has sent a message — use semantic search
        last_message = messages[-1].content
        memories = await remem.recall(
            query=last_message,
            user_id=state["user_id"],
            agent_id=AGENT_ID,
            top_k=5,
        )
    else:
        # Session just started — load by importance + recency
        result = await remem.context(
            user_id=state["user_id"],
            agent_id=AGENT_ID,
            top_k=10,
        )
        memories = result.memories

    context = "\n".join(f"- {m.content}" for m in memories) if memories else "No previous memories."
    return {**state, "memory_context": context}
```

## Tips

<AccordionGroup>
  <Accordion title="Handle first-time users gracefully">
    The first time a user interacts, `recall()` returns an empty list. Handle this explicitly so your agent does not crash or hallucinate prior context:

    ```python theme={null}
    memories = await remem.recall(query=message, user_id=user_id, agent_id=AGENT_ID)

    if memories:
        context = "\n".join(f"- {m.content}" for m in memories)
    else:
        context = "This is a new user. No previous context available."
    ```
  </Accordion>

  <Accordion title="Don't save every message">
    Not every message is worth remembering. Filter out low-signal messages before calling `save_memory` to keep your memory store clean:

    ```python theme={null}
    async def save_memory(state: AgentState) -> AgentState:
        skip = ["ok", "okay", "thanks", "sure", "hi", "hello", "bye", "got it"]
        human_messages = [m for m in state["messages"] if isinstance(m, HumanMessage)]

        if human_messages:
            content = human_messages[-1].content
            if not any(s in content.lower() for s in skip) and len(content) > 10:
                await remem.remember(
                    content=content,
                    user_id=state["user_id"],
                    agent_id=AGENT_ID,
                    memory_type="episodic",
                )

        return state
    ```
  </Accordion>

  <Accordion title="Store key facts as semantic memories">
    When your agent detects an important fact about the user, store it separately as semantic memory with high importance so it always surfaces in future sessions — regardless of what the user is currently talking about:

    ```python theme={null}
    # Inside your agent node, after detecting a key fact
    await remem.remember(
        "User is based in Lagos, Nigeria",
        user_id=state["user_id"],
        agent_id=AGENT_ID,
        memory_type="semantic",
        importance=0.9,
    )
    ```
  </Accordion>
</AccordionGroup>

## What's Next

<CardGroup cols={2}>
  <Card title="Search API" icon="magnifying-glass" href="/api-reference/search">
    Full reference for recall — semantic search across stored memories
  </Card>

  <Card title="Context API" icon="brain" href="/api-reference/context">
    Full reference for context — ranked retrieval by importance and recency
  </Card>

  <Card title="Error Handling" icon="triangle-exclamation" href="/guide/error-handling">
    Handle Remem errors gracefully so your LangGraph agent never crashes
  </Card>
</CardGroup>
