DEV Community

Syeed Talha
Syeed Talha

Posted on

User-Scoped Memory in Deep Agents: What It Is and Why You Need It

Imagine you build one AI agent and 1,000 people use it.

Alice tells the agent:

"I prefer Python examples and concise answers."

Later, Bob asks:

"How do I read a CSV file?"

Bob should not suddenly receive a short Python-focused answer just because Alice used the agent before.

This is exactly the problem user-scoped memory solves.

What is user-scoped memory?

User-scoped memory means giving each user their own separate long-term memory.

The same agent can be shared by many users, but the memory belonging to one user is isolated from everyone else.

For example:

                    One AI Agent
                         |
          +--------------+--------------+
          |              |              |
       Alice            Bob          Charlie
          |              |              |
     Alice's memory  Bob's memory  Charlie's memory
Enter fullscreen mode Exit fullscreen mode

Alice might have:

- Likes concise answers
- Prefers Python
Enter fullscreen mode Exit fullscreen mode

Bob might have:

- Likes detailed explanations
- Prefers TypeScript
Enter fullscreen mode Exit fullscreen mode

When Alice talks to the agent, the agent reads Alice's memory. When Bob talks to it, the agent reads Bob's memory.

Deep Agents implements this using a namespace. The namespace can be based on the user's ID, such as:

namespace=lambda current_runtime: (
    current_runtime.context.user_id,
)
Enter fullscreen mode Exit fullscreen mode

This makes the user's ID the boundary between their memories. LangChain's documentation describes the same concept as user-scoped memory: each user gets an isolated copy of the memory files.

Why do we need it?

Without user-scoped memory, you can accidentally create shared memory.

Suppose your application has:

User A -> Agent -> preferences.md
User B -> Agent -> preferences.md
Enter fullscreen mode Exit fullscreen mode

If both users access the same memory namespace, the agent could potentially read information written by the other user.

That can cause several problems.

1. Preferences can leak between users

Alice says:

"Always give me Python examples."

Bob could later receive Python examples even though he prefers TypeScript.

2. Private information can be exposed

A memory file might contain information about a user's previous interactions, preferences, or other personal context.

If that memory is shared, one user's information could become available to another user.

3. The agent's behavior becomes unpredictable

Imagine 100 users are constantly teaching the same agent different preferences:

User A: Be concise.
User B: Give detailed explanations.
User C: Use Python.
User D: Use TypeScript.
Enter fullscreen mode Exit fullscreen mode

If all of this goes into one shared memory, the agent has no reliable way to know whose preference it should follow.

User-scoped memory vs conversation memory

There is an important distinction.

Short-term memory is generally associated with a conversation/thread. It helps the agent remember what is happening in the current conversation.

Long-term memory survives across conversations.

User-scoped long-term memory adds another layer:

Conversation 1 ─┐
Conversation 2 ─┼──> Alice's long-term memory
Conversation 3 ─┘
Enter fullscreen mode Exit fullscreen mode

So Alice can start a completely new conversation and the agent can still know her saved preferences.

Deep Agents uses memory files for long-term memory and a backend/store to control where those files are stored.

How does Deep Agents implement it?

There are three important pieces in the example.

1. Memory path

MEMORY_PATH = "/memories/preferences.md"
Enter fullscreen mode Exit fullscreen mode

This tells the agent which memory file it should use.

2. User ID

Our application provides:

@dataclass(frozen=True)
class UserContext:
    user_id: str
Enter fullscreen mode Exit fullscreen mode

For example:

user-alice
user-bob
Enter fullscreen mode Exit fullscreen mode

3. Namespace

The important part is:

namespace=lambda current_runtime: (
    current_runtime.context.user_id,
)
Enter fullscreen mode Exit fullscreen mode

This means:

user-alice -> namespace ("user-alice",)
user-bob   -> namespace ("user-bob",)
Enter fullscreen mode Exit fullscreen mode

The same /preferences.md path can therefore exist independently for both users.

Conceptually:

Store
│
├── ("user-alice",)
│   └── /preferences.md
│
└── ("user-bob",)
    └── /preferences.md
Enter fullscreen mode Exit fullscreen mode

The file has the same name, but it belongs to a different namespace.

LangChain recommends user scope when memory should belong to individual users, and specifically notes that user A's preferences should not leak into user B's conversations.

One important security rule

A good default is:

If memory does not need to be shared, make it user-scoped.

Shared memory should be used deliberately. LangChain's documentation also warns that allowing one user to write memory that another user can read can create security problems, including malicious instructions being inserted into shared state.

For shared organizational policies, read-only memory is often more appropriate.

Try it yourself

The following example creates two users:

  • Alice prefers concise Python answers.
  • Bob prefers detailed TypeScript answers.

Both use the same agent, but their memories are isolated.

Install the required packages and set your NVIDIA_API_KEY, then save the code as user_scoped_memory.py and run:

uv run user_scoped_memory.py
Enter fullscreen mode Exit fullscreen mode

The example uses NVIDIA's model through ChatNVIDIA, while the memory isolation itself is handled by Deep Agents and InMemoryStore.

