Quick Start
1
Isolate an explicit execute_code() call
Agent(sandbox=…) gives you the caller-invoked execute_code() API. It adds no tools and does not isolate tools= callables.2
Isolate everything the model runs
AgentFlow(run_on="docker") puts every step’s shell and file tools inside one shared container boundary, so model-driven tools route through the shared sandbox.Guarantee Matrix
Each surface below isolates a different thing — read the row before you rely on it.With the docker backend, cross-command filesystem state is provided:
write_file(), run_command(), and execute() share a /sandbox bind mount, so a file written by one is visible to the next. That mount does not expose the host filesystem (ls /Users fails), and code inside the container cannot swap a symlink into the mount to redirect the host’s write_file / read_file — both open descriptor-relative with O_NOFOLLOW at every path component. list_files() never returns host paths — even on platforms where TMPDIR sits behind a symlink (macOS /var → /private/var).File persistence per backend
ATrue return from write_file() means the bytes are on disk; Modal is honest about writing nothing and returns False.
Why sandbox= is not a capability grant
Agent(sandbox=…) is a restriction flag, not a way to hand the model an execution tool.
As of PR #4875, the subprocess backend’s
execute() path enforces blocked_imports on Python code. The check parses the source with ast (no substring false positives from strings, comments, or longer identifiers) and resolves aliases back to their real dotted names. See the sandbox warning for the end-to-end example.DockerSandbox is different — as of PR #4303 it does enforce the command-level parts of SecurityPolicy on the host before Docker is invoked.- Enforced on docker:
blocked_commands,allowed_commands,blocked_paths,allowed_pathson everyrun_command();max_output_sizeon bothrun_command()andexecute(). - Not enforced on docker:
allow_subprocess— the container itself is the boundary that clause approximates on the host, so blockingshinside the container would refuse every command rather than harden it. - Not a substitute for the container: the container still bounds where code runs; policy enforcement is additional, not a replacement for the isolation boundary.
praisonai-sandbox must be installed to run any sandbox backend — pip install praisonaiagents alone is not enough. Install a backend, e.g. pip install "praisonai-sandbox[docker]".Reliability: no leaked containers
Two guarantees close the container-leak story end to end — a timed-out execution and an exitedrun_on= script both leave zero running containers.
Verified before/after:
See Reclaim Stray Sandboxes for the full mechanism and Placement for how the leaks fit the placement story.
Common Patterns
- Explicit code execution
- Give the model a sandbox tool
- Whole-workflow container
Best Practices
Treat sandbox= as a restriction, not a grant
Treat sandbox= as a restriction, not a grant
Agent(sandbox=…) configures the explicit execute_code() API. It never hands the model a tool — add one yourself only when you intend the model to run code.Use a real container for untrusted or model-driven code
Use a real container for untrusted or model-driven code
The default subprocess backend is for trusted development only. For untrusted or model-driven execution, use
AgentFlow(run_on="docker"), LocalAgent(compute="docker"), or sandlock for a kernel-enforced local boundary.Remember autonomy= injects a host tool
Remember autonomy= injects a host tool
With
autonomy=True, the agent still carries the host execute_command tool. sandbox= does not protect that path — isolate the whole workflow with AgentFlow(run_on=…) instead.Install a sandbox backend first
Install a sandbox backend first
pip install praisonaiagents cannot execute any sandbox. Install praisonai-sandbox with the backend you need before relying on isolation.Related
Sandbox
Configure the explicit
execute_code() API and choose a backendShared Sandbox
Share one container across every agent with
run_on=
