Agent sandbox quickstart
Give an agent its own isolated world: create a sandbox, wait for it to come up, lease a session to it, and tear everything down. One server, one script, no separate sandbox vendor.
Sandbox execution runs on Linux hosts. On macOS (and WSL2 on Windows),
nimbus machine provides the managed Linux VM that hosts sandboxes — see
the CLI reference — and
current capabilities has the full
status table.
1. Install Nimbus
Section titled “1. Install Nimbus”brew install nimbus/tap/nimbusOther platforms ship via the install script and release binaries — see the install options. You’ll also need Node.js 22 or newer for the script below.
2. Start the server
Section titled “2. Start the server”nimbus start --port 8080 --data-dir ./data3. Grab the admin token and create a tenant
Section titled “3. Grab the admin token and create a tenant”The native API is protected by a local admin token, created on first boot:
# Linuxexport NIMBUS_TOKEN=$(jq -r .token ~/.local/share/nimbus/auth/token)
# macOSexport NIMBUS_TOKEN=$(jq -r .token "$HOME/Library/Application Support/nimbus/auth/token")
curl -s -X POST http://localhost:8080/api/tenants \ -H "Authorization: Bearer $NIMBUS_TOKEN" \ -H "Content-Type: application/json" \ -d '{"id": "demo"}'4. Install the SDK
Section titled “4. Install the SDK”mkdir agent-sandbox && cd agent-sandboxnpm init -y && npm install @nimbus/nimbus5. Create a sandbox and lease a session
Section titled “5. Create a sandbox and lease a session”Save this as sandbox.mjs:
import { Nimbus } from "@nimbus/nimbus";
const nimbus = new Nimbus({ endpoint: "http://localhost:8080", tenantId: "demo", token: process.env.NIMBUS_TOKEN,});
// Create an isolated world for one task: a root image, a process, an id.const sandbox = await nimbus.sandboxes.create({ profile: "worker", spec: { owner: { kind: "standalone", displayName: "agent-task" }, backend: "container", root: { kind: "oci_image", source: { kind: "reference", reference: "docker.io/library/node:22-alpine" }, }, process: { argv: ["node", "-e", "setInterval(() => {}, 1000)"] }, }, labels: { purpose: "quickstart" },});console.log("created", sandbox.metadata.id, sandbox.status.lifecycleState);
// Poll until the sandbox is ready — sessions only attach to ready targets.let current = sandbox;while (current.status.lifecycleState !== "ready") { await new Promise((resolve) => setTimeout(resolve, 250)); current = await nimbus.sandboxes.get({ id: sandbox.metadata.id });}
// Lease an interactive connection. Every session expires on its own.const session = await nimbus.sessions.open({ target: { sandbox: { id: sandbox.metadata.id } }, channels: ["stdio"], requestedTtlMs: 5 * 60 * 1000,});console.log("session", session.metadata.id, "expires", session.spec.expiresAt);console.log("attached to", session.spec.targetSnapshot);
// Tear down: close the lease, stop the world.await nimbus.sessions.close({ id: session.metadata.id, reason: "done" });await nimbus.sandboxes.stop({ id: sandbox.metadata.id });console.log("stopped");6. Run it
Section titled “6. Run it”node sandbox.mjsThe script creates the sandbox, waits for ready, opens a five-minute
stdio session, prints the target snapshot the session pinned at open
time, then closes the session and stops the sandbox.
What just happened
Section titled “What just happened”- The sandbox is addressed by id — a receipt for a resource you created, not a name other code can resolve. It ran as an OCI container with deny-by-default network egress, inside the same tenant-isolation boundary as your data.
- The session is a lease, not a connection pool entry: it carried a TTL, recorded exactly what it attached to, and would have expired on its own if the script crashed before closing it.
- Nothing here was a special agent runtime. The same server is also serving your database, functions, and realtime queries.
Next steps
Section titled “Next steps”- Run sandboxes — specs, labels, listing, and what the API redacts.
- Manage services — promote work to a name other code can depend on.
- Open sessions — channels, TTLs, and target rules.
- Services, sandboxes, and sessions — why the model is shaped this way.