Module 11: Deep Agents

Virtual Filesystem

Capsule overview

The virtual filesystem gives the agent tools to read, write, and edit files. Instead of stuffing all the information into the context window (expensive and capped at 128K-200K tokens), the agent writes outputs to files and reads only what it needs for the current step. That enables research of any size without blowing up costs or degrading quality. The direct analogy is with what you built in Module 8: there you configured checkpointers to persist state. Here, the framework hands you a full filesystem as a persistence abstraction — the agent decides what to save, where to save it, and when to read it.


The concept: context offloading

The context window problem

Imagine a research agent that searches for information across 10 sources. Each source produces ~2,000 tokens of results. Without a filesystem:

Turn 1: Search source 1 → 2,000 tokens in context
Turn 2: Search source 2 → 4,000 tokens in context
Turn 3: Search source 3 → 6,000 tokens in context
...
Turn 10: Search source 10 → 20,000 tokens in context

Context window at synthesis time: 20,000+ tokens of results + system prompt + history

Three problems:

  1. Cost: every turn pays for all the accumulated tokens. Turn 10 pays for 20,000 input tokens that are mostly earlier results it doesn't need
  2. Quality: models lose attention over long contexts. The results from source 1 are "far away" when the agent synthesizes on turn 10
  3. Hard limit: 128K tokens is a ceiling. With long sources or many sources, it simply won't fit

The solution: the filesystem as external memory

With a filesystem:

Turn 1: Search source 1 → write_file("research/source_01.md", results) → clean context
Turn 2: Search source 2 → write_file("research/source_02.md", results) → clean context
...
Turn 10: Search source 10 → write_file("research/source_10.md", results) → clean context

Turn 11 (synthesis):
  read_file("research/source_01.md")  ← reads only what it needs
  read_file("research/source_05.md")  ← picks the most relevant sources
  write_file("output/report.md", report)

The context window stays small on every turn. The agent has access to all the information through files, but only loads what's necessary. This is context offloading: moving data out of the context window (expensive, limited) and into the filesystem (cheap, unlimited).

Concrete impact on cost

Without a filesystem (10 sources, ~2K tokens each):
  Total input tokens across the run:
  2K + 4K + 6K + 8K + ... + 20K = ~110K tokens
  Approximate cost with GPT-4.1: ~$0.22

With a filesystem:
  Each turn: ~2K input tokens (just the current result + instructions)
  Synthesis turn: ~6K tokens (3 selected sources)
  Total: ~26K tokens
  Approximate cost with GPT-4.1: ~$0.05

Savings: ~75%

The numbers vary by model and task, but the pattern is consistent: the filesystem cuts costs significantly whenever data accumulates.


The filesystem tools

Deep Agents injects four filesystem tools automatically:

ls(path) — list files and directories

from dotenv import load_dotenv
load_dotenv()

from deep_agents import create_deep_agent

agent = create_deep_agent(
    "openai:gpt-4.1-mini",
    tools=[],
    name="Filesystem Demo",
    instructions=(
        "You are an agent that organizes information into files. "
        "Use the filesystem to store and read data."
    ),
)

result = agent.run(
    "Create a directory structure for a research project "
    "with research/, analysis/, and output/ folders. "
    "In each folder, create a README.md describing its purpose."
)

print("=== Files generated ===")
for path, content in sorted(result.files.items()):
    print(f"  {path} ({len(content)} chars)")
# Expected output (varies):
# === Files generated ===
#   analysis/README.md (89 chars)
#   output/README.md (76 chars)
#   research/README.md (112 chars)

The agent used write_file to create the structure. Under the hood, ls lets it check what already exists.

read_file(path) — read a file's content

The agent reads a specific file when it needs that information:

# Under the hood, the agent does:
# content = read_file("research/source_01.md")
# → Returns the file's content as a string

The key point: read_file loads the content into the context window only when the agent calls it. It isn't always present — the agent decides when it needs which information.

