Universal library for AI code execution sandboxes.
sandboxes provides a unified interface for sandboxed code execution across multiple providers:
- Current providers: E2B, Modal, Daytona, Hopx, Vercel, Sprites (Fly.io)
- 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.
uv pip install cased-sandboxesOr add to your project:
uv add cased-sandboxesRun Claude Code in a secure sandbox with one command:
sandboxes claudeThat's it. You get an interactive Claude Code session in an isolated, cloud environment.
# Install and login to Sprites
curl https://sprites.dev/install.sh | bash
sprite login
# Start Claude Code
sandboxes claude# Install E2B SDK and CLI
uv pip install e2b
npm install -g @e2b/cli
# Set your API keysexport E2B_API_KEY=your_key
export ANTHROPIC_API_KEY=your_key
# Start Claude Code
sandboxes claude -p e2b# Create a named sandbox (automatically kept)
sandboxes claude -n myproject
# Work on your project...# Exit when done (/exit or Ctrl+C)# Come back later - your files are still there
sandboxes claude -n myproject
# List your sandboxes
sandboxes claude --list
# Or just get a raw shell (no Claude Code)
sandboxes shell -n mydev --keepClaude Code can read, write, and execute code. Running it in a sandbox means:
- Safe: Can't touch your local files or system
- Isolated: Each project gets its own environment
- Persistent: Named sandboxes keep your files across sessions
- Pre-configured: Claude Code, Python, Node.js ready to go
importasynciofromsandboxesimportrunasyncdefmain():
# Creates a temporary sandbox, runs the command, then destroys the sandboxresult=awaitrun("echo 'Hello from sandbox!'")
print(result.stdout)
# Behind the scenes, run() does this:# 1. Auto-detects available providers (e.g., E2B, Modal, Daytona, Vercel)# 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 sandboxasyncio.run(main())importasynciofromsandboxesimportrun_manyasyncdefmain():
# Execute multiple commands in one sandboxresults=awaitrun_many([
"pip install requests",
"python -c 'import requests; print(requests.__version__)'"
])
forresultinresults:
print(result.stdout)
asyncio.run(main())importasynciofromsandboxesimportSandboxasyncdefmain():
asyncwithSandbox.create() assandbox:
# Install dependenciesawaitsandbox.execute("pip install numpy pandas")
# Run your coderesult=awaitsandbox.execute("python analyze.py")
print(result.stdout)
awaitsandbox.upload("data.csv", "/tmp/data.csv")
awaitsandbox.download("/tmp/results.csv", "results.csv")
# Automatically cleaned up on exitasyncio.run(main())Use get_or_create with labels (which can include pre-set unique ids) to re-use particular sandboxes. Useful for agent sessions over time.
importasynciofromsandboxesimportSandboxasyncdefmain():
# First call creates a new sandboxsandbox1=awaitSandbox.get_or_create(
labels={"project": "ml-training", "gpu": "true"}
)
# Later calls reuse the same sandboxsandbox2=awaitSandbox.get_or_create(
labels={"project": "ml-training", "gpu": "true"}
)
assertsandbox1.id==sandbox2.id# Same sandboxasyncio.run(main())importasynciofromsandboxesimportSandbox, runasyncdefmain():
# Control where your code runssandbox=awaitSandbox.create(
provider="e2b", # Try E2B firstfallback=["modal", "cloudflare", "daytona"], # Automatic failover
)
# The library automatically tries the next provider if one failsprint(f"Using: {sandbox._provider_name}")
# Or specify directly with run()result=awaitrun("bash my-script.sh", provider="modal")
asyncio.run(main())importasynciofromsandboxesimportSandbox, SandboxConfigfromsandboxes.providersimportModalProvider, E2BProvider, DaytonaProviderasyncdefmain():
# High-level API - works with any providersandbox=awaitSandbox.create(image="python:3.12-slim")
# Or with specific providersdaytona_provider=DaytonaProvider()
config=SandboxConfig(image="daytonaio/ai-test:0.2.3")
sandbox=awaitdaytona_provider.create_sandbox(config)
asyncio.run(main())
# Via CLI# sandboxes run "python --version" --image python:3.12-slimSandbox: 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
# High-level functionsawaitrun(command: str, provider: str=None) ->ExecutionResultawaitrun_many(commands: list[str], provider: str=None) ->list[ExecutionResult]
# Sandbox methodsawaitSandbox.create(provider=None, fallback=None, labels=None, image=None) ->SandboxawaitSandbox.get_or_create(labels: dict) ->SandboxawaitSandbox.find(labels: dict) ->Sandbox|Noneawaitsandbox.execute(command: str) ->ExecutionResultawaitsandbox.execute_many(commands: list[str]) ->list[ExecutionResult]
awaitsandbox.stream(command: str) ->AsyncIterator[str]
awaitsandbox.upload(local_path: str, remote_path: str)
awaitsandbox.download(remote_path: str, local_path: str)
awaitsandbox.destroy()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.
# 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# 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 600Supported languages with auto-detect: python, node/javascript, typescript, go, rust, bash/sh
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 --jsonsandboxes exec sb-abc123 "ls -la" -p modal
sandboxes exec sb-abc123 "python script.py" -p e2b -e DEBUG=1sandboxes destroy sb-abc123 -p e2bsandboxes providerssandboxes test# Test all
sandboxes test -p e2b # Test specific# 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# TypeScriptecho'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 pythonAuto-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.
You'll need API keys from one of the supported providers.
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 VERCEL_TOKEN="..."export VERCEL_PROJECT_ID="..."export VERCEL_TEAM_ID="..."export SPRITES_TOKEN="..."# Or use `sprite login` for CLI modeexport CLOUDFLARE_SANDBOX_BASE_URL="https://your-worker.workers.dev"export CLOUDFLARE_API_TOKEN="..."Then just use:
importasynciofromsandboxesimportSandboxasyncdefmain():
sandbox=awaitSandbox.create() # Auto-selects first available providerasyncio.run(main())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 - Sprites - Looks for
SPRITES_TOKENorspriteCLI login - Hopx - Looks for
HOPX_API_KEY - Vercel - Looks for
VERCEL_TOKEN+VERCEL_PROJECT_ID+VERCEL_TEAM_ID - 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.
You can override the auto-detected default:
fromsandboxesimportSandbox# Option 1: Set default provider explicitlySandbox.configure(default_provider="modal")
# Option 2: Specify provider per callsandbox=awaitSandbox.create(provider="e2b")
# Option 3: Use fallback chainsandbox=awaitSandbox.create(
provider="daytona",
fallback=["e2b", "modal"]
)
# Check which providers are availableSandbox._ensure_manager()
print(f"Available: {list(Sandbox._manager.providers.keys())}")
print(f"Default: {Sandbox._manager.default_provider}")For more control, you can configure providers manually:
fromsandboxesimportSandbox# Configure providers programmaticallySandbox.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"
)For advanced use cases, you can work with providers directly:
fromsandboxes.providersimport (
E2BProvider,
ModalProvider,
DaytonaProvider,
HopxProvider,
VercelProvider,
SpritesProvider,
CloudflareProvider,
)
# E2B - Uses E2B_API_KEY env varprovider=E2BProvider()
# Modal - Uses ~/.modal.toml for authprovider=ModalProvider()
# Daytona - Uses DAYTONA_API_KEY env varprovider=DaytonaProvider()
# Hopx - Uses HOPX_API_KEY env varprovider=HopxProvider()
# Vercel - Uses VERCEL_TOKEN + VERCEL_PROJECT_ID + VERCEL_TEAM_ID env varsprovider=VercelProvider()
# Sprites - Uses SPRITES_TOKEN or sprite CLI loginprovider=SpritesProvider() # SDK mode with SPRITES_TOKENprovider=SpritesProvider(use_cli=True) # CLI mode with sprite login# Cloudflare - Requires base_url and tokenprovider=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>) - Vercel: Set
VERCEL_TOKEN,VERCEL_PROJECT_ID, andVERCEL_TEAM_ID - Sprites: Set
SPRITES_TOKENenvironment variable, or runsprite loginfor CLI mode - 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).
Sprites (Fly.io) - Best for Claude Code
Sprites are persistent Linux sandboxes with Claude Code pre-installed:
- Claude Code 2.0+ ready to go - just run
sandboxes claude- 100GB persistent storage - files persist across sessions
- Checkpoint/restore - save and restore state in ~300ms
- ~$0.46 for 4-hour session - scale-to-zero billing
See Simon Willison's writeup for more details.
importasynciofromsandboxesimportManager, SandboxConfigfromsandboxes.providersimport (
E2BProvider,
ModalProvider,
DaytonaProvider,
HopxProvider,
VercelProvider,
SpritesProvider,
CloudflareProvider,
)
asyncdefmain():
# Initialize manager and register providersmanager=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(
"vercel",
VercelProvider,
{
"token": "...",
"project_id": "...",
"team_id": "...",
},
)
manager.register_provider("sprites", SpritesProvider, {"use_cli": True})
manager.register_provider(
"cloudflare",
CloudflareProvider,
{"base_url": "https://your-worker.workers.dev", "api_token": "..."}
)
# Manager handles failover automaticallysandbox=awaitmanager.create_sandbox(
SandboxConfig(labels={"task": "test"}),
fallback_providers=["modal", "daytona"] # Try these if primary fails
)
asyncio.run(main())For advanced control, work directly with providers instead of the high-level Sandbox API:
importasynciofromsandboxesimportSandboxConfigfromsandboxes.providersimportE2BProviderasyncdefmain():
provider=E2BProvider()
# Sandboxes can be reused based on labelsconfig=SandboxConfig(
labels={"project": "ml-training", "gpu": "true"}
)
# This will find existing sandbox or create new onesandbox=awaitprovider.get_or_create_sandbox(config)
# Later in another process...# This will find the same sandboxsandbox=awaitprovider.find_sandbox({"project": "ml-training"})
asyncio.run(main())importasynciofromsandboxes.providersimportE2BProviderasyncdefmain():
provider=E2BProvider()
sandbox=awaitprovider.create_sandbox()
# Stream output as it's generatedasyncforchunkinprovider.stream_execution(
sandbox.id,
"for i in range(10): print(i); time.sleep(1)"
):
print(chunk, end="")
asyncio.run(main())importasynciofromsandboxesimportSandboxConfigfromsandboxes.poolimportConnectionPoolfromsandboxes.providersimportE2BProviderasyncdefmain():
# Create a connection pool for better performancepool=ConnectionPool(
provider=E2BProvider(),
max_connections=10,
max_idle_time=300,
ttl=3600
)
# Get or create connectionconn=awaitpool.get_or_create(
SandboxConfig(labels={"pool": "ml"})
)
# Return to pool when doneawaitpool.release(conn)
asyncio.run(main())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
# E2Bexport E2B_API_KEY="e2b_..."# Daytonaexport DAYTONA_API_KEY="dtn_..."# Modal (or use modal token set)export MODAL_TOKEN_ID="..."export MODAL_TOKEN_SECRET="..."# Hopxexport HOPX_API_KEY="hopx_live_..."# Sprites (or use `sprite login` for CLI mode)export SPRITES_TOKEN="..."# Cloudflareexport CLOUDFLARE_SANDBOX_BASE_URL="https://your-worker.workers.dev"export CLOUDFLARE_API_TOKEN="..."export CLOUDFLARE_ACCOUNT_ID="..."# OptionalWhile 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.
importasynciofromsandboxesimportSandboxasyncdefrun_typescript():
"""Execute TypeScript code in a sandbox."""asyncwithSandbox.create() assandbox:
# TypeScript codets_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=awaitsandbox.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 timeasyncio.run(run_typescript())importasynciofromsandboxesimportSandboxasyncdefrun_go():
"""Execute Go code in a sandbox."""asyncwithSandbox.create() assandbox:
# Go codego_code='''package mainimport ( "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 coderesult=awaitsandbox.execute(f'''cat > /tmp/main.go << 'EOF'{go_code}EOFgo run /tmp/main.go''')
print(result.stdout)
# Output:# Hello from Go!# Fibonacci(10) = 55# Circle area (r=5.0): 78.54asyncio.run(run_go())importasynciofromsandboxesimportSandboxasyncdefexecute_agent_code(code: str, language: str="python"):
"""Safely execute AI-generated code."""asyncwithSandbox.create() assandbox:
# Install any required packages firstif"import"incode:
# Extract and install imports (simplified)awaitsandbox.execute("pip install requests numpy")
# Execute the coderesult=awaitsandbox.execute(f"{language} -c '{code}'")
ifresult.exit_code!=0:
returnf"Error: {result.stderr}"returnresult.stdout# Example usageasyncio.run(execute_agent_code("print('Hello!')", "python"))importasynciofromsandboxesimportSandboxasyncdefprocess_dataset(dataset_url: str):
"""Process data in isolated environment."""asyncwithSandbox.create(labels={"task": "data-pipeline"}) assandbox:
# Setup environmentawaitsandbox.execute_many([
"pip install pandas numpy scikit-learn",
f"wget {dataset_url} -O data.csv"
])
# Upload processing scriptawaitsandbox.upload("process.py", "/tmp/process.py")
# Run processing with streaming outputasyncforoutputinsandbox.stream("python /tmp/process.py"):
print(output, end="")
# Download resultsawaitsandbox.download("/tmp/results.csv", "results.csv")
# Example usageasyncio.run(process_dataset("https://example.com/data.csv"))importasynciofromsandboxesimportSandboxasyncdeftest_solution(code: str, test_cases: list):
"""Test code against multiple test cases."""results= []
asyncwithSandbox.create() assandbox:
# Save the codeawaitsandbox.upload("solution.py", "/tmp/solution.py")
# Run each test casefori, testinenumerate(test_cases):
result=awaitsandbox.execute(
f"python /tmp/solution.py < {test['input']}"
)
results.append({
"test": i+1,
"passed": result.stdout.strip() ==test['expected'],
"output": result.stdout.strip()
})
returnresults# Example usageasyncio.run(test_solution("print(sum(map(int, input().split())))", [
{"input": "1 2 3", "expected": "6"}
]))# If you see: "No provider specified and no default provider set"# Solution 1: Set environment variablesexportE2B_API_KEY="your-key"# Solution 2: Configure manuallyfromsandboxesimportSandboxSandbox.configure(e2b_api_key="your-key")
# Solution 3: Use low-level APIfromsandboxes.providersimportE2BProviderprovider=E2BProvider(api_key="your-key")importasynciofromsandboxesimportSandboxfromsandboxes.exceptionsimportProviderErrorasyncdefmain():
# Enable automatic failoversandbox=awaitSandbox.create(
provider="e2b",
fallback=["modal", "cloudflare", "daytona"]
)
# Or handle errors manuallytry:
sandbox=awaitSandbox.create(provider="e2b")
exceptProviderError:
sandbox=awaitSandbox.create(provider="modal")
asyncio.run(main())importasyncioimportloggingfromsandboxesimportSandboxasyncdefmain():
# Enable debug logginglogging.basicConfig(level=logging.DEBUG)
# Check provider healthSandbox._ensure_manager()
forname, providerinSandbox._manager.providers.items():
health=awaitprovider.health_check()
print(f"{name}: {'✅'ifhealthelse'❌'}")
asyncio.run(main())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.
MIT License - see LICENSE file for details.
Built by Cased
Thanks to the teams at E2B, Modal, Daytona, Hopx, Vercel, Fly.io (Sprites), and Cloudflare for their excellent sandbox platforms.