OpenRouter for Sandboxes - Unified Python API for cloud sandbox providers
Project description
OpenRouter for Sandboxes
One API. Many sandbox providers.
Just like OpenRouter gives you a single API across LLM providers, bespokelabs-sandbox gives you a unified interface across sandbox providers. Write your code once, swap backends with a single parameter.
Why?
- No lock-in — Your code works across all backends. Switch providers without rewriting a single line.
- Easily move between providers — If one provider has an outage or capacity issue, change one string and keep running.
- Cost tracking — Monitor and compare spend across providers. (coming soon)
- Automatic scheduling to lowest cost provider — Let the library route your workloads to the cheapest available backend. (coming soon)
Install
pip install bespokelabs-sandbox
With a specific backend:
pip install bespokelabs-sandbox[docker]
pip install bespokelabs-sandbox[daytona]
pip install bespokelabs-sandbox[tensorlake]
pip install bespokelabs-sandbox[modal]
pip install bespokelabs-sandbox[e2b]
pip install bespokelabs-sandbox[ray]
pip install bespokelabs-sandbox[all]
The Safehouse backend has no Python extra. Install the CLI separately on macOS:
brew install eugene1g/safehouse/agent-safehouse
Supported Backends
Local
No API keys, no cloud accounts. Just works.
| Backend | Extra | Requires |
|---|---|---|
| Local subprocess | (none) | Python installed |
| Agent Safehouse | (none) | macOS + safehouse CLI |
| Docker | [docker] |
Docker daemon running |
| Ray | [ray] |
Ray installed (local or remote cluster) |
Cloud
| Backend | Extra | Auth |
|---|---|---|
| Daytona | [daytona] |
DAYTONA_API_KEY |
| Tensorlake | [tensorlake] |
tl login |
| Modal | [modal] |
MODAL_TOKEN_ID + MODAL_TOKEN_SECRET |
| E2B | [e2b] |
E2B_API_KEY |
You only need to install the backend you use. The others are lazily imported.
Quickstart
from bespokelabs.sandbox import Sandbox
# Zero setup — runs locally
with Sandbox("local") as sb:
result = sb.execute_code('print("hello")')
print(result.stdout)
# Or use Safehouse on macOS
with Sandbox("safehouse") as sb:
result = sb.execute_code('print("hello from safehouse")')
print(result.stdout)
# Or use Docker
with Sandbox("docker") as sb:
result = sb.execute_code('print("hello from a container")')
print(result.stdout)
# Or any cloud provider — same interface
with Sandbox("e2b") as sb:
result = sb.execute_code('print("hello from the cloud")')
print(result.stdout)
Switch backends by changing one string:
for backend in ["local", "safehouse", "docker", "modal", "e2b", "daytona", "tensorlake", "ray"]:
with Sandbox(backend) as sb:
sb.execute_code('print("same code, any backend")')
API Reference
Creating a Sandbox
from bespokelabs.sandbox import Sandbox
sb = Sandbox(
backend, # "local" | "safehouse" | "docker" | "ray" | "daytona" | "tensorlake" | "modal" | "e2b"
*,
preset=None, # Preset name or SandboxPreset object
cpu=1.0, # vCPUs (Tensorlake, Modal, Docker)
memory_mb=1024, # RAM in MB (Tensorlake, Modal, Docker)
disk_mb=None, # Disk in MB (Daytona)
timeout_secs=600, # Max lifetime / subprocess timeout
image=None, # Container image (Docker, Modal, Daytona)
template=None, # Template ID (E2B)
env_vars=None, # dict of environment variables
allow_internet=True, # Network access (Docker, Tensorlake, Daytona)
app_name=None, # App name (Modal)
snapshot_id=None, # Restore from snapshot (Tensorlake, Modal)
workdir=None, # Host directory to use as sandbox root (Safehouse)
)
Not every backend uses every parameter. Unsupported params are silently ignored.
Executing Code
result = sb.execute_code('print(1 + 1)', language="python")
print(result.stdout) # "2"
print(result.stderr) # ""
print(result.exit_code) # 0
language defaults to "python". Daytona also supports "typescript", "javascript", "ruby", and "go". Safehouse, Docker, Tensorlake, Modal, Local, and Ray accept any installed binary name.
Running Shell Commands
result = sb.execute_command("ls -la /tmp")
result = sb.execute_command("grep", args=["-r", "TODO", "/app"])
File Operations
# List files
files = sb.list_files("/home")
for f in files:
print(f.path, f.is_dir, f.size)
# Read / write in-memory content
sb.write_file("/tmp/config.json", '{"key": "value"}')
data = sb.read_file("/tmp/config.json") # returns bytes
# Upload a local file into the sandbox
sb.upload_file("./local_data.csv", "/home/user/data.csv")
# Download a file from the sandbox to local disk
sb.download_file("/home/user/results.json", "./results.json")
Presets
Presets are predefined sandbox configurations with setup commands that run after creation.
Presets that install tools with npm, such as codex, claude-code, and web-dev, assume the sandbox image already includes Node.js and npm.
# Sandbox with Codex CLI installed
with Sandbox("docker", preset="codex") as sb:
sb.execute_command("codex --version")
# Sandbox with Claude Code installed
with Sandbox("docker", preset="claude-code") as sb:
sb.execute_command("claude --version")
# Python data science stack
with Sandbox("e2b", preset="python-data-science") as sb:
sb.execute_code("import pandas as pd; print(pd.__version__)")
Built-in presets:
| Preset | What it installs | Defaults |
|---|---|---|
claude-code |
@anthropic-ai/claude-code via npm |
2GB RAM, 30min timeout |
codex |
@openai/codex via npm |
2GB RAM, 30min timeout |
python-data-science |
numpy, pandas, matplotlib, scikit-learn | 2GB RAM |
python-ml |
torch, transformers, datasets, accelerate | 2 vCPU, 4GB RAM, 30min timeout |
node |
Verifies node/npm are present | defaults |
web-dev |
typescript, ts-node, prettier, eslint | 2GB RAM |
empty |
Nothing | defaults |
Create your own:
from bespokelabs.sandbox import Sandbox, SandboxPreset
Sandbox.register_preset(SandboxPreset(
name="my-stack",
description="My custom environment",
setup_commands=["pip install my-library", "npm install -g my-tool"],
cpu=2.0,
memory_mb=4096,
))
with Sandbox("docker", preset="my-stack") as sb:
...
Explicit kwargs always override preset defaults.
Snapshots
snap = sb.snapshot()
print(snap.snapshot_id)
# Restore later
sb2 = Sandbox("tensorlake", snapshot_id=snap.snapshot_id)
| Backend | Snapshot support |
|---|---|
| Docker | Yes (container.commit()) |
| Tensorlake | Yes (filesystem + memory) |
| Modal | Yes (filesystem) |
| Daytona, E2B, Local, Ray, Safehouse | No |
Lifecycle
# Context manager (recommended) — auto-destroys on exit
with Sandbox("local") as sb:
sb.execute_code("print('hi')")
# Manual cleanup
sb = Sandbox("docker")
sb.execute_code("print('hi')")
sb.destroy()
# Check state
sb.is_alive # True/False
sb.backend_name # "docker"
Feature Support Matrix
| Feature | Local | Safehouse | Docker | Ray | Daytona | Tensorlake | Modal | E2B |
|---|---|---|---|---|---|---|---|---|
execute_code |
Any binary | Any binary | Any binary | Any binary | Python, TS, JS, Ruby, Go | Any binary | Any binary | Python |
execute_command |
Shell | Shell | Shell | Shell | Shell | Shell | Shell | Shell |
list_files |
Native | Native | find / ls |
Native | Native SDK | via ls |
Native SDK | Native SDK |
read_file |
Native | Native | get_archive |
Native | Native SDK | via cat |
Native SDK | Native SDK |
write_file |
Native | Native | put_archive |
Native | Native SDK | via base64 | Native SDK | Native SDK |
upload_file |
shutil.copy |
shutil.copy |
put_archive |
ray.put |
Native SDK | via base64 | Native SDK | Native SDK |
download_file |
shutil.copy |
shutil.copy |
get_archive |
ray.get |
Native SDK | via base64 | Native SDK | Native SDK |
snapshot |
No | No | Yes | No | No | Yes | Yes | No |
| Resource limits | No | No | cpu, memory | cpu (Ray) | Defaults | cpu, memory | cpu, memory, gpu | Tier-based |
| Network control | No | No | Yes | No | Firewall, VPN | Yes | Tunnels | No |
| Isolation | Process-level | macOS sandbox-exec |
Container | Process | Full VM | Container | Container | Full VM |
| GPU | No | No | No | Via Ray | No | No | Yes | No |
| Needs install | Nothing | safehouse CLI |
Docker daemon | ray |
API key | tl login |
API key | API key |
Exceptions
from bespokelabs.sandbox import (
SandboxError, # Base class for all errors
SandboxCreationError, # Sandbox failed to start
SandboxExecutionError, # Code or command execution failed
BackendNotInstalledError, # pip package missing for chosen backend
FeatureNotSupportedError, # Backend doesn't support this operation
)
All exceptions inherit from SandboxError, so you can catch broadly or narrowly:
try:
sb.snapshot()
except FeatureNotSupportedError:
print("This backend doesn't support snapshots")
except SandboxError as e:
print(f"Something else went wrong: {e}")
Environment Variables
# Docker — no auth needed, just a running Docker daemon
# Local — no auth needed
# Ray — optional remote cluster
export RAY_ADDRESS=ray://head-node:10001 # omit for local cluster
# Daytona
export DAYTONA_API_KEY=your_key
export DAYTONA_API_URL=https://app.daytona.io/api # optional
export DAYTONA_TARGET=us # optional
# Tensorlake (authenticate via CLI)
tl login
# Modal
export MODAL_TOKEN_ID=your_id
export MODAL_TOKEN_SECRET=your_secret
# E2B
export E2B_API_KEY=your_key
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 bespokelabs_sandbox-0.1.0.tar.gz.
File metadata
- Download URL: bespokelabs_sandbox-0.1.0.tar.gz
- Upload date:
- Size: 31.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b8f873497a7746f476bf99580a9ca0abd2eade0e1124000cc2bcad137e559a03
|
|
| MD5 |
2d51e4d4260263847b54dfae04aa7a8f
|
|
| BLAKE2b-256 |
e850b76666c81d2cccb76b9f787b9b6a8ebb640ae78e30c38d9e60b5825a684b
|
File details
Details for the file bespokelabs_sandbox-0.1.0-py3-none-any.whl.
File metadata
- Download URL: bespokelabs_sandbox-0.1.0-py3-none-any.whl
- Upload date:
- Size: 34.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2cfbf8ca0b37aff89c346798ac40fb28c537279aabc37c78c3d5c9da72ae9496
|
|
| MD5 |
dc8317eb110737a154e24642a26a7224
|
|
| BLAKE2b-256 |
61410b4e7f79696f31660991aebf2f9f85d67a3da7121c63328bbccec4cbfb75
|