In a previous example, we used StateBackend, where files belonged to a specific LangGraph conversation thread.
This time, we are using something different:
FilesystemBackendlets a Deep Agent read and write real files on your machine.
That means if the agent creates a Markdown file, Python file, or project folder, it remains on your disk even after the script finishes.
This is useful for local coding assistants, document-generation tools, and development workflows. But because the agent can modify real files, it must be used carefully.
What This Example Demonstrates
The code shows four important behaviors:
- The agent writes files to a real folder on disk.
- Those files remain after the Python program stops.
-
thread_iddoes not isolate files when using local disk storage. -
CompositeBackendcan keep temporary agent artifacts separate from your real project files.
Here is the high-level comparison:
StateBackend
────────────
Thread A → temporary files for Thread A
Thread B → separate temporary files for Thread B
FilesystemBackend
─────────────────
Thread A ─┐
├── same real folder on your computer
Thread B ─┘
Enter fullscreen mode Exit fullscreen mode
With FilesystemBackend, files are not stored inside LangGraph state. They are regular operating-system files under a configured directory. (docs.langchain.com)
The Full Example
"""
Demonstrates Deep Agents' FilesystemBackend (real files on disk).
What you will observe:
1. The agent writes real files to disk under a root_dir.
2. Files persist after the Python process ends — inspect them.
3. Thread isolation does NOT apply (files are real OS files).
4. CompositeBackend keeps internal agent artifacts out of your project.
"""
from deepagents import create_deep_agent
from deepagents.backends import CompositeBackend, FilesystemBackend, StateBackend
from langchain.chat_models import init_chat_model
from dotenv import load_dotenv
load_dotenv()
model = init_chat_model(
"openrouter:nvidia/nemotron-3-super-120b-a12b",
max_tokens=4096,
)
# ---------------------------------------------------------
# 1. FilesystemBackend (standalone) — writes under a root_dir
# ---------------------------------------------------------
# Files go to real disk under ./agent_workspace/ on your machine.
agent = create_deep_agent(
model=model,
backend=FilesystemBackend(
root_dir="./agent_workspace",
virtual_mode=True,
),
)
def run(thread_id: str, prompt: str):
result = agent.invoke(
{"messages": [{"role": "user", "content": prompt}]},
config={"configurable": {"thread_id": thread_id}},
)
print("\n" + "=" * 70)
print(f"THREAD: {thread_id}")
print("=" * 70)
print(result["messages"][-1].content)
# ---------------------------------------------------------
# Turn 1 — write a real file
# ---------------------------------------------------------
run(
"demo-thread-1",
"""
Use the write_file tool to create a file:
Path: /notes/todo.md
Content:
# To-do
- Buy groceries
- Finish the project
- Review pull requests
After writing:
1. Use ls to confirm it exists.
2. Tell me the absolute OS path where this file was written.
""",
)
# ---------------------------------------------------------
# Turn 2 — same thread reads it back
# ---------------------------------------------------------
run(
"demo-thread-1",
"""
Use read_file to read /notes/todo.md.
Then list the three to-do items.
""",
)
# ---------------------------------------------------------
# Turn 3 — different thread ALSO sees the file
# ---------------------------------------------------------
# Unlike StateBackend, files are real OS files — they are NOT
# isolated by thread_id. Any thread can read any file.
run(
"demo-thread-2",
"""
Use ls to inspect the filesystem.
Can you find /notes/todo.md? Read it and report its contents.
""",
)
print("\n" + "=" * 70)
print("Files written to: ./agent_workspace/ -- check with:")
print(" dir agent_workspace\\notes\\")
print(" type agent_workspace\\notes\\todo.md")
print("=" * 70)
# ---------------------------------------------------------
# 2. CompositeBackend (recommended for real projects)
# ---------------------------------------------------------
# Internal agent artifacts (large tool results, conversation
# history) go to ephemeral StateBackend. Only files under
# /workspace/ hit real disk.
print("\n" + "=" * 70)
print("COMPOSITE BACKEND (recommended pattern)")
print("=" * 70)
agent2 = create_deep_agent(
model=model,
backend=CompositeBackend(
default=StateBackend(),
routes={
"/workspace/": FilesystemBackend(
root_dir="./my_project",
virtual_mode=True,
),
},
),
)
result = agent2.invoke(
{
"messages": [
{
"role": "user",
"content": (
"Write a file at path /workspace/src/hello.py "
"with content: print('hello from FilesystemBackend'). "
"Then confirm it exists via ls."
),
}
]
},
config={"configurable": {"thread_id": "composite-demo"}},
)
print(result["messages"][-1].content)
print("\nFile written to: ./my_project/src/hello.py")
Enter fullscreen mode Exit fullscreen mode
Before You Run the Code
Install the required packages:
pip install deepagents langchain langgraph python-dotenv
Enter fullscreen mode Exit fullscreen mode
You also need your model provider credentials configured. Since this example uses OpenRouter, place your API key in a .env file:
OPENROUTER_API_KEY=your_api_key_here
Enter fullscreen mode Exit fullscreen mode
The call to load_dotenv() loads environment variables from that file:
from dotenv import load_dotenv
load_dotenv()
Enter fullscreen mode Exit fullscreen mode
Do not commit .env files containing real API keys to GitHub.
The Important Difference: Real Disk vs Agent State
Before studying the code, understand this difference.
FeatureStateBackend
FilesystemBackend
Where files live
LangGraph state
Your computer’s filesystem
Survives script restart
No, with InMemorySaver
Yes
Isolated by thread_id
Yes
No
Good for
Temporary agent artifacts
Local projects and durable output
Risk level
Lower
Higher
Can affect real project files
No
Yes
FilesystemBackend reads and writes directly to the local filesystem. It is intended for controlled local development or carefully configured CI/CD workflows—not directly exposed web applications or multi-user APIs. (reference.langchain.com)
Step 1: Creating the Model
model = init_chat_model(
"openrouter:nvidia/nemotron-3-super-120b-a12b",
max_tokens=4096,
)
Enter fullscreen mode Exit fullscreen mode
This creates the language model used by the Deep Agent.
The max_tokens=4096 argument limits the size of generated responses. This is practical when using paid APIs because it helps control token usage and cost.
You can use another supported model later. The storage behavior explained in this article comes from the backend configuration, not from the model itself.
Step 2: Creating a FilesystemBackend
Here is the most important part of the first agent:
agent = create_deep_agent(
model=model,
backend=FilesystemBackend(
root_dir="./agent_workspace",
virtual_mode=True,
),
)
Enter fullscreen mode Exit fullscreen mode
This tells the agent:
Use the
agent_workspacefolder as the location for real file operations.
When the agent receives this virtual path:
/notes/todo.md
Enter fullscreen mode Exit fullscreen mode
it maps to a real path similar to this:
your-project-folder/
└── agent_workspace/
└── notes/
└── todo.md
Enter fullscreen mode Exit fullscreen mode
The exact full path depends on where you run your Python script.
For example, if your project is located at:
C:\Users\Talha\deep-agents-demo
Enter fullscreen mode Exit fullscreen mode
the final file could be:
C:\Users\Talha\deep-agents-demo\agent_workspace\notes\todo.md
Enter fullscreen mode Exit fullscreen mode
On macOS or Linux, it could look like:
/home/talha/deep-agents-demo/agent_workspace/notes/todo.md
Enter fullscreen mode Exit fullscreen mode
Why Use virtual_mode=True?
virtual_mode=True
Enter fullscreen mode Exit fullscreen mode
This is an important safety setting.
It lets the agent use clean virtual paths such as:
/notes/todo.md
Enter fullscreen mode Exit fullscreen mode
while mapping them underneath the configured root_dir.
It also blocks common path-escape attempts such as:
../../some-other-folder/file.txt
Enter fullscreen mode Exit fullscreen mode
or:
~/secret-file.txt
Enter fullscreen mode Exit fullscreen mode
However, this is not full sandboxing. It provides path-based restrictions, but it does not isolate the Python process like a container or remote sandbox would. The official Deep Agents documentation recommends using virtual_mode=True together with a configured root directory. (reference.langchain.com)
A Small Improvement: Use an Absolute root_dir
For beginner projects, this works:
root_dir="./agent_workspace"
Enter fullscreen mode Exit fullscreen mode
But using an absolute path makes your code more predictable, especially if you run the script from different directories.
Here is a slightly safer version:
from pathlib import Path
workspace_dir = Path("./agent_workspace").resolve()
agent = create_deep_agent(
model=model,
backend=FilesystemBackend(
root_dir=workspace_dir,
virtual_mode=True,
),
)
Enter fullscreen mode Exit fullscreen mode
Now workspace_dir becomes a complete OS path automatically.
For example:
C:\Users\Talha\deep-agents-demo\agent_workspace
Enter fullscreen mode Exit fullscreen mode
or:
/home/talha/deep-agents-demo/agent_workspace
Enter fullscreen mode Exit fullscreen mode
Step 3: The run() Helper Function
The helper function invokes the agent:
def run(thread_id: str, prompt: str):
result = agent.invoke(
{"messages": [{"role": "user", "content": prompt}]},
config={"configurable": {"thread_id": thread_id}},
)
Enter fullscreen mode Exit fullscreen mode
The thread_id is still passed to LangGraph:
config={"configurable": {"thread_id": thread_id}}
Enter fullscreen mode Exit fullscreen mode
But with FilesystemBackend, the file itself does not belong to the thread.
This is a crucial distinction:
thread_id controls conversation/checkpoint state
root_dir controls real file location
Enter fullscreen mode Exit fullscreen mode
The thread can affect conversation memory, but a file written to disk is visible to anything that has permission to access that disk location.
Turn 1: Writing a Real Markdown File
The first prompt asks the agent to create:
/notes/todo.md
Enter fullscreen mode Exit fullscreen mode
with this content:
# To-do
- Buy groceries
- Finish the project
- Review pull requests
Enter fullscreen mode Exit fullscreen mode
Because the backend root directory is:
./agent_workspace
Enter fullscreen mode Exit fullscreen mode
the actual file becomes:
./agent_workspace/notes/todo.md
Enter fullscreen mode Exit fullscreen mode
The agent should then call ls to confirm that the file exists.
Conceptually, the filesystem now looks like this:
agent_workspace/
└── notes/
└── todo.md
Enter fullscreen mode Exit fullscreen mode
Unlike StateBackend, this file is not temporary agent state. It is a real file created on your computer.
Turn 2: Reading the File Again
The second call uses the same thread:
run(
"demo-thread-1",
...
)
Enter fullscreen mode Exit fullscreen mode
The agent reads:
/notes/todo.md
Enter fullscreen mode Exit fullscreen mode
and should report:
- Buy groceries
- Finish the project
- Review pull requests
At this point, it may look similar to the StateBackend example. But the reason the file is available is different.
With StateBackend, the same thread ID matters because files are stored in thread state.
With FilesystemBackend, the file is available because it physically exists in:
./agent_workspace/notes/todo.md
Enter fullscreen mode Exit fullscreen mode
The agent could read it even after restarting the Python script, as long as the file is still on disk.
Turn 3: A Different Thread Can Read the Same File
Now notice this call:
run(
"demo-thread-2",
...
)
Enter fullscreen mode Exit fullscreen mode
The code uses a different thread ID:
demo-thread-2
Enter fullscreen mode Exit fullscreen mode
But the agent can still find and read:
/notes/todo.md
Enter fullscreen mode Exit fullscreen mode
Why?
Because both threads point to the same real directory:
./agent_workspace
Enter fullscreen mode Exit fullscreen mode
Here is the flow:
Thread: demo-thread-1
│
│ writes
▼
agent_workspace/notes/todo.md
▲
│ reads
│
Thread: demo-thread-2
Enter fullscreen mode Exit fullscreen mode
This means thread_id is not a filesystem security boundary when using FilesystemBackend.
If two agents use the same
FilesystemBackendroot directory, they can access the same files—subject to operating-system permissions and the backend configuration.
Verifying the File Yourself
Your code prints these Windows commands:
dir agent_workspace\notes\
type agent_workspace\notes\todo.md
Enter fullscreen mode Exit fullscreen mode
Run them in Command Prompt:
dir agent_workspace\notes\
type agent_workspace\notes\todo.md
Enter fullscreen mode Exit fullscreen mode
Expected output:
# To-do
- Buy groceries
- Finish the project
- Review pull requests
Enter fullscreen mode Exit fullscreen mode
For PowerShell, you can use:
Get-ChildItem .\agent_workspace\notes\
Get-Content .\agent_workspace\notes\todo.md
Enter fullscreen mode Exit fullscreen mode
For macOS or Linux:
ls agent_workspace/notes/
cat agent_workspace/notes/todo.md
Enter fullscreen mode Exit fullscreen mode
This is a helpful test because it proves that the file exists independently of the agent and the Python process.
Why CompositeBackend Is Better for Real Projects
The second part of the code introduces this:
CompositeBackend(
default=StateBackend(),
routes={
"/workspace/": FilesystemBackend(
root_dir="./my_project",
virtual_mode=True,
),
},
)
Enter fullscreen mode Exit fullscreen mode
This is a hybrid approach.
It says:
- Use
StateBackendfor most agent files. - Use real disk only for paths beginning with
/workspace/.
Here is the routing idea:
Agent path Backend used
────────────────────────────────────────────────────
/workspace/src/hello.py FilesystemBackend
/tmp/tool-output.txt StateBackend
/internal/agent-notes.md StateBackend
Enter fullscreen mode Exit fullscreen mode
And visually:
CompositeBackend
│
┌──────────────┴──────────────┐
│ │
/workspace/... paths All other paths
│ │
▼ ▼
FilesystemBackend StateBackend
./my_project/ temporary state
Enter fullscreen mode Exit fullscreen mode
CompositeBackend routes file operations by path prefix. A matching route sends the operation to that backend; paths that do not match a route use the default backend. (reference.langchain.com)
Understanding the /workspace/ Route
This configuration:
routes={
"/workspace/": FilesystemBackend(
root_dir="./my_project",
virtual_mode=True,
),
},
Enter fullscreen mode Exit fullscreen mode
means that this agent path:
/workspace/src/hello.py
Enter fullscreen mode Exit fullscreen mode
will become a real file at:
./my_project/src/hello.py
Enter fullscreen mode Exit fullscreen mode
The agent prompt says:
"Write a file at path /workspace/src/hello.py "
"with content: print('hello from FilesystemBackend'). "
"Then confirm it exists via ls."
Enter fullscreen mode Exit fullscreen mode
After the agent finishes, your project folder should contain:
my_project/
└── src/
└── hello.py
Enter fullscreen mode Exit fullscreen mode
The contents of hello.py should be:
print("hello from FilesystemBackend")
Enter fullscreen mode Exit fullscreen mode
Why Keep Internal Files in StateBackend?
Deep Agents can create temporary artifacts while working, such as:
- Large tool results
- Intermediate research notes
- Planning files
- Conversation-related artifacts
- Temporary generated content
You may not want these files to appear in your actual source-code project.
That is why this pattern is useful:
default=StateBackend()
Enter fullscreen mode Exit fullscreen mode
Only explicit project output goes to disk:
/workspace/...
Enter fullscreen mode Exit fullscreen mode
Everything else remains temporary and isolated in agent state.
This creates a cleaner project directory and reduces the chance that internal agent artifacts are mixed with your application files.
FilesystemBackend vs CompositeBackend
Question
Standalone FilesystemBackend
CompositeBackend
Where do files go?
One real disk folder
Different locations based on path
Are all agent files written to disk?
Usually yes
Only selected routes
Can temporary artifacts stay ephemeral?
No, not by default
Yes
Best for
Small local experiments
Real development workflows
Project-folder cleanliness
Can become messy
Easier to control
Configuration complexity
Simple
Slightly more advanced
For a quick experiment, use FilesystemBackend.
For a project where you want the agent to create real source files but keep its internal work temporary, use CompositeBackend.
Important Safety Warning
FilesystemBackend is powerful because it gives an AI agent access to real files.
That also makes it risky.
The official documentation warns that agents using this backend may be able to read accessible secrets such as .env files, API keys, or credentials, and that file changes are permanent. It is intended for controlled local development and carefully managed CI/CD environments—not public web servers, multi-tenant apps, or untrusted user input. (reference.langchain.com)
Avoid These Mistakes
1. Do not point the agent at your entire computer
Avoid this:
FilesystemBackend(
root_dir="C:/",
virtual_mode=True,
)
Enter fullscreen mode Exit fullscreen mode
Or on macOS/Linux:
FilesystemBackend(
root_dir="/",
virtual_mode=True,
)
Enter fullscreen mode Exit fullscreen mode
Instead, create a dedicated workspace:
FilesystemBackend(
root_dir="./agent_workspace",
virtual_mode=True,
)
Enter fullscreen mode Exit fullscreen mode
2. Do not expose secrets inside the workspace
Avoid placing files like these inside the folder available to the agent:
.env
credentials.json
private_key.pem
config.production.json
Enter fullscreen mode Exit fullscreen mode
A safer project structure might look like:
my-app/
├── .env # Keep outside agent workspace
├── app/
├── agent_workspace/ # Agent can access this
└── run_agent.py
Enter fullscreen mode Exit fullscreen mode
3. Do not use it directly in a public API
If users can send prompts to an agent through a website, avoid giving that agent direct access to your host machine’s filesystem.
For production systems, prefer:
-
StateBackendfor temporary per-thread files -
StoreBackendfor durable managed storage - A sandbox backend for isolated execution environments
Deep Agents specifically recommends safer state, store, or sandbox alternatives for web servers and HTTP APIs. (reference.langchain.com)
4. Do not treat thread_id as access control
This is unsafe thinking:
“My files are secure because each user has a different thread_id.”
Enter fullscreen mode Exit fullscreen mode
That is true for StateBackend, but not for a shared FilesystemBackend.
If different users, agents, or processes share the same disk folder, they may see the same files.
Use proper authentication, authorization, per-user directories, and operating-system permissions in real applications.
A Recommended Project Structure
For experimenting safely, use a layout like this:
deepagents-filesystem-demo/
│
├── .env
├── filesystem_demo.py
│
├── agent_workspace/
│ └── notes/
│ └── todo.md
│
└── my_project/
└── src/
└── hello.py
Enter fullscreen mode Exit fullscreen mode
You can also add these generated folders to .gitignore:
# Agent-generated workspace files
agent_workspace/
# Add this only if you do not want generated project files in Git
# my_project/
# Never commit environment secrets
.env
Enter fullscreen mode Exit fullscreen mode
Final Takeaway
FilesystemBackend allows a Deep Agent to work with files that truly exist on your machine.
The core idea is:
StateBackend
→ files are temporary and tied to a LangGraph thread
FilesystemBackend
→ files are real, persistent, and shared through the operating system
Enter fullscreen mode Exit fullscreen mode
For simple local experiments:
FilesystemBackend(
root_dir="./agent_workspace",
virtual_mode=True,
)
Enter fullscreen mode Exit fullscreen mode
For more organized real-world projects:
CompositeBackend(
default=StateBackend(),
routes={
"/workspace/": FilesystemBackend(
root_dir="./my_project",
virtual_mode=True,
)
}
)
Enter fullscreen mode Exit fullscreen mode
This hybrid approach gives you the best of both worlds:
- real project files where you want them;
- temporary agent artifacts kept out of your project folder.
답글 남기기