Skip to main content
Core

Sandboxes

Run an agent without a sandbox, or give it a shell in E2B, Daytona, agentOS, or a sandbox provider you write.

An agent doesn’t need a sandbox. Without one, its file tools work on files in the Actor’s own database. Add a sandbox only when the agent needs a shell.

FilesShellSetup
No sandboxIn the Actor’s SQLite databaseNone. bash returns an error.None
SandboxIn the sandboxbash runs in the sandboxA provider account or agentOS

Without a sandbox

Leave out the sandbox option. Pi’s read, write, and edit tools then work on files stored in the Actor’s own SQLite database:

import { createRegistry, defineExtension } from "@earendil-works/pi-durable";
import { createEditTool, createReadTool, createWriteTool } from "@earendil-works/pi-durable/tools";
import { pi } from "@rivet-dev/pi";
import { setup } from "rivetkit";

// read, write, and edit. With no sandbox, the files live in the Actor's own database.
const extensions = createRegistry();
extensions.install(defineExtension({ name: "files", tools: [createReadTool(), createWriteTool(), createEditTool()] }));

const agent = pi({
	model: "anthropic/claude-opus-5-5",
	registry: extensions,
});

export const registry = setup({ use: { agent } });

registry.start();
  • Nothing to provision. There is no sandbox to create, pay for, pause, or delete.
  • Files are durable. They are saved with the conversation and survive sleep, crashes, and upgrades.
  • No shell. bash returns an error, so this agent installs only the file tools.

See Built-in Tools for what each tool does.

With a sandbox

When the agent needs to run commands, such as tests or a build, pass a sandbox provider. @rivet-dev/sandbox-adapter gives each Actor its own sandbox, and CodingTools run there.

SANDBOX PROVIDERSAgent ActorSandbox adapterSandboxProviderE2BDaytonaagentOStool calls

Install the adapter

npm add @rivet-dev/sandbox-adapter e2b

Pass a provider

import { createRegistry } from "@earendil-works/pi-durable";
import { CodingTools } from "@earendil-works/pi-durable/tools";
import { pi } from "@rivet-dev/pi";
import { e2bProvider } from "@rivet-dev/sandbox-adapter/e2b";
import { setup } from "rivetkit";

// read, write, edit, and bash, running in the sandbox
const extensions = createRegistry();
extensions.install(CodingTools);

const agent = pi({
	model: "anthropic/claude-opus-5-5",
	registry: extensions,
	sandbox: e2bProvider({ template: "base" }),
});

export const registry = setup({ use: { agent } });

registry.start();

Providers

ProviderSDKWhile the Actor sleepsWhen the Actor is destroyed
E2Be2bPausedDeleted
Daytona@daytonaio/sdkStoppedDeleted
agentOSNoneSleeps when idle, and keeps its filesLeft in place

Provider credentials stay on your worker and never enter the sandbox.

E2B

npm add @rivet-dev/sandbox-adapter e2b
import { createRegistry } from "@earendil-works/pi-durable";
import { CodingTools } from "@earendil-works/pi-durable/tools";
import { pi } from "@rivet-dev/pi";
import { e2bProvider } from "@rivet-dev/sandbox-adapter/e2b";
import { setup } from "rivetkit";

// read, write, edit, and bash, running in the sandbox
const extensions = createRegistry();
extensions.install(CodingTools);

const agent = pi({
	model: "anthropic/claude-opus-5-5",
	registry: extensions,
	sandbox: e2bProvider({ template: "base" }),
});

export const registry = setup({ use: { agent } });

registry.start();
OptionDescription
templateTemplate name or id. Defaults to base.
createOptions for E2B’s Sandbox.create. The API key defaults to E2B_API_KEY.
cwdWorking directory inside the sandbox. Defaults to /home/user.

E2B ends a sandbox when its timeout runs out, so the provider creates sandboxes that pause on timeout and resume on the next call.

Daytona

npm add @rivet-dev/sandbox-adapter @daytonaio/sdk
import { createRegistry } from "@earendil-works/pi-durable";
import { CodingTools } from "@earendil-works/pi-durable/tools";
import { pi } from "@rivet-dev/pi";
import { daytonaProvider } from "@rivet-dev/sandbox-adapter/daytona";
import { setup } from "rivetkit";

// read, write, edit, and bash, running in the sandbox
const extensions = createRegistry();
extensions.install(CodingTools);

const agent = pi({
	model: "anthropic/claude-opus-5-5",
	registry: extensions,
	sandbox: daytonaProvider(),
});

export const registry = setup({ use: { agent } });

registry.start();
OptionDescription
clientA Daytona client. Defaults to new Daytona(), which reads DAYTONA_API_KEY.
createOptions for Daytona’s create.

The working directory is the sandbox’s own working directory. A stopped sandbox is started again the next time the Actor uses it.

agentOS

npm add @rivet-dev/sandbox-adapter
import { createRegistry } from "@earendil-works/pi-durable";
import { CodingTools } from "@earendil-works/pi-durable/tools";
import { pi } from "@rivet-dev/pi";
import { agentOSProvider } from "@rivet-dev/sandbox-adapter/agentos";
import { setup } from "rivetkit";

// read, write, edit, and bash, running in the sandbox
const extensions = createRegistry();
extensions.install(CodingTools);

const agent = pi({
	model: "anthropic/claude-opus-5-5",
	registry: extensions,
	sandbox: agentOSProvider(),
});

export const registry = setup({ use: { agent } });

registry.start();
OptionDescription
cwdWorking directory inside the VM. Defaults to /workspace.
softwarePackages installed when the VM is created, such as coreutils for sh.

The VM is an agentOS Actor in the services pool, with the key ["sandbox", <agent Actor id>]. It keeps its files in its own SQLite database, so they persist while the agent sleeps. The VM sleeps on its own when idle. Destroying the agent leaves the VM and its files in place.

How the sandbox is managed

  • The sandbox is created the first time the agent needs it.
  • After a wake, the Actor reconnects to the same sandbox the first time a tool needs it. If it no longer exists, a new one is created and the old files are lost.
  • Actions that don’t touch the sandbox keep working while the provider is down.
  • An existing Actor cannot switch to a different provider.
  • Commands run with the sandbox’s environment, not your worker’s. File tools reject paths outside the working directory, but shell commands can reach anything inside the sandbox.
  • Binary file writes aren’t supported yet.

Write your own provider

Any sandbox can back an agent. Implement SandboxProvider and pass it as sandbox. One provider object is shared by every Actor of a definition, so keep no per-Actor state in it. The Actor stores the sandbox id.

MemberDescription
nameStored next to the sandbox id, so a changed provider is detected on wake.
create(c)Provisions a new sandbox and returns its id.
connect(c, id)Connects to an existing sandbox and returns a Sandbox. Returns undefined when the sandbox no longer exists.
suspend(c, id)Optional. Releases resources while the Actor sleeps. When omitted, the sandbox stops itself when idle or keeps running, depending on the provider.
destroy(c, id)Optional. Deletes the sandbox when the Actor is destroyed. When omitted, the sandbox is left in place.

The context c carries the owning Actor’s actorId and a client() for your registry. The Sandbox that connect returns has a cwd and the operations the tools run: exec, readFile, writeFile, mkdir, stat, readdir, and exists. All paths are absolute paths inside the sandbox. See the E2B provider for a complete example.

Next: Custom Tools, tools that run in your backend.