Skip to main content
Agents

Pi

Run the Pi coding agent as a durable Rivet Actor.

This integration is in beta. APIs may change between releases.

@rivet-dev/pi runs the Pi coding agent in a Rivet Actor. The agent loop runs in the actor. Pi’s file and shell tools run in a separate sandbox.

Each actor holds one Pi session and saves it in its SQLite database, so a conversation survives sleep and restarts.

Quickstart

Install

npm add @rivet-dev/pi rivetkit

Define the actor

import { pi } from "@rivet-dev/pi";
import { setup } from "rivetkit";

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

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

registry.start();

Set the key for your model provider, such as ANTHROPIC_API_KEY.

Send a prompt

import { createClient } from "rivetkit/client";
import type { registry } from "./server";

const client = createClient<typeof registry>("http://localhost:6420");
const agent = client.agent.getOrCreate(["support", "customer-123"]);

const conn = agent.connect();
conn.on("event", (event) => {
	if (
		event.type === "message_update" &&
		event.assistantMessageEvent.type === "text_delta"
	) {
		process.stdout.write(event.assistantMessageEvent.delta);
	}
});

await conn.prompt("Inspect the project and summarize its test failures.");

console.log(await conn.getMessages());
await conn.dispose();

prompt resolves when the run ends. Other actions use Pi’s AgentSession method names.

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:

If you are running your own workers, follow the guide for your hosting provider:

Configuration

pi() accepts the same config as actor() and Pi’s session options, plus:

OptionDescription
modelModel for a new session.
scopedModelsModels a client may switch to with setModel.
apiKeysAPI keys by provider. Otherwise keys come from the environment.
providersCustom providers, in the shape of Pi’s models.json.
credentialsLogins your application stores, such as subscriptions. See Bring your subscription.
sandboxWhere Pi’s file and shell tools run. Without it they are disabled.
import { agentOS } from "@rivet-dev/agentos";
import { pi } from "@rivet-dev/pi";
import { agentOSProvider } from "@rivet-dev/sandbox-adapter/agentos";
import { setup } from "rivetkit";

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

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

For E2B, Daytona, or Modal, use sandboxAgentProvider() from @rivet-dev/sandbox-adapter/sandbox-agent. Provider credentials never enter the sandbox.

Tracing

Each run is an invoke_agent pi span under the prompt action span, with OpenTelemetry GenAI attributes. Prompts, tool arguments, and tool results are not recorded. See OpenTelemetry to export actor traces.

Bring your subscription

Your users can run Pi on their own Claude or ChatGPT subscription instead of your API key. Pi supports headless OAuth login for these subscriptions.

Pass the logins you store to Pi with credentials:

import { pi } from "@rivet-dev/pi";

const agent = pi({
	model: "openai-codex/gpt-6-sol",
	credentials: (c) => ({
		list: () => logins.list(c.key[0]),
		read: (provider) => logins.read(c.key[0], provider),
		refresh: (provider) => logins.refresh(c.key[0], provider),
	}),
});

logins is your own storage. See PiCredentialSource for what each method returns. Pi never receives the refresh token.

The credentials example stores logins in a credentials actor and runs the login in a terminal.

ACP

Connect ACP editors such as Zed to Pi actors:

npx @rivet-dev/pi acp --actor agent --user me

Each editor session is the Pi actor with key [user, sessionId].

The command uses RIVET_TOKEN, which reaches every actor, so use it only for yourself. For your users, build a command with serveAcp() from @rivet-dev/pi/acp that reaches each actor with an actor-scoped JWT.

Add --credentials <actor> to let users log in to a subscription from the editor. The editor runs Pi’s login in a terminal, then the command calls save(provider, credential) on that actor with key [user].