Sandbox Guarantees

What each isolation surface actually guarantees — and what it does not

Each isolation surface in PraisonAI guarantees something different — this page states exactly what, so you never mistake a restriction flag for a real boundary.

Diagram

Choosing Isolation

Explicit execute_code calls

Model-driven execution

One agent’s tools

🤖 Agent

🔍 What needs isolation?

⚙️ Agent sandbox=

🐳 AgentFlow run_on=

📦 LocalAgent compute=

✅ Isolated

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.

    python
    from praisonaiagents import Agent
    
    agent = Agent(name="Coder", instructions="Run Python.", sandbox=True)
    result = agent.execute_code_sync("print(2 + 2)")
    print(result.stdout)
  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.

    python
    from praisonaiagents import Agent, AgentFlow
    
    flow = AgentFlow(
        agents=[Agent(name="Builder", instructions="Build and run code.")],
        run_on="docker",  # docker | e2b | modal | daytona | flyio | tenki | local
    )
    flow.start("Write a script that prints the first 10 primes, then run it")

Guarantee Matrix

Each surface below isolates a different thing — read the row before you rely on it.

SurfaceIsolates model-visible tools?Isolates tools= callables?Real container boundary?Enforces SecurityPolicy?
Agent(sandbox=True) (default subprocess backend)No — no tools auto-addedNoNo (subprocess only)Partial — blocked_imports now enforced on both run_command() and execute() (PR #4875, Python code, AST-based); other clauses still run_command()-only
Agent(sandbox=SandboxConfig(backend=...)) with real container backendNo — no tools auto-addedNoYes (via configured backend)Depends on backend
Agent(sandbox=SandboxConfig.docker(...))No — no tools auto-addedNoYes (container)Command-level clauses (blocked_commands, allowed_commands, blocked_paths, allowed_paths, max_output_size) enforced on the host before dispatch (PR #4303). allow_subprocess is deliberately not enforced — the container is the boundary that clause approximates.
Agent(sandbox=SandboxConfig.native()) (sandlock)Isolates only explicit execute_code()NoYes — Landlock + seccomp-bpf, the only real local boundary on LinuxKernel-enforced
`AgentFlow(run_on=”docker” \“e2b” \…)`Yes — every step’s shell/file tools route through the shared sandboxNo (callables stay in-process)YesDepends on provider
`LocalAgent(compute=”docker” \“e2b” \…)`Yes for that agent’s shell/file toolsNoYesDepends on provider
SandboxedAgent(compute=...)YesNoYesDepends on provider
praisonai code with PRAISON_SHELL=sandboxedYes — every execute_command (and ACP acp_execute_command), with or without shell operatorsNoYes — Seatbelt (macOS) / bwrap (Linux); writable only in PRAISONAI_WORKSPACE + $TMPDIR, secret-named env vars scrubbed, ~/.ssh ~/.aws ~/.gnupg ~/.config/gcloud ~/.azure maskedNo — refuses to run if the kernel jail isn’t proven enforcing
Docker write_file / read_file——Yes — symlink-race safe via open_in_sandbox() (O_NOFOLLOW per component)Mount cannot be redirected by a container-side symlink

File persistence per backend

A True return from write_file() means the bytes are on disk; Modal is honest about writing nothing and returns False.

BackendFile persistencewrite_file() return
subprocess / sandlockYesTrue when the bytes land on disk
dockerYes — shared /sandbox mountTrue when the bytes land on disk
sshYes — on the remote hostTrue when the bytes land on disk
e2b / daytona / novitaYes — on the cloud sandboxTrue when the bytes land on disk
modalNo — stateless, no file storageFalse — nothing is written; use execute(code=...)

Why sandbox= is not a capability grant

Agent(sandbox=…) is a restriction flag, not a way to hand the model an execution tool.


Reliability: no leaked containers

Two guarantees close the container-leak story end to end — a timed-out execution and an exited run_on= script both leave zero running containers.

GuaranteeHow it is achieved
A timed-out docker execution leaves no running containerEvery docker run is named praisonai-<execution_id>; on timeout the sandbox issues docker kill <name> before killing the client (PR #4109).
A run_on= instance is reclaimed at process exitComputeManagedAgent registers a weakref.finalize at provision that runs on GC and at interpreter exit (PR #4109); explicit ashutdown() detaches the finalizer to avoid double-release.

Verified before/after:

BeforeAfter
Containers after a timeout10
Containers after a run_on= script exits10
Ordinary run4242

See Reclaim Stray Sandboxes for the full mechanism and Placement for how the leaks fit the placement story.


Common Patterns

Explicit code execution

python
from praisonaiagents import Agent

agent = Agent(name="Analyst", instructions="Analyze data.", sandbox=True)
result = agent.execute_code_sync("import statistics; print(statistics.mean([1, 2, 3]))")
print(result.stdout)

Give the model a sandbox tool

python
from praisonaiagents import Agent

agent = Agent(name="Coder", instructions="Use run_code to answer.", sandbox=True)

def run_code(code: str) -> str:
    """Run Python in the agent's configured sandbox."""
    result = agent.execute_code_sync(code)
    return result.stdout or result.error or ""

agent.tools = [run_code]  # explicit, the way MCP() is
agent.start("Print the Python version")

Whole-workflow container

python
from praisonaiagents import Agent, AgentFlow

flow = AgentFlow(
    agents=[Agent(name="Builder", instructions="Build and run code.")],
    run_on="e2b",
)
flow.start("Create and run a script that lists installed packages")

Best Practices

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

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

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

pip install praisonaiagents cannot execute any sandbox. Install praisonai-sandbox with the backend you need before relying on isolation.


Related