boxd-langchain
boxd sandbox integration for Deep Agents. Because Deep Agents compiles to a LangGraph graph, this works across LangChain, LangGraph and Deep Agents.
A boxd machine is a full Ubuntu 24.04 VM (KVM microVM) with a persistent disk.
It boots in about a second, and can be hibernated, snapshotted and forked.
BoxdSandbox runs the agent's shell commands and file operations inside one.
Install
pip install boxd-langchain
# or: uv add boxd-langchain
Create an API key at app.boxd.sh/keys and export it:
export BOXD_API_KEY="bxd_..."
Inside a boxd machine you can skip the key: the SDK uses the machine's own credential.
Quick start
BoxdSandbox.create() makes a new machine and waits until it takes commands.
Used as a context manager, it deletes the machine when the block ends.
delete_after is a safety net: boxd deletes the machine after that many
seconds even if your process dies first.
from boxd_langchain import BoxdSandbox
with BoxdSandbox.create(delete_after=3600) as backend:
result = backend.execute("python3 --version")
print(result.output)
Use with Deep Agents
from deepagents import create_deep_agent
from langchain_anthropic import ChatAnthropic
from boxd_langchain import BoxdSandbox
with BoxdSandbox.create(delete_after=3600) as backend:
agent = create_deep_agent(
model=ChatAnthropic(model="claude-sonnet-5-5"),
system_prompt="You are a coding assistant with a Linux sandbox. Work in /home/boxd.",
backend=backend,
)
result = agent.invoke(
{
"messages": [
{
"role": "user",
"content": "Write hello.py that prints hello, run it and show me the output.",
}
]
}
)
print(result["messages"][-1].text)
Use a machine you already have
Pass a boxd.Boxd client and a machine id (or name). The backend never deletes
a machine it did not create, so the machine and its disk outlive the agent
run. Hibernate it between runs to stop paying for memory and CPU.
from boxd import Boxd
from boxd_langchain import BoxdSandbox
client = Boxd() # reads BOXD_API_KEY
machine = client.machines.create(labels={"app": "agent"})
client.machines.wait_until_ready(machine.id)
backend = BoxdSandbox(client=client, machine_id=machine.id)
backend.write("/home/boxd/notes.md", "# notes\n")
print(backend.execute("ls -l /home/boxd").output)
client.machines.hibernate(machine.id) # sleep, keep the disk
client.machines.delete(machine.id) # or delete it for good
Deep Agents Code
Installing this package registers a boxd sandbox provider for the
deepagents-code (dcode) terminal agent:
dcode install boxd-langchain --package
export BOXD_API_KEY="bxd_..."
dcode --sandbox boxd
dcode --sandbox boxd --sandbox-id <machine> reattaches to a machine and wakes
it if it is stopped, suspended or hibernated. --sandbox-snapshot-name <name>
restores a boxd snapshot instead of booting the default image.
| Setting | Default | Purpose |
|---|---|---|
BOXD_API_KEY (or DEEPAGENTS_CODE_BOXD_API_KEY) |
none | API key |
BOXD_REGION (or DEEPAGENTS_CODE_BOXD_REGION) |
boxd's default | region to create machines in |
BOXD_IMAGE |
the boxd image | image for new machines |
BOXD_DELETE_AFTER |
never | seconds until boxd deletes a new machine |
name, labels, image, delete_after, workdir and command_timeout can
also be set as provider params in ~/.deepagents/config.toml. A config entry
replaces what the package registers, so it repeats the provider's
class_path and capabilities next to its params:
[sandboxes.providers.boxd]
class_path = "boxd_langchain.provider:BoxdProvider"
working_dir = "/home/boxd"
package = "boxd-langchain"
supports_snapshot_name = true
[sandboxes.providers.boxd.params]
labels = { team = "ai" }
delete_after = 86400
dcode deletes a machine it created when it shuts down cleanly. Set
delete_after anyway: a dcode -n run can exit before its cleanup runs (it
did in testing with deepagents-code 0.1.88), and boxd then still deletes the
machine on time.
What you get
BoxdSandbox subclasses deepagents.backends.sandbox.BaseSandbox, so it
implements the full SandboxBackendProtocol:
| Method | Backed by |
|---|---|
execute() |
client.machines.exec, run as bash -c '<command>' in /home/boxd |
upload_files() / download_files() |
client.machines.fs.write / client.machines.fs.read |
ls / read / write / edit / glob / grep / delete |
inherited from BaseSandbox |
id |
the boxd machine id |
Async variants (aexecute, aupload_files, ...) come from the base class,
which runs the sync methods in a thread.
Options
| Argument | Default | Purpose |
|---|---|---|
machine_id |
required | machine id or name (BoxdSandbox(...) only) |
client |
Boxd() |
boxd client; reads BOXD_API_KEY and BOXD_REGION |
workdir |
/home/boxd |
directory commands run in |
timeout |
1800 |
default command timeout in seconds; 0 waits forever |
shell |
bash |
shell each command runs under; None uses the machine's /bin/sh |
region |
client's region | region the machine lives in |
BoxdSandbox.create() also takes name, image, from_snapshot, labels,
delete_after, ready_timeout, and passes anything else (vcpu, memory,
env, org, isolated, networks, spec, ...) to client.machines.create.
Good to know
- Commands run as the
boxduser, who has passwordlesssudo. The default image has Python 3, git, Node, Docker, ripgrep and more, so the inherited file tools (which shell out topython3) work out of the box. On your own image, make sure it hasbashandpython3, or passshell=None. - Everything else boxd offers (snapshots, forks, egress allowlists, secrets,
ports and HTTPS URLs) is on
backend.client, the plain boxd SDK client.
Examples
Each one runs against real boxd. They read the model from LC_MODEL
(default anthropic:claude-sonnet-5-5; any LangChain chat model works, for
example google_genai:gemini-3.8-flash).
examples/quickstart.py: a Deep Agent writes and runs code on a fresh machine, which is deleted afterwards.examples/persistent_workspace.py: a named machine the agent comes back to. Each run ends by hibernating it, and the next run wakes it with its files intact.examples/parallel_forks.py: set a project up once, fork the machine, and let two agents try two approaches in parallel.
pip install boxd-langchain langchain-anthropic
export BOXD_API_KEY=... ANTHROPIC_API_KEY=...
python examples/quickstart.py
Errors
The agent sees command failures; your code sees credential failures.
| What happened | What you get |
|---|---|
| The command exited non-zero | ExecuteResponse with that exit code and the output |
| The command ran past its timeout | boxd kills its whole process group; exit code 124 |
| boxd failed to run the command (network, machine gone, ...) | ExecuteResponse with exit code 1 and Error executing command (<type>): ... as output, so the agent can see it |
| A file upload or download failed | a per-file error: file_not_found, is_directory, permission_denied or invalid_path, else the boxd error text |
| boxd rejected the API key | boxd.AuthenticationError, raised from every method |
| The key may not use this machine | boxd.PermissionDeniedError, raised from every method |
A negative timeout |
ValueError |
create(): the machine didn't accept commands within ready_timeout |
TimeoutError; the backend deletes the machine first (if that fails too, delete_after still removes it) |
Limits
| Limit | |
|---|---|
| Command timeout | 30 minutes by default (timeout=); 0 waits forever |
| Output | stdout and stderr come back together (stdout, a newline, stderr), not truncated by this package |
| Machine ready | create() waits up to 180 seconds (ready_timeout=); it usually takes about a second |
| Machine size | 1, 2, 4 or 8 vCPUs with 4 GB of memory each, up to your organization's quota (vcpu= on create()) |
| Machines per backend | one; fork with backend.client.machines.fork(backend.id) for more |
| Standard tests | passes the langchain-tests sandbox suite against real boxd, except 7 tests that fail on any BaseSandbox with deepagents 0.7 (version skew between the two packages) |
How it works
BoxdSandboxsubclasses Deep Agents'BaseSandboxand implements the three methods it needs:execute,upload_filesanddownload_files. The file tools the agent uses (ls,read,write,edit,glob,grep) come from the base class and run throughexecute.executecalls the boxd SDK'smachines.exec, asbash -c '<command>'in/home/boxd. A machine that is asleep wakes on the first command.upload_filesanddownload_filesusemachines.fs, which writes as the machine's user, creates missing parent directories and replaces a file by a rename, so readers see the old contents or the new. boxd writes in place only where a rename can't work (a file with several hard links, another user's file, or a directory theboxduser can't write to).BoxdSandbox.create()owns the machine and deletes it when thewithblock ends. A backend made from an existing machine id never deletes it.BoxdProvideris the same backend behind thedeepagents-codeprovider interface, registered through a package entry point.
Development
uv sync --group test
make test # unit tests, no network
make lint # ruff + ty
BOXD_API_KEY=bxd_... make integration_tests
The integration tests create real machines (named t-langchain-*, labelled
boxd-langchain=integration-test, each with a one-hour delete_after) and
delete them again. They run the langchain-tests standard sandbox suite,
lifecycle and error tests, and, when ANTHROPIC_API_KEY is set, a real Deep
Agents run. They are skipped when BOXD_API_KEY is not set.
License
MIT
Metadata
Release files for boxd-langchain 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| boxd_langchain-0.1.0.tar.gz | 750.6 kB | Without provenance | Details for boxd_langchain-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| boxd_langchain-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for boxd_langchain-0.1.0-py3-none-any.whl |
Total release size: 766.3 kB
Release files / boxd_langchain-0.1.0.tar.gz
| Download URL | boxd_langchain-0.1.0.tar.gz |
|---|---|
| Size | 750.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d14b85e091df1040f8e96168fa307c122a55799af410f5e339f54579fa3ae8aa
|
|
BLAKE2b-256 checksum How to use checksums |
83a4213141205c217a38b702d84349ce8c8f4a268476060add3de5fd42db1b43
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.9
|
Release files / boxd_langchain-0.1.0-py3-none-any.whl
| Download URL | boxd_langchain-0.1.0-py3-none-any.whl |
|---|---|
| Size | 15.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e381f427bd4ef30c6d97f3658911ae98a2146161b3cf63a71ff318a7f41a42f3
|
|
BLAKE2b-256 checksum How to use checksums |
4d006e3e8a36566f0ebe8cd7fb7b77ec547b69228e0fb89ed4dd19936f0381ad
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.9
|
Security for boxd-langchain 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.