Unified sandbox SDK for AI agents — write once, run on any cloud.
Sandbank provides a single TypeScript interface for creating, managing, and orchestrating cloud sandboxes. Switch between providers without changing your application code.
AI agents need isolated execution environments. But every cloud provider has a different API — Daytona, Fly.io, Cloudflare Workers all speak different languages. Sandbank unifies them behind one interface:
import { createProvider } from '@sandbank.dev/core'
import { DaytonaAdapter } from '@sandbank.dev/daytona'
const provider = createProvider(new DaytonaAdapter({ apiKey: '...' }))
const sandbox = await provider.create({ image: 'node:22' })
const result = await sandbox.exec('echo "Hello from the sandbox"')
console.log(result.stdout) // Hello from the sandbox
await provider.destroy(sandbox.id)Swap DaytonaAdapter for FlyioAdapter or CloudflareAdapter — zero code changes.
┌──────────────────────────────────────────────────────┐
│ Your Application / AI Agent │
├──────────────────────────────────────────────────────┤
│ @sandbank.dev/core Unified Provider Interface │
│ @sandbank.dev/skills Skill Registry & Injection │
│ @sandbank.dev/agent In-sandbox Agent Client │
│ @sandbank.dev/relay Multi-agent Communication │
├──────────────────────────────────────────────────────┤
│ @sandbank.dev/daytona @sandbank.dev/flyio @sandbank.dev/cloudflare │
│ @sandbank.dev/boxlite │
│ Provider Adapters (Compute) │
├──────────────────────────────────────────────────────┤
│ @sandbank.dev/db9 Service Adapter (Data) │
├──────────────────────────────────────────────────────┤
│ Daytona Fly.io Machines Cloudflare Workers │
│ BoxLite (self-hosted Docker) db9.ai (PostgreSQL) │
└──────────────────────────────────────────────────────┘
| Package | Description |
|---|---|
@sandbank.dev/core |
Provider abstraction, capability system, error types |
@sandbank.dev/skills |
Skill registry and local filesystem loader |
@sandbank.dev/daytona |
Daytona cloud sandbox adapter |
@sandbank.dev/flyio |
Fly.io Machines adapter |
@sandbank.dev/cloudflare |
Cloudflare Workers adapter |
@sandbank.dev/boxlite |
BoxLite self-hosted Docker adapter |
@sandbank.dev/db9 |
db9.ai serverless PostgreSQL adapter (ServiceProvider) |
@sandbank.dev/relay |
WebSocket relay for multi-agent communication |
@sandbank.dev/agent |
Lightweight client for agents running inside sandboxes |
All providers implement these — the minimum contract:
| Operation | Daytona | Fly.io | Cloudflare | BoxLite |
|---|---|---|---|---|
| Create / Destroy | ✅ | ✅ | ✅ | ✅ |
| List sandboxes | ✅ | ✅ | ✅ | ✅ |
| Execute commands | ✅ | ✅ | ✅ | ✅ |
| Read / Write files | ✅ | ✅ | ✅ | ✅ |
| Skill injection | ✅ | ✅ | ✅ | ✅ |
Capabilities are opt-in. Use withVolumes(provider), withPortExpose(sandbox), etc. to safely check and access them at runtime.
| Capability | Daytona | Fly.io | Cloudflare | BoxLite | db9 | Description |
|---|---|---|---|---|---|---|
volumes |
✅ | ✅ | ❌ | — | Persistent volume management | |
port.expose |
✅ | ✅ | ✅ | — | Expose sandbox ports to the internet | |
exec.stream |
❌ | ❌ | ✅ | ✅ | — | Stream stdout/stderr in real-time |
snapshot |
❌ | ❌ | ✅ | ✅ | — | Snapshot and restore sandbox state |
terminal |
✅ | ✅ | ✅ | ✅ | — | Interactive web terminal (ttyd) |
sleep |
❌ | ❌ | ❌ | ✅ | — | Hibernate and wake sandboxes |
skills |
✅ | ✅ | ✅ | ✅ | — | Load and inject skill definitions into sandboxes |
services |
❌ | ❌ | ❌ | ❌ | ✅ | Bind data services (PostgreSQL) to sandboxes |
* Cloudflare volumes requires storage option in adapter config.
** Cloudflare reserves port 3000 for its sandbox control plane. Use any port in 1024–65535 except 3000.
| Daytona | Fly.io | Cloudflare | BoxLite | |
|---|---|---|---|---|
| Runtime | Full VM | Firecracker microVM | V8 isolate + container | Docker container |
| Cold start | ~10s | ~3-5s | ~1s | ~2-5s |
| File I/O | Native SDK | Via exec (base64) | Native SDK | Via exec (base64) |
| Regions | Multi | Multi | Global edge | Self-hosted |
| External deps | @daytonaio/sdk |
None (pure fetch) | @cloudflare/sandbox |
BoxLite API |
Sandbank includes a built-in orchestration layer for multi-agent workflows. The Relay handles real-time messaging and shared context between sandboxes.
import { createSession } from '@sandbank.dev/core'
const session = await createSession({
provider,
relay: { type: 'memory' },
})
// Spawn agents in isolated sandboxes
const architect = await session.spawn('architect', {
image: 'node:22',
env: { ROLE: 'architect' },
})
const developer = await session.spawn('developer', {
image: 'node:22',
env: { ROLE: 'developer' },
})
// Shared context — all agents can read/write
await session.context.set('spec', { endpoints: ['/users', '/posts'] })
// Wait for all agents to complete
await session.waitForAll()
await session.close()Inside the sandbox, agents use @sandbank.dev/agent:
import { connect } from '@sandbank.dev/agent'
const session = await connect() // reads SANDBANK_* env vars
session.on('message', async (msg) => {
if (msg.type === 'task') {
// do work...
await session.send(msg.from, 'done', result)
}
})
await session.complete({ status: 'success', summary: 'Built 5 API endpoints' })# Install
pnpm add @sandbank.dev/core @sandbank.dev/daytona # or @sandbank.dev/flyio, @sandbank.dev/cloudflare
# Set up provider
export DAYTONA_API_KEY=your-keyimport { createProvider } from '@sandbank.dev/core'
import { DaytonaAdapter } from '@sandbank.dev/daytona'
const provider = createProvider(
new DaytonaAdapter({ apiKey: process.env.DAYTONA_API_KEY! })
)
// Create a sandbox
const sandbox = await provider.create({
image: 'node:22',
resources: { cpu: 2, memory: 2048 },
autoDestroyMinutes: 30,
})
// Run commands
const { stdout } = await sandbox.exec('node --version')
// File operations
await sandbox.writeFile('/app/index.js', 'console.log("hi")')
await sandbox.exec('node /app/index.js')
// Clean up
await provider.destroy(sandbox.id)git clone https://github.com/chekusu/sandbank.git
cd sandbank
pnpm install
# Run all unit tests
pnpm test
# Run cross-provider conformance tests
pnpm test:conformance
# Typecheck
pnpm typecheckIntegration tests hit real APIs and are gated by environment variables:
# Daytona
DAYTONA_API_KEY=... pnpm test
# Fly.io
FLY_API_TOKEN=... FLY_APP_NAME=... pnpm test
# Cloudflare
E2E_WORKER_URL=... pnpm test
# db9
DB9_TOKEN=... pnpm --filter @sandbank.dev/db9 test:e2e| Package | Stmts | Branch | Funcs | Lines | Unit | Integration |
|---|---|---|---|---|---|---|
@sandbank.dev/core |
84% | 77% | 74% | 88% | 98 | — |
@sandbank.dev/db9 |
100% | 97% | 93% | 100% | 35 | 3 |
Run coverage locally:
pnpm --filter @sandbank.dev/db9 test -- --coverage- Minimal interface, maximum interop — only the true common denominator (exec + files + lifecycle)
- Explicit over implicit — no auto-fallback, no caching, no hidden retries
- Capability detection, not fake implementations — if a provider doesn't support it, it errors
- Idempotent operations — destroying an already-destroyed sandbox is a no-op
- Full decoupling — provider layer and session layer are independent, compose freely
MIT