write_file(path, content) — create or overwrite a file

# Under the hood, the agent does:
# write_file("research/web_results.md", "# Web search results\n\n...")
# → Creates the file (or overwrites it if it already exists)

write_file replaces the file's entire content. If you need to preserve existing content and make targeted changes, use edit_file.

edit_file(path, edits) — targeted edits

# Under the hood, the agent does:
# edit_file("output/report.md", [
#     {"old": "## Conclusion\n\nPending.", "new": "## Conclusion\n\nThe analysis reveals..."}
# ])
# → Modifies only the specified section

edit_file is for surgical changes: updating a section of the report, fixing a data point, adding a paragraph. It doesn't rewrite the whole file.


How the agent uses the filesystem

Pattern: multi-source research

The most natural use of the filesystem is storing research results:

from dotenv import load_dotenv
load_dotenv()

from deep_agents import create_deep_agent
from langchain_community.tools import TavilySearchResults

web_search = TavilySearchResults(max_results=3)

agent = create_deep_agent(
    "openai:gpt-4.1",
    tools=[web_search],
    name="Filesystem Researcher",
    instructions=(
        "You are a researcher. For every topic:\n"
        "1. Plan with write_todos\n"
        "2. For each source, write the results to research/[source].md\n"
        "3. Analyze the results and write the analysis to analysis/synthesis.md\n"
        "4. Generate the final report in output/report.md\n\n"
        "IMPORTANT: do not accumulate results in your context. "
        "Write each result to a file immediately."
    ),
)

result = agent.run("Research the most popular AI agent frameworks in 2025")

print("=== File structure ===")
for path in sorted(result.files.keys()):
    content = result.files[path]
    lines = content.count("\n") + 1
    print(f"  {path} ({lines} lines, {len(content)} chars)")

print(f"\n=== Content of output/report.md (first 5 lines) ===")
if "output/report.md" in result.files:
    for line in result.files["output/report.md"].split("\n")[:5]:
        print(f"  {line}")
# Expected output (varies):
# === File structure ===
#   analysis/synthesis.md (45 lines, 2103 chars)
#   output/report.md (78 lines, 4521 chars)
#   research/academic_papers.md (32 lines, 1876 chars)
#   research/industry_tools.md (28 lines, 1654 chars)
#   research/web_search.md (35 lines, 1932 chars)
#
# === Content of output/report.md (first 5 lines) ===
#   # AI Agent Frameworks in 2025
#
#   ## Executive Summary
#
#   The AI agent ecosystem in 2025 has consolidated around...

The agent created an organized structure: sources in research/, analysis in analysis/, the final report in output/. Each step wrote to a separate file instead of piling up in the context.

Pattern: read selectively

Not everything the agent writes needs to be read for every step. The agent picks:

Step "synthesize":
  - Reads research/academic_papers.md  ← relevant to the synthesis
  - Reads research/industry_tools.md   ← relevant to the synthesis
  - Does NOT read research/web_search.md  ← already covered by the other files
  - Writes analysis/synthesis.md

