Rivet
Run durable Actors, workflows, sandboxes, and generated apps on one control plane.
- 1 Install
npm install rivetkit@next - 2 Define an Actor index.ts
import { actor, setup } from "rivetkit"; import { db } from "rivetkit/db"; // A chat room Actor. Every room key gets its own instance with its own SQLite // database, which survives restarts and redeploys. export const chatRoom = actor({ // Create the messages table the first time a room starts. db: db({ onMigrate: async (db) => { await db.execute(` CREATE TABLE IF NOT EXISTS messages ( id INTEGER PRIMARY KEY AUTOINCREMENT, sender TEXT NOT NULL, text TEXT NOT NULL ) `); }, }), actions: { // Save a message, then push it to everyone connected to this room. sendMessage: async (c, sender: string, text: string) => { await c.db.execute( "INSERT INTO messages (sender, text) VALUES (?, ?)", sender, text, ); c.broadcast("newMessage", { sender, text }); }, // Read the room's history, oldest first. getHistory: async (c) => { return (await c.db.execute( "SELECT sender, text FROM messages ORDER BY id", )) as { sender: string; text: string }[]; }, }, }); // Register the Actor and start serving it on http://localhost:6420. export const registry = setup({ use: { chatRoom } }); registry.start(); - 3 Talk to it from a client client.ts
import { createClient } from "rivetkit/client"; import type { registry } from "./index"; // Connect to the server from index.ts. The registry type checks every call. const client = createClient<typeof registry>("http://localhost:6420"); // Get the room keyed "general", creating it on first use. const room = client.chatRoom.getOrCreate(["general"]); // Open a realtime connection and print each message as it arrives. const connection = room.connect(); connection.on("newMessage", (message: { sender: string; text: string }) => { console.log(`${message.sender}: ${message.text}`); }); // Send a message. The Actor saves it to SQLite before broadcasting it. await connection.sendMessage("alice", "Hello, Rivet!"); // The history is still there after the Actor sleeps or restarts. console.log(await room.getHistory());
- 1 Install
npm install @rivet-dev/workflows rivetkit - 2 Define a workflow index.ts
import { setup, type WorkflowStepContextOf, workflow, } from "@rivet-dev/workflows"; import { db } from "rivetkit/db"; // An order workflow, one instance per order. Each step runs exactly once: // after a crash or redeploy, the workflow replays and skips finished steps. export const order = workflow({ // Each order gets its own SQLite database to record its progress. db: db({ onMigrate: async (db) => { await db.execute(` CREATE TABLE IF NOT EXISTS progress ( id INTEGER PRIMARY KEY AUTOINCREMENT, step TEXT NOT NULL ) `); }, }), run: async (ctx) => { // Reserve stock, then record that the step finished. await ctx.step("reserve", (step) => record(step, "reserve")); // Wait durably. The workflow keeps its place across restarts. await ctx.sleep("wait-for-payment", 1_000); // Charge the card and send the receipt, recording each step. await ctx.step("charge", (step) => record(step, "charge")); await ctx.step("receipt", (step) => record(step, "receipt")); }, actions: { // Read which steps have finished so far. getProgress: async (c) => { return (await c.db.execute( "SELECT step FROM progress ORDER BY id", )) as { step: string }[]; }, }, }); // Write one progress row to this order's database. async function record( step: WorkflowStepContextOf<typeof order>, name: string, ) { await step.db.execute("INSERT INTO progress (step) VALUES (?)", name); } // Register the workflow and start serving it on http://localhost:6420. export const registry = setup({ use: { order } }); registry.start(); - 3 Start it from a client client.ts
import { createClient } from "rivetkit/client"; import type { registry } from "./index"; // Connect to the server from index.ts. The registry type checks every call. const client = createClient<typeof registry>("http://localhost:6420"); // Get the order keyed "order-123". Creating it starts its workflow. const order = client.order.getOrCreate(["order-123"]); // Give the workflow a moment to run, then read the steps it has finished. await new Promise((resolve) => setTimeout(resolve, 2_000)); console.log(await order.getProgress());
- 1 Install
npm install @rivet-dev/agentos - 2 Create a hosted VM and use it quickstart.ts
import { createAgentOsClient } from "@rivet-dev/agentos"; // Connect to Rivet Cloud. Set RIVET_NAMESPACE and RIVET_TOKEN from your // project's dashboard before running this. const client = createAgentOsClient({ endpoint: "https://api.rivet.dev" }); // Get the VM keyed "my-vm", creating it on first use. Same key, same VM. const vm = client.agentOS.getOrCreate(["my-vm"]); // Write a file. The VM's filesystem persists while the VM sleeps. await vm.v1.filesystem.writeFile({ path: "/home/agentos/hello.txt", content: "Hello from agentOS!", }); // Run a shell command and capture its output. const result = await vm.v1.process.run({ command: "cat", args: ["/home/agentos/hello.txt"], options: { captureStdio: true }, }); console.log(result.stdout); // Start a web server inside the VM. await vm.v1.filesystem.writeFile({ path: "/home/agentos/server.js", content: `require("http") .createServer((req, res) => res.end("Hello from the VM!")) .listen(3000);`, }); await vm.v1.process.spawn({ command: "node", args: ["/home/agentos/server.js"], }); // Expose the server through a preview URL that expires in one hour. const preview = await vm.v1.network.preview.create({ port: 3000, ttlMs: 60 * 60 * 1_000, }); console.log(preview.path);
- 1 Install
npm install @rivet-dev/dynamic-apps @hono/node-server hono - 2 Set up the router src/server.ts
import { serve } from "@hono/node-server"; import { appsRouter } from "@rivet-dev/dynamic-apps"; import { Hono } from "hono"; const server = new Hono(); // This is how the Rivet control plane communicates with your backend. server.all("/api/rivet/*", (c) => appsRouter.fetch(c.req.raw)); // Mount every deployed application at /apps/:appId. server.route("/apps", appsRouter); // Serve the host router over HTTP. serve({ fetch: server.fetch, port: 3000, }); - 3 Deploy an app to it src/deploy.ts
import { deployApp } from "@rivet-dev/dynamic-apps"; // An agent, upload endpoint, or any other part of the system can call // deployApp() with the files it generated. await deployApp({ appId: "hello-world", files: { "package.json": JSON.stringify({ name: "hello-world-app", version: "0.0.0", private: true, type: "module", main: "src/index.ts", dependencies: { hono: "4.13.5", }, }), "src/index.ts": ` import { Hono } from "hono"; const app = new Hono(); // Serve the application's frontend. app.get("/", (c) => c.html("<h1>Hello from Dynamic Apps</h1>")); // Serve a REST API request from the same application. app.get("/api/hello", (c) => c.json({ message: "Hello from Dynamic Apps" })); export default app; `, }, });
Once it runs
Guides
Architecture
Log users in on your backend, mint a JWT scoped to each user's Rivet Actor, connect from the browser with it, and authorize actions inside the Actor.
Why actors eliminate complexity instead of managing it, and how merging state and compute removes the biggest source of latency in modern applications.
Agents
Give each user their own agent that they sign in to with their phone number and talk to by text message.
Run an agent whose conversation, in-flight tool calls, and plan survive crashes, deploys, and restarts.
Split a large task across an orchestrator agent and specialist agents that work in parallel.
Let users connect their ChatGPT plan so your agents run on it, with no OpenAI API key.
Let programs the agent writes in its sandbox call your backend tools, authenticated with a scoped JWT.
Coding Agents
Run Claude Code in a cloud sandbox from your app, one session per Rivet Actor, and stream its output as it works.
Run the OpenAI Codex CLI in a cloud sandbox from your app, one session per Rivet Actor, and stream its output as it works.
Run OpenCode in a cloud sandbox from your app, one session per Rivet Actor, and stream its output as it works.
Run the Amp CLI in a cloud sandbox from your app, one session per Rivet Actor, and stream its output as it works.
Workflows
Pause a workflow or an agent's tool call until a person approves or rejects it, even if the answer takes days.
Chain LLM calls and tool calls into one job that picks up at the failed step after a crash or a rate-limit error.
Accept webhooks from Stripe, GitHub, or Shopify, process each event once, and keep events for the same customer in order.
Run a daily report on a cron schedule inside a Rivet Actor, then add one-shot and fixed-interval jobs that survive sleep, restarts, and deploys.
Send your app's events to customer endpoints in order, retry failed deliveries, and turn off endpoints that keep failing.
Realtime
Build a realtime chat room backend with Rivet Actors: one actor per room, SQLite-backed message history, and WebSocket broadcast to every connected client.
Build a collaborative text editor such as Google Docs or Notion with Yjs, where one Actor per document relays edits and presence and saves the document.
Build a collaborative text editor with operational transformation, where one Actor per document orders every edit and keeps the operation log.
Build live cursors and presence for a shared canvas, where one room Actor tracks who is connected and broadcasts every cursor move.
AI-Generated Apps
Databases
Games
Build a battle royale game like Fortnite, Apex Legends, or PUBG, where players fill a lobby and fight in a shrinking zone until one is left.
Build an IO game like Agar.io or Slither.io, where players drop into an open lobby at any time and the server moves everyone.
Build an arena game such as a team deathmatch, a free-for-all, or a Rocket League style match, where players queue for a mode and play a fixed-size match.
Build a turn-based game such as correspondence chess, where a match Actor validates every move and keeps the board between turns.
Build a Minecraft-style open world that is split into chunks, with one Rivet Actor per chunk and players walking between them.
Build a party game with private lobbies, where a host creates a room, friends join with a party code, and the host starts the game.
Build a ranked queue that pairs players by rating, runs each duel in its own match Actor, and updates ratings and a leaderboard when it ends.
Build an idle game like Cookie Clicker, where each player's world Actor keeps producing resources while they are offline and a shared leaderboard ranks everyone.
Build a 2D multiplayer physics game where a Rivet Actor runs Rapier on the server and streams every body's position to the players.
Build a 3D multiplayer physics game where a Rivet Actor runs Rapier on the server and players render the world with three.js.