> ## Documentation Index
> Fetch the complete documentation index at: https://praison.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Sandbox Backends

> Execute commands safely with configurable shell control across different backends

Sandbox backends provide isolated command execution environments with explicit shell control to prevent injection attacks while enabling shell features when needed.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent

agent = Agent(
    name="Coder",
    instructions="Run shell commands safely",
    sandbox=True,
)
agent.start("List Python files in the current directory")
```

The user runs commands through the agent; the sandbox backend isolates execution with explicit shell control.

<Note>
  Files an agent writes inside a remote sandbox reach the user through the outbound media path — see [Remote-sandbox media](/docs/features/outbound-media-delivery#remote-sandbox-media-modal-e2b-daytona-fly-io-ssh).
</Note>

<Note>
  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).
</Note>

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    subgraph "Sandbox Command Execution"
        A[📝 Command] --> B{🔍 shell=?}
        B -->|False| C[🛡️ Safe Parse]
        B -->|True| D[⚠️ Shell Features]
        C --> E[✅ Execute]
        D --> E
    end
    
    classDef input fill:#6366F1,stroke:#7C90A0,color:#fff
    classDef decision fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef safe fill:#10B981,stroke:#7C90A0,color:#fff
    classDef shell fill:#8B0000,stroke:#7C90A0,color:#fff
    
    class A input
    class B decision
    class C,E safe
    class D shell
```

