agentOS Apps
Deploy user-generated applications in agentOS VMs.
agentOS Apps runs user-generated HTTP applications on Rivet. Apps can add durable SQLite state, workflows, multiplayer and realtime state, queues, and cron jobs.
agentOS Apps is in preview and its API is subject to change.
Architecture
agentOS Apps is a library, not a hosted AI-generated app deployment platform. Unlike managed platforms, you can deploy it anywhere and customize the server, routing, authentication, and deployment flow.
Requests reach your Hono server, where appsRouter routes them to a prewarmed
agentOS VM serving the generated application. Rivet handles request routing and
orchestrates the pool of prewarmed VMs.
Quickstart
View the complete Quickstart example on GitHub.
Install
npm add @rivet-dev/agentos @rivet-dev/agentos-apps
npm add @hono/node-server hono
npm add --save-dev tsx
npm pkg set type=module
Setup the server
Setup the HTTP server that will serve requests for AI-generated apps. Also set up the actors that power the deployments.
import { serve } from "@hono/node-server";
import { appsRouter } from "@rivet-dev/agentos-apps";
import { Hono } from "hono";
import { registry } from "./actors.js";
// Start the actor registry before routing applications.
registry.start();
const server = new Hono();
// Mount every deployed application at /apps/:appId.
server.route("/apps", appsRouter);
// Serve the host router over HTTP.
serve({
fetch: server.fetch,
port: 3000,
});
Run the server:
npx tsx src/server.ts
Deploy an AI-generated app
Pass generated files directly to deployApp(). This can be called by an agent,
an upload endpoint, or any other part of your system:
import { deployApp } from "@rivet-dev/agentos-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.12.9",
},
}),
"src/index.ts": `
import { Hono } from "hono";
const app = new Hono();
// Serve the application's frontend.
app.get("/", (c) => c.html("<h1>Hello from agentOS Apps</h1>"));
// Serve a REST API request from the same application.
app.get("/api/hello", (c) => c.json({ message: "Hello from agentOS Apps" }));
export default app;
`,
},
});
npx tsx src/deploy.ts
Visit the AI-generated app
Open http://localhost:3000/apps/hello-world/. Pass this URL to agents,
frontends, or any other part of your system that needs to visit the deployment.
Deploy
Deploy the host server to any supported target:
See Deployment for managed and self-hosted options.
Deploy App Reference
Deploy a directory:
await deployApp({
appId: "hello-world",
source: new URL("../fixtures/app/", import.meta.url),
});
Or deploy generated files:
await deployApp({
appId: "generated-app",
files: {
"index.html": "<h1>Hello</h1>",
},
});
TypeScript repair
deployApp() returns build diagnostics when generated TypeScript does not
compile. An agent can use those diagnostics to repair the files and call
deployApp() again:
for (let attempt = 0; attempt < 3; attempt++) {
try {
await deployApp({ appId: "generated-app", files });
break;
} catch (error) {
if (attempt === 2) throw error;
files = await repairWithAgent(files, String(error));
}
}
A failed build does not replace the currently active release. See the AI App Builder example.
appId must contain 1–63 lowercase letters, numbers, or hyphens. Pass exactly
one of source or files.
Configuration
await deployApp({
appId: "my-app",
source,
regions: ["atl", "fra"],
createNamespace: true,
scaling: {
minReplicas: 0,
maxReplicas: 128,
targetConcurrency: 8,
},
});
| Option | Default |
|---|---|
regions | Current Rivet region |
createNamespace | false |
scaling.minReplicas | 0 |
scaling.maxReplicas | 128 |
scaling.targetConcurrency | 8 |
By default, apps use the namespace configured on the ordinary Rivet client.
Enable createNamespace only when the app needs its own namespace; it requires
Rivet namespace list and create permissions.
Route requests
Mount all deployed apps:
server.route("/apps", appsRouter);
This routes /apps/:appId and /apps/:appId/*. To use an explicit RivetKit
client:
import { createAppsRouter } from "@rivet-dev/agentos-apps/advanced";
server.route("/apps", createAppsRouter({ client }));
Add Capabilities to Apps
Agents can generate more than pages and REST APIs. These examples show apps with durable SQLite data, workflows, multiplayer state, queues, and scheduled jobs. The server snippets represent AI-generated app code; the client snippets show how another part of your system connects to it.
SQLite
Example AI-generated app code that stores durable data in an actor-owned SQLite database. View the complete SQLite example.
import { actor, setup } from "rivetkit";
import { db } from "rivetkit/db";
const notes = actor({
db: db({
async onMigrate(database) {
await database.execute(`
CREATE TABLE IF NOT EXISTS notes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
body TEXT NOT NULL
)
`);
},
}),
actions: {
async add(c, body: string) {
await c.db.execute("INSERT INTO notes (body) VALUES (?)", body);
},
async list(c) {
return c.db.execute("SELECT id, body FROM notes ORDER BY id");
},
},
});
export const registry = setup({
use: { notes },
});
registry.start();
export default function fetch() {
return Response.json({
app: "sqlite-notes",
message: "Use the RivetKit client to add and list notes.",
});
}
import type { Deployment } from "@rivet-dev/agentos-apps";
import { createClient } from "rivetkit/client";
import type { registry as appRegistry } from "../fixtures/app/src/index.js";
const response = await fetch("http://localhost:3000/deploy/sqlite-notes", {
method: "POST",
});
if (!response.ok) {
throw new Error(`deployment failed: ${response.status} ${await response.text()}`);
}
const deployment = (await response.json()) as Deployment;
const client = createClient<typeof appRegistry>({
namespace: deployment.namespace,
poolName: deployment.pool,
});
try {
const notes = client.notes.getOrCreate(["shared"]);
await notes.add("Hello from the RivetKit client");
console.log(await notes.list());
} finally {
await client.dispose();
}
Workflows
Example AI-generated app code that runs durable multi-step jobs that can sleep and resume. View the complete workflows example.
import { actor, setup } from "rivetkit";
import { workflow } from "rivetkit/workflow";
const job = actor({
state: {
id: "",
status: "queued" as "queued" | "running" | "complete",
},
onCreate(c) {
c.state.id = c.key[0] ?? "";
},
actions: {
inspect: (c) => c.state,
},
run: workflow(async (workflowContext) => {
await workflowContext.step("start", async (c) => {
c.state.status = "running";
});
await workflowContext.sleep("work", 1_000);
await workflowContext.step("finish", async (c) => {
c.state.status = "complete";
});
}),
});
export const registry = setup({
use: { job },
});
registry.start();
export default function fetch() {
return Response.json({
app: "durable-workflow",
message: "Use the RivetKit client to create and inspect jobs.",
});
}
import type { Deployment } from "@rivet-dev/agentos-apps";
import { createClient } from "rivetkit/client";
import type { registry as appRegistry } from "../fixtures/app/src/index.js";
const response = await fetch("http://localhost:3000/deploy/durable-workflow", {
method: "POST",
});
if (!response.ok) {
throw new Error(`deployment failed: ${response.status} ${await response.text()}`);
}
const deployment = (await response.json()) as Deployment;
const client = createClient<typeof appRegistry>({
namespace: deployment.namespace,
poolName: deployment.pool,
});
try {
const job = client.job.getOrCreate(["example-job"]);
console.log(await job.inspect());
} finally {
await client.dispose();
}
Multiplayer
Example AI-generated app code that shares realtime state between clients. View the complete multiplayer example.
import { actor, event, setup } from "rivetkit";
type Position = { x: number; y: number };
const room = actor({
state: {
players: {} as Record<string, Position>,
},
events: {
changed: event(),
},
actions: {
join(c, player: string) {
c.state.players[player] ??= { x: 0, y: 0 };
c.broadcast("changed", c.state.players);
return c.state.players;
},
move(c, player: string, x: number, y: number) {
c.state.players[player] = { x, y };
c.broadcast("changed", c.state.players);
return c.state.players;
},
inspect(c) {
return c.state.players;
},
},
});
export const registry = setup({
use: { room },
});
registry.start();
export default function fetch() {
return Response.json({
app: "multiplayer-room",
message: "Use the RivetKit client to join and move in a room.",
});
}
import type { Deployment } from "@rivet-dev/agentos-apps";
import { createClient } from "rivetkit/client";
import type { registry as appRegistry } from "../fixtures/app/src/index.js";
const response = await fetch("http://localhost:3000/deploy/multiplayer-room", {
method: "POST",
});
if (!response.ok) {
throw new Error(`deployment failed: ${response.status} ${await response.text()}`);
}
const deployment = (await response.json()) as Deployment;
const client = createClient<typeof appRegistry>({
namespace: deployment.namespace,
poolName: deployment.pool,
});
try {
const room = client.room.getOrCreate(["lobby"]);
await room.join("alice");
console.log(await room.move("alice", 4, 8));
} finally {
await client.dispose();
}
Queues
AI-generated apps can use actor queues for durable background work and ordered processing.
Cron jobs
AI-generated apps can schedule recurring work from an actor. See Cron Jobs.
These capabilities use RivetKit and its ordinary DirectActor client. agentOS Apps does not wrap the client.
Build Apps with AI
Give an agent the app requirements, let it generate the project files, and pass
those files to deployApp(). If the build returns TypeScript diagnostics, give
them back to the agent and deploy its repaired files again.
View the complete AI App Builder example.
Authentication
Use normal Hono middleware to authenticate requests before they reach deployed apps:
server.use("/apps/*", authMiddleware);
server.route("/apps", appsRouter);
Planned Improvements
- Automatically include agent skills based on the Rivet Cookbooks for better app generation.
- Billing API for tracking and charging for app usage.
- Built-in error reporting for generated apps.