Memory Isolation is a security pattern that treats agent memory as a strictly scoped, typed, and validated resource to prevent state leakage and unauthorized cross-user access in multi-agent systems. Treat memory as a scoped, typed resource — owner, retention, trust level, and validation rules checked on every write and every read.
Understanding Memory Isolation
LangGraph gives you memory. Persistence, checkpointing, thread-scoped state: you can store what an agent knows across invocations and retrieve it later. For a single-agent system, that may be enough. For a multi-agent system, it is where the design work starts.
The framework manages state. It does not decide what state one agent is allowed to know about another agent’s task, and it does not enforce that an analyst agent’s working context cannot bleed into a developer agent’s context on the next invocation. If one agent instance serves multiple users and memory is not strictly scoped, a leak is one conversation away: a support bot that stores “my password is X” from one user can surface it to a different user who later asks a broad question, if the backend has no isolation between them.
Implementing Memory Isolation
Memory Isolation defines memory type, owner, retention, trust level, write policy, read policy, and validation requirements before storing agent-generated content, and treats memory as a scoped resource rather than a shared pool.
flowchart TD
A[MemoryRecord] --> B{Memory type}
B -->|audit_record| C[Reject ordinary memory write]
B -->|user_preference| D{Allowed key and validated?}
D -->|no| E[Reject]
D -->|yes| F[Write]
B -->|long_term_knowledge or operational_memory| G{Validated?}
G -->|no| E
G -->|yes| F
B -->|session or task_state| F
F --> H[Read by memory type and owner id]
In LangGraph, memory access is scoped by a gateway that brackets the agent. load_context injects only the current tenant’s working memory into the prompt, and persist writes new memory back under the same tenant namespace — both keyed by tenant_id from state:
def load_context(state: AgentState) -> dict:
injected = gateway.inject_context("System context:", state["tenant_id"])
return {"messages": [SystemMessage(content=injected)]}
def persist(state: AgentState) -> dict:
manager.write_working_memory(state["tenant_id"], "last_summary", state["messages"][-1].content)
return {}
builder.add_edge(START, "load_context")
builder.add_edge("load_context", "agent")
builder.add_edge("agent", "persist")
builder.add_edge("persist", END)
Considerations for Agent Memory Isolation
Consider the following factors when you implement this pattern:
- What the framework does not give you. The framework usually does not know which user owns a memory item, which agent may read it, whether it was validated, how long it should live, or whether it is safe to promote into long-term memory. Left undefined, memory becomes an implicit shared datastore.
- Composite scoping key. The practical fix for cross-user leakage is a composite key, such as
tenant_id:user_id:thread_id:agent_id. - Excessive Agency and Memory. A candidate memory can carry a delayed prompt injection, granting the agent Excessive Permissions across sessions. It should pass the same input validation as fresh content before it is replayed into a future prompt.