Embedded VMs
Reference for embedding agentOS VMs in an existing Node.js application without Rivet Actors.
Use the embedded API when you already have a Node.js application and want to control agentOS VMs directly. You manage VM identity, persistence, and lifecycle yourself instead of using Rivet Actors.
To create your first embedded VM, start with the embedded quickstart.
This page covers only what is different about embedding. Each capability is
documented on its own page, which ends with a short Embedded API section.
Embedded API vs actor
| Embedded API | Actor | |
|---|---|---|
| Persistence | In-memory unless you configure database, plus mounts | Actor SQLite injected automatically |
| Distributed state | Manage yourself | Built-in |
| Stateful VMs | Complex to run yourself | Built into Rivet |
| Sleep/wake | Manual dispose() / create() | Automatic |
| Events | Direct in-process callbacks | Broadcast to every connected client |
| Preview URLs | Serve them from your own application | Built-in signed URL server |
| Multiplayer | Fan out from your own application | Multiple clients per actor |
| Orchestration | VM-local cron while the VM is alive | Workflows, queues, and cron |
| Agent-to-agent | Bindings between VMs you own | Built into Rivet Actors |
| Authentication | Your application’s own | Docs |
- Use Rivet Actors for persistence, networking, and orchestration.
- Use
AgentOs.create()to embed VM control in a Node.js application. agentOS()returns an ordinary TypeScript Rivet actor definition: VM options plus normal actor state, actions, events, queues, connection types, and lifecycle hooks (onBeforeConnect).- agentOS actions/events merge in automatically; their names are reserved.
- The VM is created lazily on the first agentOS action after wake, disposed on
sleep, so a connection can subscribe before
vmBooted. - Creation input flows through
client.vm.create("key", { input })and reachescreateState(c, input)andonCreate(c, input).
Lifecycle
AgentOs.create()resolves to a VM handle. There is no actor server, client, or connection, so your application owns VM identity: there is no actor key, nogetOrCreate, and no routing.- The VM stays alive until you call
dispose(). Nothing sleeps it for you. - Action timeouts and automatic sleep/wake are
agentOS()actor features. See Persistence & Sleep for the actor behavior the embedded API leaves to you. - Every
on*callback is registered on one handle in one process. Nothing is broadcast to other processes or clients.
Configuration reference
All VM config is a single flat object passed to AgentOs.create(). The
agentOS() actor accepts the same options and layers
persistence, sleep/wake, and preview URLs on top.
Durability is the one option you must supply yourself. Actors inject their own
SQLite database; an embedded VM keeps the root filesystem and session catalog
in memory until you pass database, and loses both on dispose().
import pi from "@agentos-software/pi";
import { AgentOs, nodeModulesMount } from "@rivet-dev/agentos-core";
// Common embedded AgentOs.create() configuration. The agentOS() actor accepts
// the same options and layers persistence, sleep/wake, and preview URLs on top.
const vm = await AgentOs.create({
// Durable SQLite storage for the root filesystem and the session catalog.
// Omit it for an in-memory VM that keeps nothing after dispose().
database: { type: "sqlite_file", path: ".agentos/agentos.sqlite" },
// 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")],
// Kernel permission policy (see /agentos/docs/permissions) and runtime caps
// (see /agentos/docs/resource-limits) take the same values as the actor.
// `bindings` does too (see /agentos/docs/bindings).
permissions: { network: "allow" },
limits: { jsRuntime: { v8HeapLimitMb: 128 } },
// Software packages to install in the VM (see /agentos/docs/software).
software: [pi],
// 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],
// Sidecar placement defaults to the shared `default` pool.
sidecar: { kind: "shared" },
});
await vm.dispose();
See Filesystem for mount plugins and Software for the package list.
Sidecar process
- Every VM runs inside a shared sidecar process, not its own process.
- All VMs default to a single process-global sidecar (the
defaultpool); each extra VM adds only a V8 isolate + its kernel state. - This keeps per-VM memory in the tens of MB and warm creation in single-digit ms (see Performance).
- Automatic for
agentOS(),AgentOs.create(), and Rivet Actors. - Disposing a VM tears down only that VM; the sidecar is reused for the host process lifetime.
- Advanced: the embedded API exposes explicit sidecar handles to isolate a group of VMs in their own process.
import { AgentOs } from "@rivet-dev/agentos-core";
// An embedded application can use one dedicated sidecar process for multiple VMs.
const sidecar = await AgentOs.createSidecar();
const a = await AgentOs.create({
sidecar: { kind: "explicit", handle: sidecar },
});
const b = await AgentOs.create({
sidecar: { kind: "explicit", handle: sidecar },
});
await a.dispose(); // tears down VM a only
await b.dispose();
await sidecar.dispose(); // tears down the shared process