DEV Community

Syeed Talha
Syeed Talha

Posted on

Agent-Scoped Memory in Deep Agents: Sharing Memory Across Conversations

In the previous article, we looked at user-scoped memory, where each user gets their own private long-term memory.

But there is another useful pattern:

Agent-scoped memory.

Instead of giving every user a separate memory, we can give one shared memory to an entire agent.

This is useful when the memory represents something about the agent itself rather than about a specific user.

What is agent-scoped memory?

Agent-scoped memory means that all conversations using the same agent can access the same long-term memory.

For example, imagine you build a coding assistant.

The agent has a memory file containing:

Response style:
- Keep responses concise.
- Use code examples where possible.
Enter fullscreen mode Exit fullscreen mode

A user in one conversation tells the agent:

"Remember that I prefer detailed explanations."

The agent saves that preference.

Later, another conversation starts with a completely different thread.

Because the memory belongs to the agent, not the conversation, the second conversation can read the updated memory.

Conceptually:

                    Coding Agent
                         |
                 Shared Agent Memory
                         |
          +--------------+--------------+
          |              |              |
       Thread 1       Thread 2       Thread 3
Enter fullscreen mode Exit fullscreen mode

All three threads can access the same long-term memory.

Why do we need agent-scoped memory?

Without agent-scoped memory, information saved in one conversation may remain limited to that conversation.

For example:

Thread 1
User: Remember that the project uses PostgreSQL.
Agent: Saved.

Thread 2
User: What database does this project use?
Agent: I don't know.
Enter fullscreen mode Exit fullscreen mode

That is not very useful for an agent that should learn information about a project.

With agent-scoped memory:

Thread 1
      |
      v
Agent Memory
      ^
      |
Thread 2
Enter fullscreen mode Exit fullscreen mode

Thread 2 can access what Thread 1 saved.

This is particularly useful for information that should be shared across conversations, such as:

  • Project instructions
  • Coding conventions
  • Team preferences
  • Agent response style
  • Shared workflows
  • Project-specific knowledge

Agent-scoped vs user-scoped memory

This distinction is important.

User-scoped memory

Memory belongs to a particular user.

Agent
 |
 +-- Alice -> Alice's memory
 |
 +-- Bob   -> Bob's memory
Enter fullscreen mode Exit fullscreen mode

For example:

Alice prefers Python.

Bob should not automatically inherit that preference.

Agent-scoped memory

Memory belongs to the agent.

Agent
 |
 +-- Shared memory
       |
       +-- Thread 1
       +-- Thread 2
       +-- Thread 3
Enter fullscreen mode Exit fullscreen mode

For example:

This agent should always provide concise answers.

Every conversation using that agent can use this information.

How does Deep Agents implement it?

The key difference is surprisingly small.

Instead of using the current user's ID as the namespace:

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

we use a fixed agent ID:

namespace=lambda _runtime: (AGENT_ID,)
Enter fullscreen mode Exit fullscreen mode

For example:

AGENT_ID = "memory-demo-agent"
Enter fullscreen mode Exit fullscreen mode

The resulting store looks conceptually like this:

InMemoryStore
│
└── ("memory-demo-agent",)
    └── /AGENTS.md
Enter fullscreen mode Exit fullscreen mode

Every thread using this agent accesses that same namespace.

The thread ID still matters for conversation state, but it does not create a separate long-term memory namespace.

So:

Thread 1 ─┐
Thread 2 ─┼──> ("memory-demo-agent",) ──> AGENTS.md
Thread 3 ─┘
Enter fullscreen mode Exit fullscreen mode

This is the core idea behind agent-scoped memory.

A simple example

Suppose the agent starts with:

## Response style
- Keep responses concise.
- Use code examples where possible.
Enter fullscreen mode Exit fullscreen mode

Thread 1 tells it:

"Remember that I prefer detailed explanations."

The agent updates AGENTS.md.

Now Thread 2 asks:

"Explain how transformers work."

The agent reads the same memory and can apply the newly saved preference.

Notice something important:

Thread 1 and Thread 2 are different conversations.

