Skip to content

Repository files navigation

sandboxes

Universal library for AI code execution sandboxes.

Python VersionLicense: MIT

Overview

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.

Installation

uv pip install cased-sandboxes

Or add to your project:

uv add cased-sandboxes

Claude Code Integration

Run Claude Code in a secure sandbox with one command:

sandboxes claude

That's it. You get an interactive Claude Code session in an isolated, cloud environment.

Setup (Sprites - recommended)

# Install and login to Sprites
curl https://sprites.dev/install.sh | bash
sprite login
# Start Claude Code
sandboxes claude

Setup (E2B - alternative)

# 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

Persistent Development Environment (Sprites only)

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

Why Sandboxes?

Claude 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

Quick Start

One-line Execution + Auto-select Provider

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())

Multiple Commands

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())

Persistent Sandbox Sessions

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())

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.

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())

Provider Selection with Automatic Failover

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())

Custom Images and Templates

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-slim

API Reference

Core Classes

  • Sandbox: High-level interface with automatic provider management
  • SandboxConfig: Configuration for sandbox creation (labels, timeout, image)
  • ExecutionResult: Standardized result object (stdout, stderr, exit_code)
  • Manager: Multi-provider orchestration with failover
  • SandboxProvider: Abstract base class for provider implementations

Key Methods

# 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()

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

# 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 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 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())

How Auto-Detection Works

When you call Sandbox.create() or run(), the library checks for providers in this priority order:

  1. Daytona - Looks for DAYTONA_API_KEY
  2. E2B - Looks for E2B_API_KEY
  3. Sprites - Looks for SPRITES_TOKEN or sprite CLI login
  4. Hopx - Looks for HOPX_API_KEY
  5. Vercel - Looks for VERCEL_TOKEN + VERCEL_PROJECT_ID + VERCEL_TEAM_ID
  6. Modal - Looks for ~/.modal.toml or MODAL_TOKEN_ID
  7. 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:

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}")

Manual Provider Configuration

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"
)

Direct Provider Usage (Low-Level API)

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_KEY environment variable
  • Modal: Run modal token set to configure
  • Daytona: Set DAYTONA_API_KEY environment variable
  • Hopx: Set HOPX_API_KEY environment variable (format: hopx_live_<keyId>.<secret>)
  • Vercel: Set VERCEL_TOKEN, VERCEL_PROJECT_ID, and VERCEL_TEAM_ID
  • Sprites: Set SPRITES_TOKEN environment variable, or run sprite login for 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.

  1. Clone the Cloudflare sandbox-sdk repository and deploy the examples/basic Worker with wrangler.
  2. Provision a Workers Paid plan and enable Containers + Docker Hub registry for your account.
  3. Define a secret (e.g. SANDBOX_API_TOKEN) in Wrangler and reuse the same value for CLOUDFLARE_API_TOKEN locally.
  4. Set CLOUDFLARE_SANDBOX_BASE_URL to 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.

Advanced Usage

Multi-Provider Orchestration

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())

Sandbox Reuse (Provider-Level)

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())

Streaming Execution

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())

Connection Pooling

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())

Architecture

Core Components

  • Sandbox: High-level interface with automatic provider management
  • SandboxProvider: Abstract base class for all providers
  • SandboxConfig: Configuration for sandbox creation
  • ExecutionResult: Standardized execution results
  • Manager: Multi-provider orchestration
  • ConnectionPool: Connection pooling with TTL
  • RetryPolicy: Configurable retry logic
  • CircuitBreaker: Fault tolerance

Environment Variables

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

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())

Running Go

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())

Common Use Cases

AI Agent Code Execution

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"))

Data Processing Pipeline

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"))

Code Testing and Validation

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"}
]))

Troubleshooting

No Providers Available

# 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")

Provider Failures

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())

Debugging

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())

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, Hopx, Vercel, Fly.io (Sprites), and Cloudflare for their excellent sandbox platforms.

About

Universal API for cloud sandboxes + CLI

Resources

Contributing

Stars

105 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages