Skip to main content
More

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 APIActor
PersistenceIn-memory unless you configure database, plus mountsActor SQLite injected automatically
Distributed stateManage yourselfBuilt-in
Stateful VMsComplex to run yourselfBuilt into Rivet
Sleep/wakeManual dispose() / create()Automatic
EventsDirect in-process callbacksBroadcast to every connected client
Preview URLsServe them from your own applicationBuilt-in signed URL server
MultiplayerFan out from your own applicationMultiple clients per actor
OrchestrationVM-local cron while the VM is aliveWorkflows, queues, and cron
Agent-to-agentBindings between VMs you ownBuilt into Rivet Actors
AuthenticationYour application’s ownDocs
  • 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 reaches createState(c, input) and onCreate(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, no getOrCreate, 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 default pool); 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