Skip to main content
Extensions

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 extensionssharp, 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) — from sandbox-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:

OptionPurpose
mountPathChanges the directory where the external filesystem appears inside the agentOS VM.
sandboxRootRestricts the mount to a directory inside the external sandbox instead of exposing its root.
readOnlyPrevents the VM from modifying files through the mount. Defaults to false.
timeoutMsSets the per-request timeout for Sandbox Agent filesystem calls.
maxFullReadBytesBounds 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.