Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 20 additions & 10 deletions examples/core/README.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,40 @@
---
title: "Core"
description: "Core AgentOs API: exec, config reference, lifecycle hooks, and mounts."
description: "Core AgentOs API: exec, config reference, lifecycle events, and mounts."
category: "Reference"
order: 1
---

The core AgentOs API surface in one place: define a VM server, connect a typed client, and drive VMs for exec, filesystem, processes, agent sessions, networking, and cron. Reach for this when you want a reference of what a `handle` can do and how the server is configured.
The core `@rivet-dev/agentos-core` API surface in one place: boot a VM with
`AgentOs.create()` and drive it directly for exec, filesystem, processes, agent
sessions, networking, and cron — no actor runtime and no client/server split.
Reach for this when you want a reference of what an `AgentOs` instance can do and
how it is configured.

## How it works

`agentOS({ ... })` defines a VM with its mounts, software, lifecycle hooks, and preview/network settings, then `setup({ use: { vm } }).start()` exposes it over the wire. On the client, `createClient<typeof registry>()` gives a typed `client`, and `client.vm.getOrCreate(id)` returns a `handle`. Everything runs through that handle: `exec`/`spawn` for processes, `readFile`/`writeFiles`/`readdirRecursive` for the filesystem, `createSession`/`sendPrompt` for agents, `openShell` for interactive terminals, `vmFetch` for in-VM servers, and `scheduleCron` for jobs. `handle.connect()` opens an event stream for process output, session events, permission requests, and cron events.

- `server.ts` / `config-reference.ts` — VM definition and the full config surface (mounts, software, loopback exemptions, preview token lifetimes, hooks).
- `hooks.ts` — server-side lifecycle hooks like `onSessionEvent`.
`AgentOs.create({ ... })` boots a VM in-process with its mounts, software, and
network settings, and returns an `AgentOs` instance. Everything runs through that
instance: `exec`/`spawn` for processes, `readFile`/`writeFiles`/`readdirRecursive`
for the filesystem, `createSession`/`prompt` for agents, `fetch` for in-VM
servers, and `scheduleCron` for jobs. Process output and session/permission/cron
events are delivered through callbacks (`spawn({ onStdout })`, `onProcessExit`,
`onSessionEvent`, `onPermissionRequest`, `onCronEvent`).

- `vm.ts` — boot a VM and every instance capability (exec, filesystem,
processes, sessions, networking, cron).
- `advanced.ts` — pin VMs to a dedicated sidecar process.
- `config-reference.ts` — the full `AgentOs.create()` config surface.
- `hooks.ts` — per-session event and permission observation.
- `mounts.ts` — host-directory and S3 mount descriptors.
- `client.ts` — every client capability against a `handle`.

## Run it

```sh
npm install
npx tsx server.ts # start the VM server, then run client.ts against it
npx tsx vm.ts
```

The server listens on `http://localhost:6420`; the client connects, creates a VM, and exercises exec, filesystem, sessions, and more.

## Source

View the source on GitHub: https://github.com/rivet-dev/agent-os/tree/main/examples/core
31 changes: 11 additions & 20 deletions examples/core/config-reference.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,22 @@
import { agentOS, nodeModulesMount, setup } from "@rivet-dev/agentos";
import { AgentOs, nodeModulesMount } from "@rivet-dev/agentos-core";
import pi from "@agentos-software/pi";