They have different thread IDs:

"agent-memory-thread-1"
"agent-memory-thread-2"
Enter fullscreen mode Exit fullscreen mode

But they share:

("memory-demo-agent",)
Enter fullscreen mode Exit fullscreen mode

Therefore, they share the same long-term memory.

When should you use agent-scoped memory?

A simple rule is:

If the memory describes the agent, project, or shared environment, agent-scoped memory may be appropriate.

For example:

Good candidates:

"This project uses PostgreSQL."
"Always use FastAPI for the backend."
"Keep responses concise."
"The project uses Python 3.12."
Enter fullscreen mode Exit fullscreen mode

But be careful with user-specific information.

If Alice says:

"My favorite programming language is Python."

That probably belongs in Alice's user-scoped memory, not the shared agent memory.

Otherwise, Bob could inherit Alice's preference.

One important limitation

The example below uses:

InMemoryStore()
Enter fullscreen mode Exit fullscreen mode

This is useful for learning and testing, but the store is process-local.

If you stop the application and start it again, the memory will be gone.

For a real application, you would normally use a persistent LangGraph/LangSmith store so the agent's long-term memory survives process restarts.

Try it yourself

The following example demonstrates the complete idea.

It creates one agent with shared memory.

Thread 1 saves a new preference:

"I prefer detailed explanations."
Enter fullscreen mode Exit fullscreen mode

Then Thread 2 starts a separate conversation and reads the same agent memory.

Run it with:

uv run agent_scoped_memory.py
Enter fullscreen mode Exit fullscreen mode

Make sure NVIDIA_API_KEY is available in your environment or .env file.

import os

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


AGENT_ID = "memory-demo-agent"
MEMORY_PATH = "/memories/AGENTS.md"
STORE_MEMORY_PATH = "/AGENTS.md"


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 the shared memory file for this agent."""
    store.put(
        (AGENT_ID,),
        STORE_MEMORY_PATH,
        create_file_data(
            """## Response style
- Keep responses concise.
- Use code examples where possible.
"""
        ),
    )


def build_backend(store: InMemoryStore) -> CompositeBackend:
    """Route long-term memory to the namespace shared by this agent."""
    return CompositeBackend(
        default=StateBackend(),
        routes={
            "/memories/": StoreBackend(
                store=store,
                namespace=lambda _runtime: (AGENT_ID,),
            ),
        },
    )


def build_agent(store: InMemoryStore):
    return create_deep_agent(
        model=build_model(),
        memory=[MEMORY_PATH],
        backend=build_backend(store),
        store=store,
        name=AGENT_ID,
    )


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

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


def main() -> None:
    load_dotenv()

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

    print("Thread 1: save a new preference")

    try:
        print(
            invoke(
                agent,
                "agent-memory-thread-1",
                "Remember that I prefer detailed explanations. Update "
                f"{MEMORY_PATH} with this preference, then confirm what you saved.",
            )
        )
    except Timeout:
        print(
            "The NVIDIA model timed out. Try "
            "NVIDIA_MODEL=meta/llama-3.2-3b-instruct "
            "or increase NVIDIA_TIMEOUT_SECONDS."
        )

    print("\nThread 2: read the same agent memory")

    try:
        print(
            invoke(
                agent,
                "agent-memory-thread-2",
                "Explain how transformers work. Apply the response preferences "
                f"stored in {MEMORY_PATH}, and begin by naming the preference "
                "you found.",
            )
        )
    except Timeout:
        print(
            "The NVIDIA model timed out. 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

The easiest way to remember the difference is:

User-scoped memory
        ↓
Memory belongs to the USER

Agent-scoped memory
        ↓
Memory belongs to the AGENT
Enter fullscreen mode Exit fullscreen mode

So if you are building a multi-user application, ask yourself:

"Should this information follow the user, or should it be shared by everyone using this agent?"

If it follows the user, use user-scoped memory.

If it belongs to the shared agent or project, agent-scoped memory can be the better choice.

That small namespace decision determines who can see and use the long-term memory.

Top comments (0)