Quickstart
Run your first Pi agent in a Rivet Actor. Its files live in the Actor's database, so there is nothing else to set up.
An agent is a Rivet Actor in your backend. Clients prompt it like any other Actor. Each step of a run is saved to the Actor’s SQLite database, so a run survives sleep, crashes, and upgrades.
Install
npm add @rivet-dev/pi @earendil-works/pi-durable rivetkit
Define the agent
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();
Set ANTHROPIC_API_KEY. For another model provider, change model and set that provider’s key, such as OPENAI_API_KEY. See LLM API Keys.
The agent needs no sandbox. Its read, write, and edit tools work on files stored in the Actor’s own database.
Send a prompt
TypeScript
import { createClient } from "rivetkit/client";
import type { registry } from "./server";
const client = createClient<typeof registry>();
const agent = client.agent.getOrCreate(["user-123"]);
const result = await agent.prompt(
"Write a hello-world Rivet Actor to counter.ts, then read the file back to me.",
{ requestId: crypto.randomUUID() },
);
console.log(result.status === "done" ? result.text : `Unanswered: ${result.reason}`);
curl
URL="http://localhost:6420/gateway/agent/action"
AGENT="rvt-namespace=default&rvt-method=getOrCreate&rvt-key=user-123&rvt-pool=default"
curl -X POST "$URL/prompt?$AGENT" \
-H "Content-Type: application/json" \
-d '{"args": ["Write a hello-world Rivet Actor to counter.ts, then read the file back to me.", {"requestId": "counter-1"}]}'
Each action is a POST to the gateway with its arguments in args. See Call Action.
The prompt action waits for the run to finish and returns { status, text }. A model error doesn’t reject it: status is unanswered, with Pi’s reason. Sending the same requestId again returns the first answer instead of running twice.
Add a sandbox (optional)
To let the agent run commands, give it a sandbox and Pi’s CodingTools, which add bash:
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();
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(),
});
export const registry = setup({ use: { agent } });
registry.start();
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();
agentOS needs no provider account. For E2B, install e2b and set E2B_API_KEY. For Daytona, install @daytonaio/sdk and set DAYTONA_API_KEY. See Sandboxes.
Deploy
By default, Rivet stores Actor state on the local file system.
To scale Rivet in production, pick how much of it you want to run yourself:
Fully managed
Bring your own compute
Full self-hosting
If you are running your own workers, follow the guide for your hosting provider:
Docker Compose
Kubernetes
Virtual Machine
AWS ECS
Railway
Render
Custom Platform
Vercel
Google Cloud Run
AWS Lambda
Next: Pi, for how a run survives crashes and upgrades, and its known limitations.