const vm = agentOS({
// The full AgentOs.create() configuration surface. The agentOS() actor accepts
// this same options object and layers persistence, sleep/wake, and preview URLs
// on top.
const vm = await AgentOs.create({
// Filesystems to mount at boot. Use nodeModulesMount() to expose a host
// node_modules tree at /root/node_modules.
mounts: [nodeModulesMount("/path/to/project/node_modules")],
// Software packages to install in the VM (see /docs/software)
software: [pi],
// Ports exempt from SSRF checks
// Also install the default software bundle (sh + coreutils). Defaults to true;
// set false for a bare VM with only the software you list.
defaultSoftware: true,
// Ports exempt from SSRF checks (for testing against host-side mock servers)
loopbackExemptPorts: [3000],
// Extra instructions appended to agent system prompts
additionalInstructions: "Always write tests first.",

// Preview URL token lifetimes
preview: {
defaultExpiresInSeconds: 3600, // 1 hour (default)
maxExpiresInSeconds: 86400, // 24 hours (default)
},

// Lifecycle hooks (see below)
onSessionEvent: async (sessionId, event) => {
console.log("Session event:", sessionId, event.method);
},
onPermissionRequest: async (sessionId, request) => {
console.log("Permission request:", sessionId, request.permissionId);
},
// Sidecar placement — defaults to the shared `default` pool
sidecar: { kind: "shared" },
});

export const registry = setup({ use: { vm } });
registry.start();
21 changes: 15 additions & 6 deletions examples/core/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import { agentOS } from "@rivet-dev/agentos";
import { AgentOs } from "@rivet-dev/agentos-core";
import pi from "@agentos-software/pi";

export const vm = agentOS({
// Runs once per session event, server-side, for every session.
onSessionEvent: async (sessionId, event) => {
console.log("Session event:", sessionId, event.method);
},
// With the core package, session events and permission requests are observed
// per-session on the AgentOs instance (there is no actor-factory hook).
const vm = await AgentOs.create({ software: [pi] });
const { sessionId } = await vm.createSession("pi");

// Runs for every event on this session.
vm.onSessionEvent(sessionId, (event) => {
console.log("Session event:", sessionId, event.method);
});

// Fires when the agent requests permission.
vm.onPermissionRequest(sessionId, (request) => {
console.log("Permission request:", sessionId, request.permissionId);
});
10 changes: 5 additions & 5 deletions examples/core/mounts.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { agentOS, setup } from "@rivet-dev/agentos";
import { AgentOs } from "@rivet-dev/agentos-core";

const vm = agentOS({
// Configure filesystem backends at boot. Native mount plugins (host
// directories, S3, etc.) are passed via `plugin`, each identified by an `id`
// and a `config` object.
const vm = await AgentOs.create({
mounts: [
// Host directory (read-only)
{
Expand All @@ -15,6 +18,3 @@ const vm = agentOS({
},
],
});

export const registry = setup({ use: { vm } });
registry.start();
9 changes: 0 additions & 9 deletions examples/core/server.ts

This file was deleted.

108 changes: 48 additions & 60 deletions examples/core/client.ts → examples/core/vm.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,29 @@
// docs:start boot
import { createClient } from "@rivet-dev/agentos/client";
import type { registry } from "./server";
import { AgentOs } from "@rivet-dev/agentos-core";
import pi from "@agentos-software/pi";

const client = createClient<typeof registry>({ endpoint: "http://localhost:6420" });
const handle = client.vm.getOrCreate("my-agent");
// Create a VM directly with the core package — no actor runtime, no
// client/server split. `AgentOs.create()` boots the VM in-process.
const vm = await AgentOs.create({ software: [pi] });

const result = await handle.exec("echo hello");
const result = await vm.exec("echo hello");
console.log(result.stdout); // "hello\n"
// docs:end boot

// ── Filesystem ────────────────────────────────────────────────────
async function filesystem() {
// docs:start filesystem
await handle.writeFile("/home/agentos/hello.txt", "Hello, world!");
const content = await handle.readFile("/home/agentos/hello.txt");
await vm.writeFile("/home/agentos/hello.txt", "Hello, world!");
const content = await vm.readFile("/home/agentos/hello.txt");
console.log(new TextDecoder().decode(content));

await handle.mkdir("/home/agentos/src");
await handle.writeFiles([
await vm.mkdir("/home/agentos/src");
await vm.writeFiles([
{ path: "/home/agentos/src/index.ts", content: "console.log('hi');" },
{ path: "/home/agentos/src/utils.ts", content: "export const add = (a: number, b: number) => a + b;" },
]);

const entries = await handle.readdirRecursive("/home/agentos");
const entries = await vm.readdirRecursive("/home/agentos");
for (const entry of entries) {
console.log(entry.type, entry.path);
}
Expand All @@ -33,113 +34,100 @@ async function filesystem() {
async function processes() {
// docs:start processes
// One-shot execution
const result = await handle.exec("ls -la /home/agentos");
const result = await vm.exec("ls -la /home/agentos");
console.log(result.stdout);

// Long-running process with streaming output
await handle.writeFile(
// Long-running process with streaming output. spawn() returns synchronously;
// stdout/stderr are delivered through the callbacks you pass in.
await vm.writeFile(
"/tmp/server.mjs",
'import http from "http"; http.createServer((req, res) => res.end("ok")).listen(3000); console.log("listening");',
);
const { pid } = await handle.spawn("node", ["/tmp/server.mjs"]);

const conn = handle.connect();
conn.on("processOutput", (data) => {
if (data.pid === pid && data.stream === "stdout") {
console.log("stdout:", new TextDecoder().decode(data.data));
}
});
conn.on("processExit", (data) => {
if (data.pid === pid) console.log("exited:", data.exitCode);
const { pid } = vm.spawn("node", ["/tmp/server.mjs"], {
onStdout: (data) => console.log("stdout:", new TextDecoder().decode(data)),
});

vm.onProcessExit(pid, (exitCode) => console.log("exited:", exitCode));

// Write to stdin
await handle.writeProcessStdin(pid, "some input\n");
await vm.writeProcessStdin(pid, "some input\n");

// Stop or kill
await handle.stopProcess(pid);
vm.stopProcess(pid);
// docs:end processes
}

// ── Agent sessions ────────────────────────────────────────────────
async function agentSessions() {
// docs:start sessions
const conn = handle.connect();

// Stream session events (each event is a JSON-RPC notification)
conn.on("sessionEvent", (data) => {
console.log(data.sessionId, data.event.method, data.event.params);
// createSession() resolves to a session record. All session operations take
// its `sessionId`.
const { sessionId } = await vm.createSession("pi", {
env: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY! },
});

// Observe permission requests from the agent
conn.on("permissionRequest", (data) => {
console.log("Permission:", data.sessionId, data.request.description);
// Stream session events (each event is a JSON-RPC notification).
vm.onSessionEvent(sessionId, (event) => {
console.log(event.method, event.params);
});

// createSession() resolves to the session ID string.
const sessionId = await handle.createSession("pi", {
env: { ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY! },
// Observe permission requests from the agent.
vm.onPermissionRequest(sessionId, (request) => {
console.log("Permission:", request.description ?? request.permissionId);
});

// Send a prompt. sendPrompt() resolves to { response, text }, where `text` is
// the accumulated agent message text and `response` is the raw JSON-RPC response.
const { text } = await handle.sendPrompt(sessionId, "Write a hello world script");
// Send a prompt. prompt() resolves to { response, text }, where `text` is the
// accumulated agent message text and `response` is the raw JSON-RPC response.
const { text } = await vm.prompt(sessionId, "Write a hello world script");
console.log(text);

await handle.closeSession(sessionId);
vm.closeSession(sessionId);
// docs:end sessions
}

// ── Networking ────────────────────────────────────────────────────
async function networking() {
// docs:start networking
// Start a server inside the VM
await handle.writeFile(
await vm.writeFile(
"/tmp/app.mjs",
'import http from "http"; http.createServer((req, res) => res.end("hello")).listen(3000);',
);
await handle.spawn("node", ["/tmp/app.mjs"]);
vm.spawn("node", ["/tmp/app.mjs"]);

// Fetch from it
const response = await handle.vmFetch(3000, "/");
console.log(new TextDecoder().decode(response.body));
// Fetch from it — fetch(port, Request) reaches services running in the VM.
const response = await vm.fetch(3000, new Request("http://vm/"));
console.log(await response.text());
// docs:end networking
}

// ── Cron jobs ─────────────────────────────────────────────────────
async function cronJobs() {
// docs:start cron
const { id } = await handle.scheduleCron({
const job = vm.scheduleCron({
id: "cleanup",
schedule: "0 * * * *",
action: { type: "exec", command: "rm", args: ["-rf", "/tmp/cache"] },
});
console.log("Scheduled:", id);
console.log("Scheduled:", job.id);

// Run an agent session on a schedule
await handle.scheduleCron({
vm.scheduleCron({
schedule: "0 9 * * *",
action: {
type: "session",
agentType: "pi",
prompt: "Review the logs and summarize any errors",
cwd: "/workspace",
options: { cwd: "/workspace" },
},
});

const conn = handle.connect();
conn.on("cronEvent", (data) => {
console.log("Cron event:", data.event.type, data.event.jobId);
vm.onCronEvent((event) => {
console.log("Cron event:", event.type, event.jobId);
});

console.log(await handle.listCronJobs());
console.log(vm.listCronJobs());
// docs:end cron
}

export {
filesystem,
processes,
agentSessions,
networking,
cronJobs,
};
export { filesystem, processes, agentSessions, networking, cronJobs };
Loading
Loading