External Sandboxes
Extend agentOS with full sandboxes for heavy workloads like browsers, desktop automation, and compilation.
Pair agentOS with a full sandbox on demand for heavy workloads. The sandbox filesystem mounts into the VM as a native directory, and its process management is exposed as bindings — provider-agnostic through Sandbox Agent.
- agentOS covers most use cases; a sandbox adds a full Linux environment for special software.
- Lazily start a sandbox only when a task needs it, then tear it down.
- One agent session can mix lightweight coding with heavy system work.
- See agentOS vs Sandbox for the full comparison.
When to use a sandbox
- Native binaries not yet supported in the agentOS runtime.
- Browsers / desktop automation — Playwright, Puppeteer, Selenium, anything needing a display server.
- Heavy compilation — large builds or native toolchains.
- GUI apps — desktop apps, VNC, graphical workloads.
- npm packages with native extensions —
sharp,bcrypt,better-sqlite3.
Default to the agentOS VM; spin up a sandbox only when required. Sandboxes bill per second of uptime.
Getting started
Ships as @rivet-dev/agentos-sandbox, working through two mechanisms:
- Filesystem mount — projects the sandbox into the VM as a native directory. Read/write files through the mount.
- Bindings — exposes sandbox process management as bindings. Run commands on the sandbox from the VM.
Both powered by Sandbox Agent; swap providers without changing agent code.
npm install @rivet-dev/agentos-sandbox sandbox-agent
createSandboxFs,createSandboxBindings— from@rivet-dev/agentos-sandbox.SandboxAgent+ provider helpers (e.g.docker) — fromsandbox-agent.- Pass a provider as
sandbox: { provider: docker() }. agentOS starts the client, mounts it at/mnt/sandbox, registers process bindings, and disposes it with the VM. - In RivetKit actors, pass the provider to
agentOS(...)— a fresh client per actor VM.
Configuration
The sandbox is mounted at /mnt/sandbox by default. The sandbox object also
accepts these options alongside provider or client:
| Option | Purpose |
|---|---|
mountPath | Changes the directory where the external filesystem appears inside the agentOS VM. |
sandboxRoot | Restricts the mount to a directory inside the external sandbox instead of exposing its root. |
readOnly | Prevents the VM from modifying files through the mount. Defaults to false. |
timeoutMs | Sets the per-request timeout for Sandbox Agent filesystem calls. |
maxFullReadBytes | Bounds files buffered by full-read and truncate fallbacks. |
The server example above changes mountPath to
/home/agentos/sandbox. Paths used inside the external sandbox remain relative
to sandboxRoot, so a VM file such as /home/agentos/sandbox/app/index.ts maps
to /app/index.ts when sandboxRoot is /.
Calling the mounted bindings
Write code through the filesystem, run it inside the sandbox. Bindings are a CLI
command, called through the same exec/spawn surface as any command.
import { createClient } from "@rivet-dev/agentos/client";
import type { registry } from "./server";
const client = createClient<typeof registry>({
endpoint: "http://localhost:6420",
});
const vm = client.vm.getOrCreate("my-agent");
// Write code via the filesystem. The /home/agentos/sandbox mount maps to the sandbox root.
await vm.filesystem.writeFile("/home/agentos/sandbox/app/index.ts", 'console.log("hello")');
// Run it inside the sandbox through the generated binding command.
// The VM path above maps to /app/index.ts at the sandbox root.
const result = await vm.process.exec(
"agentos-sandbox run-command --command node --args /app/index.ts",
);
console.log(result.stdout); // "hello\n"
const install = await vm.process.exec(
"agentos-sandbox run-command --command npm --args install --args --prefix --args /app",
);
console.log(install.exitCode, install.stdout);
// Spawn a long-running process and stream its output. Connect to the VM,
// then subscribe to `processOutput` events for the spawned pid.
const { pid } = await vm.process.spawn("npm", ["run", "dev", "--prefix", "/home/agentos/sandbox/app"]);
const conn = vm.connect();
conn.on("processOutput", (payload) => {
if (payload.pid !== pid) return;
console.log(payload.stream, new TextDecoder().decode(payload.data));
});
Bindings reference
# Run a command synchronously
agentos-sandbox run-command --command "npm install" --cwd "/app"
# Start a background process
agentos-sandbox create-process --command "npm" --args "run" --args "dev"
# List running processes
agentos-sandbox list-processes
# Get process output
agentos-sandbox get-process-logs --id "proc_abc123"
# Stop or kill a process
agentos-sandbox stop-process --id "proc_abc123"
agentos-sandbox kill-process --id "proc_abc123"
# Send input to an interactive process
agentos-sandbox send-input --id "proc_abc123" --data "yes"
Sandbox providers
The package provides isolated entrypoints for Docker, local execution, E2B,
Daytona, Vercel, Cloudflare, Modal, ComputeSDK, and Sprites. Import only the
provider you use (for example, @rivet-dev/agentos-sandbox/e2b) so its optional
SDK is isolated from the rest of your application. Docker is also exported from
the package root, as shown above.
Install the provider’s SDK when required: @e2b/code-interpreter for E2B,
@daytonaio/sdk for Daytona, @vercel/sandbox for Vercel,
@cloudflare/sandbox for Cloudflare, modal for Modal, computesdk for
ComputeSDK, or @fly/sprites for Sprites. Docker and local execution need no
additional provider SDK.
For another backend, adapt any Sandbox Agent
provider with sandboxAgentProvider from @rivet-dev/agentos-sandbox. Provider
mode is the preferred lifecycle: each VM gets a fresh sandbox, and disposal of
the VM destroys it.
Advanced: mount an existing client
Standalone AgentOs.create() can mount an already-connected,
Sandbox-Agent-compatible client. Install sandbox-agent directly for this
manual path. The caller owns the client by default; set dispose to true when
the client implements disposal, or provide a callback to transfer lifecycle
ownership to the VM.
import { AgentOs } from "@rivet-dev/agentos";
import { SandboxAgent } from "sandbox-agent";
import { docker } from "sandbox-agent/docker";
const sandbox = await SandboxAgent.start({ sandbox: docker() });
const vm = await AgentOs.create({
sandbox: {
client: sandbox,
dispose: () => sandbox.destroySandbox(),
},
});
await vm.dispose();
RivetKit agentOS() intentionally rejects the client form because one client
cannot be shared safely across actor VMs. Pass a provider there so every actor
VM starts and owns a fresh sandbox.