execute_code() API for running code in an isolated environment — you invoke it directly, the model does not call it on its own.
Sandbox backends ship in the standalone
praisonai-sandbox package. pip install praisonaiagents alone cannot execute any sandbox — install a backend first, e.g. pip install "praisonai-sandbox[docker]". Installing praisonai gives you every backend. See the praisonai-sandbox Package for the from praisonai_sandbox import … import path.Need model-generated code to call your registered tools? See Code Execution with Tools. A sandbox can now service tool calls via a
CodeToolBridge — the script runs isolated while tool calls are gated in the parent by the same allow-list and approval gate.Quick Start
1
Simple Usage
Enable the sandbox with a single line, then invoke it explicitly with
execute_code_sync().2
With Configuration
Use factory methods for specific sandbox types.
3
Full Configuration
Complete control over sandbox settings.
Agent Sandbox API
Whensandbox is configured, agents gain powerful execution capabilities:
SandboxMixin API
What sandbox= configures (and what it does NOT)
Agent(sandbox=…) configures the explicit, caller-invoked execution API only — agent.execute_code(), agent.execute_code_sync(), and agent.run_shell_command(). It does not add any tools to agent.tools, so the model gains no new capability from the flag alone.
Verify that no tools are attached — the model sees an empty tool list from sandbox= alone:
MCP(). Giving the model a sandboxed execution tool is a deliberate act by the caller, never a side effect of sandbox=:
There is no first-class
SandboxTool(...) helper yet — wrap agent.execute_code_sync() in your own function as above. For a model-visible shell tool with a real container boundary, prefer AgentFlow(run_on="docker") (see Shared Sandbox).Scope: what sandbox= isolates
Agent(sandbox=…) isolates only code you invoke through the explicit agent.execute_code(...) / run_shell_command(...) API.
It does not isolate:
- Python callables passed via
tools=— ordinary in-process functions that run on the host. - Tools attached by other layers — e.g.
autonomy=Trueinjects a hostexecute_commandtool that runs unsandboxed regardless ofsandbox=.
Sandbox Backends
New import path
Backends now live inpraisonai_sandbox. The old praisonai.sandbox path still works as a shim.
pip install "praisonai-sandbox[docker]" — see the praisonai-sandbox Package.
Choose the right backend for your security and performance needs:
- Subprocess (Local)
- Docker
- E2B Cloud
- Native OS
- Sandlock
- SSH Remote
- Modal Cloud
- Daytona
- Novita Cloud
- Capsule (Plugin)
Fastest, minimal isolation - development only
Zero import cost when unused. The sandbox subsystem — including
SandboxConfig, SandboxManager, security checks, and all backends — is lazy-loaded. from praisonaiagents import Agent does not load any sandbox module. Sandbox modules are only imported the first time you set sandbox=True (or pass a SandboxConfig) on an Agent, or call a sandbox method. This keeps the default import Agent path light for agents that never execute code.What SecurityPolicy actually controls in subprocess
Resource limits in practice
- Linux / macOS
- Windows
- Docker
POSIX When limits are exceeded:
setrlimit mapping enforces hard limits:- Memory: Process is killed by the kernel
- Processes:
fork()fails with EAGAIN - Files:
open()fails with EMFILE - Timeout: Whole process group killed with SIGKILL
Timeout handling
On POSIX the whole process group is killed (killpg(SIGKILL)); on Windows only the leader is killed.
Subprocess execution flow
Security Pre-checks
Static code analysis warns about potentially dangerous patterns before execution:Security API
SecurityWarning fields:
Agent Integration
Agents automatically run security checks unless disabled:Security warnings travel only through
result.metadata["security_warnings"] — they are never forwarded as a metadata kwarg to the backend’s execute(). A custom SandboxProtocol.execute() implementation does not need to accept a metadata argument.Path Traversal Protection
DockerSandbox and SubprocessSandbox reject every path passed to
write_file, read_file, and list_files that resolves outside the
sandbox root — via ../, absolute paths, or symlink escapes:
Path traversal attempt blocked: <path>. This is in addition to the
container/OS isolation the sandbox already provides — defense in depth.
Symlink-safe file I/O on Docker
Yourwrite_file / read_file are symlink-race safe on the Docker backend — no change to how you call them:
ln -s /etc/passwd notes.txt against repeated writes could swap the name between a regular file and a symlink and win the race — escaping to arbitrary host files and disclosing a host secret within a few hundred attempts.
No amount of stricter string checking fixes this: anything verified before the open can be invalidated after it.
The fix walks each path one component at a time relative to an open directory descriptor (dir_fd=) with O_NOFOLLOW at every step. A symlink substituted at any level fails the open instead of redirecting it. Two helpers in praisonai_sandbox._compat implement the walk:
Windows note:
os.O_NOFOLLOW / os.O_DIRECTORY / os.supports_dir_fd don’t exist on Windows, so _HAS_OPENAT is False and the resolved-string guard (safe_sandbox_path()) is used instead. That is safe here because Docker Desktop runs Linux containers inside a VM — there is no host-shared directory to race. Non-admin Windows users also cannot create symlinks unless Developer Mode is enabled or the process holds SeCreateSymbolicLinkPrivilege. See PR #3224 / issue #3214.list_files() resolves symlinks on both sides — the sandbox root and every walked path — before comparing them, and silently drops any entry that would still resolve outside the sandbox root. This defends against the macOS /var → /private/var symlink and any similar case, so host paths are never returned:
SandboxManager
Factory and async context manager for sandbox backends:SandboxManager API
Configuration Options
Progressive disclosure from simple to advanced. Every level below configures the explicitagent.execute_code() API; none adds a tool to agent.tools, and none isolates tools= callables.
Factory Shortcuts
SandboxConfig Options
ResourceLimits Presets
SecurityPolicy Presets
Common Patterns
- Data Science Agent
- Secure Code Review
- Batch Processing
Timeout Behavior & Resource Cleanup
Understanding how different backends handle timeouts and resource cleanup ensures your resource limits are properly enforced.Docker Timeout Handling
Docker containers get deterministic names and are properly killed on timeout: Why containers are no longer orphaned: Everydocker run is launched with --name praisonai-<execution_id>. On timeout, the sandbox issues docker kill <name> to stop the actual container — not just detach the client. Your memory_mb and cpu_percent limits are now enforced through the entire execution lifecycle.
This is the shipped behaviour as of PR #4109. The fix has two moving parts:
- Every
docker runfromexecute()gets--name praisonai-<execution_id>— previously the container was unnamed, so Docker assigned a random name that neither the timeout handler norpraisonai managed pscould find. - On
asyncio.TimeoutError, the sandbox spawnsdocker kill <container_name>and awaits it before callingproc.kill()— killing the container, not just the docker client. Verified before/after: containers left after a timeout went from 1 → 0.
Two labels, two lifecycles.
execute()-path containers carry praisonai=sandbox-exec, not praisonai=managed. They are ephemeral (--rm, one per execution, named praisonai-<uuid>), while managed instances are long-lived (named praisonai_<id>). praisonai managed ps deliberately lists only praisonai=managed; to see ephemeral execution containers use docker ps --filter label=praisonai=sandbox-exec. Tagging --rm containers as managed would make managed ps list a name that managed stop could never reclaim. See Sandbox Backends and Reclaim Stray Sandboxes.SSH Timeout Handling
SSH backend prevents both remote process leaks and temp file accumulation: Remote process cleanup: Commands are wrapped withtimeout N sh -c ... to ensure remote processes terminate even if the SSH connection drops.
Temp file cleanup: File cleanup (rm -f) is now in a finally block. Even if execution raises (timeout, network blip), the remote temp file is removed. Cleanup errors are swallowed so they never mask the real execution result.
Best Practices
Use Docker for untrusted code
Use Docker for untrusted code
Always use Docker sandbox when executing code from untrusted sources. Subprocess isolation is not sufficient for security-critical applications.
Enable security pre-checks
Enable security pre-checks
Keep
check_security=True (default) when calling execute_code(). Review warnings in result.metadata["security_warnings"] for insights.Set appropriate resource limits
Set appropriate resource limits
Configure memory and timeout limits based on expected workload. Start with minimal limits and increase as needed.
Disable network by default
Disable network by default
Keep
allow_network=False unless code specifically needs network access. This prevents data exfiltration.Use context managers for persistence
Use context managers for persistence
For multiple operations on the same sandbox, use
async with SandboxManager(config) as sandbox: to reuse the environment efficiently.File system boundaries are enforced
File system boundaries are enforced
Filesystem boundaries are enforced — write_file/read_file/list_files reject paths that escape the sandbox root, even before the backend’s isolation kicks in.
Related
Where Does It Run
Ask any agent where its thinking and tools actually execute
Sandboxed Agent
Complete agent with built-in sandbox
Sandbox Backends
Shell control and backend selection
Isolated Code with Tools
Service registered tool calls from an isolated run via a
CodeToolBridge
