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 Alice might have: - Likes concise answers - Prefers Python Bob might have: - Likes detailed explanations - Prefers TypeScript 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, ) 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 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. 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 ─┘ 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" This tells the agent which memory file it should use. 2. User ID Our application provides: @dataclass(frozen=True) class UserContext: user_id: str For example: user-alice user-bob 3. Namespace The important part is: namespace=lambda current_runtime: ( current_runtime.context.user_id, ) This means: user-alice -> namespace ("user-alice",) user-bob -> namespace ("user-bob",) The same /preferences.md path can therefore exist independently for both users. Conceptually: Store │ ├── ("user-alice",) │ └── /preferences.md │ └── ("user-bob",) └── /preferences.md 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 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() 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 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.
This is a summary aggregated from Dev.to. Read the complete article on the original site:
Read full article at Dev.to