DEV Community

Syeed Talha
Syeed Talha

Posted on

Deep Agents LocalShellBackend Tutorial Real Files and Host Shell Execution

AI agents become much more useful when they can do more than generate text.

For example, an agent can:

  1. Create a Python file.
  2. Save it to your real local filesystem.
  3. Run that file using a shell command.
  4. Read the file again in a later interaction.
  5. 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, and write_file
  • An execute tool 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

2. Create a .env file

Create a file named .env beside your Python script:

NVIDIA_API_KEY=your_nvidia_api_key_here
Enter fullscreen mode Exit fullscreen mode

3. Add private files and generated folders to .gitignore

.env
shell_workspace/
Enter fullscreen mode Exit fullscreen mode

💡 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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Because your backend is configured with:

LocalShellBackend(
    root_dir="./shell_workspace",
    virtual_mode=True,
)
Enter fullscreen mode Exit fullscreen mode

the virtual agent path:

/scripts/hello.py
Enter fullscreen mode Exit fullscreen mode

maps to the real local path:

./shell_workspace/scripts/hello.py
Enter fullscreen mode Exit fullscreen mode

The resulting project structure becomes:

your-project/
├── local_shell_demo.py
├── .env
└── shell_workspace/
    └── scripts/
        └── hello.py
Enter fullscreen mode Exit fullscreen mode

The generated Python file contains:

for i in range(20):
    print("Hello from LocalShellBackend!")
Enter fullscreen mode Exit fullscreen mode

Step 2: The Agent Executes the File

Your first prompt instructs the agent to run:

python scripts/hello.py
Enter fullscreen mode Exit fullscreen mode

Because the shell working directory is ./shell_workspace, the relative command path resolves to:

./shell_workspace/scripts/hello.py
Enter fullscreen mode Exit fullscreen mode

Expected output:

Hello from LocalShellBackend!
Hello from LocalShellBackend!
Hello from LocalShellBackend!
...
Enter fullscreen mode Exit fullscreen mode

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.py file 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"
Enter fullscreen mode Exit fullscreen mode

The agent is instructed to:

  1. Read /scripts/hello.py
  2. Print the current directory
  3. List the scripts folder

For Windows, the requested command is:

cd && dir scripts
Enter fullscreen mode Exit fullscreen mode

For macOS or Linux, the requested command is:

pwd && ls -la scripts
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

Even though this is a different thread, it can still access:

/scripts/hello.py
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

This allows the agent to use clean virtual paths:

/scripts/hello.py
Enter fullscreen mode Exit fullscreen mode

instead of machine-specific paths such as:

C:\Users\YourName\project\shell_workspace\scripts\hello.py
Enter fullscreen mode Exit fullscreen mode

or:

/home/yourname/project/shell_workspace/scripts/hello.py
Enter fullscreen mode Exit fullscreen mode

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=True does 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,
)
Enter fullscreen mode Exit fullscreen mode

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/
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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,
    ),
)
Enter fullscreen mode Exit fullscreen mode
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 LocalShellBackend into 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
Enter fullscreen mode Exit fullscreen mode

✅ 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)