Skip to main content
Sandbox backends provide isolated command execution environments with explicit shell control to prevent injection attacks while enabling shell features when needed.
The user runs commands through the agent; the sandbox backend isolates execution with explicit shell control.
Sandbox backends now ship in a dedicated praisonai-sandbox package. If you use the agent-level API (sandbox=True, SandboxConfig), the existing from praisonai.sandbox import ... imports and praisonai extras continue to work — the standalone package is installed for you. Install praisonai-sandbox directly only when you want the sandbox stack without the full praisonai wrapper (e.g. embedding a sandbox in a small script or another project).
Package layout. Sandbox backends and the vendor compute providers behind tools_run_on= / run_in= (LocalCompute, DockerCompute, E2BCompute, ModalCompute, DaytonaCompute, FlyioCompute, TenkiCompute) both live in the standalone praisonai-sandbox package as of PR #4092. pip install praisonai pulls it in transitively; you can also install it alone with pip install praisonai-sandbox when you don’t need the rest of the framework. All praisonai.sandbox.* and praisonai.integrations.compute.* imports keep working via a compatibility shim, so existing agent code does not need to change. See The praisonai-sandbox Package for details.

Quick Start

1

Simple Usage

Enable sandbox on the agent — subprocess backend is the default:
2

With Configuration

Pick a specific backend via SandboxConfig or the CLI --sandbox-type flag:
Default Docker image: the docker sandbox backend defaults to python:3.12-slim. Both Agent(tools_run_on="docker") and Agent(sandbox=SandboxConfig.docker()) use the same image, so run_in="docker" and tools_run_on="docker" hand you the same Python runtime. Pass an explicit image= argument to override.

How It Works


Docker Shared Workspace

DockerSandbox mounts a per-sandbox host directory at /sandbox and runs every command in it. That means:
  • write_file("data.txt", "…") places the file at /sandbox/data.txt inside the container.
  • The next run_command("cat data.txt") sees it.
  • execute(...) writes and reads the same directory — the script itself lands at /sandbox/code_<id>.py.
  • Each command is still --rm; persistence comes from the mount, not from a reused container.
The /sandbox mount is per-sandbox and persists for the sandbox’s lifetime, not per command. The SANDBOX_ROOT constant (value /sandbox) is importable from praisonai_sandbox.docker.

Docker labels: two lifecycles

