Crash Course
Run coding agents inside isolated VMs with full filesystem, process, and network control.
agentOS is in preview and the API is subject to change. If you run into issues, please report them on GitHub or join our Discord.
When to Use agentOS
- Coding agents: Run any coding agent with full OS access, file editing, shell execution, and tool use.
- Automated pipelines: CI-like workflows where agents clone repos, fix bugs, run tests, and open PRs.
- Multi-agent systems: Coordinators dispatching to specialized agents, review pipelines, planning chains.
- Scheduled maintenance: Cron-based agents that audit code, update dependencies, or generate reports.
- Collaborative workspaces: Multiple users observing and interacting with the same agent session in realtime.
Minimal Project
import { createClient } from "@rivet-dev/agentos/client";
import type { registry } from "./server";
const client = createClient<typeof registry>({ endpoint: "http://localhost:6420" });
const agent = client.vm.getOrCreate("my-agent");
// Subscribe to streaming events
const conn = agent.connect();
conn.on("sessionEvent", (data) => {
console.log(data.event);
});
// Create a session and send a prompt
const sessionId = await agent.createSession("pi", {
env: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY! },
});
const response = await agent.sendPrompt(
sessionId,
"Write a hello world script to /workspace/hello.js",
);
console.log(response.text);
// Read the file the agent created
const content = await agent.readFile("/workspace/hello.js");
console.log(new TextDecoder().decode(content));
After the quickstart, customize your agent with the Registry.
Agents
Sessions & Transcripts
Create agent sessions, send prompts, and stream responses in realtime. Transcripts are persisted automatically across sleep/wake cycles.
import { createClient } from "@rivet-dev/agentos/client";
import type { registry } from "./server";
const client = createClient<typeof registry>({ endpoint: "http://localhost:6420" });
const agent = client.vm.getOrCreate("my-agent");
// Stream events as they arrive
const conn = agent.connect();
conn.on("sessionEvent", (data) => {
console.log(data.event.method, data.event);
});
// Create a session
const sessionId = await agent.createSession("pi", {
env: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY! },
});
// Send a prompt and wait for the response
const response = await agent.sendPrompt(
sessionId,
"List all files in the home directory",
);
console.log(response.text);
See Full Example or Documentation
Approvals
Approve or deny agent tool use with human-in-the-loop patterns or auto-approve for trusted workloads.
import { agentOS, setup } from "@rivet-dev/agentos";
import pi from "./software/pi";
// Auto-approve all permissions server-side
const vm = agentOS({
software: [pi],
onPermissionRequest: async (sessionId, request) => {
console.log("Auto-approving", sessionId, request.permissionId);
},
});
export const registry = setup({ use: { vm } });
registry.start();
import { createClient } from "@rivet-dev/agentos/client";
import type { registry } from "./server";
const client = createClient<typeof registry>({ endpoint: "http://localhost:6420" });
const agent = client.vm.getOrCreate("my-agent");
// Observe permission requests client-side for human-in-the-loop review.
const conn = agent.connect();
conn.on("permissionRequest", (data) => {
console.log("Permission requested:", data.request);
});
See Full Example or Documentation
Bindings
Expose your JavaScript functions to agents as CLI commands inside the VM. Each binding group becomes a binary at /usr/local/bin/agentos-{name}, and each binding becomes a subcommand with flags auto-generated from its Zod input schema. The server below defines a weather binding group with a forecast binding; the client opens a session and prompts the agent, which calls the binding itself as a shell command.
import { agentOS, setup } from "@rivet-dev/agentos";
import { z } from "zod";
// Define a group of bindings (host functions). Each tool has a Zod input
// schema and an `execute` handler that runs on the host. The group is exposed to
// the agent as a CLI command at /usr/local/bin/agentos-{name} inside the VM.
const weatherBindings = {
name: "weather",
description: "Weather data bindings",
tools: {
forecast: {
description: "Get the weather forecast for a city",
inputSchema: z.object({
city: z.string().describe("City name"),
days: z.number().optional().describe("Number of days"),
}),
execute: async (input: { city: string; days?: number }) => {
const res = await fetch(
`https://api.weather.example/forecast?city=${input.city}&days=${input.days ?? 3}`,
);
return res.json();
},
examples: [
{ description: "3-day forecast for Paris", input: { city: "Paris", days: 3 } },
],
},
},
};
const vm = agentOS({
toolKits: [weatherBindings],
});
export const registry = setup({ use: { vm } });
registry.start();
import { createClient } from "@rivet-dev/agentos/client";
import type { registry } from "./server";
const client = createClient<typeof registry>({ endpoint: "http://localhost:6420" });
const agent = client.vm.getOrCreate("my-agent");
// The agent invokes the binding itself as a shell command:
// agentos-weather forecast --city Paris --days 3
const sessionId = await agent.createSession("claude", {
env: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY! },
});
await agent.sendPrompt(sessionId, "What's the weather in Paris?");
See Full Example or Documentation
Agent-to-Agent
Let one agent call another through a binding. The coder gets a review binding it invokes itself, which bridges into the reviewer’s isolated VM.
import { agentOS, setup } from "@rivet-dev/agentos";
import { createClient } from "@rivet-dev/agentos/client";
import { z } from "zod";
import pi from "./software/pi";
// The reviewer is its own isolated agent VM.
const reviewer = agentOS({ software: [pi] });
// The coder gets a `review` toolkit it can call itself: it copies a file from the
// coder's VM into the reviewer's VM and asks the reviewer to review it.
const coder = agentOS({
software: [pi],
toolKits: [
{
name: "review",
description: "Send a file to the reviewer agent and get back a review.",
tools: {
submit: {
description: "Submit a file path for review by the reviewer agent.",
inputSchema: z.object({ path: z.string() }),
execute: async ({ path }: { path: string }) => {
const client = createClient<typeof registry>({
endpoint: "http://localhost:6420",
});
const content = await client.coder
.getOrCreate("feature-auth")
.readFile(path);
const reviewerHandle = client.reviewer.getOrCreate("feature-auth");
await reviewerHandle.writeFile(path, content);
const sessionId = await reviewerHandle.createSession("pi", {
env: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY! },
});
const result = await reviewerHandle.sendPrompt(
sessionId,
`Review ${path} for security issues`,
);
return { review: result.text };
},
},
},
},
],
});
export const registry = setup({ use: { coder, reviewer } });
registry.start();
import { createClient } from "@rivet-dev/agentos/client";
import type { registry } from "./agent-to-agent-server";
const client = createClient<typeof registry>({ endpoint: "http://localhost:6420" });
const coderAgent = client.coder.getOrCreate("feature-auth");
const sessionId = await coderAgent.createSession("pi", {
env: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY! },
});
// The coder implements the feature, then calls the `review` binding itself so the
// reviewer agent reviews the code. This is true agent-to-agent: the coder drives it.
await coderAgent.sendPrompt(
sessionId,
"Implement the login feature in /home/agentos/src/auth.ts, then run `agentos-review submit --path /home/agentos/src/auth.ts` to have it reviewed.",
);
See Full Example or Documentation
Multiplayer & Realtime
Connect multiple clients to the same agent VM. All subscribers see session output, process logs, and shell data in realtime.
import { createClient } from "@rivet-dev/agentos/client";
import type { registry } from "./server";
// Client A: creates the session and sends prompts
const clientA = createClient<typeof registry>({ endpoint: "http://localhost:6420" });
const agentA = clientA.vm.getOrCreate("shared-agent");
const connA = agentA.connect();
connA.on("sessionEvent", (data) =>
console.log("[A]", data.event.method),
);
const sessionId = await agentA.createSession("pi", {
env: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY! },
});
await agentA.sendPrompt(sessionId, "Build a REST API");
// Client B: observes the same session (separate process)
const clientB = createClient<typeof registry>({ endpoint: "http://localhost:6420" });
const connB = clientB.vm.getOrCreate("shared-agent").connect();
connB.on("sessionEvent", (data) =>
console.log("[B]", data.event.method),
);
// Client B sees the same events as Client A
See Full Example or Documentation
Workflows
Orchestrate multi-step agent tasks with durable workflows that survive crashes and restarts.
import { agentOS, setup } from "@rivet-dev/agentos";
import { actor, queue } from "rivetkit";
import { workflow } from "rivetkit/workflow";
import pi from "./software/pi";
const vm = agentOS({ software: [pi] });
// A durable workflow actor. Its `run` is built with `workflow()`, so every
// `step(...)` is recorded, retried, and resumed: if the process crashes
// mid-run, replay skips completed steps and continues where it left off.
const bugFixer = actor({
queues: {
fixBug: queue<{ repo: string; issue: string }>(),
},
run: workflow(async (ctx) => {
await ctx.loop("fix-bug-loop", async (loopCtx) => {
// Wait durably for the next bug-fix request from the queue.
const message = await loopCtx.queue.next("wait-fix-bug");
const { repo, issue } = message.body;
// The typed client is reached from inside a `step` (the only scope with
// actor data). Each step re-derives the VM handle from `step.client()`.
await loopCtx.step("clone-repo", (step) =>
step
.client<typeof registry>()
.vm.getOrCreate("bug-fixer")
.exec(`git clone ${repo} /home/agentos/repo`),
);
await loopCtx.step("fix-bug", async (step) => {
const agent = step.client<typeof registry>().vm.getOrCreate("bug-fixer");
const sessionId = await agent.createSession("claude", {
env: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY! },
});
await agent.sendPrompt(sessionId, `Fix the bug in issue: ${issue}`);
await agent.closeSession(sessionId);
});
await loopCtx.step("run-tests", (step) =>
step
.client<typeof registry>()
.vm.getOrCreate("bug-fixer")
.exec("cd /home/agentos/repo && npm test"),
);
});
}),
});
export const registry = setup({ use: { vm, bugFixer } });
registry.start();
Operating System
Filesystem
Read, write, and manage files inside the VM. The /home/agentos directory is persisted automatically across sleep/wake cycles.
import { createClient } from "@rivet-dev/agentos/client";
import type { registry } from "./server";
const client = createClient<typeof registry>({ endpoint: "http://localhost:6420" });
const agent = client.vm.getOrCreate("my-agent");
// Write a file
await agent.writeFile("/home/agentos/config.json", JSON.stringify({ key: "value" }));
// Read a file
const content = await agent.readFile("/home/agentos/config.json");
console.log(new TextDecoder().decode(content));
// List directory contents recursively
const files = await agent.readdirRecursive("/home/agentos");
for (const entry of files) {
console.log(entry.type, entry.path);
}
See Full Example or Documentation
Processes & Shell
Execute commands, spawn long-running processes, and open interactive shells.
import { createClient } from "@rivet-dev/agentos/client";
import type { registry } from "./server";
const client = createClient<typeof registry>({ endpoint: "http://localhost:6420" });
const agent = client.vm.getOrCreate("my-agent");
// One-shot execution
const result = await agent.exec("echo hello && ls /home/agentos");
console.log("stdout:", result.stdout);
console.log("exit code:", result.exitCode);
// Spawn a long-running process
const conn = agent.connect();
conn.on("processOutput", (data) => {
console.log(`[pid ${data.pid}]`, new TextDecoder().decode(data.data));
});
const { pid } = await agent.spawn("node", ["server.js"]);
console.log("Process ID:", pid);
See Full Example or Documentation
Networking & Previews
Proxy HTTP requests into VMs with vmFetch. Create preview URLs for port forwarding VM services to shareable public URLs.
import { createClient } from "@rivet-dev/agentos/client";
import type { registry } from "./server";
const client = createClient<typeof registry>({ endpoint: "http://localhost:6420" });
const agent = client.vm.getOrCreate("my-agent");
// Fetch from a service running inside the VM
const response = await agent.vmFetch(3000, "/api/health");
console.log("Status:", response.status);
// Create a preview URL (port forwarding to a public URL), valid for 1 hour
const preview = await agent.createSignedPreviewUrl(3000, 3600);
console.log("Public URL:", preview.url);
console.log("Expires at:", new Date(preview.expiresAt));
See Full Example or Documentation
Cron Jobs
Schedule recurring commands and agent sessions with cron expressions.
import { createClient } from "@rivet-dev/agentos/client";
import type { registry } from "./server";
const client = createClient<typeof registry>({ endpoint: "http://localhost:6420" });
const agent = client.vm.getOrCreate("my-agent");
// Schedule a command every hour
await agent.scheduleCron({
schedule: "0 * * * *",
action: { type: "exec", command: "rm", args: ["-rf", "/tmp/cache/*"] },
});
// Schedule an agent session daily at 9 AM
await agent.scheduleCron({
schedule: "0 9 * * *",
action: {
type: "session",
agentType: "pi",
prompt: "Review the codebase for security issues and write a report to /home/agentos/audit.md",
},
});
See Full Example or Documentation
Sandbox Mounting
agentOS uses a hybrid model: agents run in a lightweight VM by default and mount a full sandbox on demand for heavy workloads like browsers, compilation, and desktop automation. Sandboxes are powered by Sandbox Agent, so you can swap providers without changing agent code. Mount the sandbox as a filesystem and expose its process management as bindings.
import { agentOS, setup } from "@rivet-dev/agentos";
import { createSandboxFs, createSandboxBindings } from "@rivet-dev/agentos-sandbox";
import { SandboxAgent } from "sandbox-agent";
import { docker } from "sandbox-agent/docker";
const sandbox = await SandboxAgent.start({ sandbox: docker() });
const vm = agentOS({
// Toolkits let the agent control the sandbox
toolKits: [createSandboxBindings({ client: sandbox })],
// Mounts let the agent read the sandbox filesystem (optional)
mounts: [
{ path: "/home/agentos/sandbox", plugin: createSandboxFs({ client: sandbox }) },
],
});
export const registry = setup({ use: { vm } });
registry.start();