Back to Rivet

Overview

website/src/content/docs/agent-os/index.mdx

2.2.117.1 KB
Original Source

import { faRocket } from "@rivet-gg/icons";

<Note> agentOS is in preview and the API is subject to change. If you run into issues, please [report them on GitHub](https://github.com/rivet-dev/rivet/issues) or [join our Discord](https://rivet.dev/discord). </Note>

Quickstart

<CardGroup> <Card title="Get Started" href="/docs/agent-os/quickstart" icon={faRocket}> Boot a VM and run your first coding agent in minutes </Card> </CardGroup>

Features

  • Isolated VMs: Each agent gets its own filesystem, processes, and networking. No shared state, no cross-contamination.
  • Multi-Agent Support: Run Amp, Claude Code, Codex, OpenCode, and PI with a unified API. Swap agents without changing your code.
  • Host Tools: Expose your JavaScript functions to agents as CLI commands. Direct binding with near-zero latency and automatic code mode for up to 80% token reduction.
  • Persistent State: Filesystem and transcripts survive sleep/wake cycles automatically. No external database needed.
  • Orchestration: Workflows, queues, cron jobs, and multi-agent coordination built on Rivet Actors.
  • Hybrid Sandboxes: Run agents in the lightweight VM by default. Spin up a full sandbox on demand for browsers, compilation, and desktop automation.

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

<CodeGroup> ```ts @nocheck client.ts import { createClient } from "rivetkit/client"; import type { registry } from "./server";

const client = createClient<typeof registry>("http://localhost:6420"); const agent = client.vm.getOrCreate(["my-agent"]);

// Subscribe to streaming events agent.on("sessionEvent", (data) => { console.log(data.event); });

// Create a session and send a prompt const session = await agent.createSession("pi", { env: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY! }, }); const response = await agent.sendPrompt( session.sessionId, "Write a hello world script to /home/user/hello.js", ); console.log(response);

// Read the file the agent created const content = await agent.readFile("/home/user/hello.js"); console.log(new TextDecoder().decode(content));


```ts @nocheck server.ts
import { agentOs } from "rivetkit/agent-os";
import { setup } from "rivetkit";
import common from "@rivet-dev/agent-os-common";
import pi from "@rivet-dev/agent-os-pi";

const vm = agentOs({
  options: { software: [common, pi] },
});

export const registry = setup({ use: { vm } });
registry.start();
</CodeGroup>

After the quickstart, customize your agent with the Registry.

Quick Reference

Sessions & Transcripts

Create agent sessions, send prompts, and stream responses in realtime. Transcripts are persisted automatically across sleep/wake cycles.

<CodeGroup> ```ts @nocheck client.ts import { createClient } from "rivetkit/client"; import type { registry } from "./server";

const client = createClient<typeof registry>("http://localhost:6420"); const agent = client.vm.getOrCreate(["my-agent"]);

// Stream events as they arrive agent.on("sessionEvent", (data) => { console.log(data.event.method, data.event); });

// Create a session with MCP servers const session = await agent.createSession("pi", { env: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY! }, mcpServers: [ { type: "local", command: "npx", args: ["-y", "@modelcontextprotocol/server-filesystem", "/home/user"], env: {}, }, ], });

// Send a prompt and wait for the response const response = await agent.sendPrompt( session.sessionId, "List all files in the home directory", ); console.log(response);


```ts @nocheck server.ts
import { agentOs } from "rivetkit/agent-os";
import { setup } from "rivetkit";
import common from "@rivet-dev/agent-os-common";
import pi from "@rivet-dev/agent-os-pi";

const vm = agentOs({
  options: { software: [common, pi] },
});

export const registry = setup({ use: { vm } });
registry.start();
</CodeGroup>

Documentation

Permissions

Approve or deny agent tool use with human-in-the-loop patterns or auto-approve for trusted workloads.

<CodeGroup stacked> ```ts @nocheck server.ts import { agentOs } from "rivetkit/agent-os"; import { setup } from "rivetkit"; import common from "@rivet-dev/agent-os-common"; import pi from "@rivet-dev/agent-os-pi";

// Auto-approve all permissions server-side const vm = agentOs({ onPermissionRequest: async (c, sessionId, request) => { await c.respondPermission(sessionId, request.permissionId, "always"); }, options: { software: [common, pi] }, });

export const registry = setup({ use: { vm } }); registry.start();


```ts @nocheck client.ts
import { createClient } from "rivetkit/client";
import type { registry } from "./server";

const client = createClient<typeof registry>("http://localhost:6420");
const agent = client.vm.getOrCreate(["my-agent"]);

// Or handle permissions client-side for human-in-the-loop
agent.on("permissionRequest", async (data) => {
  console.log("Permission requested:", data.request);
  // "once" | "always" | "reject"
  await agent.respondPermission(data.sessionId, data.request.permissionId, "once");
});
</CodeGroup>

Documentation

Tools

Expose your JavaScript functions to agents as CLI commands inside the VM. Agents call them as shell commands with auto-generated flags from Zod schemas.

ts
import { toolKit, hostTool } from "@rivet-dev/agent-os-core";
import { z } from "zod";

const myTools = toolKit({
  name: "myapp",
  description: "Application tools",
  tools: {
    createTicket: hostTool({
      description: "Create a ticket in the issue tracker",
      inputSchema: z.object({
        title: z.string().describe("Ticket title"),
        priority: z.enum(["low", "medium", "high"]).describe("Priority level"),
      }),
      execute: async (input) => {
        const ticket = await db.tickets.create(input);
        return { id: ticket.id, url: ticket.url };
      },
    }),
  },
});

// Agent calls: agentos-myapp createTicket --title "Fix login" --priority high

Documentation

Filesystem

Read, write, and manage files inside the VM. The /home/user directory is persisted automatically across sleep/wake cycles.

<CodeGroup> ```ts @nocheck client.ts import { createClient } from "rivetkit/client"; import type { registry } from "./server";

const client = createClient<typeof registry>("http://localhost:6420"); const agent = client.vm.getOrCreate(["my-agent"]);

// Write a file await agent.writeFile("/home/user/config.json", JSON.stringify({ key: "value" }));

// Read a file const content = await agent.readFile("/home/user/config.json"); console.log(new TextDecoder().decode(content));

// List directory contents recursively const files = await agent.readdirRecursive("/home/user", { maxDepth: 2 }); console.log(files);


```ts @nocheck server.ts
import { agentOs } from "rivetkit/agent-os";
import { setup } from "rivetkit";
import common from "@rivet-dev/agent-os-common";
import pi from "@rivet-dev/agent-os-pi";

const vm = agentOs({
  options: { software: [common, pi] },
});

export const registry = setup({ use: { vm } });
registry.start();
</CodeGroup>

Documentation

Processes & Shell

Execute commands, spawn long-running processes, and open interactive shells.

<CodeGroup> ```ts @nocheck client.ts import { createClient } from "rivetkit/client"; import type { registry } from "./server";

const client = createClient<typeof registry>("http://localhost:6420"); const agent = client.vm.getOrCreate(["my-agent"]);

// One-shot execution const result = await agent.exec("echo hello && ls /home/user"); console.log("stdout:", result.stdout); console.log("exit code:", result.exitCode);

// Spawn a long-running process agent.on("processOutput", (data) => { console.log([${data.processId}], data.output); });

const proc = await agent.spawn("node", ["server.js"]); console.log("Process ID:", proc.processId);


```ts @nocheck server.ts
import { agentOs } from "rivetkit/agent-os";
import { setup } from "rivetkit";
import common from "@rivet-dev/agent-os-common";
import pi from "@rivet-dev/agent-os-pi";

const vm = agentOs({
  options: { software: [common, pi] },
});

export const registry = setup({ use: { vm } });
registry.start();
</CodeGroup>

Documentation

Networking & Previews

Proxy HTTP requests into VMs with vmFetch. Create preview URLs for port forwarding VM services to shareable public URLs.

<CodeGroup> ```ts @nocheck client.ts import { createClient } from "rivetkit/client"; import type { registry } from "./server";

const client = createClient<typeof registry>("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) const preview = await agent.createSignedPreviewUrl(3000); console.log("Public URL:", preview.path); console.log("Expires at:", new Date(preview.expiresAt));


```ts @nocheck server.ts
import { agentOs } from "rivetkit/agent-os";
import { setup } from "rivetkit";
import common from "@rivet-dev/agent-os-common";
import pi from "@rivet-dev/agent-os-pi";

const vm = agentOs({
  options: { software: [common, pi] },
});

export const registry = setup({ use: { vm } });
registry.start();
</CodeGroup>

Documentation

Cron Jobs

Schedule recurring commands and agent sessions with cron expressions.

<CodeGroup> ```ts @nocheck client.ts import { createClient } from "rivetkit/client"; import type { registry } from "./server";

const client = createClient<typeof registry>("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", agent: "pi", prompt: "Review the codebase for security issues and write a report to /home/user/audit.md", }, });


```ts @nocheck server.ts
import { agentOs } from "rivetkit/agent-os";
import { setup } from "rivetkit";
import common from "@rivet-dev/agent-os-common";
import pi from "@rivet-dev/agent-os-pi";

const vm = agentOs({
  options: { software: [common, pi] },
});

export const registry = setup({ use: { vm } });
registry.start();
</CodeGroup>

Documentation

Sandbox Mounting

agentOS uses a hybrid model: agents run in a lightweight VM by default and spin up a full sandbox on demand for heavy workloads like browsers, compilation, and desktop automation.

ts
import { agentOs } from "rivetkit/agent-os";
import { setup } from "rivetkit";
import common from "@rivet-dev/agent-os-common";
import pi from "@rivet-dev/agent-os-pi";

const vm = agentOs({
  options: { software: [common, pi],
    sandbox: {
      enabled: true,
    },
  },
});

export const registry = setup({ use: { vm } });
registry.start();

Documentation

Multiplayer & Realtime

Connect multiple clients to the same agent VM. All subscribers see session output, process logs, and shell data in realtime.

<CodeGroup> ```ts @nocheck client.ts import { createClient } from "rivetkit/client"; import type { registry } from "./server";

// Client A: creates the session and sends prompts const clientA = createClient<typeof registry>("http://localhost:6420"); const agentA = clientA.vm.getOrCreate(["shared-agent"]); agentA.on("sessionEvent", (data) => console.log("[A]", data.event.method));

const session = await agentA.createSession("pi", { env: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY! }, }); await agentA.sendPrompt(session.sessionId, "Build a REST API");

// Client B: observes the same session (separate process) const clientB = createClient<typeof registry>("http://localhost:6420"); const agentB = clientB.vm.getOrCreate(["shared-agent"]); agentB.on("sessionEvent", (data) => console.log("[B]", data.event.method)); // Client B sees the same events as Client A


```ts @nocheck server.ts
import { agentOs } from "rivetkit/agent-os";
import { setup } from "rivetkit";
import common from "@rivet-dev/agent-os-common";
import pi from "@rivet-dev/agent-os-pi";

const vm = agentOs({
  options: { software: [common, pi] },
});

export const registry = setup({ use: { vm } });
registry.start();
</CodeGroup>

Documentation

Agent-to-Agent

Compose specialized agents into pipelines. Each agent gets its own isolated VM and filesystem.

<CodeGroup stacked> ```ts @nocheck server.ts import { agentOs } from "rivetkit/agent-os"; import { setup } from "rivetkit"; import common from "@rivet-dev/agent-os-common"; import pi from "@rivet-dev/agent-os-pi";

const coder = agentOs({ options: { software: [common, pi] }, }); const reviewer = agentOs({ options: { software: [common, pi] }, });

export const registry = setup({ use: { coder, reviewer } }); registry.start();


```ts @nocheck client.ts
import { createClient } from "rivetkit/client";
import type { registry } from "./server";

const client = createClient<typeof registry>("http://localhost:6420");

// Coder writes the feature
const coderAgent = client.coder.getOrCreate(["feature-auth"]);
const coderSession = await coderAgent.createSession("pi", {
  env: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY! },
});
await coderAgent.sendPrompt(coderSession.sessionId, "Implement the login feature");

