AI agents become much more useful when they can do more than generate text.
For example, an agent can:
- Create a Python file.
- Save it to your real local filesystem.
- Run that file using a shell command.
- Read the file again in a later interaction.
- Let another agent thread access the same project folder.
Deep Agents provides LocalShellBackend for this kind of trusted local-development workflow.
It gives an agent access to:
- Real files and folders under a configured working directory
- File tools such as
ls,read_file, andwrite_file - An
executetool for running commands through your computer’s shell
⚠️ Security Warning
LocalShellBackend executes commands directly on the machine running your Python script.
It is not a sandbox.
This means an agent may be able to:
- Run shell commands with your user permissions
- Read accessible files, including secrets and credentials
- Modify or delete files
- Install packages
- Start processes
- Consume CPU, memory, or disk space
- Access files outside the configured workspace through shell commands
Use it only in a trusted local development environment. Do not expose it through a public website, API, chatbot, shared machine, or multi-user system. LangChain strongly recommends human approval for operations and a real sandbox for production or untrusted workloads. (reference.langchain.com)
What This Script Demonstrates
| Step | What happens in your code |
|---|---|
| 1 | The agent creates /scripts/hello.py. |
| 2 | The file is physically written to ./shell_workspace/scripts/hello.py. |
| 3 | The agent runs python scripts/hello.py through the host shell. |
| 4 | The same thread reads the file again. |
| 5 | A different thread can still find, read, and run the same file. |
Key idea
LocalShellBackend
│
┌──────────────────┴──────────────────┐
│ │
▼ ▼
Real files on local disk Shell commands on host
./shell_workspace/ python, ls, dir, pwd, etc.
│ │
└──────────────────┬──────────────────┘
│
▼
Your local computer
The file is not stored only in agent memory or LangGraph state. It is an ordinary operating-system file in your project directory.
Before You Begin
1. Install dependencies
pip install deepagents langchain langchain-nvidia-ai-endpoints python-dotenv
2. Create a .env file
Create a file named .env beside your Python script:
NVIDIA_API_KEY=your_nvidia_api_key_here
3. Add private files and generated folders to .gitignore
.env
shell_workspace/
💡 Do not place credentials, SSH keys, database files, or private configuration files inside
shell_workspace. A local-shell agent may be able to read accessible files.
Complete Code
"""
Demonstrates Deep Agents' LocalShellBackend (real files + shell on host).
What you will observe:
1. The agent writes real files to disk under a root_dir.
2. The agent can execute shell commands directly on your machine.
3. Files persist across threads because they are real OS files.
SECURITY NOTE:
LocalShellBackend runs shell commands directly on the host and is not
sandboxed. Use only in local development environments that you trust.
"""
import os
from deepagents import create_deep_agent
from deepagents.backends import LocalShellBackend
from dotenv import load_dotenv
from langchain_nvidia_ai_endpoints import ChatNVIDIA
# Load NVIDIA_API_KEY from the local .env file.
load_dotenv()
# Create the NVIDIA chat model used by the Deep Agent.
model = ChatNVIDIA(
model="nvidia/nemotron-3-super-120b-a12b",
nvidia_api_key=os.getenv("NVIDIA_API_KEY"),
max_completion_tokens=1500,
)
# ---------------------------------------------------------
# LocalShellBackend — real disk files + host shell execution
# ---------------------------------------------------------
# Files are saved under ./shell_workspace/.
#
# With virtual_mode=True:
# /scripts/hello.py
#
# maps to:
# ./shell_workspace/scripts/hello.py
#
# IMPORTANT:
# virtual_mode=True affects file-tool paths, but it does NOT sandbox
# shell commands executed through the execute tool.
agent = create_deep_agent(
model=model,
backend=LocalShellBackend(
root_dir="./shell_workspace",
virtual_mode=True,
),
)
def run(thread_id: str, prompt: str):
"""Send one prompt to the agent and print its final response."""
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 — create a real Python file and execute it
# ---------------------------------------------------------
run(
"shell-thread-1",
"""
Use the write_file tool to create:
Path: /scripts/hello.py
Content:
for i in range(20):
print("Hello from LocalShellBackend!")
After writing:
1. Use ls to confirm /scripts/hello.py exists.
2. Use the execute tool to run: python scripts/hello.py
3. Report the exact output of the command.
""",
)
# ---------------------------------------------------------
# Turn 2 — same thread reads the file and inspects workspace
# ---------------------------------------------------------
run(
"shell-thread-1",
"""
Use read_file to read /scripts/hello.py.
Then use the execute tool to run a command that prints the current
working directory and lists the contents of the scripts directory.
If you are on Windows, use:
cd && dir scripts
If you are on macOS or Linux, use:
pwd && ls -la scripts
""",
)
# ---------------------------------------------------------
# Turn 3 — a different thread can still access the same file
# ---------------------------------------------------------
run(
"shell-thread-2",
"""
Use ls to inspect the filesystem.
Can you find /scripts/hello.py?
Read it and run it with the execute tool.
Report:
1. The file contents
2. The command output
""",
)
# ---------------------------------------------------------
# Confirm where the generated file exists on your computer
# ---------------------------------------------------------
print("\n" + "=" * 70)
print("Files written to: ./shell_workspace/")
print("Windows commands:")
print(" dir shell_workspace\\scripts\\")
print(" type shell_workspace\\scripts\\hello.py")
print()
print("macOS/Linux commands:")
print(" ls -la shell_workspace/scripts/")
print(" cat shell_workspace/scripts/hello.py")
print("=" * 70)
How LocalShellBackend Works
LocalShellBackend is a filesystem backend with host shell execution added.
Filesystem access
│
├── ls
├── read_file
├── write_file
├── edit_file
├── glob
└── grep
│
▼
LocalShellBackend
│
└── execute
The execute tool runs shell commands on the computer where your script is running. Its working directory is set to the configured root_dir, which in this example is:
./shell_workspace
Commands are executed through the system shell, and LocalShellBackend does not isolate those commands from the rest of the host machine. (docs.langchain.com)
Step 1: The Agent Creates a Real Python File
In the first prompt, the agent is asked to create:
/scripts/hello.py
Because your backend is configured with:
LocalShellBackend(
root_dir="./shell_workspace",
virtual_mode=True,
)
the virtual agent path:
/scripts/hello.py
maps to the real local path:
./shell_workspace/scripts/hello.py
The resulting project structure becomes:
your-project/
├── local_shell_demo.py
├── .env
└── shell_workspace/
└── scripts/
└── hello.py
The generated Python file contains:
for i in range(20):
print("Hello from LocalShellBackend!")
Step 2: The Agent Executes the File
Your first prompt instructs the agent to run:
python scripts/hello.py
Because the shell working directory is ./shell_workspace, the relative command path resolves to:
./shell_workspace/scripts/hello.py
Expected output:
Hello from LocalShellBackend!
Hello from LocalShellBackend!
Hello from LocalShellBackend!
...
The message should appear 20 times.
⚠️ The exact wording of the agent’s final explanation may vary because an LLM chooses its own tool calls and response text. However, the generated
hello.pyfile should print the message 20 times if the command succeeds.
Step 3: The Same Thread Reads the File Again
The second call uses the same thread ID:
"shell-thread-1"
The agent is instructed to:
- Read
/scripts/hello.py - Print the current directory
- List the
scriptsfolder
For Windows, the requested command is:
cd && dir scripts
For macOS or Linux, the requested command is:
pwd && ls -la scripts
This verifies that the shell command is running with ./shell_workspace as its working directory.
Step 4: A Different Thread Sees the Same File
The third call uses a new thread ID:
"shell-thread-2"
Even though this is a different thread, it can still access:
/scripts/hello.py
Why?
Because the file is stored on your real filesystem:
shell-thread-1
│
│ writes
▼
shell_workspace/scripts/hello.py
▲
│ reads and runs
│
shell-thread-2
The thread_id can distinguish agent execution or conversation state, but it does not isolate files saved through LocalShellBackend.
| Storage type | Shared across threads? | Reason |
|---|---|---|
StateBackend |
Usually no | Files are stored in thread-associated agent state. |
FilesystemBackend |
Yes | Files are stored on local disk. |
LocalShellBackend |
Yes | It uses real local filesystem storage plus shell execution. |
StateBackend is intended for thread-scoped ephemeral files, whereas filesystem-based backends operate on persistent local files. (docs.langchain.com)
Understanding virtual_mode=True
Your code uses:
virtual_mode=True
This allows the agent to use clean virtual paths:
/scripts/hello.py
instead of machine-specific paths such as:
C:\Users\YourName\project\shell_workspace\scripts\hello.py
or:
/home/yourname/project/shell_workspace/scripts/hello.py
For filesystem tools, virtual mode treats root_dir as the virtual root and blocks filesystem-tool traversal patterns such as .. and ~.
However, this is critical:
virtual_mode=Truedoes not sandbox shell commands.
An execute command can still potentially access paths outside ./shell_workspace, because host shell execution is unrestricted. (reference.langchain.com)
⚠️ Common Mistakes to Avoid
1. Assuming root_dir makes shell commands safe
This is not a security boundary:
LocalShellBackend(
root_dir="./shell_workspace",
virtual_mode=True,
)
It organizes file operations under the workspace, but shell commands can still access other host locations.
2. Exposing this agent to public users
Do not attach this backend directly to:
- Public web applications
- REST APIs
- Telegram, Discord, or Slack bots
- Shared servers
- Multi-user SaaS products
- User-submitted prompt workflows
Untrusted prompts can influence an agent to run unsafe commands.
3. Keeping secrets inside the workspace
Avoid this structure:
shell_workspace/
├── .env
├── credentials.json
├── private_key.pem
└── scripts/
Keep API keys and credentials outside the agent-accessible workspace whenever possible.
4. Assuming python works on every computer
Your code asks the agent to run:
python scripts/hello.py
This may fail on some machines:
| Environment | Possible command |
|---|---|
| Windows | python scripts/hello.py |
| macOS/Linux | python3 scripts/hello.py |
| Virtual environment | Path to that environment’s Python interpreter |
If python is unavailable in the shell, update the prompt to use:
python3 scripts/hello.py
A more portable advanced approach is to pass sys.executable into the prompt so the agent uses the exact Python interpreter that launched your script.
Optional Improvement: Add Execution Limits
Your current code uses the default shell execution settings. According to the current backend reference, the default timeout is 120 seconds and the default captured-output limit is 100,000 bytes. (docs.langchain.com)
For safer local experimentation, you can update the backend configuration:
agent = create_deep_agent(
model=model,
backend=LocalShellBackend(
root_dir="./shell_workspace",
virtual_mode=True,
timeout=20,
max_output_bytes=20_000,
),
)
| Setting | Purpose |
|---|---|
timeout=20 |
Stops commands that run longer than 20 seconds. |
max_output_bytes=20_000 |
Prevents extremely large command output from being captured. |
These settings reduce accidental resource usage, but they do not turn
LocalShellBackendinto a sandbox.
Final Takeaway
Your script demonstrates this workflow:
Agent writes code
↓
Code is saved on real disk
↓
Agent executes code on your host machine
↓
Agent reads and inspects files later
↓
Another thread accesses the same real files
✅ Use this pattern for
- Trusted local coding experiments
- Personal development tools
- Internal developer workflows
- Controlled CI environments with careful secret handling
❌ Do not use this pattern for
- Public APIs
- Production web applications
- Shared servers
- Multi-tenant applications
- Untrusted users or user-provided code
For a project that needs isolated code execution, use a proper sandbox, container, or VM-based backend rather than LocalShellBackend. (reference.langchain.com)
Top comments (0)