Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 11 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,42 +45,33 @@ result = agent.invoke({"messages": "Research the latest trends in AI and write a

## Configuration

Constructor options let you change the image, workspace path, command timeout, resource limits, outbound network access, and any extra `docker run` flags:
Constructor options let you change the docker image of the container, shared folder path, command timeout, resource limits, outbound network access, and any extra `docker run` flags:

```python
DockerSandbox(
image="python:3.12-bookworm", # default image (Debian-based, includes curl, etc.)
allow_outbound_traffic=True, # False → no network; True (default) → allow outbound traffic
workspace_dir="/path/to/project", # host dir for agent files; see note below
shared_dir="/path/to/project", # host folder shared with the container; see note below
timeout=120, # per-command timeout (seconds)
max_output_bytes=100_000, # combined stdout/stderr cap per command
memory="512m",
cpus=1.0,
memory="256m", # default memory limit
cpus=0.5, # default CPU limit
pids_limit=128,
auto_remove=True, # remove container on close()
extra_run_args=["--env", "FOO=bar"],
)
```

> [!NOTE]
> When `workspace_dir` is omitted, a temporary directory is created under the host temp folder and **removed on `close()`** when the sandbox owns it. Pass an explicit path to keep files after the container stops.
> Pass an explicit `shared_dir` path to keep files after the container stops. When omitted, a temporary directory is created within the host filesystem and **removed when the DockerSandbox is closed**.


## How it works

`DockerSandbox` implements the Deep Agents backend protocol by splitting work across the host and a container:

- **File tools** (`read`, `write`, `edit`, `grep`, `glob`, `ls`) run against a workspace directory on your machine.
- **`execute`** runs shell commands in a long-lived Docker container. The same directory is bind-mounted at `/workspace`, so files stay in sync between tools and commands.

On startup, the sandbox creates a container with conservative defaults:

- [`python:3.12-bookworm`](https://hub.docker.com/_/python) as the default image
- Outbound traffic allowed by default
- No elevated Linux privileges
- Read-only root filesystem (with small `tmpfs` mounts for `/tmp` and `/var/tmp`)
- Memory, CPU, and PID limits
The creation of a `DockerSandbox` object results in the starting of a long-running docker container. Every shell command executed by the agent is actually run **inside the container**, not on your host OS. Therefore, library installations, cURL downloads, and any other filesystem changes stay inside Docker, not on your host. The only link between the container and your machine is **shared_dir** (if provided), a folder on disk that is mounted at `/shared` (with that directory as the shell working directory) so you can share files between the agent and your host.

> [!NOTE]
> Docker does not allow bind-mounting a volume to `/` (it would hide the image’s system files and break the container). File tools (`read_file`, `write_file`, …) use virtual paths under `/` (for example `/sales.csv`); shell commands run in `/shared`, so the same file is `sales.csv` or `/shared/sales.csv` in the container.

> [!NOTE]
> The container is stopped and removed automatically when the Python process exits (`atexit`). Use a context manager (below) to tear down earlier.
Expand All @@ -104,14 +95,14 @@ print("Done!")

## Example

The [pizza agent](examples/pizza_agent.py) searches the web for a Neapolitan pizza recipe and writes it to a file in the workspace:
The [pizza agent](examples/pizza_agent.py) searches the web for a Neapolitan pizza recipe and writes it to a file in the shared folder:

```python
from deepagents import create_deep_agent
from deepagents_docker import DockerSandbox

backend = DockerSandbox(
workspace_dir="examples/data",
shared_dir="examples/data",
allow_outbound_traffic=True,
)

Expand Down Expand Up @@ -154,7 +145,7 @@ Contributions are welcome! Please feel free to open an issue or submit a pull re

## Security

Use this for trusted workloads and development, not as a hard multi-tenant boundary. Do not put secrets in the workspace. See [Deep Agents security](https://github.com/langchain-ai/deepagents?tab=security-ov-file).
Use this for trusted workloads and development, not as a hard multi-tenant boundary. Do not put secrets in the shared folder. See [Deep Agents security](https://github.com/langchain-ai/deepagents?tab=security-ov-file).

## License

Expand Down
140 changes: 140 additions & 0 deletions examples/data/sales.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
date,product,quantity,price
2026-01-01,pizza,10,12.99
2026-01-01,pasta,14,9.99
2026-01-01,salad,4,15.99
2026-01-01,espresso,22,2.50
2026-01-01,tiramisu,6,6.99
2026-01-02,pizza,18,12.99
2026-01-02,pasta,9,9.99
2026-01-02,calzone,5,11.99
2026-01-02,salad,7,15.99
2026-01-02,house wine,8,7.50
2026-01-03,pizza,24,12.99
2026-01-03,pasta,12,9.99
2026-01-03,margherita,15,10.99
2026-01-03,diavola,11,13.99
2026-01-03,espresso,31,2.50
2026-01-04,pizza,8,12.99
2026-01-04,pasta,16,9.99
2026-01-04,salad,5,15.99
2026-01-04,tiramisu,9,6.99
2026-01-05,pizza,21,12.99
2026-01-05,calzone,7,11.99
2026-01-05,margherita,19,10.99
2026-01-05,house wine,12,7.50
2026-01-05,espresso,28,2.50
2026-01-06,pizza,14,12.99
2026-01-06,pasta,11,9.99
2026-01-06,diavola,8,13.99
2026-01-06,salad,6,15.99
2026-01-07,pizza,27,12.99
2026-01-07,margherita,22,10.99
2026-01-07,pasta,13,9.99
2026-01-07,tiramisu,14,6.99
2026-01-07,house wine,15,7.50
2026-01-08,pizza,12,12.99
2026-01-08,calzone,9,11.99
2026-01-08,salad,8,15.99
2026-01-08,espresso,19,2.50
2026-01-09,pizza,19,12.99
2026-01-09,pasta,17,9.99
2026-01-09,diavola,10,13.99
2026-01-09,margherita,16,10.99
2026-01-10,pizza,32,12.99
2026-01-10,pasta,20,9.99
2026-01-10,calzone,11,11.99
2026-01-10,tiramisu,18,6.99
2026-01-10,house wine,20,7.50
2026-01-11,pizza,15,12.99
2026-01-11,salad,10,15.99
2026-01-11,espresso,25,2.50
2026-01-12,pizza,23,12.99
2026-01-12,margherita,14,10.99
2026-01-12,pasta,15,9.99
2026-01-12,diavola,12,13.99
2026-01-13,pizza,11,12.99
2026-01-13,calzone,6,11.99
2026-01-13,salad,9,15.99
2026-01-13,tiramisu,7,6.99
2026-01-14,pizza,26,12.99
2026-01-14,pasta,18,9.99
2026-01-14,margherita,20,10.99
2026-01-14,house wine,11,7.50
2026-01-14,espresso,34,2.50
2026-01-15,pizza,17,12.99
2026-01-15,diavola,9,13.99
2026-01-15,salad,12,15.99
2026-01-16,pizza,29,12.99
2026-01-16,pasta,14,9.99
2026-01-16,calzone,10,11.99
2026-01-16,tiramisu,11,6.99
2026-01-17,pizza,35,12.99
2026-01-17,margherita,25,10.99
2026-01-17,pasta,22,9.99
2026-01-17,house wine,18,7.50
2026-01-17,espresso,41,2.50
2026-01-18,pizza,13,12.99
2026-01-18,salad,7,15.99
2026-01-18,diavola,6,13.99
2026-01-19,pizza,20,12.99
2026-01-19,pasta,10,9.99
2026-01-19,calzone,8,11.99
2026-01-19,tiramisu,10,6.99
2026-01-20,pizza,22,12.99
2026-01-20,margherita,17,10.99
2026-01-20,salad,11,15.99
2026-01-20,house wine,9,7.50
2026-01-21,pizza,16,12.99
2026-01-21,pasta,19,9.99
2026-01-21,diavola,13,13.99
2026-01-21,espresso,27,2.50
2026-01-22,pizza,28,12.99
2026-01-22,margherita,21,10.99
2026-01-22,calzone,12,11.99
2026-01-22,tiramisu,15,6.99
2026-01-23,pizza,9,12.99
2026-01-23,pasta,8,9.99
2026-01-23,salad,5,15.99
2026-01-24,pizza,31,12.99
2026-01-24,pasta,24,9.99
2026-01-24,margherita,18,10.99
2026-01-24,house wine,16,7.50
2026-01-24,espresso,36,2.50
2026-01-25,pizza,25,12.99
2026-01-25,diavola,14,13.99
2026-01-25,calzone,9,11.99
2026-01-25,tiramisu,12,6.99
2026-01-26,pizza,14,12.99
2026-01-26,pasta,12,9.99
2026-01-26,salad,8,15.99
2026-01-27,pizza,19,12.99
2026-01-27,margherita,16,10.99
2026-01-27,house wine,10,7.50
2026-01-27,espresso,23,2.50
2026-01-28,pizza,27,12.99
2026-01-28,pasta,15,9.99
2026-01-28,diavola,11,13.99
2026-01-28,tiramisu,8,6.99
2026-01-29,pizza,12,12.99
2026-01-29,calzone,7,11.99
2026-01-29,salad,9,15.99
2026-01-30,pizza,30,12.99
2026-01-30,margherita,23,10.99
2026-01-30,pasta,21,9.99
2026-01-30,house wine,14,7.50
2026-01-30,espresso,38,2.50
2026-01-31,pizza,33,12.99
2026-01-31,pasta,18,9.99
2026-01-31,diavola,15,13.99
2026-01-31,tiramisu,16,6.99
2026-02-01,pizza,11,12.99
2026-02-01,salad,6,15.99
2026-02-01,espresso,20,2.50
2026-02-02,pizza,21,12.99
2026-02-02,margherita,19,10.99
2026-02-02,pasta,14,9.99
2026-02-02,calzone,8,11.99
2026-02-03,pizza,24,12.99
2026-02-03,diavola,12,13.99
2026-02-03,house wine,13,7.50
2026-02-03,tiramisu,9,6.99
9 changes: 7 additions & 2 deletions examples/pizza_agent.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
"""

This example agent finds the best neapolitan pizza recipe on the web and writes it to a file.

"""

from deepagents import create_deep_agent

from deepagents_docker import DockerSandbox

backend = DockerSandbox(
workspace_dir="examples/data",
shared_dir="examples/data",
allow_outbound_traffic=True,
)

agent = create_deep_agent(
model="openai:gpt-5.5",
backend=backend,
Expand Down
38 changes: 38 additions & 0 deletions examples/sales_analyst.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""

In this example, we create an agent that analyzes sales data and writes a report to a markdown file.
Please note that the agent will create a python script to analyze the data, in case of missing packages,
it will automatically install any necessary python packages to make the analysis possible (like pandas, matplotlib, etc.).
Finally, it will run the script to generate the report as well as the images.
"""

from deepagents import create_deep_agent

from deepagents_docker import DockerSandbox

backend = DockerSandbox(
shared_dir="examples/data",
allow_outbound_traffic=True,
)


agent = create_deep_agent(
model="openai:gpt-5.5",
backend=backend,
system_prompt="""You are a sales analyst assistant.""",
)

if __name__ == "__main__":
for step in agent.stream(
{
"messages": """
Analyze the "sales.csv" data and write a report (with charts) into a file called "sales_report.md".
Do not write any temporary files in our "shared" directory: write only the final report and put the images into a "img" directory.
"""
},
stream_mode="updates",
):
for update in step.values():
if update and (messages := update.get("messages")):
for message in messages:
message.pretty_print()
2 changes: 0 additions & 2 deletions src/deepagents_docker/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
"""Docker-backed sandbox backend for DeepAgents."""

from .backend import DockerSandbox
from .errors import DockerError

Expand Down
15 changes: 0 additions & 15 deletions src/deepagents_docker/_docker.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
"""Low-level helpers for invoking the Docker CLI."""

from __future__ import annotations

import json
import subprocess
from collections.abc import Sequence
Expand Down Expand Up @@ -55,17 +51,6 @@ def docker_available() -> bool:
return result.returncode == 0


def inspect_container_id(container_name: str) -> str:
"""Return the container ID for a running container name."""
result = run_docker(
["inspect", "--format", "{{.Id}}", container_name],
)
if result.returncode != 0:
msg = result.stderr.strip() or f"failed to inspect container {container_name!r}"
raise DockerError(msg)
return result.stdout.strip()


def format_docker_error(result: DockerRunResult) -> str:
"""Combine stderr/stdout into a single error string."""
detail = (result.stderr or result.stdout).strip()
Expand Down
Loading
Loading