// Pass files to the reviewer
const src = await coderAgent.readFile("/home/user/src/auth.ts");
const reviewerAgent = client.reviewer.getOrCreate(["feature-auth"]);
await reviewerAgent.writeFile("/home/user/src/auth.ts", src);

// Reviewer checks the code
const reviewSession = await reviewerAgent.createSession("pi", {
  env: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY! },
});
await reviewerAgent.sendPrompt(
  reviewSession.sessionId,
  "Review auth.ts for security issues",
);
</CodeGroup>

Documentation

Workflows

Orchestrate multi-step agent tasks with durable workflows that survive crashes and restarts.

ts
import { agentOs } from "rivetkit/agent-os";
import common from "@rivet-dev/agent-os-common";
import pi from "@rivet-dev/agent-os-pi";
import { actor, setup, workflow } from "rivetkit";

const automator = actor({
  workflows: {
    fixBug: workflow<{ repo: string; issue: string }>(),
  },
  run: async (c) => {
    for await (const message of c.workflow.iter("fixBug")) {
      const { repo, issue } = message.body;
      const agentHandle = c.actors.vm.getOrCreate([`fix-${issue}`]);

      await c.step("clone-repo", async () => {
        return agentHandle.exec(`git clone ${repo} /home/user/repo`);
      });

      await c.step("fix-bug", async () => {
        const session = await agentHandle.createSession("pi", {
          env: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY! },
        });
        const response = await agentHandle.sendPrompt(
          session.sessionId,
          `Fix the bug described in issue: ${issue}`,
        );
        await agentHandle.closeSession(session.sessionId);
        return response;
      });

      await c.step("run-tests", async () => {
        return agentHandle.exec("cd /home/user/repo && npm test");
      });

      await message.complete();
    }
  },
});

const vm = agentOs({
  options: { software: [common, pi] },
});

export const registry = setup({ use: { automator, vm } });
registry.start();

Documentation

SQLite

Use actor-local SQLite as structured long-term memory that persists across sessions and sleep/wake cycles.

ts
import { actor, setup } from "rivetkit";
import { db } from "rivetkit/db";

const memoryAgent = actor({
  db: db({
    onMigrate: async (db) => {
      await db.execute(`
        CREATE TABLE IF NOT EXISTS memories (
          id INTEGER PRIMARY KEY AUTOINCREMENT,
          session_id TEXT NOT NULL,
          category TEXT NOT NULL,
          content TEXT NOT NULL,
          created_at INTEGER NOT NULL
        );
      `);
    },
  }),
  actions: {
    store: async (c, sessionId: string, category: string, content: string) => {
      await c.db.execute(
        "INSERT INTO memories (session_id, category, content, created_at) VALUES (?, ?, ?, ?)",
        sessionId, category, content, Date.now(),
      );
    },
    search: async (c, query: string) => {
      return c.db.execute(
        "SELECT category, content FROM memories WHERE content LIKE ? ORDER BY created_at DESC LIMIT 20",
        `%${query}%`,
      );
    },
  },
});

Documentation