PlatformSandbox
Client for provisioning sandboxes in a Mastra Platform environment. Each PlatformSandbox instance owns one remote sandbox: start() provisions it, executeCommand() runs against it, and destroy() tears it down. Construct additional instances to own additional remote sandboxes. Use clone() to derive them from a configured template (see Cloning).
Sandboxes boot from a pre-built recipe checkpoint with Python 3, Node 22, TypeScript, tsx, and common build tooling already installed. Pass a stable id to opt into checkpoint recovery so a new sandbox boots from the previous one's filesystem.
Related providers: RailwaySandbox for self-hosted Railway sandboxes, LocalSandbox for local sandboxes.
For interface details, see WorkspaceSandbox interface.
InstallationDirect link to Installation
- npm
- pnpm
- Yarn
- Bun
npm install @mastra/platform-workspace
pnpm add @mastra/platform-workspace
yarn add @mastra/platform-workspace
bun add @mastra/platform-workspace
Configure the platform credentials. The access token, project ID, and environment ID fall back to environment variables, so a Mastra Platform deployment can pass zero constructor options.
- .env file
- Constructor
MASTRA_PLATFORM_ACCESS_TOKEN=your-platform-access-token
MASTRA_PROJECT_ID=your-project-id
MASTRA_ENVIRONMENT_ID=your-environment-id
new PlatformSandbox({
accessToken: 'your-platform-access-token',
projectId: 'your-project-id',
environmentId: 'your-environment-id',
})
On a Mastra Platform deployment, MASTRA_PLATFORM_ACCESS_TOKEN, MASTRA_PROJECT_ID, and MASTRA_ENVIRONMENT_ID are injected automatically, so the constructor can be called with no options. For local development, MASTRA_PLATFORM_ACCESS_TOKEN can contain an sk_ API token from your organization's settings page under API Tokens.
UsageDirect link to Usage
Add a PlatformSandbox to a workspace and assign it to an agent:
import { Agent } from '@mastra/core/agent'
import { Workspace } from '@mastra/core/workspace'
import { PlatformSandbox } from '@mastra/platform-workspace'
const workspace = new Workspace({
sandbox: new PlatformSandbox({
// accessToken, projectId, environmentId all fall back to env vars
idleTimeoutMinutes: 30,
}),
})
const agent = new Agent({
id: 'code-agent',
name: 'Code Agent',
instructions: 'You are a coding assistant working in this workspace.',
model: 'anthropic/claude-sonnet-4-6',
workspace,
})
const response = await agent.generate(
'Print "Hello, world!" and show the current working directory.',
)
console.log(response.text)
Private networkingDirect link to Private networking
Set networkIsolation to PRIVATE to join the environment's private network and reach other services running in the same Mastra Platform environment:
const workspace = new Workspace({
sandbox: new PlatformSandbox({
networkIsolation: 'PRIVATE',
}),
})
The default ISOLATED mode allows outbound internet access only, with no private network connectivity.
Reattaching to a running sandboxDirect link to Reattaching to a running sandbox
Pass an existing sandboxId to reattach to a live sandbox instead of creating a new one:
const sandbox = new PlatformSandbox({
sandboxId: 'sbx_abc123',
})
await sandbox.start()
const result = await sandbox.executeCommand('cat', ['/workspace/state.json'])
When sandboxId is set, environmentId isn't required because the sandbox already exists.
Checkpoint recoveryDirect link to Checkpoint recovery
The constructor id (explicit or auto-generated) is sent to the platform on POST /sandbox as an advisory recovery key:
- If the platform recognises the
idfrom a previous session, the new sandbox boots from the most recent checkpoint of that earlier sandbox's filesystem instead of the base recipe. - If the
idisn't recognised, the platform starts a fresh sandbox from the base recipe. Auto-generated ids never match, so omittingiddisables checkpoint recovery.
Pass a stable id to preserve a sandbox's filesystem across sessions or across a destroy()/start() cycle:
const sandbox = new PlatformSandbox({
id: `project-${projectId}`,
})
await sandbox.start() // Boots from the most recent checkpoint for this id, or fresh if unknown
Checkpoint recovery is coarser than sandboxId reattachment. Reattaching (via sandboxId) rejoins the exact live sandbox and its running processes. Checkpoint recovery constructs a brand new sandbox and restores its filesystem from the latest checkpoint the platform captured for the previous sandbox with that id. Running processes and any filesystem writes made after the last checkpoint aren't restored.
Call snapshot() after a filesystem update to capture the configured recovery checkpoint immediately. It resolves without capturing when the sandbox has no caller-provided id or isn't running.
await sandbox.snapshot()
Each id maps to one independent filesystem. Reusing the same id across unrelated sandboxes causes the platform to boot them from each other's checkpoint.
Cloning for a fleet of sandboxesDirect link to Cloning for a fleet of sandboxes
clone() returns an independent sibling PlatformSandbox that inherits credentials and defaults (access token, project, environment, network isolation, timeout, instructions, env, idle timeout) with per-instance overrides. The returned sandbox is unstarted and provisions on its own start(), so clone() performs no I/O:
const template = new PlatformSandbox({
networkIsolation: 'PRIVATE',
idleTimeoutMinutes: 30,
})
const perProject = template.clone({ id: `project-${projectId}` })
await perProject.start()
Combine clone() with a stable id per clone to opt each clone into checkpoint recovery independently.
Executing commandsDirect link to Executing commands
executeCommand runs a command on the remote sandbox and returns its output. Pass args to have arguments safely shell-quoted:
const result = await sandbox.executeCommand('python', ['analyze.py'], {
timeout: 30_000,
cwd: '/workspace',
env: { INPUT: 'repo' },
})
console.log(result.stdout)
console.log(result.exitCode)
The command argument is a shell string and is concatenated verbatim into the remote shell. This lets you use pipes, redirects, and chaining (ls -la | grep foo) but means untrusted input must be passed through args (safely quoted) or shell-quoted by the caller. Untrusted command values allow arbitrary shell execution on the sandbox.
Constructor parametersDirect link to Constructor parameters
accessToken?:
projectId?:
environmentId?:
sandboxId?:
idleTimeoutMinutes?:
networkIsolation?:
env?:
timeout?:
instructions?:
id?:
fetch?:
PropertiesDirect link to Properties
id:
name:
provider:
status:
processes:
MethodsDirect link to Methods
start:
destroy:
stop:
executeCommand:
clone:
snapshot:
getInfo:
getInstructions:
ErrorsDirect link to Errors
Platform API failures raise PlatformApiError. Structured { error: { message, type } } responses are parsed into .code (machine-readable kind) and .proxyMessage (human string); the raw response body stays available on .body:
import { PlatformApiError } from '@mastra/platform-workspace'
try {
await sandbox.executeCommand('cat', ['/missing.txt'])
} catch (err) {
if (err instanceof PlatformApiError) {
if (err.code === 'not_found') {
// handle missing resource
} else if (err.code === 'authentication_error') {
// refresh token
}
console.error(err.status, err.code, err.proxyMessage)
}
}
code and proxyMessage are undefined when the response body isn't JSON, for example an HTML 502 from a load balancer.
executeCommand runs over the direct-exec data plane (a WebSocket to the Railway tcp-proxy) and can also throw two typed sandbox errors on unrecoverable failure:
import { SandboxDestroyedError, SandboxExecTransportError } from '@mastra/platform-workspace'
try {
await sandbox.executeCommand('pytest')
} catch (err) {
if (err instanceof SandboxDestroyedError) {
// /exec-lease returned 410; the sandbox has been destroyed.
// The cached sandbox id and lease have already been cleared,
// so reusing the instance will reprovision on the next call.
} else if (err instanceof SandboxExecTransportError) {
// Both the initial WebSocket attempt and the built-in retry
// closed without an exit frame against a live sandbox.
console.error(err.closeCode, err.closeReason, err.wsEndpoint)
}
}
SandboxExecTransportError carries diagnostic fields (opened, closeCode, closeReason, wsEndpoint, plus sandboxId, command, and attempts) so operators can distinguish a broken Railway data plane from a failed command.