Skip to content
GitHub Get Started
Agent

Custom Agents

A custom agent is a program that runs inside the VM to drive a coding agent. agentOS spawns it when you call createSession() and talks to it over the Agent Client Protocol. You ship it as a software package, exactly like the built-in agents.

agentOS speaks the Agent Client Protocol (ACP) to every agent: JSON-RPC over stdio. The agent reads protocol messages on stdin and writes them on stdout, so stdout is reserved for ACP and stderr is used for logs. Your program only needs to speak ACP; how it runs the underlying model is up to you. See the ACP documentation for the full protocol.

There are two shapes, depending on whether the agent runs in the ACP process or in its own.

The ACP adapter embeds the agent SDK and runs it in the same process. One process inside the VM, lower memory footprint.

HostACPVMACP adapter +agent (embedded)

For example, an adapter to run OpenCode, which speaks ACP natively. One package is both the ACP process and the agent, so there’s no separate adapter and nothing else is spawned.

opencode.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)), "..");
// Example: an adapter to run OpenCode.
export default defineSoftware({
name: "opencode",
type: "agent",
packageDir,
// A single package provides everything that runs in the VM.
requires: ["@agentos-software/opencode"],
agent: {
id: "opencode",
// Same package for both: OpenCode *is* the ACP process. It speaks ACP
// on stdio itself, so there is no separate adapter to spawn the agent.
acpAdapter: "@agentos-software/opencode",
agentPackage: "@agentos-software/opencode",
staticEnv: {
OPENCODE_DISABLE_CONFIG_DEP_INSTALL: "1",
OPENCODE_DISABLE_EMBEDDED_WEB_UI: "1",
},
},
});

The ACP adapter is a thin bridge that spawns the real agent as its own process (a CLI or SDK) and translates between it and ACP. Full agent feature set, higher memory.

HostACPVMACP adapterspawnsAgent process(CLI / SDK)

For example, an adapter to run Pi: the pi CLI doesn’t speak ACP, so pi-acp speaks ACP and spawns the CLI as a separate process. The descriptor names two packages, the adapter and the agent.

pi-cli.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)), "..");
// Example: a pi-acp adapter that runs the Pi CLI.
export default defineSoftware({
name: "pi-cli",
type: "agent",
packageDir,
// Two separate packages: the ACP adapter, and the agent it drives.
requires: ["pi-acp", "@mariozechner/pi-coding-agent"],
agent: {
id: "pi-cli",
// `pi-acp` is a thin ACP adapter. It does NOT run the agent itself:
// it speaks ACP on stdio and spawns the `pi` CLI as a separate child
// process, translating between ACP and the CLI.
acpAdapter: "pi-acp",
// The actual agent, launched by pi-acp as its own process.
agentPackage: "@mariozechner/pi-coding-agent",
// Tell the adapter where to find the `pi` CLI inside the VM
// (resolved at boot to a guest path under /root/node_modules).
env: (ctx) => ({
PI_ACP_PI_COMMAND: ctx.resolveBin("@mariozechner/pi-coding-agent", "pi"),
}),
},
});

Register the package on the server with software. Sessions are then created from the client by id, exactly like any built-in agent.

server.ts
import { agentOS, setup, defineSoftware } from "@rivet-dev/agentos";
const myAgent = defineSoftware({
type: "agent",
/* ...name, packageDir, requires, agent (see above)... */
});
const vm = agentOS({ software: [myAgent] });
export const registry = setup({ use: { vm } });
registry.start();

See Sessions for creating and driving sessions. Ship your adapter as a package so its dependencies resolve from node_modules/ (via requires) inside the VM, rather than as a loose file.

All built-in agents are defined exactly this way. Browse them for reference on GitHub.

  • Defining software packages: the full descriptor reference, including every agent field (staticEnv, env, launchArgs), the SoftwareContext helpers, and the tool and WASM-command software types.
  • Building binaries: compile WASM command binaries and use the registry.

When a custom agent exits mid-turn or a tool call fails, capture the agent’s stderr with the onAgentStderr hook on AgentOs.create(). The agent uses stdout for ACP, so stderr carries its logs and crash output. See Debugging for that hook and the runtime (sidecar) logs.