Skip to content
GitHub Get Started
Reference

Software Definition

Software is anything you install into a VM: an agent, a command package, or a set of WASM commands. Every package, including the built-ins, is declared with defineSoftware() and passed to the software option.

import { agentOS, setup } from "@rivet-dev/agentos";
import pi from "@agentos-software/pi";
const vm = agentOS({ software: [pi /*, …more */] });
export const registry = setup({ use: { vm } });
registry.start();

defineSoftware() is a typed identity helper. It returns the descriptor unchanged but gives you full type-checking. The descriptor’s type field discriminates between the three kinds of software.

  • "agent": a coding agent runnable via createSession(id). Key fields: agent, requires, packageDir.
  • "tool": command-package software exposed inside the VM. Key fields: bins, requires, packageDir.
  • "wasm-commands": compiled WASM command binaries. Key fields: commandDir, aliases, permissions.

All descriptors share two base fields:

  • name (required). The software package name.
  • type (required). One of "agent", "tool", or "wasm-commands".

Registers a coding agent. See Custom Agents for the agent-focused guide; this is the full field reference.

  • packageDir (required). This package’s directory on the host. Resolves requires from its own node_modules/, mounted into the VM at /root/node_modules/<pkg>.
  • requires (required). npm packages that must be available inside the VM (must include the adapter and agent packages).
  • agent.id (required). The id passed to createSession(id). Reuse a built-in id to override it, or pick a new one.
  • agent.acpAdapter (required). npm package of the ACP adapter, spawned inside the VM. Must be in requires.
  • agent.agentPackage (required). npm package of the underlying agent SDK/CLI. Must be in requires.
  • agent.staticEnv (optional). Static env vars passed when spawning the adapter (merged under user env).
  • agent.env (optional). (ctx: SoftwareContext) => Record<string, string>; env computed at boot, e.g. to resolve a bin path.
  • agent.launchArgs (optional). Extra CLI args prepended when launching the adapter.
  • agent.snapshot (optional, default false). Opt in to evaluating this agent’s SDK into the per-sidecar V8 heap snapshot so it is loaded once per sidecar and reused across every session, instead of re-evaluated on each createSession. This can remove most of the per-session SDK load/eval cost, but only works if the SDK is snapshot-safe (see below). SDKs that aren’t — or where snapshot creation fails — automatically fall back to the normal per-session dynamic-import path, so this flag never affects correctness, only startup latency.

A V8 heap snapshot freezes the JavaScript heap after the SDK’s modules have been evaluated, then seeds a fresh isolate from that frozen heap for each new session. Capturing the heap has hard requirements: the SDK’s module-initialization code (everything that runs at import/require time, before any function is called) must not do any of the following, or the snapshot cannot be built and the agent falls back to per-session loading:

  • Create native handles: load a .node addon, instantiate WebAssembly, or otherwise produce a V8 External/Foreign object at module top level. (Defer these behind a function/lazy import() that runs per-session.)
  • Open a file descriptor, socket, timer, or worker, or leave a pending promise at the end of evaluation.
  • Read non-deterministic or per-session state at top level — process.env, cwd, model, Date.now(), Math.random(), a random UUID — and bake it into a module constant. (Read these inside functions instead; per-session config is injected after restore.)

Snapshot-friendly SDKs keep module-init to pure, deterministic work and load anything native/lazy on first use. Set agent.snapshot: false (the default) for any SDK that doesn’t meet these rules; the agent still runs, just without the shared-snapshot speedup.

pi.ts
import { defineSoftware } from "@rivet-dev/agentos";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const packageDir = resolve(dirname(fileURLToPath(import.meta.url)), "..");
export default defineSoftware({
name: "pi",
type: "agent",
packageDir,
requires: ["@agentos-software/pi", "@mariozechner/pi-coding-agent"],
agent: {
id: "pi",
acpAdapter: "@agentos-software/pi",
agentPackage: "@mariozechner/pi-coding-agent",
},
});

Exposes one or more CLI commands inside the VM by mapping a command name to the npm package that provides its bin. The descriptor value is still "tool" because this is the software package type, separate from host bindings.

  • packageDir (required). This package’s directory on the host (resolves requires).
  • requires (required). npm packages that must be available inside the VM.
  • bins (required). Record<commandName, packageName>, mapping the command name as invoked in the VM to the npm package providing it.
my-tool.ts
import { defineSoftware } from "@rivet-dev/agentos";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const packageDir = resolve(dirname(fileURLToPath(import.meta.url)), "..");
export default defineSoftware({
name: "my-tool",
type: "tool",
packageDir,
requires: ["@my-org/my-cli"],
bins: { "my-cli": "@my-org/my-cli" },
});

Registers compiled WebAssembly command binaries (coreutils, ripgrep, jq, …). See Building Binaries for how to produce the binaries.

  • commandDir (required). Absolute host path to the directory of .wasm command binaries.
  • aliases (optional). Record<aliasName, targetCommandName>; symlink-style aliases.
  • permissions (optional). Permission-tier assignments: full, readWrite, readOnly (string[] or "*"), isolated.
my-commands.ts
import { defineSoftware } from "@rivet-dev/agentos";
export default defineSoftware({
name: "my-commands",
type: "wasm-commands",
commandDir: "/abs/path/to/wasm",
aliases: { ll: "ls" },
permissions: { readOnly: "*" },
});

The agent.env callback (and other dynamic config) receives a SoftwareContext for resolving VM-side paths:

interface SoftwareContext {
// Resolve a package's bin to its VM path, e.g.
// ctx.resolveBin("@mariozechner/pi-coding-agent", "pi")
// -> "/root/node_modules/@mariozechner/pi-coding-agent/dist/cli.js"
resolveBin(packageName: string, binName?: string): string;
// Resolve a package root to its VM path, e.g.
// ctx.resolvePackage("pi-acp") -> "/root/node_modules/pi-acp"
resolvePackage(packageName: string): string;
}
agent: {
id: "pi-cli",
acpAdapter: "pi-acp",
agentPackage: "@mariozechner/pi-coding-agent",
env: (ctx) => ({
PI_ACP_PI_COMMAND: ctx.resolveBin("@mariozechner/pi-coding-agent", "pi"),
}),
}

A software entry may be an array of descriptors, letting one package bundle several (e.g. a “build-essential” set). Pass arrays directly to software:

const vm = agentOS({
software: [pi, buildEssential /* = [coreutils, make, git, curl] */],
});