Using FilesystemMiddleware with virtual filesystems, backends (State, Store, Filesystem, Composite), and context management for Deep Agents.
Install
npx skillscat add christian-bromann/langchain-skills/skills-deepagents-filesystem-python Install via the SkillsCat registry.
deepagents-filesystem (Python)
Overview
FilesystemMiddleware solves context engineering challenges by providing file operations through a pluggable backend system. It allows agents to offload large context to filesystem storage, preventing context window overflow.
Built-in Filesystem Tools:
ls- List files in a directoryread_file- Read entire files or specific line rangeswrite_file- Create new filesedit_file- Edit existing files with exact string replacementglob- Find files matching patternsgrep- Search for text across files
When to Use Filesystem Middleware
| Use Filesystem Tools When | Alternative Approach |
|---|---|
| Tool results are variable-length (web_search, RAG) | Keep in message history (if small) |
| Working with large documents or code | Use specialized tools |
| Need persistent storage across turns | Use short-term message history |
| Multiple files need coordination | Single-turn operations |
Backend Types
StateBackend (Default)
Ephemeral storage in agent state - persists within a thread only.
from deepagents import create_deep_agent
# Default backend (StateBackend)
agent = create_deep_agent()
result = agent.invoke({
"messages": [{"role": "user", "content": "Write notes to /draft.txt"}]
})
# File exists only within this threadFilesystemBackend (Local Disk)
Direct access to local filesystem.
from deepagents import create_deep_agent
from deepagents.backends import FilesystemBackend
agent = create_deep_agent(
backend=FilesystemBackend(
root_dir=".", # Root directory
virtual_mode=True # Enable path restrictions
)
)
# Agent can now read/write to actual files on disk
result = agent.invoke({
"messages": [{"role": "user", "content": "Read the README.md file"}]
})Security Considerations:
- Use
virtual_mode=Trueto prevent..,~, and absolute path access - Enable Human-in-the-Loop for sensitive operations
- Never use in web servers - use StateBackend or sandbox instead
- Secrets (API keys, .env) are readable by the agent
StoreBackend (Persistent Cross-Thread)
Storage that persists across threads using LangGraph's Store.
from deepagents import create_deep_agent
from deepagents.backends import StoreBackend
from langgraph.store.memory import InMemoryStore
store = InMemoryStore()
agent = create_deep_agent(
backend=lambda rt: StoreBackend(rt),
store=store
)
# Files persist across different thread_idsCompositeBackend (Hybrid Storage)
Route different paths to different backends.
from deepagents import create_deep_agent
from deepagents.backends import CompositeBackend, StateBackend, StoreBackend
from langgraph.store.memory import InMemoryStore
store = InMemoryStore()
composite_backend = lambda rt: CompositeBackend(
default=StateBackend(rt),
routes={
"/memories/": StoreBackend(rt), # Persistent storage
}
)
agent = create_deep_agent(
backend=composite_backend,
store=store
)
# /draft.txt -> ephemeral (StateBackend)
# /memories/user-prefs.txt -> persistent (StoreBackend)Decision Table: Which Backend to Use
| Use Case | Backend | Why |
|---|---|---|
| Temporary working files | StateBackend | Default, no setup needed |
| Local development CLI | FilesystemBackend | Direct disk access |
| Cross-session memory | StoreBackend | Persists across threads |
| Hybrid storage | CompositeBackend | Mix ephemeral + persistent |
| Production web app | StateBackend or Sandbox | Never use FilesystemBackend |
Code Examples
Example 1: Managing Large Context
from deepagents import create_deep_agent
agent = create_deep_agent()
# Agent offloads search results to filesystem
result = agent.invoke({
"messages": [{
"role": "user",
"content": "Search for information about Python asyncio and save the results for later analysis"
}]
})
# Agent workflow:
# 1. Use search tool -> large results
# 2. write_file("/search-results.txt", results)
# 3. Continue with compact context
# 4. Later: read_file("/search-results.txt") when neededExample 2: Custom Tool Descriptions
from langchain.agents import create_agent
from deepagents.middleware.filesystem import FilesystemMiddleware
agent = create_agent(
model="claude-sonnet-4-5-20250929",
middleware=[
FilesystemMiddleware(
backend=None, # Use default StateBackend
system_prompt="Save intermediate results to /workspace/ directory",
custom_tool_descriptions={
"read_file": "Read files you've previously written. Use offset/limit for large files.",
"write_file": "Save data to avoid context overflow. Organize in /workspace/.",
}
),
],
)Example 3: Long-term Memory with CompositeBackend
from deepagents import create_deep_agent
from deepagents.backends import CompositeBackend, StateBackend, StoreBackend
from langgraph.store.memory import InMemoryStore
store = InMemoryStore()
agent = create_deep_agent(
backend=lambda rt: CompositeBackend(
default=StateBackend(rt),
routes={"/memories/": StoreBackend(rt)}
),
store=store
)
# Thread 1: Save user preferences
config1 = {"configurable": {"thread_id": "thread-1"}}
agent.invoke({
"messages": [{"role": "user", "content": "Save my preference: I like concise explanations to /memories/prefs.txt"}]
}, config=config1)
# Thread 2: Access saved preferences
config2 = {"configurable": {"thread_id": "thread-2"}}
agent.invoke({
"messages": [{"role": "user", "content": "Read my preferences and explain asyncio"}]
}, config=config2)
# Agent reads /memories/prefs.txt and provides concise explanationExample 4: FilesystemBackend for Local Development
from deepagents import create_deep_agent
from deepagents.backends import FilesystemBackend
agent = create_deep_agent(
backend=FilesystemBackend(
root_dir="/Users/username/project",
virtual_mode=True
),
interrupt_on={"write_file": True, "edit_file": True} # Safety
)
# Agent can read actual project files
result = agent.invoke({
"messages": [{"role": "user", "content": "Analyze the code in src/main.py"}]
})Boundaries
What Agents CAN Configure
✅ Backend type and configuration
✅ Custom tool descriptions
✅ File paths and organization
✅ Human-in-the-loop for file operations
✅ Root directory for FilesystemBackend
✅ Routing rules for CompositeBackend
What Agents CANNOT Configure
❌ Tool names (ls, read_file, write_file, edit_file, glob, grep)
❌ The fundamental file operation protocol
❌ Disable filesystem tools in create_deep_agent
❌ Access files outside virtual_mode restrictions
❌ Cross-thread file access without proper backend setup
Gotchas
1. StateBackend Files Don't Persist Across Threads
# ❌ Files lost when thread changes
config1 = {"configurable": {"thread_id": "thread-1"}}
agent.invoke({"messages": [{"role": "user", "content": "Write to /notes.txt"}]}, config=config1)
config2 = {"configurable": {"thread_id": "thread-2"}}
agent.invoke({"messages": [{"role": "user", "content": "Read /notes.txt"}]}, config=config2)
# File not found! Different thread
# ✅ Use same thread_id OR use StoreBackend for persistence2. FilesystemBackend Needs virtual_mode for Security
# ❌ Insecure - agent can access anywhere
backend = FilesystemBackend(root_dir="/project", virtual_mode=False)
# ✅ Secure - agent restricted to /project
backend = FilesystemBackend(root_dir="/project", virtual_mode=True)3. StoreBackend Requires a Store Instance
# ❌ Missing store
agent = create_deep_agent(
backend=lambda rt: StoreBackend(rt)
)
# ✅ Provide store
from langgraph.store.memory import InMemoryStore
agent = create_deep_agent(
backend=lambda rt: StoreBackend(rt),
store=InMemoryStore()
)4. edit_file Requires Exact String Match
# The edit_file tool needs exact string matching
# ❌ Won't work - whitespace mismatch
old_string = "def hello():\n print('hi')"
new_string = "def hello():\n print('hi')" # Different indentation
# ✅ Match exactly as it appears in the file
old_string = " print('hi')" # Exact match from file
new_string = " print('hi')" # New content