Agent OS Exec vs Cloudflare Workers
How Agent OS Exec and Cloudflare Workers differ in isolation model, permissions, networking, and Node.js compatibility.
Agent OS Exec and Cloudflare Workers both run untrusted JavaScript inside V8, but they solve different problems. Workers is a managed, multi-tenant edge platform: you deploy code and Cloudflare runs it for you across its network. Agent OS Exec is a library you embed in your own application to run guest code locally inside a fully virtualized VM that you control. This page focuses on how the two differ in isolation, permissions, networking, and Node.js surface.
At a glance
| Agent OS Exec | Cloudflare Workers | |
|---|---|---|
| Form factor | Library you embed (agentos), runs where your app runs | Managed edge platform you deploy to |
| Isolation unit | Per runtime: each JavaScriptRuntime.create() is its own VM in the shared sidecar pool | Per Worker isolate, scheduled by Cloudflare |
| Guest runtime | V8 isolate inside a virtualized POSIX kernel (filesystem, processes, sockets, PTYs) | V8 isolate with the workerd runtime |
| Permissions | Host-configured capability policy per VM | Platform-managed; no per-call capability policy |
| Subprocesses | Real node:child_process against kernel-managed processes | Not available |
| Filesystem | Full virtualized filesystem per runtime | Limited in-memory node:fs surface, ephemeral |
| You operate it | Yes (your process, your machine) | No (Cloudflare operates it) |
Isolation model
This is the most important difference, and the easiest one to misstate.
Cloudflare Workers isolates code dynamically. Cloudflare’s runtime schedules many Workers into a pool of V8 isolates and can move work between them, with platform-level mitigations (such as I/O-gated clock coarsening) layered on to defend against side-channel attacks across that shared infrastructure.
Agent OS Exec creates a separate kernel-owned VM for every JavaScriptRuntime.
VMs do not share filesystems, globals, module state, process tables, sockets, or
resource accounting. They use a process-wide shared sidecar pool by default, so
sidecar process failure is a wider boundary than VM state isolation. You can
provide an explicit sidecar placement when your deployment needs a different
failure boundary.
Within a single runtime, every execute() or evaluate() call runs in a fresh
guest process, so in-memory state from one execution does not leak into the next.
Process Isolation
How runtimes and runs are isolated, and how to choose granularity.
The practical consequence: with Workers you trust Cloudflare’s platform to keep tenants apart. With Agent OS Exec you get a hard, statically-defined boundary per runtime that you place yourself, which is well suited to running one tenant or one task per runtime and disposing it when finished.
Permissions
Cloudflare Workers does not expose a per-invocation capability policy. What a Worker can reach is governed by its bindings and platform configuration.
Agent OS Exec applies a host-configured capability policy per VM. The high-level
client currently defaults scopes to "allow", so applications executing
untrusted code should pass an explicit restrictive policy.
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();
}
Networking
Inside the VM, guest fetch() runs through undici in the V8 isolate and then
through the kernel’s socket table, gated by the network permission. Guest code
can also bind ports inside the VM. The host can observe the process’s readiness
output and then send a request with runtime.vm.httpRequest().
From the host side, rt.vm.httpRequest(input) drives an HTTP request into a guest server listening inside the VM and reads the response back. The request never leaves the VM (it connects to the guest’s loopback listener through the kernel socket table), so it works even when guest network egress is denied.
Cloudflare Workers provides outbound fetch() and a Sockets API for outbound TCP/TLS, but a Worker does not listen on a port the way a normal server does; it responds to incoming requests routed by the platform.
import { JavaScriptRuntime } from "@rivet-dev/agentos/javascript";
const serverSource = `
import http from "node:http";
const app = http.createServer((req, res) => {
res.writeHead(200, { "content-type": "application/json" });
res.end(JSON.stringify({ ok: true, path: req.url }));
});
app.listen(3000, "127.0.0.1", () => console.log("ready"));
await new Promise(() => {});
`;
const runtime = await JavaScriptRuntime.create({
permissions: { network: "allow" },
});
let ready!: () => void;
const serverReady = new Promise<void>((resolve) => {
ready = resolve;
});
try {
const server = await runtime.spawn(serverSource, {
onStdout: (chunk) => {
process.stdout.write(chunk);
if (chunk.includes("ready")) ready();
},
});
await serverReady;
const response = await runtime.vm.httpRequest({ port: 3000, path: "/health" });
console.log(response.status, new TextDecoder().decode(response.body));
server.kill("SIGTERM");
await server.wait();
} finally {
await runtime.dispose();
}
Subprocesses and the POSIX surface
Agent OS Exec presents a virtualized POSIX environment. Guest code can use node:child_process to spawn commands that exist inside the VM (such as sh and the mounted coreutils), and every child is a kernel-managed process, never a real host process. The host can also start a long-running guest program and drive it with rt.spawn().
Cloudflare Workers has no subprocess model. There is no child_process, no process table, and no shell.
Child Processes
Spawn kernel-managed processes from guest code, or drive a long-running guest program from the host.
Node.js compatibility
Both runtimes aim to present Node.js semantics on top of V8, and both implement a large subset of the Node.js standard library. The character of the gaps differs:
- Agent OS Exec is backed by a real virtualized kernel, so capabilities that need an OS, a full filesystem, real child processes, sockets that listen, and a process table, are first-class. Pure-JS builtins are provided through
node-stdlib-browser, and kernel-backed modules (such asfs,net,child_process,dns,http,os) are wired through the bridge to the kernel. - Cloudflare Workers provides Node.js compatibility through the
nodejs_compatflag on top ofworkerd. Its filesystem is a limited in-memory surface, there is no subprocess support, and listening servers are replaced by the request/response handler model. In exchange it ships a comprehensive nativenode:cryptoand Web Crypto surface and platform-native logging.
When to choose which
Choose Cloudflare Workers when you want a managed, globally distributed platform to deploy your own trusted code to, with the operations handled for you.
Choose Agent OS Exec when you need to run untrusted or AI-generated code inside your own application with a hard, self-placed isolation boundary, a real virtualized filesystem and process model, and a capability policy you control. One runtime per tenant or per task gives you a clean blast radius you can dispose on demand.
Process Isolation
The isolation model in depth, plus how to pick isolation granularity.