Module 11: Deep Agents
Long-term Memory and Pluggable Backends
Capsule overview
In Module 8 you built long-term memory from scratch: you created an InMemoryStore, defined namespaces, wrote the put/get/search logic, and wired the store into the graph by hand. It worked, but every new agent meant repeating the whole setup.
Deep Agents ships long-term memory as a built-in capability. Instead of building the infrastructure, you pick a backend — filesystem, LangGraph Store, or a composite of both — and the agent handles the rest: it decides what to remember, when to retrieve relevant memories, and how to apply them to the task at hand.
The difference isn't what you achieve — the result is the same: an agent that remembers across sessions. The difference is how much code you write to get there. In M8 it was ~80 lines of setup + integration. Here it's ~5 lines of configuration.
How memory works in Deep Agents
The memory cycle
When a Deep Agent has memory enabled, it runs an automatic cycle on every interaction:
1. RETRIEVE → When a task starts, search for relevant memories
2. EXECUTE → Use those memories as context during execution
3. STORE → When it finishes, save new facts, preferences, and patterns
Session 1: Session 2:
┌───────────────────────────┐ ┌───────────────────────────┐
│ User: "Find papers about │ │ User: "Find papers about │
│ RAG, I prefer arxiv │ │ AI safety" │
│ sources" │ │ │
│ │ │ RETRIEVE: "This user │
│ STORE: { │─────────→│ prefers arxiv and │
│ "source_pref": "arxiv", │ memory │ bullet_points format" │
│ "format": "bullets" │ │ │
│ } │ │ → Search arxiv first │
│ │ │ → Format as bullets │
└───────────────────────────┘ └───────────────────────────┘
There is no code to write for this cycle. It happens because you enabled memory with a backend.
Backend 1: Filesystem — local files
The simplest backend. It writes memories as JSON files on disk. No external dependencies, no database, no infrastructure setup.
Configuration
from dotenv import load_dotenv
load_dotenv()
from deep_agents import create_deep_agent
from deep_agents.memory import FilesystemMemoryBackend
memory_backend = FilesystemMemoryBackend(
base_path="./agent_memory",
max_memories=500,
)
agent = create_deep_agent(
"openai:gpt-4.1-mini",
tools=[],
name="assistant",
instructions="You are a research assistant. Remember the user's preferences.",
memory=memory_backend,
)
result = agent.run("I prefer reports in English, formatted as bullet points.")
print(result.output)
# Expected output (varies by model):
# Got it. I've recorded your preferences: reports in English, formatted as bullet points.
# I'll apply them in future research.
What gets saved to disk
After that run, the ./agent_memory directory contains:
agent_memory/
├── index.json ← Index of every memory
└── memories/
└── pref_001.json ← A single memory
import json
with open("./agent_memory/memories/pref_001.json", "r") as f:
memory = json.load(f)
print(json.dumps(memory, indent=2, ensure_ascii=False))
# Expected output (approximate structure):
# {
# "id": "pref_001",
# "type": "preference",
# "content": "The user prefers reports in English, formatted as bullet points",
# "created_at": "2025-06-15T10:30:00Z",
# "relevance_tags": ["format", "language", "reporting"],
# "access_count": 0
# }
Retrieval in the next session
from dotenv import load_dotenv
load_dotenv()
from deep_agents import create_deep_agent
from deep_agents.memory import FilesystemMemoryBackend
memory_backend = FilesystemMemoryBackend(
base_path="./agent_memory",
max_memories=500,
)
agent = create_deep_agent(
"openai:gpt-4.1-mini",
tools=[],
name="assistant",
instructions="You are a research assistant. Use the user's memories.",
memory=memory_backend,
)
result = agent.run("Give me a summary of the state of AI agents")
print(result.output)
# Expected output (varies by model):
# Here's the summary in bullet-point format, the way you prefer it:
#
# • AI agents have evolved from simple chatbots into autonomous systems...
# • The main frameworks include LangGraph, CrewAI, AutoGen...
# • The trend is toward specialized agents coordinated by supervisors...
The agent didn't need you to repeat the preference. It pulled it automatically from the filesystem backend.
When to use the filesystem backend
- ✅ Local development and quick prototypes
- ✅ Agents that run on a single machine
- ✅ You don't want to set up a database
- ✅ You need to inspect memories by hand (they're readable JSON)
- ❌ Multiple instances of the agent (no locking)
- ❌ Production with high concurrency
- ❌ You need advanced semantic search
Backend 2: LangGraph Store — familiar and scalable
If you already used InMemoryStore or PostgresStore in Module 8, this backend will feel familiar. It uses the same LangGraph Store infrastructure, except Deep Agents handles it for you.
With InMemoryStore (development)
from dotenv import load_dotenv
load_dotenv()
from deep_agents import create_deep_agent
from deep_agents.memory import LangGraphStoreBackend
from langgraph.store.memory import InMemoryStore
store = InMemoryStore()
memory_backend = LangGraphStoreBackend(
store=store,
namespace_prefix=("agents", "research-assistant"),
)
agent = create_deep_agent(
"openai:gpt-4.1-mini",
tools=[],
name="assistant",
instructions="You are a research assistant.",
memory=memory_backend,
)
result = agent.run("Always include a methodology section in my reports.")
print(result.output)
# Expected output (varies by model):
# Noted. I'll include a methodology section in all your reports.
Verify the memory landed in the Store
from langgraph.store.memory import InMemoryStore
store = InMemoryStore()
from deep_agents.memory import LangGraphStoreBackend
backend = LangGraphStoreBackend(
store=store,
namespace_prefix=("agents", "research-assistant"),
)
backend.save({"type": "preference", "content": "Include methodology in reports"})
items = store.search(("agents", "research-assistant", "memories"))
for item in items:
print(f"Key: {item.key}, Value: {item.value}")
# Expected output:
# Key: mem_001, Value: {'type': 'preference', 'content': 'Include methodology in reports', ...}
The data lives in the same Store you already know. Namespaces follow the same hierarchical logic from M8.
With PostgresStore (production)
from dotenv import load_dotenv
load_dotenv()
from deep_agents import create_deep_agent
from deep_agents.memory import LangGraphStoreBackend
from langgraph.store.postgres import PostgresStore
store = PostgresStore(
conn_string="postgresql://user:pass@localhost:5432/agents_db",
)
memory_backend = LangGraphStoreBackend(
store=store,
namespace_prefix=("agents", "research-assistant"),
)
agent = create_deep_agent(
"openai:gpt-4.1",
tools=[],
name="assistant",
instructions="You are a research assistant.",
memory=memory_backend,
)
print(f"Backend: {type(store).__name__}")
print(f"Namespace prefix: {memory_backend.namespace_prefix}")
# Expected output:
# Backend: PostgresStore
# Namespace prefix: ('agents', 'research-assistant')
Going from InMemoryStore to PostgresStore is a one-line change. The API is identical. That's what makes the Store backend powerful for production.
When to use the LangGraph Store backend
- ✅ You already use LangGraph Store in your stack
- ✅ You need real persistence (PostgresStore)
- ✅ Multiple agents share the same Store
- ✅ You want consistency with the LangGraph ecosystem
- ❌ You don't want to install PostgreSQL just for memory
- ❌ You need quick manual inspection (files are more readable than DB rows)
Backend 3: Composite — the best of both worlds
The composite backend combines multiple backends. The most common case: filesystem for fast local access + PostgresStore for durability and syncing.
Configuration
from dotenv import load_dotenv
load_dotenv()
from deep_agents import create_deep_agent
from deep_agents.memory import (
CompositeMemoryBackend,
FilesystemMemoryBackend,
LangGraphStoreBackend,
)
from langgraph.store.postgres import PostgresStore
filesystem = FilesystemMemoryBackend(
base_path="./agent_memory",
max_memories=500,
)
pg_store = PostgresStore(
conn_string="postgresql://user:pass@localhost:5432/agents_db",
)
langgraph_store = LangGraphStoreBackend(
store=pg_store,
namespace_prefix=("agents", "research-assistant"),
)
memory_backend = CompositeMemoryBackend(
backends=[filesystem, langgraph_store],
read_strategy="first_available",
write_strategy="all",
)
agent = create_deep_agent(
"openai:gpt-4.1",
tools=[],
name="assistant",
instructions="You are a research assistant.",
memory=memory_backend,
)
print(f"Read strategy: {memory_backend.read_strategy}")
print(f"Write strategy: {memory_backend.write_strategy}")
print(f"Backends: {len(memory_backend.backends)}")
# Expected output:
# Read strategy: first_available
# Write strategy: all
# Backends: 2
Read and write strategies
Read strategies:
┌────────────────────┬──────────────────────────────────────────┐
│ first_available │ Reads from the first backend that has it │
│ │ (filesystem first = faster) │
├────────────────────┼──────────────────────────────────────────┤
│ merge_all │ Reads from all backends and merges │
│ │ (more complete, slower) │
└────────────────────┴──────────────────────────────────────────┘
Write strategies:
┌────────────────────┬──────────────────────────────────────────┐
│ all │ Writes to every backend │
│ │ (maximum durability) │
├────────────────────┼──────────────────────────────────────────┤
│ primary_only │ Writes only to the first backend │
│ │ (faster, you sync manually) │
└────────────────────┴──────────────────────────────────────────┘
When to use the composite backend
- ✅ Local development that needs to sync with production
- ✅ You want redundancy (if one backend fails, the other still has the data)
- ✅ Local performance (filesystem) + remote durability (Postgres)
- ❌ Simple agents that don't justify the complexity
- ❌ Quick prototypes where filesystem alone is enough
How the agent decides what to remember
Deep Agents doesn't save everything. It has internal criteria for deciding what's worth persisting.
Memory categories
┌─────────────────────┬────────────────────────────────┬───────────────────────┐
│ Category │ Example │ Save priority │
├─────────────────────┼────────────────────────────────┼───────────────────────┤
│ Explicit │ "I prefer academic sources" │ High │
│ preferences │ "Format as bullet points" │ │
├─────────────────────┼────────────────────────────────┼───────────────────────┤
│ Observed │ Always asks to verify data │ Medium │
│ patterns │ Tends to research AI topics │ │
├─────────────────────┼────────────────────────────────┼───────────────────────┤
│ User │ "Works in fintech" │ Medium │
│ facts │ "Their project is called Atlas"│ │
├─────────────────────┼────────────────────────────────┼───────────────────────┤
│ Results from │ "We researched RAG in May" │ Low │
│ previous tasks │ "The report on X is ready" │ │
└─────────────────────┴────────────────────────────────┴───────────────────────┘
What does NOT get saved
- ❌ Ephemeral data from a single interaction ("search for X" → result → the full result isn't stored)
- ❌ Information the agent can fetch with tools (it doesn't memorize public APIs)
- ❌ Execution context (which node ran, how many tokens it used)
Controlling what gets remembered
You can steer memory behavior through instructions:
from dotenv import load_dotenv
load_dotenv()
from deep_agents import create_deep_agent
from deep_agents.memory import FilesystemMemoryBackend
memory = FilesystemMemoryBackend(base_path="./memory")
agent = create_deep_agent(
"openai:gpt-4.1-mini",
tools=[],
name="assistant",
instructions=(
"You are a research assistant. "
"ALWAYS remember: formatting preferences, favorite sources, "
"and the user's topics of interest. "
"NEVER remember: sensitive personal data, passwords, "
"or specific financial information."
),
memory=memory,
)
result = agent.run("My name is Carlos, I work in fintech, and my API key is sk-abc123.")
print(result.output)
# Expected output (varies by model):
# Hi Carlos. I've recorded that you work in fintech.
# For security reasons, I don't store API keys or credentials.
Instructions guide the model on what to memorize. They are not a cryptographic guarantee — they're a hint for the LLM.
Comparison: M8 by hand vs Deep Agents memory
This is the central contrast of the capsule. In M8 you built everything step by step. Here you configure a backend.
M8: long-term memory by hand (~80 lines of setup)
from dotenv import load_dotenv
load_dotenv()
from langgraph.store.memory import InMemoryStore
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import MemorySaver
from langchain_openai import ChatOpenAI
from typing import TypedDict, Annotated
import operator
store = InMemoryStore()
checkpointer = MemorySaver()
class State(TypedDict):
messages: Annotated[list, operator.add]
user_id: str
def load_memories(state, *, store):
user_id = state["user_id"]
items = store.search(("users", user_id, "preferences"))
memories = [f"{item.key}: {item.value['value']}" for item in items]
system_msg = f"User preferences: {', '.join(memories)}" if memories else ""
return {"messages": [{"role": "system", "content": system_msg}]} if system_msg else {}
def save_memories(state, *, store):
user_id = state["user_id"]
last_msg = state["messages"][-1]
if "prefer" in last_msg.get("content", "").lower():
content = last_msg["content"]
store.put(
("users", user_id, "preferences"),
f"pref_{len(store.search(('users', user_id, 'preferences')))}",
{"value": content}
)
return {}
def respond(state):
model = ChatOpenAI(model="gpt-4.1-mini")
response = model.invoke(state["messages"])
return {"messages": [{"role": "assistant", "content": response.content}]}
builder = StateGraph(State)
builder.add_node("load_memories", load_memories)
builder.add_node("respond", respond)
builder.add_node("save_memories", save_memories)
builder.add_edge(START, "load_memories")
builder.add_edge("load_memories", "respond")
builder.add_edge("respond", "save_memories")
builder.add_edge("save_memories", END)
graph = builder.compile(checkpointer=checkpointer, store=store)
result = graph.invoke(
{"messages": [{"role": "user", "content": "I prefer arxiv sources"}], "user_id": "mike"},
config={"configurable": {"thread_id": "session-1"}}
)
print(f"Nodes in the graph: 3")
print(f"Lines of code: ~45 (imports not counted)")
print(f"Control over what gets saved: total")
# Expected output:
# Nodes in the graph: 3
# Lines of code: ~45 (imports not counted)
# Control over what gets saved: total
You decided everything: when to load memories, what to save, how to detect preferences, where to put them in the Store.
Deep Agents: memory configured (~5 lines of setup)
from dotenv import load_dotenv
load_dotenv()
from deep_agents import create_deep_agent
from deep_agents.memory import FilesystemMemoryBackend
agent = create_deep_agent(
"openai:gpt-4.1-mini",
tools=[],
name="assistant",
instructions="You are a research assistant. Remember the user's preferences.",
memory=FilesystemMemoryBackend(base_path="./memory"),
)
result = agent.run("I prefer arxiv sources")
print(f"Lines of memory configuration: 1")
print(f"Control over what gets saved: delegated to the framework")
# Expected output:
# Lines of memory configuration: 1
# Control over what gets saved: delegated to the framework
Comparison table
| Aspect | M8 by hand | Deep Agents Memory |
|---|---|---|
| Lines of code | ~45-80 for the full setup | ~5 (backend configuration) |
| Control | Total — you define what, when, where | Partial — the framework decides, you steer with instructions |
| Preference detection | Manual logic (if "prefer" in msg) | Automatic (the LLM decides) |
| Organization | You design the namespaces | The framework organizes by category |
| Backend | You choose and integrate it by hand | You pick it, the framework integrates it |
| Debugging | You can inspect every step | You inspect the backend, not the process |
| Best for | Production with specific requirements | Prototypes, autonomous tasks |
The rule: if you need control over exactly what gets remembered and how it's structured, use M8. If you want it to "just work," use Deep Agents memory.
Memory management: capacity and forgetting
Capacity limits
from deep_agents.memory import FilesystemMemoryBackend
memory = FilesystemMemoryBackend(
base_path="./memory",
max_memories=500,
max_memory_age_days=90,
)
print(f"Max memories: {memory.max_memories}")
print(f"Max age: {memory.max_memory_age_days} days")
# Expected output:
# Max memories: 500
# Max age: 90 days
When max_memories is reached, the backend applies an eviction policy:
Eviction policy (default: LRU + relevance):
1. Computes score = usage_frequency × recency × relevance
2. The lowest-scoring memories get deleted first
3. Explicit preferences get a score boost (they stick around longer)
Example:
"Prefers arxiv" → high score (preference + used 15 times)
"Researched RAG in May" → medium score (referenced 3 times)
"Asked for a short summary" → low score (mentioned once, 80 days ago)
→ "Asked for a short summary" gets deleted first
Clearing memories by hand
from deep_agents.memory import FilesystemMemoryBackend
memory = FilesystemMemoryBackend(base_path="./memory")
all_memories = memory.list_all()
print(f"Memories before: {len(all_memories)}")
memory.forget("pref_001")
print(f"Memories after forget: {len(memory.list_all())}")
memory.clear_all()
print(f"Memories after clear: {len(memory.list_all())}")
# Expected output:
# Memories before: 3
# Memories after forget: 2
# Memories after clear: 0
When to clear memories
- ✅ The user asks you to "forget my preferences"
- ✅ You switch projects and the previous memories no longer apply
- ✅ Testing: start every test with clean memory
- ❌ Clearing automatically between sessions (that defeats the purpose of long-term memory)
Picking a backend: a decision tree
Is this a prototype or local development?
└─ YES → FilesystemMemoryBackend
└─ NO ↓
Do you already use LangGraph Store in your stack?
└─ YES → LangGraphStoreBackend (with your existing store)
└─ NO ↓
Do you need real persistence (survives restarts)?
└─ YES → LangGraphStoreBackend + PostgresStore
└─ NO → FilesystemMemoryBackend
Do you need fast local access + remote durability?
└─ YES → CompositeMemoryBackend (filesystem + Postgres)
└─ NO → A single backend is enough
Quick summary
| Backend | Setup | Persistence | Scalability | Best for |
|---|---|---|---|---|
| Filesystem | 1 line | Local disk | One agent | Development, prototypes |
| LangGraph Store (InMemory) | 3 lines | In memory (lost on exit) | Testing | Tests, demos |
| LangGraph Store (Postgres) | 3 lines | Database | Multi-agent | Production |
| Composite | 5-8 lines | Multiple | Configurable | Dev + Prod, redundancy |
Troubleshooting
1. The agent remembers nothing between sessions
Cause: you're using InMemoryStore as the backend. It's gone when the process ends.
Fix: use FilesystemMemoryBackend, or LangGraphStoreBackend with PostgresStore:
from deep_agents.memory import FilesystemMemoryBackend
memory = FilesystemMemoryBackend(base_path="./memory")
2. Memories get saved but never retrieved
Cause: the agent's instructions never mention that it should use previous memories.
Fix: be explicit in the instructions:
agent = create_deep_agent(
"openai:gpt-4.1-mini",
tools=[],
name="assistant",
instructions=(
"You are a research assistant. "
"ALWAYS check your memories before answering. "
"Apply any user preferences you have on record."
),
memory=memory_backend,
)
3. The memory directory grows without bound
Cause: you never configured max_memories or max_memory_age_days.
Fix: set explicit limits:
memory = FilesystemMemoryBackend(
base_path="./memory",
max_memories=200,
max_memory_age_days=60,
)
4. The composite backend is slow on reads
Cause: you're using read_strategy="merge_all", which queries every backend.
Fix: switch to "first_available" if you don't need merging:
composite = CompositeMemoryBackend(
backends=[filesystem, langgraph_store],
read_strategy="first_available",
write_strategy="all",
)
5. The agent saves sensitive information
Cause: you never told the agent what NOT to remember.
Fix: add explicit restrictions to instructions:
instructions=(
"NEVER remember: passwords, API keys, tokens, "
"card numbers, or medical information."
)
Exercises
Exercise 1: Basic filesystem backend
Create an agent with FilesystemMemoryBackend. On the first run, tell it "I prefer reports in table format with at most 3 sources." End the script. Run a second script with the same backend path and ask for "Give me a report on cloud computing." Check that it applies your preferences.
See solution
from dotenv import load_dotenv
load_dotenv()
from deep_agents import create_deep_agent
from deep_agents.memory import FilesystemMemoryBackend
memory = FilesystemMemoryBackend(base_path="./memory_ex1")
agent = create_deep_agent(
"openai:gpt-4.1-mini",
tools=[],
name="assistant",
instructions="You are a research assistant. Remember the user's preferences.",
memory=memory,
)
result = agent.run("I prefer reports in table format with at most 3 sources.")
print("Session 1:", result.output)
all_mems = memory.list_all()
print(f"Memories saved: {len(all_mems)}")
for m in all_mems:
print(f" - {m['type']}: {m['content'][:60]}...")
# Expected output:
# Session 1: Got it. Your preferences have been recorded...
# Memories saved: 1
# - preference: The user prefers reports in table format with at most 3 ...
Second script:
from dotenv import load_dotenv
load_dotenv()
from deep_agents import create_deep_agent
from deep_agents.memory import FilesystemMemoryBackend
memory = FilesystemMemoryBackend(base_path="./memory_ex1")
agent = create_deep_agent(
"openai:gpt-4.1-mini",
tools=[],
name="assistant",
instructions="You are a research assistant. Apply any preferences you remember.",
memory=memory,
)
result = agent.run("Give me a report on cloud computing.")
print("Session 2:", result.output)
# Expected output (varies by model):
# Session 2: Here's your report in table format (3 sources max):
# | Aspect | Detail | Source |
# |---------|---------|--------|
# ...
Exercise 2: LangGraph Store backend with namespaces
Create an agent that uses LangGraphStoreBackend with InMemoryStore. Set the namespace prefix to ("project", "my-app"). Save 3 different preferences. Then use store.search() directly to verify the memories landed in the right namespace.
See solution
from dotenv import load_dotenv
load_dotenv()
from deep_agents import create_deep_agent
from deep_agents.memory import LangGraphStoreBackend
from langgraph.store.memory import InMemoryStore
store = InMemoryStore()
backend = LangGraphStoreBackend(
store=store,
namespace_prefix=("project", "my-app"),
)
agent = create_deep_agent(
"openai:gpt-4.1-mini",
tools=[],
name="assistant",
instructions="You are an assistant. Remember every preference the user tells you.",
memory=backend,
)
preferences = [
"I prefer Python code with type hints",
"Always include unit tests in your suggestions",
"Use Google-style docstrings",
]
for pref in preferences:
result = agent.run(pref)
print(f"Saved: {pref[:50]}...")
items = store.search(("project", "my-app", "memories"))
print(f"\nMemories in store: {len(items)}")
for item in items:
print(f" Namespace: {item.namespace}")
print(f" Key: {item.key}")
print(f" Content: {item.value.get('content', 'N/A')[:60]}...")
# Expected output:
# Saved: I prefer Python code with type hints...
# Saved: Always include unit tests in your suggestions...
# Saved: Use Google-style docstrings...
#
# Memories in store: 3
# Namespace: ('project', 'my-app', 'memories')
# Key: mem_001
# Content: The user prefers Python code with type hints...
# ...
Exercise 3: Composite backend with strategies
Implement a CompositeMemoryBackend with filesystem and an InMemoryStore. Use write_strategy="all" and read_strategy="first_available". Save a preference and verify it exists in both backends. Then delete the filesystem memory and check that the agent still retrieves it from the Store.
See solution
from dotenv import load_dotenv
load_dotenv()
from deep_agents.memory import (
CompositeMemoryBackend,
FilesystemMemoryBackend,
LangGraphStoreBackend,
)
from langgraph.store.memory import InMemoryStore
filesystem = FilesystemMemoryBackend(base_path="./memory_ex3")
store = InMemoryStore()
langgraph_backend = LangGraphStoreBackend(
store=store,
namespace_prefix=("test", "composite"),
)
composite = CompositeMemoryBackend(
backends=[filesystem, langgraph_backend],
read_strategy="first_available",
write_strategy="all",
)
composite.save({"type": "preference", "content": "Prefers markdown format"})
fs_memories = filesystem.list_all()
store_memories = store.search(("test", "composite", "memories"))
print(f"Filesystem: {len(fs_memories)} memories")
print(f"Store: {len(store_memories)} memories")
filesystem.clear_all()
print(f"\nAfter clearing the filesystem:")
print(f"Filesystem: {len(filesystem.list_all())} memories")
retrieved = composite.retrieve("format")
print(f"Composite still retrieves: {retrieved[0]['content'][:50]}...")
# Expected output:
# Filesystem: 1 memories
# Store: 1 memories
#
# After clearing the filesystem:
# Filesystem: 0 memories
# Composite still retrieves: Prefers markdown format...
Exercise 4: Controlling what gets remembered
Create an agent with explicit instructions about what to remember and what not to. Send 5 messages: 2 with valid preferences, 1 with sensitive data (a fake API key), 1 with an operational instruction, and 1 conversational. Check how many memories were saved and that no sensitive data made it in.
See solution
from dotenv import load_dotenv
load_dotenv()
from deep_agents import create_deep_agent
from deep_agents.memory import FilesystemMemoryBackend
memory = FilesystemMemoryBackend(base_path="./memory_ex4")
agent = create_deep_agent(
"openai:gpt-4.1-mini",
tools=[],
name="assistant",
instructions=(
"You are an assistant. "
"REMEMBER: formatting preferences, topics of interest, preferred tools. "
"NEVER remember: API keys, passwords, tokens, financial data."
),
memory=memory,
)
messages = [
"I'd rather you use Python 3.12 with asyncio",
"I'm very interested in MLOps topics",
"My OpenAI API key is sk-fake123456789",
"Look up information about Docker best practices",
"Thanks, nice work",
]
for msg in messages:
result = agent.run(msg)
print(f"Sent: {msg[:50]}...")
all_mems = memory.list_all()
print(f"\nMemories saved: {len(all_mems)}")
for m in all_mems:
content = m.get("content", "")
has_sensitive = "sk-" in content or "api key" in content.lower()
print(f" - [{m['type']}] {content[:60]}... {'⚠️ SENSITIVE' if has_sensitive else '✅ OK'}")
# Expected output:
# Sent: I'd rather you use Python 3.12 with asyncio...
# Sent: I'm very interested in MLOps topics...
# Sent: My OpenAI API key is sk-fake123456789...
# Sent: Look up information about Docker best practices...
# Sent: Thanks, nice work...
#
# Memories saved: 2
# - [preference] The user prefers Python 3.12 with asyncio... ✅ OK
# - [interest] The user is interested in MLOps topics... ✅ OK
Exercise 5: Memory with limits and eviction
Create a FilesystemMemoryBackend with max_memories=3. Save 5 memories one after another. Verify that only 3 survive and that the most relevant ones are the ones left.
See solution
from deep_agents.memory import FilesystemMemoryBackend
memory = FilesystemMemoryBackend(
base_path="./memory_ex5",
max_memories=3,
)
memories_to_save = [
{"type": "preference", "content": "Prefers Python"},
{"type": "observation", "content": "Asked about the weather once"},
{"type": "preference", "content": "Wants table format"},
{"type": "observation", "content": "Said hello"},
{"type": "preference", "content": "Always include academic sources"},
]
for m in memories_to_save:
memory.save(m)
print(f"Saved: {m['content'][:40]}... (total: {len(memory.list_all())})")
final = memory.list_all()
print(f"\nFinal memories: {len(final)}")
for m in final:
print(f" - [{m['type']}] {m['content']}")
# Expected output:
# Saved: Prefers Python... (total: 1)
# Saved: Asked about the weather once... (total: 2)
# Saved: Wants table format... (total: 3)
# Saved: Said hello... (total: 3)
# Saved: Always include academic sources... (total: 3)
#
# Final memories: 3
# - [preference] Prefers Python
# - [preference] Wants table format
# - [preference] Always include academic sources
Explicit preferences score higher than generic observations, so they survive eviction.
Exercise 6: Migrating from M8 by hand to Deep Agents memory (Advanced)
You have an InMemoryStore with M8 data (3 preferences in the namespace ("users", "mike", "preferences")). Create a LangGraphStoreBackend pointing at that same store and namespace. Verify that the Deep Agent can read the memories you saved by hand back in M8.
See solution
from dotenv import load_dotenv
load_dotenv()
from langgraph.store.memory import InMemoryStore
store = InMemoryStore()
store.put(("users", "mike", "preferences"), "sources", {"value": "academic", "content": "Prefers academic sources"})
store.put(("users", "mike", "preferences"), "format", {"value": "bullet_points", "content": "Prefers bullet points"})
store.put(("users", "mike", "preferences"), "language", {"value": "en", "content": "Prefers content in English"})
items = store.search(("users", "mike", "preferences"))
print(f"Existing M8 memories: {len(items)}")
for item in items:
print(f" {item.key}: {item.value}")
from deep_agents.memory import LangGraphStoreBackend
backend = LangGraphStoreBackend(
store=store,
namespace_prefix=("users", "mike"),
legacy_namespace=("users", "mike", "preferences"),
)
memories = backend.retrieve("user preferences")
print(f"\nMemories retrieved by Deep Agents: {len(memories)}")
for m in memories:
print(f" - {m.get('content', m.get('value', 'N/A'))}")
from deep_agents import create_deep_agent
agent = create_deep_agent(
"openai:gpt-4.1-mini",
tools=[],
name="assistant",
instructions="Apply any user preferences you remember.",
memory=backend,
)
result = agent.run("What preferences do you have on record about me?")
print(f"\nResponse: {result.output}")
# Expected output:
# Existing M8 memories: 3
# sources: {'value': 'academic', 'content': 'Prefers academic sources'}
# format: {'value': 'bullet_points', 'content': 'Prefers bullet points'}
# language: {'value': 'en', 'content': 'Prefers content in English'}
#
# Memories retrieved by Deep Agents: 3
# - Prefers academic sources
# - Prefers bullet points
# - Prefers content in English
#
# Response: I have the following preferences on record:
# • Academic sources as the priority
# • Bullet-point format
# • Content in English
The key point: if you use the same store, moving from M8 by hand to Deep Agents memory is compatible.
Summary
- Deep Agents ships long-term memory as a built-in capability — you pick a backend, and the agent decides what to remember, when to retrieve, and how to apply memories automatically
- Three backends available: Filesystem (simple, local JSON files), LangGraph Store (familiar if you come from M8, scalable with PostgresStore), and Composite (combines multiple backends for redundancy)
- The agent categorizes memories into explicit preferences (high priority), observed patterns (medium), user facts (medium), and previous results (low). You can steer this with instructions, but you can't control it 100%
- Comparison with M8: in M8 you designed the entire memory logic (~80 lines). In Deep Agents, you configure a backend (~5 lines). The trade-off: M8 gives you total control, Deep Agents gives you convenience with partial control
- Memory management includes limits (
max_memories,max_memory_age_days), automatic eviction by relevance, and manual cleanup (forget,clear_all) - Decision tree: filesystem for development, LangGraph Store with PostgresStore for production, composite when you need both
Next capsule: Deep Agents CLI — how to run autonomous agents straight from the terminal. It's a productivity tool, not a demo.
Additional resources
- Deep Agents — Memory Configuration — Official documentation for memory backends and configuration
- LangGraph Memory Store — Documentation for InMemoryStore and PostgresStore
- LangGraph Cross-thread Persistence — Memory across sessions in native LangGraph
- PostgresStore Guide — Setting up PostgresStore for production
- Cognitive Architectures for Language Agents — Paper on memory in agents: types, organization, and retrieval
Module 11 — LangChain & LangGraph: From Chains to Agents