PraisonAI tags Docker containers with two different labels, one per lifecycle. praisonai=managed is the sole input to DockerCompute.list_instances() — the label praisonai managed ps filters on. The two labels also carry different name shapes: managed instances are praisonai_<id>, ephemeral execution containers are praisonai-<uuid> (named as of PR #4109 so a timed-out execution can be killed by name). managed ps deliberately does not list sandbox-exec containers — tagging --rm containers as managed would surface a name that managed stop could never reclaim. To see them directly:
See Reclaim Stray Sandboxes and Placement.

Both spellings mean the same file

Pass either the container path (/sandbox/report.txt, the path you see inside the container) or the bare relative name (report.txt) to write_file / read_file — both resolve to the same file:
_sandbox_relative() (in praisonai_sandbox.docker) strips the exact /sandbox / /sandbox/… prefix before joining onto the sandbox root. A genuine subdirectory literally named sandbox still works — only the exact mount prefix is stripped.
Before PR #4107, write_file("/sandbox/report.txt", …) joined the prefix onto the root again and landed at /sandbox/sandbox/report.txtwrite_file returned True, yet cat /sandbox/report.txt could not find it. Both spellings now point at the same file.

Which Backend Should I Use?

Pick a backend based on where the code runs and how much you trust it.

All Built-in Sandbox Backends

PR #2003 exposes all built-in sandboxes through SandboxRegistry — selectable by string name from the CLI or Python. Plugin-registered backends (installed separately, resolved via the praisonai.sandbox entry-point group):

Two different destination sets

Sandbox backends and compute providers are two separate sets — a name valid for one is not automatically valid for the other. Agent(sandbox=…) sandbox backends (via SandboxManager / SandboxRegistry) run explicit execute_code() calls. They include the local-only boundaries: run_on= / compute= providers (via the compute bridge) place a whole flow’s tools or one LocalAgent’s tools. They are compute providers, not SandboxManager backends:
flyio and tenki are compute providers (run_on= / compute=) only — they are not Agent(sandbox=…) backends. sandlock, subprocess and ssh are sandbox backends only — they are not run_on= providers.As of PR #4071, novita is also accepted by run_on= (whole-loop hosting) and tools_run_on=, not just as a sandbox backend — so it no longer belongs on the “sandbox backends only” list. See Placement.
subprocess / local is not a security boundary. Its blocked-command list is bypassable by ordinary shell syntax (cat $(echo /etc/passwd) reads the file). For untrusted code, use docker, a cloud provider, or sandlock. See Placement.

Select by Name

Using the Novita backend

Run agent code in a Novita cloud sandbox by selecting the novita backend.
Use it directly without an Agent:
Novita requires pip install "praisonai-sandbox[novita]" (or pip install "praisonai[novita]") and a NOVITA_API_KEY environment variable.

Using the Daytona backend

Run agent code in a Daytona cloud sandbox by selecting the daytona backend.
Daytona requires pip install "praisonai[daytona]" (or pip install "praisonai-sandbox[daytona]") and a DAYTONA_API_KEY environment variable. Optional DAYTONA_API_URL and DAYTONA_TARGET env vars override the API endpoint and region.

Using the Novita backend

Run agent code in a Novita cloud sandbox by selecting the novita backend.
Novita requires pip install "praisonai-sandbox[novita]" (or pip install "praisonai[novita]") and a NOVITA_API_KEY environment variable. The sandbox reads credentials from the environment at startup and fails loudly with RuntimeError if the key is missing. The backend follows the same lazy-load + async lifecycle pattern as the E2B and Daytona backends.
The Novita backend runs agent code end to end in Novita’s cloud.

Installing Optional Backends

Only subprocess and sandlock ship in the base install — every other backend requires an optional extra. The standalone praisonai-sandbox package is the preferred install; the legacy praisonai[...] extras still work as compatibility shims.
As of PR #4073 the praisonai[e2b], praisonai[docker], and praisonai[daytona] extras are real and delegate to the sandbox package — earlier releases printed those install commands but the extras installed nothing.
The daytona backend is now a real cloud provider (DaytonaSandbox) backed by daytona-sdk, mirroring the Modal / E2B compute-provider pattern. It lives in the standalone praisonai-sandbox package — install it via praisonai-sandbox[daytona]. Set DAYTONA_API_KEY (and optional DAYTONA_API_URL) before selecting it.
The “Preferred install” column uses the praisonai-sandbox Package — install it when you want the sandbox subsystem without the full wrapper. Selecting an uninstalled backend exits with code 2 and prints a fix-it hint — no silent downgrade to subprocess:
You’ll never get an unexpected backend — if --sandbox-type X isn’t available, the CLI tells you exactly what to install.
For plugin-registered backends (like capsule), SandboxManager resolves the name through SandboxRegistry and raises a clearer error when the plugin is missing. When praisonai is installed but the plugin is not registered:
When praisonai itself is not installed (the registry import fails):

Third-Party Sandbox Plugins

Register custom sandboxes via the praisonai.sandbox entry-point group. Discovery is now driven from praisonai_sandbox._plugin_registry; the entry-point group name is unchanged.
After pip install, the new name appears alongside the built-ins when you call registry.list_names().

Example: Capsule (from praisonai-plugins)

The capsule backend is registered by praisonai-plugins under the praisonai.sandbox entry-point group. SandboxManager resolves the name via SandboxRegistry the first time the sandbox starts.

Using the standalone package

The built-in backends live in a dedicated package so you can use them without pulling in the full praisonai wrapper.
Nothing to change — the standalone package is installed alongside praisonai:

Which Sandbox Should I Pick?


Configuration Options

Shell Parameter Control

Security: No shell injection possible. String commands are parsed with shlex.split().
Set shell=True only when you need shell features (pipes, &&, globbing). With untrusted input always keep shell=False.

Decision Guide


Common Patterns

Backend Selection

Safe Data Processing

Resource Limits


Best Practices

Model-generated commands or user input should never use shell=True to prevent injection attacks. The default shell=False provides automatic protection.
If you must use shell=True, quote all dynamic arguments with shlex.quote():
Using argument lists avoids shell parsing entirely:
Choose the sandbox backend based on your isolation requirements:
  • Development: SubprocessSandbox for speed and convenience (no longer inherits host environment)
  • Production: DockerSandbox for container-level isolation
  • Remote: SSHSandbox for network-isolated execution
  • High Security: Always use Docker or SSH backends with shell=False
Catch exit code 2 from praisonai sandbox run --type <X> and either install the extra or fall back to --sandbox-type subprocess:
In CI pipelines, install the required extra before running:

Where Does It Run

Ask any agent where its thinking and tools actually execute

Sandbox

Agent-level sandbox=True and SandboxConfig

praisonai-sandbox Package

Standalone backends and the praisonai_sandbox import path

Sandbox CLI

CLI reference for praisonai sandbox run and praisonai sandbox shell