<Info>
  **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](https://github.com/MervinPraison/PraisonAI/pull/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](/docs/features/praisonai-sandbox-package) for details.
</Info>

## Quick Start

<Steps>
  <Step title="Simple Usage">
    Enable sandbox on the agent — subprocess backend is the default:

    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    from praisonaiagents import Agent

    agent = Agent(
        name="System Agent",
        instructions="Execute system commands safely",
        sandbox=True,
    )
    agent.start("List files in the current directory")
    ```
  </Step>

  <Step title="With Configuration">
    Pick a specific backend via `SandboxConfig` or the CLI `--sandbox-type` flag:

    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    from praisonaiagents import Agent, SandboxConfig

    agent = Agent(
        name="Data Agent",
        instructions="Process data in an isolated container",
        sandbox=SandboxConfig.docker("python:3.12-slim"),
    )
    agent.start("Run pip list and summarise installed packages")
    ```

    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    praisonai sandbox run --code "print('hello')" --type docker
    ```

    <Note>
      **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.
    </Note>
  </Step>
</Steps>

***

## How It Works

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant Agent
    participant Sandbox
    participant Shell
    participant Process
    
    Agent->>Sandbox: run_command(cmd, shell=False)
    alt shell=False (Safe)
        Sandbox->>Shell: shlex.split(cmd)
        Shell->>Process: exec(*argv)
    else shell=True (Features)
        Sandbox->>Shell: sh -c "cmd"
        Shell->>Process: shell execution
    end
    Process-->>Sandbox: Result
    Sandbox-->>Agent: SandboxResult
```

| Backend             | Use Case                   | Security Level                          |
| ------------------- | -------------------------- | --------------------------------------- |
| `SubprocessSandbox` | Local development, scripts | Medium (OS-level isolation, POSIX only) |
| `DockerSandbox`     | Production, untrusted code | High (container isolation)              |
| `SSHSandbox`        | Remote execution           | High (network isolation)                |

***

## 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.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import SandboxConfig
from praisonai_sandbox import DockerSandbox
import asyncio

async def demo():
    sandbox = DockerSandbox(config=SandboxConfig.docker("python:3.11-slim"))
    await sandbox.start()
    await sandbox.run_command("echo hello > f.txt")
    r = await sandbox.run_command("cat f.txt")
    print(r.stdout)   # "hello"
    await sandbox.stop()

asyncio.run(demo())
```

| Property                     | Value                                            |
| ---------------------------- | ------------------------------------------------ |
| Workspace inside container   | `/sandbox`                                       |
| Default working directory    | `/sandbox`                                       |
| Persistence across commands  | Yes, via bind mount                              |
| Persistence across sandboxes | No (`_temp_dir` is per `DockerSandbox` instance) |
| Host filesystem visible?     | No (`ls /Users` fails)                           |

<Note>
  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`.
</Note>

### 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.

| Label                    | Applied by                                                    | Container lifetime                                                       | Listed by `praisonai managed ps` |
| :----------------------- | :------------------------------------------------------------ | :----------------------------------------------------------------------- | :------------------------------- |
| `praisonai=managed`      | `DockerCompute` (managed instance for `run_on=` / `compute=`) | Long-lived; reclaimed by `managed stop`, the finalizer, or `ashutdown()` | ✅                                |
| `praisonai=sandbox-exec` | `DockerSandbox.execute()` (per-execution)                     | Ephemeral (`--rm`); killed on timeout by name                            | ❌                                |

The two labels also carry different name shapes: managed instances are `praisonai_<id>`, ephemeral execution containers are `praisonai-<uuid>` (named as of [PR #4109](https://github.com/MervinPraison/PraisonAI/pull/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:

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
docker ps --filter label=praisonai=sandbox-exec
```

See [Reclaim Stray Sandboxes](/docs/features/reclaim-stray-sandboxes) and [Placement](/docs/features/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:

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
await sandbox.write_file("/sandbox/report.txt", "done")
await sandbox.read_file("report.txt")            # -> "done"
await sandbox.run_command("cat /sandbox/report.txt")  # -> "done"
```

`_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.

| You pass              | Resolves to                                     |
| --------------------- | ----------------------------------------------- |
| `report.txt`          | `<temp_dir>/report.txt`                         |
| `/report.txt`         | `<temp_dir>/report.txt`                         |
| `/sandbox/report.txt` | `<temp_dir>/report.txt`                         |
| `sandbox/nested.txt`  | `<temp_dir>/sandbox/nested.txt` (a real subdir) |

<Warning>
  Before PR #4107, `write_file("/sandbox/report.txt", …)` joined the prefix onto the root again and landed at `/sandbox/sandbox/report.txt` — `write_file` returned `True`, yet `cat /sandbox/report.txt` could not find it. Both spellings now point at the same file.
</Warning>

***

## Modal caveats

`ModalSandbox` functions are stateless, so it has no file storage — the opposite of Docker's `/sandbox` mount above.

<Warning>
  **Modal backend has no file storage.** `write_file()` returns `False` and logs:

  > "Modal sandbox cannot persist files: write\_file(`<path>`) stored nothing. Pass the code to execute() directly, or use a sandbox backend with file storage (docker, subprocess, ssh)."

  `read_file()` returns `None`, and `execute_file()` fails with:

  > "Modal sandbox has no file storage, so `<path>` cannot be read. write\_file() cannot persist it either; pass the code to execute() instead."

  Pass code inline via `execute(code=...)`, or pick a backend with file storage — `docker`, `subprocess`, or `ssh`. Persisting files on Modal would require Modal Volumes, which the sandbox backend does not integrate yet.
</Warning>

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# ❌ Won't work on Modal — write_file returns False, execute_file cannot read it
await sandbox.write_file("script.py", "print('hi')")
result = await sandbox.execute_file("script.py")

# ✅ On Modal, pass the code directly
result = await sandbox.execute(code="print('hi')")

# ✅ Or use a backend that has file storage
config = SandboxConfig(sandbox_type="docker")  # or "subprocess", "ssh"
```

Unlike other backends, a `False` return from `write_file()` on Modal is expected — callers that branch on the result (`if not await sandbox.write_file(...)`) can now detect the failed write instead of trusting a false `True`.

***

## Docker SecurityPolicy enforcement

`DockerSandbox` enforces command-level `SecurityPolicy` clauses on every `run_command()` — the same enforcement `SubprocessSandbox` applies, run on the host before Docker is invoked ([PR #4303](https://github.com/MervinPraison/PraisonAI/pull/4303), closing [issue #4302](https://github.com/MervinPraison/PraisonAI/issues/4302)). A refused command returns `SandboxResult(status=FAILED, error=…)` and Docker is never contacted.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent, SandboxConfig
from praisonaiagents.sandbox import SecurityPolicy

agent = Agent(
    name="Coder",
    instructions="Only run trusted Python scripts.",
    sandbox=SandboxConfig(
        sandbox_type="docker",
        security_policy=SecurityPolicy(
            allowed_commands=["python"],
            blocked_paths=["/etc/passwd", "~/.ssh"],
            max_output_size=1_000_000,
        ),
    ),
)

# Refused on the host before Docker starts — "Command not in allowlist: sh":
agent.execute_code_sync("import os; os.system('sh -c \"cat /etc/passwd\"')")
```

Use it directly without an Agent:

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonai_sandbox import DockerSandbox
from praisonaiagents.sandbox import SandboxConfig, SecurityPolicy

config = SandboxConfig(
    sandbox_type="docker",
    security_policy=SecurityPolicy(allowed_commands=["python"]),
)
sandbox = DockerSandbox(image=config.image, config=config)
```

Each clause maps to a specific check on the host:

| Clause             | Enforced by `DockerSandbox`?                                    |
| ------------------ | --------------------------------------------------------------- |
| `blocked_commands` | Yes — on `run_command()` before dispatch                        |
| `allowed_commands` | Yes — on `run_command()` (has no container analogue)            |
| `blocked_paths`    | Yes — realpath + expanduser, on `run_command()`                 |
| `allowed_paths`    | Yes — realpath + expanduser, on `run_command()`                 |
| `max_output_size`  | Yes — on both `run_command()` and `execute()` output buffers    |
| `allow_subprocess` | **No** — the container is the boundary that clause approximates |

<Note>
  `allow_subprocess` is deliberately not enforced on Docker. `run_command()` always dispatches through `sh -c` inside the container by design, so refusing shell binaries would refuse every command and defeat the backend, not harden it. The container is the boundary that clause approximates on the host.
</Note>

<Warning>
  Before PR #4303, `DockerSandbox` accepted a `SecurityPolicy` and enforced **none** of it — moving from `subprocess` to `docker` silently discarded every command-level control (`blocked_commands`, `allowed_commands`, `blocked_paths`, `allowed_paths`, `max_output_size`). It now enforces them on the host before dispatch.
</Warning>

***

## Which Backend Should I Use?

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

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph TB
    Start([Where does code run?]) --> Q1{Run locally?}
    Q1 -->|Yes, fast dev| A[subprocess<br/>default, zero setup]
    Q1 -->|Yes, hardened| B[native / sandlock<br/>OS-level isolation]
    Q1 -->|Container isolation| C[docker<br/>production, untrusted code]
    Q1 -->|Remote server| D[ssh<br/>network isolation]
    Q1 -->|Cloud sandbox| E[modal / e2b / daytona / novita<br/>managed cloud runtime]
    E -->|Novita| K[novita]

    classDef start fill:#6366F1,stroke:#7C90A0,color:#fff
    classDef question fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef answer fill:#10B981,stroke:#7C90A0,color:#fff

    class Start start
    class Q1 question
    class A,B,C,D,E,K answer
```

***

## All Built-in Sandbox Backends

PR #2003 exposes all built-in sandboxes through `SandboxRegistry` — selectable by string name from the CLI or Python.

| Name         | Class               | Typical use                                                                   |
| ------------ | ------------------- | ----------------------------------------------------------------------------- |
| `docker`     | `DockerSandbox`     | Container isolation for production                                            |
| `subprocess` | `SubprocessSandbox` | Fast local development (default)                                              |
| `sandlock`   | `SandlockSandbox`   | Hardened local sandbox                                                        |
| `ssh`        | `SSHSandbox`        | Remote server execution                                                       |
| `modal`      | `ModalSandbox`      | Modal cloud sandboxes — **no file storage** ([Modal caveats](#modal-caveats)) |
| `daytona`    | `DaytonaSandbox`    | Daytona cloud sandboxes (requires the `daytona` extra + `DAYTONA_API_KEY`)    |
| `e2b`        | `E2BSandbox`        | E2B cloud code interpreter                                                    |
| `novita`     | `NovitaSandbox`     | Novita cloud sandboxes (requires the `novita` extra + `NOVITA_API_KEY`)       |

**Plugin-registered backends** (installed separately, resolved via the `praisonai.sandbox` entry-point group):

| Name      | Source                           | Typical use                                                               |
| --------- | -------------------------------- | ------------------------------------------------------------------------- |
| `capsule` | Plugin (via `praisonai.sandbox`) | Plugin-provided secure sandbox — install via `praisonai-plugins[capsule]` |

### 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:

| Backend      | One-line description                                                                                   |
| ------------ | ------------------------------------------------------------------------------------------------------ |
| `subprocess` | A separate process on this machine — **not a security boundary**                                       |
| `sandlock`   | A locked-down process (Landlock + seccomp-bpf) — the only real local boundary                          |
| `docker`     | A Docker container                                                                                     |
| `e2b`        | An E2B cloud sandbox                                                                                   |
| `modal`      | A Modal cloud sandbox — **no file storage**, use `execute(code=...)` ([Modal caveats](#modal-caveats)) |
| `daytona`    | A Daytona cloud sandbox                                                                                |
| `novita`     | A Novita cloud sandbox                                                                                 |
| `ssh`        | A remote machine over SSH (pass an `SSHSandbox(...)` object for the host)                              |

**`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:

| Provider  | One-line description                                                                                                               |
| --------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `local`   | This machine, in a local subprocess                                                                                                |
| `docker`  | A Docker container                                                                                                                 |
| `e2b`     | An E2B cloud sandbox                                                                                                               |
| `modal`   | A Modal cloud sandbox                                                                                                              |
| `daytona` | A Daytona cloud sandbox                                                                                                            |
| `flyio`   | A Fly.io machine                                                                                                                   |
| `tenki`   | A Tenki cloud sandbox                                                                                                              |
| `novita`  | A Novita cloud sandbox (accepted by `run_on=` / `compute=` since [PR #4071](https://github.com/MervinPraison/PraisonAI/pull/4071)) |

<Note>
  `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](https://github.com/MervinPraison/PraisonAI/pull/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](/docs/features/placement#where-run-on-and-compute-can-point).
</Note>

<Warning>
  `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](/docs/features/placement).
</Warning>

### Select by Name

<Tabs>
  <Tab title="CLI">
    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    # Run code in any registered backend
    praisonai sandbox run --code "print('hello')" --type subprocess
    praisonai sandbox run --code "print('hello')" --type e2b
    praisonai sandbox run --file script.py --type modal
    praisonai sandbox run --code "ls" --type docker --image python:3.12-slim
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    from praisonaiagents import Agent
    # New (recommended); the old praisonai.sandbox._registry path still works as a shim
    from praisonai_sandbox._registry import SandboxRegistry

    agent = Agent(name="Coder", instructions="Run code safely")

    registry = SandboxRegistry.default()
    sandbox_cls = registry.resolve("daytona")  # or docker, e2b, novita, modal, ssh, sandlock, subprocess
    sandbox = sandbox_cls()
    ```
  </Tab>
</Tabs>

### Using the Novita backend

Run agent code in a Novita cloud sandbox by selecting the `novita` backend.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
import os
from praisonaiagents import Agent, SandboxConfig

os.environ["NOVITA_API_KEY"] = "your-api-key"

agent = Agent(
    name="Cloud Runner",
    instructions="Run code in a Novita cloud sandbox.",
    sandbox=SandboxConfig(sandbox_type="novita"),
)
agent.start("Print the Python version")
```

Use it directly without an Agent:

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonai_sandbox import NovitaSandbox

sandbox = NovitaSandbox()
result = await sandbox.execute("print('Hello, World!')")
print(result.stdout)  # Hello, World!
```

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
praisonai sandbox run --code "import sys; print(sys.version)" --type novita
```

<Note>
  Novita requires `pip install "praisonai-sandbox[novita]"` (or `pip install "praisonai[novita]"`) and a `NOVITA_API_KEY` environment variable.
</Note>

### Using the Daytona backend

Run agent code in a Daytona cloud sandbox by selecting the `daytona` backend.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent, SandboxConfig

agent = Agent(
    name="Cloud Runner",
    instructions="Run code in a Daytona cloud sandbox.",
    sandbox=SandboxConfig(sandbox_type="daytona"),
)
agent.start("Print the Python version")
```

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
praisonai sandbox run --code "import sys; print(sys.version)" --type daytona
```

<Note>
  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.
</Note>

### Using the Novita backend

Run agent code in a Novita cloud sandbox by selecting the `novita` backend.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent, SandboxConfig

agent = Agent(
    name="Cloud Runner",
    instructions="Run code in a Novita cloud sandbox.",
    sandbox=SandboxConfig.novita(),
)
agent.start("Print the Python version")
```

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
praisonai sandbox run --code "import sys; print(sys.version)" --type novita
```

<Note>
  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.
</Note>

The Novita backend runs agent code end to end in Novita's cloud.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant User
    participant Agent
    participant Novita as Novita Sandbox
    participant Cloud as Novita Cloud

    User->>Agent: "Print the Python version"
    Agent->>Novita: execute(code, language="python")
    Novita->>Cloud: AsyncSandbox.create(timeout=…)
    Cloud-->>Novita: sandbox handle
    Novita->>Cloud: commands.run("python3 -c …")
    Cloud-->>Novita: stdout / exit_code
    Novita-->>Agent: SandboxResult(status=COMPLETED, stdout=…)
    Agent-->>User: "Python 3.11.9"
```

### 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.

<Note>
  As of [PR #4073](https://github.com/MervinPraison/PraisonAI/pull/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.
</Note>

| Backend      | Preferred install                                                | Also works (legacy shim)           |
| ------------ | ---------------------------------------------------------------- | ---------------------------------- |
| `subprocess` | Built in — no extra required                                     | —                                  |
| `sandlock`   | Built in — no extra required                                     | —                                  |
| `docker`     | `pip install 'praisonai-sandbox[docker]'`                        | `pip install 'praisonai[docker]'`  |
| `ssh`        | `pip install 'praisonai-sandbox[ssh]'`                           | `pip install 'praisonai[ssh]'`     |
| `modal`      | `pip install 'praisonai-sandbox[modal]'`                         | `pip install 'praisonai[modal]'`   |
| `daytona`    | `pip install 'praisonai-sandbox[daytona]'` (+ `DAYTONA_API_KEY`) | `pip install 'praisonai[daytona]'` |
| `e2b`        | `pip install 'praisonai-sandbox[e2b]'`                           | `pip install 'praisonai[e2b]'`     |
| `novita`     | `pip install 'praisonai-sandbox[novita]'` (+ `NOVITA_API_KEY`)   | `pip install 'praisonai[novita]'`  |
| `capsule`    | `pip install 'praisonai-plugins[capsule]'`                       | —                                  |

<Note>
  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`](/docs/features/praisonai-sandbox-package) package — install it via `praisonai-sandbox[daytona]`. Set `DAYTONA_API_KEY` (and optional `DAYTONA_API_URL`) before selecting it.
</Note>

The "Preferred install" column uses the [praisonai-sandbox Package](/docs/docs/features/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:

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
$ praisonai sandbox run --code "print('hi')" --type modal
Error: sandbox 'modal' is unavailable: <reason>
Available: ['subprocess', 'sandlock']
To install the optional backend:  pip install "praisonai-sandbox[modal]"
                                  # or (legacy):  pip install "praisonai[modal]"
Or explicitly choose another sandbox:  --sandbox-type subprocess
$ echo $?
2
```

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    A[--sandbox-type X] --> B{Registry}
    B -->|installed| C[✅ Run]
    B -->|missing| D[❌ Error + hint\nexit code 2]

    classDef input fill:#6366F1,stroke:#7C90A0,color:#fff
    classDef registry fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef success fill:#10B981,stroke:#7C90A0,color:#fff
    classDef error fill:#8B0000,stroke:#7C90A0,color:#fff

    class A input
    class B registry
    class C success
    class D error
```

<Note>
  You'll never get an unexpected backend — if `--sandbox-type X` isn't available, the CLI tells you exactly what to install.
</Note>

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:

```
ValueError: Unknown sandbox type: 'capsule'. Available: ['docker', 'subprocess', 'sandlock', 'ssh', 'modal', 'daytona', 'novita', 'e2b']
```

When `praisonai` itself is not installed (the registry import fails):

```
ValueError: Unknown sandbox type: 'capsule'. Supported built-ins: 'docker', 'subprocess', 'e2b', 'sandlock', 'ssh', 'modal', 'daytona', 'novita'. Install a plugin package that registers 'capsule' under the 'praisonai.sandbox' entry-point group (e.g. pip install praisonai-plugins[capsule]).
```

### 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.

```toml theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# pyproject.toml
[project.entry-points."praisonai.sandbox"]
my-sandbox = "my_pkg.sandbox:MySandbox"
```

After `pip install`, the new name appears alongside the built-ins when you call `registry.list_names()`.

### Example: Capsule (from praisonai-plugins)

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
pip install "praisonai-plugins[capsule]"
```

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent, SandboxConfig

agent = Agent(
    name="SecureRunner",
    instructions="Execute code inside the Capsule sandbox.",
    sandbox=SandboxConfig.capsule(),   # strict security policy applied automatically
)

agent.start("Run a Python script")
```

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.

<Tabs>
  <Tab title="Agent (default)">
    Nothing to change — the standalone package is installed alongside `praisonai`:

    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    from praisonaiagents import Agent, SandboxConfig

    agent = Agent(
        name="Coder",
        instructions="Run code inside a container",
        sandbox=SandboxConfig.docker("python:3.12-slim"),
    )
    agent.start("List files and print Python version")
    ```
  </Tab>

  <Tab title="Direct import">
    Import a backend directly when you don't need an Agent:

    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    from praisonai_sandbox import DockerSandbox
    from praisonaiagents.sandbox import SandboxConfig

    config = SandboxConfig.docker("python:3.12-slim")
    sandbox = DockerSandbox(image=config.image, config=config)
    ```
  </Tab>

  <Tab title="CLI">
    The package installs a `praisonai-sandbox` CLI for one-off runs and scripts:

    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    pip install praisonai-sandbox[docker]
    praisonai-sandbox --help
    ```
  </Tab>

  <Tab title="Legacy shim">
    Existing code keeps working — `praisonai.sandbox` transparently re-exports from `praisonai_sandbox`:

    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    from praisonai.sandbox import SubprocessSandbox   # shim → praisonai_sandbox
    ```
  </Tab>
</Tabs>

### Which Sandbox Should I Pick?

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph TB
    A[Need sandbox execution?] --> B{Where should code run?}
    B -->|Local machine| C{Need container isolation?}
    B -->|Remote server| D[ssh]
    B -->|Cloud provider| E{Which platform?}
    C -->|No, fast dev| F[subprocess or sandlock]
    C -->|Yes| G[docker]
    E -->|Modal| H[modal]
    E -->|Daytona| I[daytona]
    E -->|E2B| J[e2b]
    E -->|Novita| K[novita]

    classDef decision fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef choice fill:#10B981,stroke:#7C90A0,color:#fff

    class A,B,C,E decision
    class D,F,G,H,I,J,K choice
```

***

## Configuration Options

### Shell Parameter Control

<Tabs>
  <Tab title="shell=False (Default)">
    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    # String commands are parsed safely
    result = await sandbox.run_command("python script.py --arg value")

    # List commands are executed directly
    result = await sandbox.run_command(["python", "script.py", "--arg", "value"])
    ```

    **Security**: No shell injection possible. String commands are parsed with `shlex.split()`.
  </Tab>

  <Tab title="shell=True (Opt-in)">
    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    # Shell features available: pipes, redirects, globs
    result = await sandbox.run_command(
        "find . -name '*.py' | xargs grep 'TODO' > todos.txt",
        shell=True
    )

    # Environment variable expansion
    result = await sandbox.run_command(
        "echo $HOME && ls $PWD/*.log", 
        shell=True
    )
    ```

    **Security**: Shell evaluation enabled. Only use with trusted input.
  </Tab>
</Tabs>

<Warning>
  Set `shell=True` only when you need shell features (pipes, `&&`, globbing). With untrusted input always keep `shell=False`.
</Warning>

### Decision Guide

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph TB
    A[Need to run command?] --> B{Need pipes/globs?}
    B -->|No| C[Use shell=False]
    B -->|Yes| D{Input trusted?}
    D -->|Yes| E[Use shell=True]
    D -->|No| F[Sanitize OR use shell=False]
    
    classDef safe fill:#10B981,stroke:#7C90A0,color:#fff
    classDef caution fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef danger fill:#8B0000,stroke:#7C90A0,color:#fff
    
    class C,F safe
    class D,E caution
```

| Use Case                                   | Recommended `shell` Value |
| ------------------------------------------ | ------------------------- |
| Running a single executable with arguments | `False`                   |
| Pipelines (`grep \| sort`)                 | `True`                    |
| Globs and env-var expansion                | `True`                    |
| Untrusted / model-generated commands       | `False`                   |

***

## Common Patterns

### Backend Selection

<Tabs>
  <Tab title="Development">
    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    # New (recommended); shim: from praisonai.sandbox import SubprocessSandbox
    from praisonai_sandbox import SubprocessSandbox

    # Local development with subprocess
    sandbox = SubprocessSandbox()
    result = await sandbox.run_command("python test.py")
    ```
  </Tab>

  <Tab title="Production">
    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    # New (recommended); shim: from praisonai.sandbox import DockerSandbox
    from praisonai_sandbox import DockerSandbox

    # Isolated container execution
    sandbox = DockerSandbox(
        image="python:3.12-slim",
        timeout=30
    )
    result = await sandbox.run_command("python app.py", shell=False)
    ```
  </Tab>

  <Tab title="Remote">
    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    # New (recommended); shim: from praisonai.sandbox import SSHSandbox
    from praisonai_sandbox import SSHSandbox

    # Remote server execution
    sandbox = SSHSandbox(
        host="remote.server.com",
        username="runner"
    )
    result = await sandbox.run_command(["python", "remote_task.py"])
    ```
  </Tab>
</Tabs>

### Safe Data Processing

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# New (recommended); shim: from praisonai.sandbox import DockerSandbox
from praisonai_sandbox import DockerSandbox

sandbox = DockerSandbox()

# Process user data safely
async def process_file(filename):
    # Safe: no shell injection possible
    result = await sandbox.run_command([
        "python", "process.py", "--input", filename
    ], shell=False)
    return result.stdout

# Process with shell features when controlled
async def count_errors(log_file):
    import shlex
    # Trusted input, need shell features  
    result = await sandbox.run_command(
        f"grep 'ERROR' {shlex.quote(log_file)} | wc -l",
        shell=True
    )
    return int(result.stdout.strip())
```

### Resource Limits

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents.sandbox import ResourceLimits
# New (recommended); shim: from praisonai.sandbox import SubprocessSandbox
from praisonai_sandbox import SubprocessSandbox

limits = ResourceLimits(
    timeout_seconds=30,
    memory_mb=512
)

sandbox = SubprocessSandbox()
result = await sandbox.run_command(
    "python heavy_task.py",
    limits=limits,
    shell=False
)
```

***

## Best Practices

<AccordionGroup>
  <Accordion title="Always use shell=False for untrusted input">
    Model-generated commands or user input should never use `shell=True` to prevent injection attacks. The default `shell=False` provides automatic protection.

    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    # ✅ Safe with any user input
    user_script = request.get("script")
    result = await sandbox.run_command(f"python {user_script}", shell=False)

    # ❌ Vulnerable to injection
    result = await sandbox.run_command(f"python {user_script}", shell=True)
    ```
  </Accordion>

  <Accordion title="Quote arguments when building shell commands">
    If you must use `shell=True`, quote all dynamic arguments with `shlex.quote()`:

    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    import shlex

    filename = user_input  # Could contain special characters
    command = f"process.py --file {shlex.quote(filename)}"
    result = await sandbox.run_command(command, shell=True)
    ```
  </Accordion>

  <Accordion title="Prefer list form for complex commands">
    Using argument lists avoids shell parsing entirely:

    ```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    # ✅ Clear and injection-safe
    result = await sandbox.run_command([
        "python", "script.py", 
        "--input", input_file,
        "--output", output_file
    ], shell=False)

    # ❌ Requires careful quoting
    import shlex
    command = f"python script.py --input {shlex.quote(input_file)} --output {shlex.quote(output_file)}"
    result = await sandbox.run_command(command, shell=True)
    ```
  </Accordion>

  <Accordion title="Use appropriate backend for your security needs">
    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`
  </Accordion>

  <Accordion title="Handle missing backends explicitly in scripts and CI">
    Catch exit code 2 from `praisonai sandbox run --type <X>` and either install the extra or fall back to `--sandbox-type subprocess`:

    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    praisonai sandbox run --code "print('hello')" --type docker
    if [ $? -eq 2 ]; then
      echo "Docker not available, falling back to subprocess"
      praisonai sandbox run --code "print('hello')" --type subprocess
    fi
    ```

    In CI pipelines, install the required extra before running:

    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    pip install "praisonai-sandbox[docker]"
    praisonai sandbox run --code "print('hello')" --type docker
    ```
  </Accordion>
</AccordionGroup>

***

## Related

<CardGroup cols={2}>
  <Card title="Where Does It Run" icon="location-dot" href="/docs/features/where-does-it-run">
    Ask any agent where its thinking and tools actually execute
  </Card>

  <Card title="Sandbox" icon="shield-halved" href="/docs/features/sandbox">
    Agent-level sandbox=True and SandboxConfig
  </Card>

  <Card title="praisonai-sandbox Package" icon="box" href="/docs/features/praisonai-sandbox-package">
    Standalone backends and the praisonai\_sandbox import path
  </Card>

  <Card title="Sandbox CLI" icon="terminal" href="/docs/cli/sandbox">
    CLI reference for praisonai sandbox run and praisonai sandbox shell
  </Card>
</CardGroup>