Step "generate report":
  - Reads analysis/synthesis.md        ← needs the analysis
  - Does NOT read research/*           ← already synthesized
  - Writes output/report.md

That selective reading is what keeps the context window small. The agent doesn't load every file — only the ones it needs for the current step.


Files as working memory

The filesystem isn't just storage — it's the agent's working memory. While the context window is short-term memory (what the agent "has in mind" right now), the files are medium-term memory (what the agent "wrote in its notebook" and can look up).

The researcher analogy

A human researcher:
  - Reads a paper → takes notes in a notebook (write_file)
  - Reads another paper → more notes on another page (write_file)
  - Opens the notebook to the relevant page (read_file)
  - Writes a section of the thesis consulting only the notes needed
  - Doesn't try to hold everything in memory — that's what the notes are for

An agent with a filesystem:
  - Searches for information → writes to research/source_01.md
  - Searches for more information → writes to research/source_02.md
  - Reads only the file relevant to the current step
  - Generates the output by consulting specific files
  - Doesn't try to keep everything in the context window

Example: an agent that writes a multi-section report

from dotenv import load_dotenv
load_dotenv()

from deep_agents import create_deep_agent

agent = create_deep_agent(
    "openai:gpt-4.1",
    tools=[],
    name="Report Writer",
    instructions=(
        "You are a technical report writer. For every report:\n"
        "1. Plan the sections with write_todos\n"
        "2. Write each section as a separate file in sections/\n"
        "3. At the end, read all the sections and compose the final report in output/\n\n"
        "Each section must be self-contained: title, content, partial conclusion. "
        "The final report ties them together with transitions."
    ),
)

result = agent.run(
    "Write a technical report on the differences between "
    "microservices and monoliths for an engineering team"
)

print("=== Files generated ===")
for path in sorted(result.files.keys()):
    print(f"  {path}")

if "output/report.md" in result.files:
    report = result.files["output/report.md"]
    print(f"\n=== Final report: {len(report)} chars, {report.count(chr(10))+1} lines ===")
    for line in report.split("\n")[:10]:
        print(f"  {line}")
# Expected output (varies):
# === Files generated ===
#   output/report.md
#   sections/01-introduction.md
#   sections/02-monolithic-architecture.md
#   sections/03-microservices-architecture.md
#   sections/04-comparison.md
#   sections/05-recommendations.md
#
# === Final report: 5234 chars, 98 lines ===
#   # Microservices vs Monoliths: A Guide for Engineering Teams
#
#   ## Introduction
#
#   The decision between a monolithic architecture and microservices is...

The agent wrote each section as an independent file and then composed them into a final report. Without a filesystem, it would try to generate the whole report in a single step, which produces less structured results.


Organizing outputs: directory structure

A good agent organizes its files the way a good developer organizes code. These patterns emerge with the right instructions:

For research

workspace/
├── research/              ← Raw data from sources
│   ├── source_academic.md
│   ├── source_industry.md
│   └── source_news.md
├── analysis/              ← Intermediate processing
│   ├── comparison.md
│   └── synthesis.md
└── output/                ← Final deliverables
    └── report.md

For code generation

workspace/
├── specs/                 ← Specifications and requirements
│   └── requirements.md
├── src/                   ← Generated code
│   ├── main.py
│   ├── models.py
│   └── utils.py
├── tests/                 ← Generated tests
│   └── test_main.py
└── docs/                  ← Documentation
    └── README.md

For comparative analysis

workspace/
├── data/                  ← Data per compared entity
│   ├── option_a.md
│   ├── option_b.md
│   └── option_c.md
├── comparison/            ← Matrices and cross-analysis
│   └── comparison_matrix.md
└── output/                ← Final recommendation
    └── recommendation.md

The structure isn't magic — you define it in the instructions:

instructions = (
    "Organize your files into three folders:\n"
    "- research/ for raw data\n"
    "- analysis/ for intermediate processing\n"
    "- output/ for final deliverables\n"
    "Never write directly into output/ without first having files in research/ and analysis/."
)

Impact on cost and quality: a concrete comparison

Scenario: research across 5 sources

from dotenv import load_dotenv
load_dotenv()

from deep_agents import create_deep_agent
from langchain_community.tools import TavilySearchResults

web_search = TavilySearchResults(max_results=3)

agent_with_fs = create_deep_agent(
    "openai:gpt-4.1-mini",
    tools=[web_search],
    name="With Filesystem",
    instructions=(
        "Research the topic. Write EACH search result "
        "to a separate file in research/. "
        "At the end, read the relevant files and generate the report in output/report.md."
    ),
)

agent_without_fs = create_deep_agent(
    "openai:gpt-4.1-mini",
    tools=[web_search],
    name="Without Filesystem",
    instructions=(
        "Research the topic. Accumulate all the results in your context. "
        "Do NOT use files. Generate the report directly at the end."
    ),
)

task = "Research the main trends in AI engineering in 2025"

result_fs = agent_with_fs.run(task)
result_no_fs = agent_without_fs.run(task)

print("=== With filesystem ===")
print(f"  Files: {len(result_fs.files)}")
print(f"  Token usage: {result_fs.usage.total_tokens:,} tokens")
print(f"  Output length: {len(result_fs.output):,} chars")

print("\n=== Without filesystem ===")
print(f"  Files: {len(result_no_fs.files)}")
print(f"  Token usage: {result_no_fs.usage.total_tokens:,} tokens")
print(f"  Output length: {len(result_no_fs.output):,} chars")

if result_fs.usage.total_tokens < result_no_fs.usage.total_tokens:
    saving = (1 - result_fs.usage.total_tokens / result_no_fs.usage.total_tokens) * 100
    print(f"\nSavings with filesystem: {saving:.0f}%")
# Expected output (varies):
# === With filesystem ===
#   Files: 5
#   Token usage: 18,432 tokens
#   Output length: 3,847 chars
#
# === Without filesystem ===
#   Files: 0
#   Token usage: 31,205 tokens
#   Output length: 2,156 chars
#
# Savings with filesystem: 41%

Two visible benefits: fewer tokens consumed AND longer output (more complete). The filesystem lets the agent process more information without the penalty of a saturated context window.


Security: sandboxing the virtual filesystem

A fair question: if the agent can write files, can it write to your real filesystem?

No. The Deep Agents virtual filesystem is a sandbox:

Real filesystem:                    Virtual filesystem:
/home/user/                         workspace/          ← all the agent sees
├── documents/                      ├── research/
├── code/                           ├── analysis/
├── .ssh/         ← inaccessible    └── output/
└── .env          ← inaccessible

What the agent can do

  • ✅ Create files and directories inside the virtual workspace
  • ✅ Read files it created itself
  • ✅ Edit files inside the workspace
  • ✅ List the workspace's contents

What it can NOT do

  • ❌ Access files outside the virtual workspace
  • ❌ Read the system's environment variables
  • ❌ Run operating system commands
  • ❌ Access the network directly (only through the tools you give it)

Configuring the sandbox

from dotenv import load_dotenv
load_dotenv()

from deep_agents import create_deep_agent

agent = create_deep_agent(
    "openai:gpt-4.1-mini",
    tools=[],
    name="Sandboxed Agent",
    instructions="Write a test file.",
    filesystem_config={
        "max_file_size": 100_000,      # Max 100KB per file
        "max_total_size": 10_000_000,  # Max 10MB total
        "max_files": 50,               # Max 50 files
    },
)

result = agent.run("Create a file with a summary of generative AI")
print(f"Files: {list(result.files.keys())}")
print(f"Total size: {sum(len(c) for c in result.files.values()):,} chars")
# Expected output (varies):
# Files: ['output/summary.md']
# Total size: 1,243 chars

The limits prevent the agent from consuming excessive resources. If it tries to create 100 files when the limit is 50, the tool call fails with a clear error.


Connection with M8: the filesystem as a persistence abstraction

In Module 8 you built persistence with checkpointers:

# M8: Manual persistence with a checkpointer
checkpointer = MemorySaver()
graph = builder.compile(checkpointer=checkpointer)
result = graph.invoke(input, config={"configurable": {"thread_id": "session_1"}})

The virtual filesystem abstracts that at a higher level:

AspectM8 (Checkpointers)M11 (Virtual Filesystem)
What gets persistedThe graph's full stateThe files the agent decides to create
Who decides what to saveThe framework (saves everything)The agent (writes what it deems relevant)
GranularityPer checkpoint (full snapshot)Per file (logical unit)
AccessRestore the full stateRead individual files
Use caseCrash recovery, resume, time-travelWorking memory, context offloading

They aren't competitors — they're complementary. The checkpointer saves the agent's state (including the filesystem's files) for crash recovery. The filesystem is the interface the agent works with.


Troubleshooting

Problem 1: The agent doesn't use the filesystem

Symptom: The agent piles all the information into the context window instead of writing to files. Search results stay in the message history. Cause: The instructions aren't explicit about when to use the filesystem. Fix: Be prescriptive:

instructions = (
    "RULE: after each search, ALWAYS write the results to a file. "
    "NEVER accumulate more than 1 search result in your context without writing it to disk. "
    "Use write_file('research/[descriptive_name].md', content)."
)

Problem 2: Files that are too large

Symptom: The agent writes one 50K-character file with all the research in it, losing the benefits of context offloading. Cause: The agent got no instructions about file granularity. Fix: Define the expected structure:

instructions = (
    "Each file must cover ONE source or ONE aspect of the topic. "
    "If a file exceeds 3,000 characters, split it into smaller files. "
    "Use directories to organize: research/ for sources, analysis/ for processing."
)

Problem 3: The agent doesn't read earlier files when synthesizing

Symptom: The final report is shallow because the agent generated the synthesis from memory instead of reading the research files. Cause: The agent wasn't instructed to consult its files before synthesizing. Fix:

instructions = (
    "Before generating the final report:\n"
    "1. Use ls('research/') to see which sources you researched\n"
    "2. Read EVERY file with read_file()\n"
    "3. Only then write the report, based on the real data"
)

Problem 4: Conflicts when editing files

Symptom: edit_file fails because the old text doesn't exactly match the file's current content. Cause: The agent "remembers" an earlier version of the file that has already been modified. Fix: Instruct the agent to read before editing:

instructions = (
    "Before using edit_file, ALWAYS read the file with read_file first "
    "so you have the current content. Never edit based on what you remember."
)

Problem 5: A disorganized directory structure

Symptom: The agent creates files like file1.md, temp.md, results.md in the root directory with no logical organization. Cause: With no defined structure, the agent falls back on generic names. Fix: Define the convention in the instructions:

instructions = (
    "Required file structure:\n"
    "- research/[source]_[topic].md — raw data from each source\n"
    "- analysis/[type]_analysis.md — intermediate processing\n"
    "- output/[deliverable].md — final deliverables\n"
    "Descriptive names, no spaces, in snake_case."
)

Exercises

Exercise 1: Create a file structure (Easy)

Create a Deep Agent that generates a file structure for a research project. It should create at least 3 folders with README files explaining the purpose of each one.

See solution
from dotenv import load_dotenv
load_dotenv()

from deep_agents import create_deep_agent

agent = create_deep_agent(
    "openai:gpt-4.1-mini",
    tools=[],
    name="Structure Creator",
    instructions=(
        "Create a file structure for an AI research project. "
        "Include these folders: research/, analysis/, output/. "
        "In each folder, create a README.md explaining what kind of files belong there."
    ),
)

result = agent.run("Create the file structure to research LLMs in production")

for path in sorted(result.files.keys()):
    content = result.files[path]
    print(f"\n--- {path} ---")
    print(content[:200])
# Expected output (varies):
# --- analysis/README.md ---
# # Analysis
# Analysis files and intermediate processing of the research data.
#
# --- output/README.md ---
# # Output
# Final deliverables: reports, executive summaries, presentations.
#
# --- research/README.md ---
# # Research
# Raw data from each source researched. One file per source.

Explanation: The agent uses write_file to create each file. The structure reflects an organized workflow: raw data → analysis → output. That sets up the conventions for the following exercises.

Exercise 2: Multi-file research (Easy)

Create an agent that researches 3 different aspects of a topic and writes each aspect to a separate file inside research/. At the end, it should create an output/summary.md file with a summary.

See solution
from dotenv import load_dotenv
load_dotenv()

from deep_agents import create_deep_agent

agent = create_deep_agent(
    "openai:gpt-4.1-mini",
    tools=[],
    name="Multi-File Researcher",
    instructions=(
        "For the topic you're given:\n"
        "1. Identify 3 key aspects\n"
        "2. Write each aspect to research/aspect_N.md\n"
        "3. Read the 3 files in research/\n"
        "4. Generate a summary integrating the 3 aspects in output/summary.md"
    ),
)

result = agent.run("Research the impact of AI on the job market")

research_files = [f for f in result.files if f.startswith("research/")]
output_files = [f for f in result.files if f.startswith("output/")]

print(f"Research files: {len(research_files)}")
for f in sorted(research_files):
    print(f"  {f} ({len(result.files[f])} chars)")

print(f"\nOutput files: {len(output_files)}")
for f in sorted(output_files):
    print(f"  {f} ({len(result.files[f])} chars)")
# Expected output (varies):
# Research files: 3
#   research/aspect_1_automation.md (1243 chars)
#   research/aspect_2_new_roles.md (1087 chars)
#   research/aspect_3_education.md (956 chars)
#
# Output files: 1
#   output/summary.md (2456 chars)

Explanation: The agent wrote each aspect to a separate file, then read them back to generate the summary. The summary integrates all three aspects because the agent consulted the files, not its memory of the context window.

Exercise 3: Measure the filesystem's impact on tokens (Medium)

Run the same task with two agents: one that uses the filesystem and one that doesn't. Compare the total tokens consumed.

See solution
from dotenv import load_dotenv
load_dotenv()

from deep_agents import create_deep_agent

agent_fs = create_deep_agent(
    "openai:gpt-4.1-mini",
    tools=[],
    name="With FS",
    instructions=(
        "For each aspect of the topic, write a file in research/. "
        "At the end, read the files and generate output/report.md. "
        "Research at least 4 aspects."
    ),
)

agent_no_fs = create_deep_agent(
    "openai:gpt-4.1-mini",
    tools=[],
    name="Without FS",
    instructions=(
        "Research at least 4 aspects of the topic. "
        "Do NOT use files. Keep all the information in your direct response. "
        "Generate a complete report at the end."
    ),
)

task = "Analyze the pros and cons of the 4 main microservices architectures"

result_fs = agent_fs.run(task)
result_no_fs = agent_no_fs.run(task)

print("=== Token usage comparison ===")
print(f"\n  With filesystem:")
print(f"    Tokens: {result_fs.usage.total_tokens:,}")
print(f"    Files: {len(result_fs.files)}")
print(f"    Output: {len(result_fs.output):,} chars")

print(f"\n  Without filesystem:")
print(f"    Tokens: {result_no_fs.usage.total_tokens:,}")
print(f"    Files: {len(result_no_fs.files)}")
print(f"    Output: {len(result_no_fs.output):,} chars")

diff = result_no_fs.usage.total_tokens - result_fs.usage.total_tokens
if diff > 0:
    print(f"\n  Savings: {diff:,} tokens ({diff/result_no_fs.usage.total_tokens*100:.0f}%)")
else:
    print(f"\n  Filesystem overhead: {abs(diff):,} tokens")
# Expected output (varies):
# === Token usage comparison ===
#
#   With filesystem:
#     Tokens: 15,234
#     Files: 6
#     Output: 4,102 chars
#
#   Without filesystem:
#     Tokens: 23,891
#     Files: 0
#     Output: 2,567 chars
#
#   Savings: 8,657 tokens (36%)

Explanation: The agent with a filesystem consumes fewer tokens because it doesn't drag all the previous results along on every turn. The gap widens with more sources and more data. On top of that, the output is longer because the context window wasn't saturated.

Exercise 4: edit_file for iterative reports (Medium)

Create an agent that writes an initial report and then refines it iteratively. It should use edit_file to improve specific sections, not rewrite the whole report every time.

See solution
from dotenv import load_dotenv
load_dotenv()

from deep_agents import create_deep_agent

agent = create_deep_agent(
    "openai:gpt-4.1",
    tools=[],
    name="Iterative Writer",
    instructions=(
        "Follow this process to write a report:\n"
        "1. Write an initial draft in output/report.md with sections marked [DRAFT]\n"
        "2. Read the full draft with read_file\n"
        "3. Use edit_file to improve EACH section individually:\n"
        "   - Replace [DRAFT] with final content\n"
        "   - Add specific data\n"
        "   - Improve the transitions between sections\n"
        "4. Read the final report to verify quality"
    ),
)

result = agent.run(
    "Write a report on prompt engineering best practices for production"
)

if "output/report.md" in result.files:
    report = result.files["output/report.md"]
    draft_markers = report.count("[DRAFT]")
    print(f"Remaining [DRAFT] markers: {draft_markers}")
    print(f"Final length: {len(report):,} chars")
    print(f"\nFirst 10 lines:")
    for line in report.split("\n")[:10]:
        print(f"  {line}")
# Expected output (varies):
# Remaining [DRAFT] markers: 0
# Final length: 4,892 chars
#
# First 10 lines:
#   # Prompt Engineering Best Practices for Production
#
#   ## 1. Prompt Structure
#
#   A production prompt has three mandatory components...

Explanation: edit_file lets the agent refine specific sections without rewriting the entire document. That's more efficient than write_file for long documents and produces higher-quality results because the agent focuses on one section at a time.

Exercise 5: Filesystem combined with planning (Advanced)

Create an agent that uses write_todos to plan AND the filesystem to store. Each step of the plan should correspond to a file. Print the mapping table: step → file.

See solution
from dotenv import load_dotenv
load_dotenv()

from deep_agents import create_deep_agent
from langchain_community.tools import TavilySearchResults

web_search = TavilySearchResults(max_results=3)

agent = create_deep_agent(
    "openai:gpt-4.1",
    tools=[web_search],
    name="Planning + Filesystem",
    instructions=(
        "For every task:\n"
        "1. Create a plan with write_todos\n"
        "2. For EACH step of the plan, write the result to a file:\n"
        "   - Research step → research/[step_N].md\n"
        "   - Analysis step → analysis/[step_N].md\n"
        "   - Output step → output/[step_N].md\n"
        "3. In each todo's result, include the path of the file you generated\n"
        "4. Update your progress with write_todos after each step"
    ),
)

result = agent.run("Compare 3 vector databases for RAG in production")

print("=== Mapping: Plan → Files ===")
print(f"{'Step':<50} {'Status':<12} {'Associated file'}")
print("-" * 90)
for todo in result.todos:
    file_ref = todo.get("result", "")[:60] if todo.get("result") else "N/A"
    print(f"{todo['title']:<50} {todo['status']:<12} {file_ref}")

print(f"\n=== Files generated: {len(result.files)} ===")
for path in sorted(result.files.keys()):
    print(f"  {path}")
# Expected output (varies):
# === Mapping: Plan → Files ===
# Step                                               Status       Associated file
# ------------------------------------------------------------------------------------------
# Research Pinecone                                   completed    research/pinecone.md
# Research Weaviate                                   completed    research/weaviate.md
# Research ChromaDB                                   completed    research/chromadb.md
# Build the comparison matrix                         completed    analysis/comparison.md
# Generate the final recommendation                   completed    output/recommendation.md
#
# === Files generated: 5 ===
#   analysis/comparison.md
#   output/recommendation.md
#   research/chromadb.md
#   research/pinecone.md
#   research/weaviate.md

Explanation: Planning and the filesystem complement each other: write_todos defines WHAT to do, the filesystem stores the results of EACH step. Together they produce an organized, traceable run where you can see both the plan and the outputs of every step.

Exercise 6: Configure filesystem limits (Advanced)

Create an agent with filesystem limits (max 5 files, max 5KB per file) and watch how it behaves when it hits them. Does the agent adapt?

See solution
from dotenv import load_dotenv
load_dotenv()

from deep_agents import create_deep_agent

agent = create_deep_agent(
    "openai:gpt-4.1-mini",
    tools=[],
    name="Limited FS Agent",
    instructions=(
        "Research the topic in depth. Write each aspect to a separate file. "
        "If you hit the file limit, consolidate information into existing files "
        "using edit_file instead of creating new ones."
    ),
    filesystem_config={
        "max_files": 5,
        "max_file_size": 5_000,
    },
)

result = agent.run(
    "Research 8 machine learning frameworks: "
    "TensorFlow, PyTorch, JAX, Keras, scikit-learn, XGBoost, LightGBM, Hugging Face"
)

print(f"=== Result with a 5-file limit ===")
print(f"  Files created: {len(result.files)}")
for path in sorted(result.files.keys()):
    size = len(result.files[path])
    print(f"  {path} ({size:,} chars)")

if len(result.files) <= 5:
    print("\n  ✅ The agent respected the file limit")
    print("  Strategy: it consolidated several frameworks per file")
else:
    print("\n  ❌ The agent exceeded the limit (edge case)")
# Expected output (varies):
# === Result with a 5-file limit ===
#   Files created: 5
#   research/deep_learning_frameworks.md (4,230 chars)
#   research/ml_traditional_frameworks.md (3,890 chars)
#   research/gradient_boosting.md (2,456 chars)
#   analysis/comparison.md (4,102 chars)
#   output/report.md (4,890 chars)
#
#   ✅ The agent respected the file limit
#   Strategy: it consolidated several frameworks per file

Explanation: With a 5-file limit and 8 topics, the agent adapts by grouping related frameworks into a single file. Filesystem limits force the agent to be efficient with space, much like a tight budget forces prioritization. That's especially useful for controlling costs in production.


Summary

  • The virtual filesystem lets the agent read, write, and edit files. Instead of accumulating data in the context window (expensive, limited), the agent writes outputs to files and reads only what it needs — that's context offloading
  • Four tools: ls (list), read_file (read), write_file (create/overwrite), edit_file (targeted edits). Deep Agents injects them automatically
  • Files act as the agent's working memory: while the context window is short-term memory, the files are a persistent notebook the agent consults selectively
  • Impact on cost: in multi-source research, the filesystem can cut token consumption by 30-75% because each turn only loads the data it needs
  • Impact on quality: with a leaner context window, the model keeps better attention on the relevant data, producing more complete and coherent outputs
  • The filesystem is a sandbox: the agent can't reach the system's real files, run commands, or leave the virtual workspace. Configure file and size limits to control resources
  • Connection with M8: the filesystem abstracts the persistence you built by hand with checkpointers. The checkpointer saves the full state (including files) for crash recovery; the filesystem is the interface the agent works with

Next capsule: Subagent Spawning and Delegation — how the agent creates specialized subagents on demand to handle subtasks, with isolated context and control limits.


Additional resources

  1. Deep Agents — Filesystem — Official documentation for the virtual filesystem and its tools
  2. Context Window Management — Anthropic — Anthropic's guide on handling long contexts efficiently
  3. LangGraph — Checkpointing — The persistence foundation the filesystem runs on
  4. The Needle in a Haystack Test — Paper on how models lose information in long contexts, the direct motivation for context offloading
  5. Retrieval-Augmented Generation Survey — RAG is another form of context offloading (external knowledge); the filesystem offloads working data
  6. Sandboxing AI Agents — Safety Considerations — Security considerations when giving agents access to filesystem tools

Module 11 — LangChain & LangGraph: From Chains to Agents

Next capsule: Subagent Spawning and Delegation — you'll learn how the agent creates specialized subagents on demand, with isolated context for each subtask, and how to configure limits to control cost and complexity.