Skip to main content

Cloudflare Sandbox

CloudflareSandbox executes commands and manages files in a remote Cloudflare Sandbox through the Sandbox Bridge HTTP API.

warning

Deploy and secure a Sandbox Bridge Worker before using this provider. The bridge can create and delete sandboxes, execute commands, and write files on behalf of its callers.

Installation
Direct link to Installation

npm install @mastra/cloudflare-sandbox

Usage
Direct link to Usage

Add CloudflareSandbox to a workspace and assign it to an agent:

src/mastra/agents/dev-agent.ts
import { Agent } from '@mastra/core/agent'
import { Workspace } from '@mastra/core/workspace'
import { CloudflareSandbox } from '@mastra/cloudflare-sandbox'

const workspace = new Workspace({
sandbox: new CloudflareSandbox({
baseUrl: process.env.CLOUDFLARE_SANDBOX_BRIDGE_URL!,
apiToken: process.env.CLOUDFLARE_SANDBOX_API_KEY,
workingDirectory: '/workspace',
commandTimeout: 300_000,
}),
})

export const agent = new Agent({
id: 'dev-agent',
name: 'Development agent',
instructions: 'You are a helpful development assistant.',
model: 'anthropic/claude-sonnet-4-6',
workspace,
})

The provider creates a remote sandbox when the workspace starts. Pass sandboxId to reconnect to an existing sandbox instead.

Execute commands
Direct link to Execute commands

Pass command arguments, environment variables, a working directory, and streaming callbacks through executeCommand(). The provider sends the command as an argv array, so the bridge handles shell escaping:

const result = await workspace.sandbox?.executeCommand?.('npm', ['test'], {
cwd: '/workspace/project',
env: {
NODE_ENV: 'test',
},
onStdout: chunk => process.stdout.write(chunk),
onStderr: chunk => process.stderr.write(chunk),
})

Write files
Direct link to Write files

Relative paths are resolved under /workspace. Absolute paths must also resolve within /workspace. Each file is sent as its own bridge request, and the bridge caps a single file at 32 MiB.

The Cloudflare sandbox cannot set per-file permissions, so a writeFiles call that includes a mode is rejected rather than silently ignored.

await workspace.sandbox?.writeFiles?.([
{ path: 'src/index.ts', content: "console.log('hello')\n" },
{ path: '/workspace/package.json', content: JSON.stringify({ type: 'module' }) },
])

Read files
Direct link to Read files

Read a single file back from /workspace as raw bytes. Relative paths resolve under /workspace, and absolute paths must also resolve within it.

const bytes = await workspace.sandbox?.readFile?.('src/index.ts')
const source = new TextDecoder().decode(bytes)

Keep files across container sleep
Direct link to Keep files across container sleep

Cloudflare stops an idle container after sleepAfter (10 minutes by default). The next command starts a fresh container, so anything written under /workspace is gone. Treat /workspace as scratch space, and put files that must survive on a mounted bucket.

Mount an S3-compatible bucket such as Cloudflare R2 through the Workspace mounts option. The sandbox mounts it on start(). A slept container boots without its mounts, so the sandbox re-mounts any dropped paths before the next filesystem operation, keeping mounted data durable across sleep.

import { Workspace } from '@mastra/core/workspace'
import { S3Filesystem } from '@mastra/s3'
import { CloudflareSandbox } from '@mastra/cloudflare-sandbox'

const workspace = new Workspace({
sandbox: new CloudflareSandbox({
baseUrl: process.env.CLOUDFLARE_SANDBOX_BRIDGE_URL!,
apiToken: process.env.CLOUDFLARE_SANDBOX_BRIDGE_TOKEN!,
}),
mounts: {
'/workspace/data': new S3Filesystem({
bucket: 'agent-data',
region: 'auto',
endpoint: `https://${process.env.CLOUDFLARE_ACCOUNT_ID}.r2.cloudflarestorage.com`,
accessKeyId: process.env.R2_ACCESS_KEY_ID,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
}),
},
})

The bridge mounts buckets with s3fs, so only type: 's3' mount configs are accepted. GCS and Azure filesystems, and S3 credentials that include a sessionToken, return a failed MountResult without contacting the bridge.

The default getInstructions() text tells the model that /workspace does not survive between commands and lists the mounted paths to write to instead.

Persist and restore the workspace
Direct link to Persist and restore the workspace

persistWorkspace() archives /workspace and returns the raw tar bytes; hydrateWorkspace() restores it from those bytes. Use this for one-off backups. It is not a substitute for mounts: restoring must happen before the first command after a wake, and a persist taken from a freshly woken container overwrites the archive with an empty workspace.

// Back up before the container can sleep
const archive = await workspace.sandbox?.persistWorkspace?.({ excludes: ['node_modules'] })
await myStorage.put('workspace-backup', archive)

// Later, on a fresh or reconnected sandbox
const archive = await myStorage.get('workspace-backup')
await workspace.sandbox?.hydrateWorkspace?.(archive)

For execution sessions, use the CloudflareSandboxBridgeClient methods (createSession, deleteSession) directly.

Constructor parameters
Direct link to Constructor parameters

baseUrl:

string
URL of the deployed Cloudflare Sandbox Bridge Worker.

apiToken?:

string
Bearer token matching the Worker's SANDBOX_API_KEY secret.

sandboxId?:

string
Existing Cloudflare sandbox ID to reconnect to instead of creating a sandbox.

id?:

string
Stable Mastra identifier. Defaults to a generated UUID-based value.

name?:

string
= Cloudflare Sandbox
Human-readable sandbox name.

env?:

Record<string, string>
Environment variables applied to every command.

workingDirectory?:

string
Default directory for command execution when no per-command cwd is given. A per-command cwd always wins.

commandTimeout?:

number
= 300000
Default command timeout in milliseconds.

instructions?:

string | ((options) => string)
Custom instructions returned by getInstructions().

Lifecycle behavior
Direct link to Lifecycle behavior

  • start(): Reconnects to sandboxId or creates a remote sandbox, then mounts any Workspace mounts.
  • stop(): Detaches the Mastra lifecycle without deleting the remote sandbox because the bridge doesn't expose a suspend operation.
  • destroy(): Deletes the remote sandbox.

Limitations
Direct link to Limitations

The provider surfaces command execution, streamed output, file reads and writes, S3-compatible bucket mounts, and workspace persistence directly on the sandbox. Sessions are available on the bridge client (CloudflareSandboxBridgeClient) but not on the CloudflareSandbox surface. PTY terminals are not exposed, and the provider doesn't support background process management, stdin, snapshots, or port URLs.