Agent OS Exec vs isolated-vm
How Agent OS Exec and isolated-vm differ in isolation surface, security confinement, performance, and developer experience.
Both Agent OS Exec and isolated-vm run untrusted JavaScript inside V8 isolates, but they operate at different layers. isolated-vm is a low-level npm library that exposes a bare V8 isolate with manual value marshaling and no system surface. Agent OS Exec wraps V8 isolates in a full virtualized kernel: a POSIX-like VFS, process table, socket table, permission policy, and resource limits. This page focuses on the security and performance characteristics of that core runtime layer.
At a glance
| Agent OS Exec | isolated-vm | |
|---|---|---|
| Layer | Full virtualized runtime around V8 isolates | Bare V8 isolate primitive |
| System surface | Virtualized filesystem, processes, sockets, PTYs, DNS | None (you build any surface yourself) |
| Host and guest data | Normal Node/POSIX I/O through the kernel | Manual Reference/Copy/ExternalCopy marshaling |
| Permission model | Host-configured capability policy per VM | None (whatever you expose is reachable) |
| Network egress | Mediated by the kernel socket table, gated by policy | None by default; any bridge you add is unmediated |
| Resource limits | Per-VM CPU, memory, and other caps | Memory limit and timeout per isolate |
| npm / Node compat | Real npm packages run unmodified | None (no module system, no node: builtins) |
Isolation boundary
The core difference is how much exists inside the boundary.
- isolated-vm gives you a raw V8 isolate and nothing else. There is no filesystem, no network, no process model, and no globals beyond the ECMAScript spec. Anything the guest can reach, you have to inject yourself across the host/guest boundary by hand.
- Agent OS Exec boots a fully virtualized VM per runtime. The guest sees a POSIX-like filesystem, a process table, sockets, pipes, PTYs, and DNS, all backed by a kernel that the guest can never escape. Every guest syscall is routed through kernel-owned paths.
The trade-off: isolated-vm is a primitive you assemble a VM out of. Agent OS Exec is the assembled VM.
Security
A bare V8 isolate confines memory and CPU, but it confines nothing else, because there is nothing else inside it. The security of an isolated-vm deployment is entirely a property of the bridge code you write around it.
- What isolated-vm confines: heap memory (per-isolate limit) and execution time (per-call timeout). It cannot leak host memory directly because the guest has no references it was not given.
- What you must build yourself: every capability the guest needs (file access, network, subprocesses) is a function you expose across the boundary. Each exposed function is attack surface, and each is unmediated unless you add your own checks.
- What Agent OS Exec confines: in addition to memory and time, it enforces a host-configured capability policy over filesystem, network, child-process, process, and env scopes. A denied operation fails with
EACCES. - Egress control: guest
fetch(),node:http, and raw sockets flow through the kernel socket table, so outbound traffic can be allowed, denied, or rule-matched. With isolated-vm there is no networking until you build it, and once built it is not policed.
Reference, or an injected function that does unchecked I/O) is a VM escape. Agent OS Exec moves that boundary into a kernel so the surface the guest sees is virtualized rather than hand-wired per project.import { JavaScriptRuntime } from "@rivet-dev/agentos/javascript";
const runtime = await JavaScriptRuntime.create({
permissions: {
fs: "allow",
network: "deny",
childProcess: "allow",
process: "allow",
env: "allow",
},
});
try {
const result = await runtime.evaluate<{
fileContents: string;
networkBlocked: boolean;
}>(`(async () => {
const { writeFileSync, readFileSync } = await import("node:fs");
writeFileSync("/workspace/note.txt", "inside the VM");
let networkBlocked = false;
try { await fetch("https://example.com"); } catch { networkBlocked = true; }
return {
fileContents: readFileSync("/workspace/note.txt", "utf8"),
networkBlocked,
};
})()`);
if (!result.success) throw new Error(result.error.message);
console.log(result.value);
} finally {
await runtime.dispose();
}
Permissions
Configure per-scope capability modes and rule sets.
Performance
Both runtimes execute guest code in a V8 isolate, so raw JavaScript throughput is comparable. The cost differences come from what surrounds the isolate.
- isolated-vm is the thinner layer: creating an isolate is cheap, and the dominant per-call cost is marshaling values across the host/guest boundary, which is explicit and pay-as-you-go.
- Agent OS Exec adds syscall virtualization. Guest I/O (files, sockets, processes) is routed through the kernel, so operations that touch the system carry virtualization overhead that a bare isolate does not. Pure compute that never makes a syscall runs at isolate speed.
- Startup: Agent OS Exec boots a virtualized VM per runtime, which is heavier than spinning up a bare isolate. Reusing a
JavaScriptRuntimekeeps that VM, its filesystem, and its mounts alive while eachexecute()call starts a fresh guest process. See Benchmarks for the reproducible public-API benchmark.
The summary: isolated-vm has lower overhead because it does less; Agent OS Exec spends that overhead on a real system surface and enforcement you would otherwise build and pay for yourself.
Developer experience
This is where the two diverge most for everyday use.
- isolated-vm: you write to the isolate API directly. There is no module loader, no
node:builtins, and no normal I/O. Passing data in or out meansReference,Copy, orExternalCopy, and any capability is a function you wrap and inject. Running an existing npm package is not a goal of the library. - Agent OS Exec: the guest sees normal Linux and Node semantics. Real npm packages run unmodified, resolved over a faithfully mounted
node_modules, and kernel-backed modules (fs,net,child_process,dns,http,os) work as they would on a real machine.
import { JavaScriptRuntime } from "@rivet-dev/agentos/javascript";
// Boot a fully virtualized runtime. Guest code runs inside the kernel
// isolation boundary - no host escapes.
const runtime = await JavaScriptRuntime.create();
try {
// evaluate() returns a JSON-serializable value and captures stdout/stderr.
const result = await runtime.evaluate<{ message: string; sum: number }>(`
(() => {
console.log("hello from Agent OS Exec");
return { message: "hello from Agent OS Exec", sum: 1 + 2 };
})()
`);
if (!result.success) throw new Error(result.error.message);
console.log("stdout:", JSON.stringify(result.stdout.trim()));
console.log("value:", result.value);
console.log("exitCode:", result.exitCode);
} finally {
// Tear down the VM and release the sidecar.
await runtime.dispose();
}
const typedRuntime = await JavaScriptRuntime.create();
try {
const typed = await typedRuntime.typescript.execute(`
const answer: number = 21 * 2;
console.log(answer);
`);
if (!typed.success) throw new Error(typed.stderr);
} finally {
await typedRuntime.dispose();
}
When to choose which
| Choose | When |
|---|---|
| isolated-vm | You want a minimal V8 isolate primitive and intend to design and own the entire VM surface, capability injection, and enforcement yourself. |
| Agent OS Exec | You want to run untrusted or AI-generated code with a virtualized filesystem, process model, networking, an explicit permission policy, and resource limits without hand-building the VM. |
Process Isolation
The isolation model in depth, plus how to pick isolation granularity.