"""Runnable user-scoped long-term memory example.

Run with ``uv run user_scoped_memory.py`` after setting ``NVIDIA_API_KEY`` in
the environment or in a ``.env`` file.

Each invocation supplies a user ID through the graph context. The backend uses
that ID as the store namespace, so users can share one agent without sharing
their preference files.
"""

import os
from dataclasses import dataclass

from dotenv import load_dotenv
from langchain_nvidia_ai_endpoints import ChatNVIDIA
from langgraph.store.memory import InMemoryStore
from requests.exceptions import Timeout

from deepagents import create_deep_agent
from deepagents.backends import CompositeBackend, StateBackend, StoreBackend
from deepagents.backends.utils import create_file_data


MEMORY_PATH = "/memories/preferences.md"
STORE_MEMORY_PATH = "/preferences.md"


@dataclass(frozen=True)
class UserContext:
    user_id: str


def build_model():
    """Create the chat model used by the demo."""
    if not os.getenv("NVIDIA_API_KEY"):
        raise RuntimeError(
            "Set NVIDIA_API_KEY in the environment or a .env file before "
            "running this example."
        )

    model_name = os.getenv(
        "NVIDIA_MODEL",
        "nvidia:nvidia/nemotron-3-ultra-550b-a55b",
    ).removeprefix("nvidia:")

    timeout_seconds = int(
        os.getenv("NVIDIA_TIMEOUT_SECONDS", "180")
    )

    max_completion_tokens = int(
        os.getenv("NVIDIA_MAX_COMPLETION_TOKENS", "1024")
    )

    return ChatNVIDIA(
        model=model_name,
        timeout=timeout_seconds,
        max_completion_tokens=max_completion_tokens,
        model_kwargs={"parallel_tool_calls": False},
    )


def seed_memory(store: InMemoryStore) -> None:
    """Create an isolated preference file for each demo user."""
    preferences = {
        "user-alice": """## Preferences
- Likes concise bullet points.
- Prefers Python examples.
""",
        "user-bob": """## Preferences
- Likes detailed explanations.
- Prefers TypeScript examples.
""",
    }

    for user_id, content in preferences.items():
        store.put(
            (user_id,),
            STORE_MEMORY_PATH,
            create_file_data(content),
        )


def build_backend(store: InMemoryStore) -> CompositeBackend:
    """Route memory to the namespace belonging to the current user."""
    return CompositeBackend(
        default=StateBackend(),
        routes={
            "/memories/": StoreBackend(
                store=store,
                namespace=lambda current_runtime: (
                    current_runtime.context.user_id,
                ),
            ),
        },
    )


def build_agent(store: InMemoryStore):
    return create_deep_agent(
        model=build_model(),
        memory=[MEMORY_PATH],
        backend=build_backend(store),
        context_schema=UserContext,
        store=store,
        name="user-scoped-memory-demo",
    )


def invoke_for_user(
    agent,
    user_id: str,
    thread_id: str,
    prompt: str,
) -> str:
    result = agent.invoke(
        {"messages": [{"role": "user", "content": prompt}]},
        config={"configurable": {"thread_id": thread_id}},
        context=UserContext(user_id=user_id),
    )

    return result["messages"][-1].content


def main() -> None:
    load_dotenv()

    store = InMemoryStore()
    seed_memory(store)
    agent = build_agent(store)

    prompt = (
        "How do I read a CSV file? Use only the preferences stored in "
        f"{MEMORY_PATH}. State which language and response style you used."
    )

    print("Alice's isolated memory")

    try:
        print(
            invoke_for_user(
                agent,
                user_id="user-alice",
                thread_id="alice-memory-thread",
                prompt=prompt,
            )
        )
    except Timeout:
        print(
            "The NVIDIA model timed out. The memory store is configured "
            "correctly; try NVIDIA_MODEL=meta/llama-3.2-3b-instruct or "
            "increase NVIDIA_TIMEOUT_SECONDS."
        )

    print("\nBob's isolated memory")

    try:
        print(
            invoke_for_user(
                agent,
                user_id="user-bob",
                thread_id="bob-memory-thread",
                prompt=prompt,
            )
        )
    except Timeout:
        print(
            "The NVIDIA model timed out. The memory store is configured "
            "correctly; try NVIDIA_MODEL=meta/llama-3.2-3b-instruct or "
            "increase NVIDIA_TIMEOUT_SECONDS."
        )


if __name__ == "__main__":
    main()
Enter fullscreen mode Exit fullscreen mode

The key idea to remember

You do not need a separate agent for every user.

You can have:

One Agent
   |
   +-- User ID A -> Memory A
   |
   +-- User ID B -> Memory B
   |
   +-- User ID C -> Memory C
Enter fullscreen mode Exit fullscreen mode

The user ID becomes the memory boundary.

That is the core idea behind user-scoped memory in Deep Agents.

For production applications, InMemoryStore is only suitable for a simple demonstration. Deep Agents' documentation notes that a persistent/platform store should be used when deploying rather than relying on an in-memory store.

For the complete and current implementation details, see LangChain Deep Agents Memory documentation.

Top comments (0)