Access sandbox functionality through ctx.sandbox in agents or c.var.sandbox in routes. Choose between one-shot execution for single commands or interactive sandboxes for multi-step workflows.
Use the @agentuity/sandbox standalone package to access this service from any Node.js or Bun app without the runtime.
One-shot Execution
Use sandbox.run() when you need to execute a single command. The sandbox is automatically created and destroyed.
import { createAgent } from '@agentuity/runtime';
import { z } from 'zod';
const agent = createAgent('CodeRunner', {
schema: {
input: z.object({ code: z.string() }),
output: z.object({
success: z.boolean(),
output: z.string(),
exitCode: z.number(),
}),
},
handler: async (ctx, input) => {
const result = await ctx.sandbox.run({
command: {
exec: ['python3', '-c', input.code],
},
resources: { memory: '256Mi', cpu: '500m' },
timeout: { execution: '30s' },
});
return {
success: result.exitCode === 0,
output: result.stdout || result.stderr || '',
exitCode: result.exitCode,
};
},
});With File Input
Write files to the sandbox before execution:
const result = await ctx.sandbox.run({
command: {
exec: ['bun', 'run', 'index.ts'],
files: [
{
path: 'index.ts',
content: Buffer.from('console.log("Hello from TypeScript!")'),
},
{
path: 'data.json',
content: Buffer.from(JSON.stringify({ items: [1, 2, 3] })),
},
],
},
resources: { memory: '512Mi' },
});Interactive Sandbox
Use sandbox.create() for multi-step workflows. The sandbox persists until you explicitly destroy it.
import { createAgent } from '@agentuity/runtime';
const agent = createAgent('ProjectBuilder', {
handler: async (ctx, input) => {
// Create a persistent sandbox
const sandbox = await ctx.sandbox.create({
runtime: 'node:lts',
resources: { memory: '1Gi', cpu: '1000m' },
network: { enabled: true }, // Allow package downloads
dependencies: ['git'], // Pre-install apt packages
});
try {
// Run multiple commands in sequence
await sandbox.execute({ command: ['npm', 'init', '-y'] });
await sandbox.execute({ command: ['npm', 'install', 'zod'] });
// Write project files
await sandbox.writeFiles([
{
path: 'index.ts',
content: Buffer.from(`
import { z } from 'zod';
const schema = z.object({ name: z.string() });
console.log(schema.parse({ name: 'test' }));
`),
},
]);
// Build and run
const result = await sandbox.execute({
command: ['npx', 'tsx', 'index.ts'],
});
return {
exitCode: result.exitCode,
stdoutStreamUrl: result.stdoutStreamUrl,
};
} finally {
// Always clean up
await sandbox.destroy();
}
},
});Writing Files
Write files to the sandbox workspace before or during execution:
await sandbox.writeFiles([
{ path: 'src/main.py', content: Buffer.from('print("Hello")') },
{ path: 'config.json', content: Buffer.from('{"debug": true}') },
]);Exposing Ports
Expose a port from the sandbox to make it accessible via a public URL:
const sandbox = await ctx.sandbox.create({
network: {
enabled: true,
port: 3000, // Expose port 3000 (valid range: 1024-65535)
},
resources: { memory: '512Mi' },
});
// Start a web server inside the sandbox
await sandbox.execute({ command: ['npm', 'run', 'serve'] });
// Get the public URL
const info = await ctx.sandbox.get(sandbox.id);
if (info.url) {
ctx.logger.info('Server accessible at', { url: info.url, port: info.networkPort });
}Setting network.port enables networking for the exposed port. Include network.enabled: true when the sandbox also needs outbound network access.
When using the CLI, --port enables networking for you and returns the public URL directly in the create response. See CLI Sandbox Commands for details.
Project Association
Associate sandboxes with a project for organization and filtering:
const sandbox = await ctx.sandbox.create({
projectId: 'proj_abc123', // Associate with project
resources: { memory: '512Mi' },
});
// List sandboxes by project
const { sandboxes } = await ctx.sandbox.list({
projectId: 'proj_abc123',
});Reading Files
Read files from the sandbox as streams:
const stream = await sandbox.readFile('output/results.json');
const reader = stream.getReader();
const chunks: Uint8Array[] = [];
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
}
const content = new TextDecoder().decode(Buffer.concat(chunks));
const data = JSON.parse(content);Reading Execution Output
sandbox.execute() waits for the command to finish, then returns stream URLs for stdout and stderr when output was captured. Fetch those URLs when you need the command output without loading it into the execution response.
const sandbox = await ctx.sandbox.create();
try {
// Run the command and wait for its final status
const execution = await sandbox.execute({ command: ['npm', 'run', 'build'] });
// Read captured output only when the service returned stream URLs
const stdout = execution.stdoutStreamUrl
? await readTextStream(execution.stdoutStreamUrl)
: '';
const stderr = execution.stderrStreamUrl
? await readTextStream(execution.stderrStreamUrl)
: '';
if (execution.exitCode !== 0) {
ctx.logger.error('Build failed', {
exitCode: execution.exitCode,
stderr,
});
}
return {
exitCode: execution.exitCode,
stdout,
stderr,
};
} finally {
await sandbox.destroy();
}
async function readTextStream(url: string): Promise<string> {
const response = await fetch(url);
if (!response.ok || !response.body) {
throw new Error(`Unable to read sandbox output: ${response.status}`);
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let output = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
output += decoder.decode(value, { stream: true });
}
output += decoder.decode();
return output;
}Creating from Snapshot
Start sandboxes from pre-configured snapshots for faster cold starts:
A snapshot is a saved filesystem state. You create sandboxes from snapshots rather than running them directly. See Snapshots for how to create them.
const sandbox = await ctx.sandbox.create({
snapshot: 'node-project-base', // Use tag or snapshot ID
resources: { memory: '512Mi' },
});
// Sandbox already has node_modules and dependencies installed
await sandbox.execute({ command: ['npm', 'run', 'build'] });Environment Variables
Pass environment variables when creating or running sandboxes:
const result = await ctx.sandbox.run({
command: { exec: ['node', '-e', 'console.log(process.env.API_KEY)'] },
env: {
API_KEY: 'secret-key',
NODE_ENV: 'test',
DEBUG: 'true',
},
resources: { memory: '256Mi' },
});Update an existing interactive sandbox with setEnv(). Set a value to null to delete that variable.
const sandbox = await ctx.sandbox.create();
await sandbox.setEnv({
API_KEY: 'secret-key',
DEBUG: 'true',
});
await sandbox.setEnv({
DEBUG: null, // Delete DEBUG
});Code running inside the sandbox is not running in your agent process. Use standard logging (console.log, print, etc.) inside the sandboxed command, and pass any values it needs explicitly with env or setEnv().
Cancelling Execution
Use an AbortSignal to cancel long-running commands:
const controller = new AbortController();
// Set a 5-second timeout
setTimeout(() => controller.abort(), 5000);
try {
const result = await sandbox.execute({
command: ['npm', 'run', 'long-task'],
signal: controller.signal,
});
return { exitCode: result.exitCode };
} catch (error) {
if (error instanceof DOMException && error.name === 'AbortError') {
ctx.logger.warn('Execution cancelled');
return { exitCode: null, cancelled: true };
}
throw error;
}Using in Routes
Routes access sandbox through c.var.sandbox:
import { Hono } from 'hono';
import type { Env } from '@agentuity/runtime';
const router = new Hono<Env>();
router.post('/execute', async (c) => {
const { language, code } = await c.req.json();
const commands: Record<string, string[]> = {
python: ['python3', '-c', code],
javascript: ['node', '-e', code],
typescript: ['bun', '-e', code],
};
const command = commands[language];
if (!command) {
return c.json({ error: 'Unsupported language' }, 400);
}
const result = await c.var.sandbox.run({
command: { exec: command },
timeout: { execution: '10s' },
resources: { memory: '128Mi', cpu: '250m' },
});
return c.json({
success: result.exitCode === 0,
output: result.stdout || result.stderr,
exitCode: result.exitCode,
durationMs: result.durationMs,
});
});
export default router;Sandbox Management
Listing Sandboxes
const { sandboxes, total } = await ctx.sandbox.list({
status: 'idle', // Filter by status
projectId: 'proj_x', // Filter by project
snapshotId: 'snp_y', // Filter by snapshot
limit: 10,
offset: 0,
});
for (const info of sandboxes) {
ctx.logger.info('Sandbox', {
id: info.sandboxId,
status: info.status,
executions: info.executions,
});
}Getting Sandbox Info
const info = await ctx.sandbox.get('sbx_abc123');
ctx.logger.info('Sandbox details', {
status: info.status,
createdAt: info.createdAt,
snapshot: info.snapshot?.id,
});Destroying Sandboxes
// Via sandbox instance
await sandbox.destroy();
// Via service (by ID)
await ctx.sandbox.destroy('sbx_abc123');Configuration Reference
SandboxCreateOptions
| Option | Type | Description |
|---|---|---|
runtime | string | Runtime environment: 'bun:1', 'python:3.14' |
runtimeId | string | Runtime ID, such as 'srt_xxx' |
name | string | Optional sandbox name |
description | string | Optional description |
resources.memory | string | Memory limit: '256Mi', '1Gi' |
resources.cpu | string | CPU in millicores: '500m', '1000m' |
resources.disk | string | Disk limit: '512Mi', '2Gi' |
network.enabled | boolean | Enable outbound network (default: false) |
network.port | number | Port to expose to internet (1024-65535) |
projectId | string | Associate sandbox with a project |
timeout.idle | string | Auto-destroy after idle: '10m', '1h' |
timeout.execution | string | Max command duration: '30s', '5m' |
dependencies | string[] | Apt packages: ['python3', 'git'] |
packages | string[] | npm/bun packages to install globally |
snapshot | string | Snapshot ID or tag to restore from |
env | Record<string, string> | Environment variables |
metadata | Record<string, unknown> | User-defined metadata for tracking |
scopes | string[] | Permission scopes for automatic service access |
When snapshot is set, do not also set runtime or runtimeId. The snapshot already includes its base runtime.
ExecuteOptions
| Option | Type | Description |
|---|---|---|
command | string[] | Command and arguments |
files | FileToWrite[] | Files to create before execution |
timeout | string | Override execution timeout |
stream | object | Optional stdout, stderr, and timestamp stream configuration |
signal | AbortSignal | Cancel the execution |
Execution
Returned by sandbox.execute():
| Field | Type | Description |
|---|---|---|
executionId | string | Unique execution ID for debugging |
status | string | 'queued', 'running', 'completed', 'failed', 'timeout', 'cancelled' |
exitCode | number | undefined | Process exit code, when available |
durationMs | number | undefined | Execution duration in milliseconds, when available |
stdoutStreamUrl | string | undefined | URL to fetch stdout stream |
stderrStreamUrl | string | undefined | URL to fetch stderr stream |
SandboxRunResult
| Field | Type | Description |
|---|---|---|
sandboxId | string | Sandbox ID (for debugging) |
exitCode | number | Process exit code |
durationMs | number | Execution duration |
stdout | string | Captured stdout (if available) |
stderr | string | Captured stderr (if available) |
SandboxInfo
Returned by ctx.sandbox.get() and in list results:
| Field | Type | Description |
|---|---|---|
sandboxId | string | Unique sandbox identifier |
status | SandboxStatus | 'creating', 'idle', 'running', 'paused', 'stopping', 'suspended', 'terminated', 'failed', 'deleted' |
createdAt | string | ISO timestamp |
runtime | SandboxRuntimeInfo | Runtime details, when available |
snapshot | SandboxSnapshotInfo | Source snapshot, when available |
networkPort | number | Port exposed from sandbox (if configured) |
url | string | Public URL (when port is configured) |
user | SandboxUserInfo | User who created the sandbox |
agent | SandboxAgentInfo | Agent that created the sandbox |
project | SandboxProjectInfo | Associated project |
org | SandboxOrgInfo | Organization (always present) |
Access context information from sandbox info:
const info = await ctx.sandbox.get('sbx_abc123');
// Organization is always present
ctx.logger.info('Organization', { id: info.org.id, name: info.org.name });
// User info (when created by a user)
if (info.user) {
ctx.logger.info('Created by', {
userId: info.user.id,
name: `${info.user.firstName} ${info.user.lastName}`,
});
}
// Agent info (when created by an agent)
if (info.agent) {
ctx.logger.info('Agent', { id: info.agent.id, name: info.agent.name });
}
// Project info (when associated with a project)
if (info.project) {
ctx.logger.info('Project', { id: info.project.id, name: info.project.name });
}Snapshot Management API
Manage snapshots programmatically through ctx.sandbox.snapshot. This lets agents create, list, and manage snapshots without CLI access.
Creating Snapshots
Save the current state of a sandbox as a snapshot:
const sandbox = await ctx.sandbox.create({
runtime: 'node:lts',
network: { enabled: true },
resources: { memory: '1Gi' },
});
// Set up the environment
await sandbox.execute({ command: ['npm', 'init', '-y'] });
await sandbox.execute({ command: ['npm', 'install', 'typescript', 'zod'] });
// Save as snapshot
const snapshot = await ctx.sandbox.snapshot.create(sandbox.id, {
name: 'typescript-zod-env',
description: 'TypeScript environment with Zod validation',
tag: 'latest',
public: false, // Keep private to your org
});
ctx.logger.info('Snapshot created', {
snapshotId: snapshot.snapshotId,
sizeBytes: snapshot.sizeBytes,
fileCount: snapshot.fileCount,
});
await sandbox.destroy();Listing Snapshots
const { snapshots, total } = await ctx.sandbox.snapshot.list({
sandboxId: 'sbx_abc123', // Filter by source sandbox
limit: 50,
offset: 0,
});
for (const snap of snapshots) {
ctx.logger.info('Snapshot', {
id: snap.snapshotId,
name: snap.name,
tag: snap.tag,
createdAt: snap.createdAt,
});
}Getting Snapshot Details
const snapshot = await ctx.sandbox.snapshot.get('snp_xyz789');
ctx.logger.info('Snapshot details', {
name: snapshot.name,
sizeBytes: snapshot.sizeBytes,
fileCount: snapshot.fileCount,
files: snapshot.files, // Array of file info
});Tagging Snapshots
Update or remove a snapshot's tag:
// Add or update tag
await ctx.sandbox.snapshot.tag('snp_xyz789', 'v1.0');
// Point "latest" to a new snapshot
await ctx.sandbox.snapshot.tag('snp_newversion', 'latest');
// Remove tag
await ctx.sandbox.snapshot.tag('snp_xyz789', null);Deleting Snapshots
await ctx.sandbox.snapshot.delete('snp_xyz789');SnapshotCreateOptions
| Option | Type | Description |
|---|---|---|
name | string | Display name (URL-safe: letters, numbers, underscores, dashes) |
description | string | Description of the snapshot |
tag | string | Tag name (defaults to "latest") |
public | boolean | Make snapshot publicly accessible (default: false) |
SnapshotInfo
Returned by create, get, and list operations:
| Field | Type | Description |
|---|---|---|
snapshotId | string | Unique identifier |
name | string | Display name |
tag | string | null | Current tag |
sizeBytes | number | Total size in bytes |
fileCount | number | Number of files |
files | SnapshotFileInfo[] | File details (path, size, mime type) |
createdAt | string | ISO timestamp |
downloadUrl | string | URL to download snapshot archive |
public | boolean | Whether publicly accessible |
Best Practices
- Set resource limits: Control memory and CPU usage for predictable performance
- Use timeouts: Always set execution timeouts for untrusted code
- Enable network when needed: Required for package installation, API calls, and external requests
- Clean up interactive sandboxes: Use try/finally to ensure
destroy()is called - Use snapshots for common environments: Pre-install dependencies to reduce cold start time
- Tag important snapshots: Use semantic versioning tags (
v1.0,latest) for reproducibility
Next Steps
- Snapshots: CLI commands and declarative snapshot definitions
- CLI Commands: Debug sandboxes from the terminal