Sandboxes and Execution
Give an agent a sandbox and it gets one more tool,execute: a shell command
run inside an isolated container, not on your machine. By the end of this page
you will have an agent that runs Python in Docker against a file from its
workspace, writes a result back, and leaves a record of every command.
Every output on this page is what the code printed when it was run, with
Docker on Linux and
gpt-5.4-mini. The model writes its own commands, so the
commands and the wording will differ on your run.A first run in Docker
1
Install the Docker extra
python:3.12-slim, which can take a minute; later runs start in seconds.2
Put a file in the workspace
The agent’s workspace is
./workspace/files/ by default. Create
workspace/files/sales.csv:3
Run the agent
workspace/files/total.txt now holds 450. The command ran in a
container; the file it wrote came back to your workspace.How it works
1
The tool appears only with a sandbox
execute is added when governance is on (the default) and the
configured provider can run commands. With governance turned off
("enabled": False), or with provider none, the agent has no way to run
a command.2
Each command is authorized twice
The tool call is
sandbox.execute; the command inside the sandbox is
process.exec, on the sandbox surface (or host for the
local provider).
The development profiles allow sandboxed commands; strict-production
allows none without a rule of yours (Policies).3
One sandbox per run
The first command opens a session; later commands in the same run reuse it,
so files persist between them. When the run ends — success, error, a pause
for approval, or a cancel — the session is closed and the container removed.
A resumed run opens a fresh one.
4
Workspace files go in, text outputs come back
Before each command, workspace files that changed since the last copy are
uploaded into the sandbox’s working directory (
/workspace). After it, text
files the command created or changed are copied back as governed workspace
writes. The sandbox never mounts your workspace.command (run with sh -c in the working directory) and an
optional timeout in seconds: 60 by default, at most the agent’s
tool_call_timeout (180 by default). A command that exits non-zero or times
out comes back to the model as an error, with its exit code, stdout and
stderr.
With your own strict policy
Astrict policy allows nothing it does not name, the sandbox included. These
three rules let the agent use execute and run commands only inside the
sandbox:
process.exec decision is the model’s command. The last one is the
runtime’s own: after a command, it lists what changed in the sandbox to copy
it back to the workspace. That listing is governed too, as
sandbox.workspace.sync (part of sandbox.*), and it is not judged as a
command the agent asked for, so rules on shell
commands do not apply to it.
If no rule names it, as in a strict policy that allows commands but not the
sync, it follows the decision for the command that triggered it: the trace
shows an allow with the reason follows_command and the command’s request id,
not a rule match. A rule you write for it still decides, so a deny rule stops
the copy-back (the model is told so, and the command’s files stay in the
sandbox). Every file it copies back is still decided by the workspace file
rules, below.
Leave out sandbox.* and the sandbox cannot start; leave out the
process.exec rule and every command is refused. sandbox.network.configure
is part of sandbox.* here: to keep the network behind a person, add an
ask rule for it, as the development profiles do (Policy
reference).
Under these three rules alone no workspace file is copied into the sandbox or
back: each copy is checked as a read or a write of that file, and a strict
policy refuses what it does not name. To let files through, add the two file
rules, with a path target to narrow them:
Rules on what a command runs
Aprocess.exec rule can also name a command by its first words, with a
command matcher: deny rm, ask before git push, allow only git status
and git log. Only a plain chain of commands (git status && rm -rf build)
is split and matched command by command. Anything else, such as a redirect,
$(...), a variable, a glob, eval or sh -c, is one unreadable command:
no rule allows it, and where the policy has command rules it is asked about
(refused in strict), so eval "rm -rf ~" does not pass a deny on rm.
Wrappers are not looked inside: sudo rm is a command named sudo. Command
rules decide when to ask; what a command can touch is the sandbox’s to decide.
See Rules on shell commands.
Files between the workspace and the sandbox
What is copied, and what is not:
Limits: 500 files, 2 MB per file and 20 MB per copy. To copy less, give glob
patterns over workspace paths (
* crosses folders; no include copies
everything not excluded). The patterns apply both ways: a file outside them is
not copied in, and a file a command creates or changes outside them is not
copied back either (it is skipped, with the reason “outside the bridge’s
include/exclude patterns”):
enable_workspace_files: False nothing is copied. With the local
provider nothing is copied either: commands already run on the machine, so
point its working_dir at the workspace directory instead.
The network is off
A sandbox has no network unless its manifest asks for it, and the policy allows that. Here the agent looks at its own sandbox:LLM_API_KEY is not there.
The variables listed come from the python:3.12-slim image itself
(GPG_KEY is the ID of the key Python’s releases are signed with).
To turn the network on, ask for it in sandbox_manifest. The development
profiles ask a person first (rule ask_sandbox_network), so the run pauses
before its first command:
"allow" opens the whole internet. To allow only named hosts, use
"allowed_hosts": ["pypi.org", "*.pythonhosted.org"] on a provider that
enforces a host allowlist; Docker, E2B, Vercel and local cannot, and the
agent is refused when it is built rather than given more network than it asked
for. Which providers enforce what is on
Sandbox providers.
What the manifest can ask for
sandbox_config says who runs the sandbox; sandbox_manifest says what
it is. Each thing it asks for is a policy question when the session opens:
A setting the chosen provider does not apply is refused when the agent is
built, naming the provider and the setting: a workspace mount works on Docker
only, a host allowlist on Modal and
http only, CPU and memory limits
everywhere but E2B. There is no list of paths a sandboxed command may read or
write: it reaches the sandbox’s own disk and, on Docker, the workspace mount;
filesystem_policy was removed, since no provider applied it. lifecycle does not change the agent’s own sandbox, which is
closed when its run ends: cleanup ("always", "on_success", "never")
applies only to a one-off command run through the sandbox service without a
session, and nothing acts on snapshot yet.
A manifest with an unknown field, or one that sets provider or sandbox_id,
is refused when the agent is built.
Secrets stay out
A sandbox gets only the environment you give it insandbox_manifest["environment"]["plain"] — never your process’s environment,
and never your model key or provider credentials, which stay in your process
with the policy and the telemetry. Docker also never mounts the Docker socket,
system directories or credential folders such as ~/.ssh or ~/.aws, whatever
the policy says (Security model).
plain: the model’s commands can read it and print it.
environment.secret_refs is refused when the agent is built, since no built-in
provider delivers secrets to a command. Keep a secret in a tool that runs in your
process, and let the model call the tool.
What a command’s record holds
Each command is in the run’s trajectory under the tool call that ran it (call["executions"]):
call["workspace_sync"] lists what was copied in, written back and skipped
(with the reason), and trajectory["totals"]["executions"] counts sessions,
commands, failures and timeouts. The trace also has the session itself, as
sandbox_session_created and sandbox_session_closed events: provider, the
provider’s own name for the sandbox (sandbox_ref, the container ID for
Docker), image, network, working directory, and on close the number of
commands, how long it was open, and lost. See
Observability.
A sandbox that dies
A sandbox can die under a run: the provider stopped it, its lifetime ran out, someone removed it, or a command ignored its time limit and the sandbox was stopped. That is not reported as a slow command. The model is told:lost: true. A run that pauses for approval, or whose process
dies, continues where it stopped (Durable runs).
Options
Every agent setting is in the agent settings reference;
every capability in the policy reference.
When things go wrong
The agent never calls execute
The agent never calls execute
The tool is not there. Check that governance is not turned off
(
"enabled": False) and that governance_config has a sandbox_config
whose provider runs commands (none does not). Do not add
"enabled": True to fix it: without a profile that switches to
interactive-dev. A model that asks for it anyway gets:Command timed out after 1s
Command timed out after 1s
The command ran longer than the The model can retry with a longer
timeout the model gave it. It is killed
inside the sandbox and the model receives:timeout, up to tool_call_timeout.Command exited with 2
Command exited with 2
The command failed; the model sees its
stderr and usually corrects itself:Temporary failure in name resolution / pip install fails
Temporary failure in name resolution / pip install fails
The sandbox has no network, as intended. Either build an image with what
your tasks need and name it (
"sandbox_manifest": {"image": "..."}), or
allow the network in the manifest (above).ValueError: The docker sandbox does not enforce a network host allowlist
ValueError: The docker sandbox does not enforce a network host allowlist
Raised when the agent is built with Use
allowed_hosts on a provider that
cannot keep it:"default": "allow", or Modal
or your own http service.Sandbox image … is not present and pulling is disabled
Sandbox image … is not present and pulling is disabled
With
"pull": "never", the image must already be on the Docker host. The
command’s call fails with:Next
Sandbox providers
Docker, E2B, Daytona, Modal, Vercel, your own service, or this machine.
Policies
Allow, ask or deny each command, image and network request.
Approvals
Pause a run for a person, and resume it later.
Budgets
Cap dollars, tokens, calls and sandbox seconds.
Security model
What is enforced, and where the boundaries are.
Privacy
What the record of a command keeps.