Universal library for AI code execution sandboxes
Project description
sandboxes
Universal library for AI code execution sandboxes.
Overview
sandboxes provides a unified interface for sandboxed code execution across multiple providers:
- Current providers: E2B, Modal, Daytona, Hopx
- Experimental: Cloudflare (requires self-hosted Worker deployment)
Write your code once and switch between providers with a single line change, or let the library automatically select a provider. Includes a Python API plus full-featured CLI for use from any runtime.
Installation
Add to your project:
uv add cased-sandboxes
or install with your preferred Python package manager and use the CLI for any language, e.g.,:
uv pip install cased-sandboxes
Quick Start
One-line Execution + Auto-select Provider
import asyncio
from sandboxes import run
async def main():
# Creates a temporary sandbox, runs the command, then destroys the sandbox
result = await run("echo 'Hello from sandbox!'")
print(result.stdout)
# Behind the scenes, run() does this:
# 1. Auto-detects available providers (e.g., E2B, Modal, Daytona)
# 2. Creates a new sandbox with the first available provider
# 3. Executes your command in that isolated environment
# 4. Returns the result
# 5. Automatically destroys the sandbox
asyncio.run(main())
Multiple Commands
import asyncio
from sandboxes import run_many
async def main():
# Execute multiple commands in one sandbox
results = await run_many([
"pip install requests",
"python -c 'import requests; print(requests.__version__)'"
])
for result in results:
print(result.stdout)
asyncio.run(main())
Persistent Sandbox Sessions
import asyncio
from sandboxes import Sandbox
async def main():
async with Sandbox.create() as sandbox:
# Install dependencies
await sandbox.execute("pip install numpy pandas")
# Run your code
result = await sandbox.execute("python analyze.py")
print(result.stdout)
await sandbox.upload("data.csv", "/tmp/data.csv")
await sandbox.download("/tmp/results.csv", "results.csv")
# Automatically cleaned up on exit
asyncio.run(main())
Smart Sandbox Reuse
Use get_or_create with labels (which can include pre-set unique ids) to re-use particular sandboxes. Useful for agent sessions over time.
import asyncio
from sandboxes import Sandbox
async def main():
# First call creates a new sandbox
sandbox1 = await Sandbox.get_or_create(
labels={"project": "ml-training", "gpu": "true"}
)
# Later calls reuse the same sandbox
sandbox2 = await Sandbox.get_or_create(
labels={"project": "ml-training", "gpu": "true"}
)
assert sandbox1.id == sandbox2.id # Same sandbox
asyncio.run(main())
Provider Selection with Automatic Failover
import asyncio
from sandboxes import Sandbox, run
async def main():
# Control where your code runs
sandbox = await Sandbox.create(
provider="e2b", # Try E2B first
fallback=["modal", "cloudflare", "daytona"], # Automatic failover
)
# The library automatically tries the next provider if one fails
print(f"Using: {sandbox._provider_name}")
# Or specify directly with run()
result = await run("bash my-script.sh", provider="modal")
asyncio.run(main())
Custom Images and Templates
import asyncio
from sandboxes import Sandbox, SandboxConfig
from sandboxes.providers import ModalProvider, E2BProvider, DaytonaProvider
async def main():
# High-level API - works with any provider
sandbox = await Sandbox.create(image="python:3.12-slim")
# Or with specific providers
daytona_provider = DaytonaProvider()
config = SandboxConfig(image="daytonaio/ai-test:0.2.3")
sandbox = await daytona_provider.create_sandbox(config)
asyncio.run(main())
# Via CLI
# sandboxes run "python --version" --image python:3.12-slim
API Reference
Core Classes
Sandbox: High-level interface with automatic provider managementSandboxConfig: Configuration for sandbox creation (labels, timeout, image)ExecutionResult: Standardized result object (stdout, stderr, exit_code)Manager: Multi-provider orchestration with failoverSandboxProvider: Abstract base class for provider implementations
Key Methods
# High-level functions
await run(command: str, provider: str = None) -> ExecutionResult
await run_many(commands: list[str], provider: str = None) -> list[ExecutionResult]
# Sandbox methods
await Sandbox.create(provider=None, fallback=None, labels=None, image=None) -> Sandbox
await Sandbox.get_or_create(labels: dict) -> Sandbox
await Sandbox.find(labels: dict) -> Sandbox | None
await sandbox.execute(command: str) -> ExecutionResult
await sandbox.execute_many(commands: list[str]) -> list[ExecutionResult]
await sandbox.stream(command: str) -> AsyncIterator[str]
await sandbox.upload(local_path: str, remote_path: str)
await sandbox.download(remote_path: str, local_path: str)
await sandbox.destroy()
Command Line Interface
sandboxes includes a CLI for running code in any language from your terminal. TypeScript, Go, Rust, Python, or any other language in isolated sandboxes.
Call the CLI from any language, or write a wrapper for it.
Quick Start
# Run TypeScript from a file
sandboxes run --file script.ts
# Run Go code from stdin
cat main.go | sandboxes run --lang go
# Direct command execution
sandboxes run "python3 -c 'print(sum(range(100)))'"
# Run with specific provider
sandboxes run "python3 --version" --provider e2b
# List all sandboxes
sandboxes list
Commands
run - Execute Code
# 1. From file (auto-detects language)
sandboxes run --file script.py
sandboxes run --file main.go
# 2. From stdin/pipe
cat script.py | sandboxes run --lang python
echo 'console.log("Hello!")' | sandboxes run --lang node
# 3. Direct command
sandboxes run "python3 -c 'print(42)'"
Options:
# Specify provider
sandboxes run --file app.py -p e2b
# Environment variables
sandboxes run --file script.py -e API_KEY=secret -e DEBUG=1
# Labels for reuse
sandboxes run --file app.py -l project=myapp --reuse
# Keep sandbox (don't auto-destroy)
sandboxes run --file script.py --keep
# Timeout
sandboxes run --file script.sh -t 600
Supported languages with auto-detect: python, node/javascript, typescript, go, rust, bash/sh
list - List Sandboxes
View all active sandboxes:
# List all sandboxes
sandboxes list
# Filter by provider
sandboxes list -p e2b
# Filter by labels
sandboxes list -l env=prod
# JSON output
sandboxes list --json
exec - Execute in Existing Sandbox
sandboxes exec sb-abc123 "ls -la" -p modal
sandboxes exec sb-abc123 "python script.py" -p e2b -e DEBUG=1
destroy - Remove Sandbox
sandboxes destroy sb-abc123 -p e2b
providers - Check Providers
sandboxes providers
test - Test Provider Connectivity
sandboxes test # Test all
sandboxes test -p e2b # Test specific
CLI Examples
Development Workflow
# Create development sandbox
sandboxes run "git clone https://github.com/user/repo.git /app" \
-l project=myapp \
-l env=dev \
--keep
# List to get sandbox ID
sandboxes list -l project=myapp
# Run commands in the sandbox
sandboxes exec sb-abc123 "cd /app && npm install" -p e2b
sandboxes exec sb-abc123 "cd /app && npm test" -p e2b
# Cleanup when done
sandboxes destroy sb-abc123 -p e2b
Multi-Language Code Testing
# TypeScript
echo 'const x: number = 42; console.log(x)' > test.ts
sandboxes run --file test.ts
# Go with automatic dependency installation
sandboxes run --file main.go --deps
# Go from stdin
cat main.go | sandboxes run --lang go
# Python from remote URL
curl -s https://example.com/script.py | sandboxes run --lang python
Auto-Dependency Installation (golang only for now): Use --deps to automatically install dependencies from go.mod (located in the same directory as your code file). The CLI will upload go.mod and go.sum (if present) and run go mod download before executing your code.
Provider Configuration
You'll need API keys from one of the supported providers.
Automatic Configuration
The library automatically detects available providers from environment variables:
# Set any of these environment variables:
export E2B_API_KEY="..."
export MODAL_TOKEN_ID="..." # Or use `modal token set`
export DAYTONA_API_KEY="..."
export HOPX_API_KEY="hopx_live_<keyId>.<secret>"
export CLOUDFLARE_SANDBOX_BASE_URL="https://your-worker.workers.dev"
export CLOUDFLARE_API_TOKEN="..."
Then just use:
import asyncio
from sandboxes import Sandbox
async def main():
sandbox = await Sandbox.create() # Auto-selects first available provider
asyncio.run(main())
How Auto-Detection Works
When you call Sandbox.create() or run(), the library checks for providers in this priority order:
- Daytona - Looks for
DAYTONA_API_KEY - E2B - Looks for
E2B_API_KEY - Hopx - Looks for
HOPX_API_KEY - Modal - Looks for
~/.modal.tomlorMODAL_TOKEN_ID - Cloudflare (experimental) - Looks for
CLOUDFLARE_SANDBOX_BASE_URL+CLOUDFLARE_API_TOKEN
The first provider with valid credentials becomes the default. Cloudflare requires deploying your own Worker.
Customizing the Default Provider
You can override the auto-detected default:
from sandboxes import Sandbox
# Option 1: Set default provider explicitly
Sandbox.configure(default_provider="modal")
# Option 2: Specify provider per call
sandbox = await Sandbox.create(provider="e2b")
# Option 3: Use fallback chain
sandbox = await Sandbox.create(
provider="daytona",
fallback=["e2b", "modal"]
)
# Check which providers are available
Sandbox._ensure_manager()
print(f"Available: {list(Sandbox._manager.providers.keys())}")
print(f"Default: {Sandbox._manager.default_provider}")
Manual Provider Configuration
For more control, you can configure providers manually:
from sandboxes import Sandbox
# Configure providers programmatically
Sandbox.configure(
e2b_api_key="your-key",
hopx_api_key="hopx_live_<keyId>.<secret>",
cloudflare_config={
"base_url": "https://your-worker.workers.dev",
"api_token": "your-token",
},
default_provider="hopx"
)
Direct Provider Usage (Low-Level API)
For advanced use cases, you can work with providers directly:
from sandboxes.providers import (
E2BProvider,
ModalProvider,
DaytonaProvider,
HopxProvider,
CloudflareProvider,
)
# E2B - Uses E2B_API_KEY env var
provider = E2BProvider()
# Modal - Uses ~/.modal.toml for auth
provider = ModalProvider()
# Daytona - Uses DAYTONA_API_KEY env var
provider = DaytonaProvider()
# Hopx - Uses HOPX_API_KEY env var
provider = HopxProvider()
# Cloudflare - Requires base_url and token
provider = CloudflareProvider(
base_url="https://your-worker.workers.dev",
api_token="your-token",
)
Each provider requires appropriate authentication:
- E2B: Set
E2B_API_KEYenvironment variable - Modal: Run
modal token setto configure - Daytona: Set
DAYTONA_API_KEYenvironment variable - Hopx: Set
HOPX_API_KEYenvironment variable (format:hopx_live_<keyId>.<secret>) - Cloudflare (experimental): Deploy the Cloudflare sandbox Worker and set
CLOUDFLARE_SANDBOX_BASE_URL,CLOUDFLARE_API_TOKEN, and (optionally)CLOUDFLARE_ACCOUNT_ID
Cloudflare setup tips (experimental)
⚠️ Note: Cloudflare support is experimental and requires self-hosting a Worker.
- Clone the Cloudflare
sandbox-sdkrepository and deploy theexamples/basicWorker withwrangler.- Provision a Workers Paid plan and enable Containers + Docker Hub registry for your account.
- Define a secret (e.g.
SANDBOX_API_TOKEN) in Wrangler and reuse the same value forCLOUDFLARE_API_TOKENlocally.- Set
CLOUDFLARE_SANDBOX_BASE_URLto the Worker URL (e.g.https://cf-sandbox.your-subdomain.workers.dev).
Advanced Usage
Multi-Provider Orchestration
import asyncio
from sandboxes import Manager, SandboxConfig
from sandboxes.providers import E2BProvider, ModalProvider, DaytonaProvider, CloudflareProvider
async def main():
# Initialize manager and register providers
manager = Manager(default_provider="e2b")
manager.register_provider("e2b", E2BProvider, {})
manager.register_provider("modal", ModalProvider, {})
manager.register_provider("daytona", DaytonaProvider, {})
manager.register_provider("hopx", HopxProvider, {})
manager.register_provider(
"cloudflare",
CloudflareProvider,
{"base_url": "https://your-worker.workers.dev", "api_token": "..."}
)
# Manager handles failover automatically
sandbox = await manager.create_sandbox(
SandboxConfig(labels={"task": "test"}),
fallback_providers=["modal", "daytona"] # Try these if primary fails
)
asyncio.run(main())
Sandbox Reuse (Provider-Level)
For advanced control, work directly with providers instead of the high-level Sandbox API:
import asyncio
from sandboxes import SandboxConfig
from sandboxes.providers import E2BProvider
async def main():
provider = E2BProvider()
# Sandboxes can be reused based on labels
config = SandboxConfig(
labels={"project": "ml-training", "gpu": "true"}
)
# This will find existing sandbox or create new one
sandbox = await provider.get_or_create_sandbox(config)
# Later in another process...
# This will find the same sandbox
sandbox = await provider.find_sandbox({"project": "ml-training"})
asyncio.run(main())
Streaming Execution
import asyncio
from sandboxes.providers import E2BProvider
async def main():
provider = E2BProvider()
sandbox = await provider.create_sandbox()
# Stream output as it's generated
async for chunk in provider.stream_execution(
sandbox.id,
"for i in range(10): print(i); time.sleep(1)"
):
print(chunk, end="")
asyncio.run(main())
Connection Pooling
import asyncio
from sandboxes import SandboxConfig
from sandboxes.pool import ConnectionPool
from sandboxes.providers import E2BProvider
async def main():
# Create a connection pool for better performance
pool = ConnectionPool(
provider=E2BProvider(),
max_connections=10,
max_idle_time=300,
ttl=3600
)
# Get or create connection
conn = await pool.get_or_create(
SandboxConfig(labels={"pool": "ml"})
)
# Return to pool when done
await pool.release(conn)
asyncio.run(main())
Architecture
Core Components
Sandbox: High-level interface with automatic provider managementSandboxProvider: Abstract base class for all providersSandboxConfig: Configuration for sandbox creationExecutionResult: Standardized execution resultsManager: Multi-provider orchestrationConnectionPool: Connection pooling with TTLRetryPolicy: Configurable retry logicCircuitBreaker: Fault tolerance
Environment Variables
# E2B
export E2B_API_KEY="e2b_..."
# Daytona
export DAYTONA_API_KEY="dtn_..."
# Modal (or use modal token set)
export MODAL_TOKEN_ID="..."
export MODAL_TOKEN_SECRET="..."
# Cloudflare
export CLOUDFLARE_SANDBOX_BASE_URL="https://your-worker.workers.dev"
export CLOUDFLARE_API_TOKEN="..."
export CLOUDFLARE_ACCOUNT_ID="..." # Optional
Multi-Language Support
While sandboxes is a Python library, it can execute code in any language available in the sandbox environment. The sandboxes run standard Linux containers, so you can execute TypeScript, Go, Rust, Java, or any other language.
Running TypeScript
import asyncio
from sandboxes import Sandbox
async def run_typescript():
"""Execute TypeScript code in a sandbox."""
async with Sandbox.create() as sandbox:
# TypeScript code
ts_code = '''
const greeting: string = "Hello from TypeScript!";
const numbers: number[] = [1, 2, 3, 4, 5];
const sum: number = numbers.reduce((a, b) => a + b, 0);
console.log(greeting);
console.log(`Sum of numbers: ${sum}`);
console.log(`Type system ensures safety at compile time`);
'''
# Run with ts-node (npx auto-installs)
result = await sandbox.execute(
f"echo '{ts_code}' > /tmp/app.ts && npx -y ts-node /tmp/app.ts"
)
print(result.stdout)
# Output:
# Hello from TypeScript!
# Sum of numbers: 15
# Type system ensures safety at compile time
asyncio.run(run_typescript())
Running Go
import asyncio
from sandboxes import Sandbox
async def run_go():
"""Execute Go code in a sandbox."""
async with Sandbox.create() as sandbox:
# Go code
go_code = '''package main
import (
"fmt"
"math"
)
func main() {
fmt.Println("Hello from Go!")
// Calculate fibonacci
n := 10
fmt.Printf("Fibonacci(%d) = %d\\n", n, fibonacci(n))
// Demonstrate type safety
radius := 5.0
area := math.Pi * radius * radius
fmt.Printf("Circle area (r=%.1f): %.2f\\n", radius, area)
}
func fibonacci(n int) int {
if n <= 1 {
return n
}
return fibonacci(n-1) + fibonacci(n-2)
}
'''
# Save and run Go code
result = await sandbox.execute(f'''
cat > /tmp/main.go << 'EOF'
{go_code}
EOF
go run /tmp/main.go
''')
print(result.stdout)
# Output:
# Hello from Go!
# Fibonacci(10) = 55
# Circle area (r=5.0): 78.54
asyncio.run(run_go())
Common Use Cases
AI Agent Code Execution
import asyncio
from sandboxes import Sandbox
async def execute_agent_code(code: str, language: str = "python"):
"""Safely execute AI-generated code."""
async with Sandbox.create() as sandbox:
# Install any required packages first
if "import" in code:
# Extract and install imports (simplified)
await sandbox.execute("pip install requests numpy")
# Execute the code
result = await sandbox.execute(f"{language} -c '{code}'")
if result.exit_code != 0:
return f"Error: {result.stderr}"
return result.stdout
# Example usage
asyncio.run(execute_agent_code("print('Hello!')", "python"))
Data Processing Pipeline
import asyncio
from sandboxes import Sandbox
async def process_dataset(dataset_url: str):
"""Process data in isolated environment."""
async with Sandbox.create(labels={"task": "data-pipeline"}) as sandbox:
# Setup environment
await sandbox.execute_many([
"pip install pandas numpy scikit-learn",
f"wget {dataset_url} -O data.csv"
])
# Upload processing script
await sandbox.upload("process.py", "/tmp/process.py")
# Run processing with streaming output
async for output in sandbox.stream("python /tmp/process.py"):
print(output, end="")
# Download results
await sandbox.download("/tmp/results.csv", "results.csv")
# Example usage
asyncio.run(process_dataset("https://example.com/data.csv"))
Code Testing and Validation
import asyncio
from sandboxes import Sandbox
async def test_solution(code: str, test_cases: list):
"""Test code against multiple test cases."""
results = []
async with Sandbox.create() as sandbox:
# Save the code
await sandbox.upload("solution.py", "/tmp/solution.py")
# Run each test case
for i, test in enumerate(test_cases):
result = await sandbox.execute(
f"python /tmp/solution.py < {test['input']}"
)
results.append({
"test": i + 1,
"passed": result.stdout.strip() == test['expected'],
"output": result.stdout.strip()
})
return results
# Example usage
asyncio.run(test_solution("print(sum(map(int, input().split())))", [
{"input": "1 2 3", "expected": "6"}
]))
Troubleshooting
No Providers Available
# If you see: "No provider specified and no default provider set"
# Solution 1: Set environment variables
export E2B_API_KEY="your-key"
# Solution 2: Configure manually
from sandboxes import Sandbox
Sandbox.configure(e2b_api_key="your-key")
# Solution 3: Use low-level API
from sandboxes.providers import E2BProvider
provider = E2BProvider(api_key="your-key")
Provider Failures
import asyncio
from sandboxes import Sandbox
from sandboxes.exceptions import ProviderError
async def main():
# Enable automatic failover
sandbox = await Sandbox.create(
provider="e2b",
fallback=["modal", "cloudflare", "daytona"]
)
# Or handle errors manually
try:
sandbox = await Sandbox.create(provider="e2b")
except ProviderError:
sandbox = await Sandbox.create(provider="modal")
asyncio.run(main())
Debugging
import asyncio
import logging
from sandboxes import Sandbox
async def main():
# Enable debug logging
logging.basicConfig(level=logging.DEBUG)
# Check provider health
Sandbox._ensure_manager()
for name, provider in Sandbox._manager.providers.items():
health = await provider.health_check()
print(f"{name}: {'✅' if health else '❌'}")
asyncio.run(main())
Security Disclosure
If you discover a security vulnerability in this library or any of its dependencies, please report it responsibly.
Responsible Disclosure:
- Email security reports to: ted@cased.com
- Detailed description of the vulnerability
- Steps to reproduce if possible
- Allow reasonable time for a fix before public disclosure
Will acknowledge your report within 48 hours and work with you to address the issue.
License
MIT License - see LICENSE file for details.
Acknowledgments
Built by Cased
Thanks to the teams at E2B, Modal, Daytona, and Cloudflare for their excellent sandbox platforms.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cased_sandboxes-0.4.0.tar.gz.
File metadata
- Download URL: cased_sandboxes-0.4.0.tar.gz
- Upload date:
- Size: 47.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9c8a8ab427c0b6534a47a23fc9b74e183912d91794c214fdc339947b5129e85d
|
|
| MD5 |
4224996d42b45d946347f3681b449bfd
|
|
| BLAKE2b-256 |
bc98c699e62838840baee3fb3c8d948078a17a25f3c8018c1f50223c63738195
|
File details
Details for the file cased_sandboxes-0.4.0-py3-none-any.whl.
File metadata
- Download URL: cased_sandboxes-0.4.0-py3-none-any.whl
- Upload date:
- Size: 55.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1317ec7cae46289e0e1642a22c3315dea966c667ff35b83a69a9744630cd8393
|
|
| MD5 |
6e457f506f93403068557b6815a212f9
|
|
| BLAKE2b-256 |
22a268f23e0f31c9263defbe255457c27a68775fa3a1c51b7a22d42ab4f96bcd
|