Introducing agentOS Execution API for JavaScript and Python
agentOS now has an execution API for running JavaScript (Node.js) and Python instead of using bash.
agentOS is a lightweight operating system for agents that runs inside your existing backend, with no sandboxes, no virtual machines, and no third-party service.
It now supports execution APIs for JavaScript (Node.js) and Python, letting agents execute code directly in those languages instead of going through bash. This technique is called Code Mode, and it has a few advantages over raw bash:
- Fewer tokens: Ten chained operations cost one round trip, not ten.
- Type checking: You can check generated TypeScript before you run it.
- Real data processing:
mapandfilterinstead ofjqandawk. - Parallelism:
Promise.allinstead of shell job control. - Real Node.js: The
node:standard library, npm packages, and ESM or CommonJS.
JavaScript (Node.js) and Python run through a similar execution API to the one bash already had, with the added power of dynamically evaluating objects and type checking them. Here is what that looks like.
Evaluate an expression
evaluate() returns a JSON-serializable value.
import { AgentOs } from "@rivet-dev/agentos";
const runtime = await AgentOs.create();
try {
const result = await runtime.javascript.evaluate<number>("1 + 2");
console.log(result.outcome === "succeeded" ? result.value : result.error); // 3
} finally {
await runtime.dispose();
}
import { AgentOs } from "@rivet-dev/agentos";
const runtime = await AgentOs.create();
try {
const result = await runtime.python.evaluate<number>("21 * 2");
console.log(result.outcome === "succeeded" ? result.value : result.error); // 42
} finally {
await runtime.dispose();
}
Execute code
execute() runs source and captures its output instead of returning a value.
import { AgentOs } from "@rivet-dev/agentos";
const runtime = await AgentOs.create();
try {
const result = await runtime.javascript.execute(
`console.log("hello from agentOS")`,
{ output: { capture: "all" } },
);
console.log(result.outcome === "succeeded" ? result.stdout : result.error); // "hello from agentOS\n"
} finally {
await runtime.dispose();
}
import { AgentOs } from "@rivet-dev/agentos";
const runtime = await AgentOs.create();
try {
const result = await runtime.python.execute(`print("hello from agentOS")`, {
output: { capture: "all" },
});
console.log(result.outcome === "succeeded" ? result.stdout : result.error); // "hello from agentOS\n"
} finally {
await runtime.dispose();
}
Executions are ephemeral, so capture stdio only when you want it.
Pass data into code
inputs hands host values to the guest as real objects, so data never gets interpolated into source.
import { AgentOs } from "@rivet-dev/agentos";
const runtime = await AgentOs.create();
try {
const result = await runtime.javascript.evaluate<number>(
"inputs.items.reduce((total, item) => total + item.price, 0)",
{ inputs: { items: [{ price: 10 }, { price: 32 }] } },
);
console.log(result.outcome === "succeeded" ? result.value : result.error); // 42
} finally {
await runtime.dispose();
}
import { AgentOs } from "@rivet-dev/agentos";
const runtime = await AgentOs.create();
try {
const result = await runtime.python.evaluate<number>(
"sum(item['price'] for item in inputs['items'])",
{ inputs: { items: [{ price: 10 }, { price: 32 }] } },
);
console.log(result.outcome === "succeeded" ? result.value : result.error); // 42
} finally {
await runtime.dispose();
}
Keep state between calls
Pass a contextId to keep globals, imports, and modules alive across calls.
import { AgentOs } from "@rivet-dev/agentos";
const runtime = await AgentOs.create();
try {
await runtime.createContext("analysis");
await runtime.javascript.execute("const answer = 40", {
contextId: "analysis",
});
const result = await runtime.javascript.evaluate<number>("answer + 2", {
contextId: "analysis",
});
console.log(result.outcome === "succeeded" && result.value); // 42
await runtime.contexts.delete("analysis");
} finally {
await runtime.dispose();
}
import { AgentOs } from "@rivet-dev/agentos";
const runtime = await AgentOs.create();
try {
await runtime.createContext("analysis");
await runtime.python.execute("answer = 40", { contextId: "analysis" });
const result = await runtime.python.evaluate<number>("answer + 2", {
contextId: "analysis",
});
console.log(result.outcome === "succeeded" && result.value); // 42
await runtime.contexts.delete("analysis");
} finally {
await runtime.dispose();
}
Type check before running
Type checking validates the agent’s generated code before it executes.
import { AgentOs } from "@rivet-dev/agentos";
const runtime = await AgentOs.create();
try {
const checked = await runtime.typescript.check(
`const total: number = "not a number";`,
);
for (const diagnostic of checked.diagnostics) {
// error TS2322: Type 'string' is not assignable to type 'number'.
// Pass this back to the agent so it can fix the code and try again.
console.log(
`${diagnostic.category} TS${diagnostic.code}: ${diagnostic.message}`,
);
}
if (checked.diagnostics.length === 0) {
await runtime.typescript.execute(`const total: number = 42;`, {
output: { capture: "all" },
});
}
} finally {
await runtime.dispose();
}
Install packages programmatically
import { AgentOs } from "@rivet-dev/agentos";
const runtime = await AgentOs.create();
try {
await runtime.javascript.npm.install({ frozen: true });
const build = await runtime.javascript.npm.runScript("build");
console.log(build.outcome); // "succeeded"
await runtime.javascript.npm.runPackage("prettier", {
args: ["--check", "."],
});
} finally {
await runtime.dispose();
}
import { AgentOs } from "@rivet-dev/agentos";
const runtime = await AgentOs.create();
try {
const installed = await runtime.python.install(["requests==2.32.4"], {
upgrade: true,
});
console.log(installed.outcome); // "succeeded"
await runtime.python.install({ requirementsFile: "requirements.txt" });
} finally {
await runtime.dispose();
}
Installs modify the VM-wide filesystem, so a package installed once is importable by every later execution in that VM, in any language.
Background processes and web servers
spawn starts a long-lived process and returns a pid. From there you get stdin, output, signals, and waiting.
import { AgentOs } from "@rivet-dev/agentos";
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 AgentOs.create({ permissions: { network: "allow" } });
try {
const server = await runtime.javascript.spawn(serverSource, {
onStdout: (chunk) => process.stdout.write(new TextDecoder().decode(chunk)),
});
const response = await runtime.network.httpRequest({
port: 3000,
path: "/health",
});
console.log(response.status); // 200
await runtime.process.signal(server.pid, "SIGTERM");
await runtime.process.wait(server.pid);
} finally {
await runtime.dispose();
}
import { AgentOs } from "@rivet-dev/agentos";
const runtime = await AgentOs.create({ permissions: { network: "allow" } });
try {
const server = await runtime.python.spawnModule("http.server", {
args: ["8000"],
output: { retainEvents: true },
});
const response = await runtime.network.httpRequest({ port: 8000, path: "/" });
console.log(response.status); // 200
await runtime.process.readOutput(server.pid);
await runtime.process.signal(server.pid, "SIGINT");
await runtime.process.wait(server.pid);
} finally {
await runtime.dispose();
}
A full Linux-compatible environment underneath
There is a real Linux environment behind all of this, shared by every language. If you can do it in a sandbox, you can do it in agentOS.
import { AgentOs } from "@rivet-dev/agentos";
const runtime = await AgentOs.create({ permissions: { network: "allow" } });
try {
// Filesystem
const file = await runtime.javascript.execute(
`
import fs from "node:fs/promises";
await fs.writeFile("/workspace/data.txt", "hello");
console.log(await fs.readFile("/workspace/data.txt", "utf8"));
`,
{ output: { capture: "all" } },
);
console.log(file.stdout); // "hello\n"
// Networking
const status = await runtime.javascript.evaluate<number>(
`(async () => (await fetch("http://localhost:8000/")).status)()`,
);
console.log(status.outcome === "succeeded" && status.value); // 200
// Process trees
const listing = await runtime.javascript.execute(
`
import { execFileSync } from "node:child_process";
console.log(execFileSync("ls", ["-la", "/workspace"], { encoding: "utf8" }));
`,
{ output: { capture: "all" } },
);
console.log(listing.stdout); // Directory listing for /workspace
} finally {
await runtime.dispose();
}
import { AgentOs } from "@rivet-dev/agentos";
const runtime = await AgentOs.create({ permissions: { network: "allow" } });
try {
// Filesystem
const file = await runtime.python.execute(
`
from pathlib import Path
Path("/workspace/data.txt").write_text("hello")
print(Path("/workspace/data.txt").read_text())
`,
{ output: { capture: "all" } },
);
console.log(file.stdout); // "hello\n"
// Networking
const status = await runtime.python.execute(
`
import urllib.request
print(urllib.request.urlopen("http://localhost:8000").status)
`,
{ output: { capture: "all" } },
);
console.log(status.stdout); // "200\n"
// Process trees
const listing = await runtime.python.execute(
`
import subprocess
subprocess.run(["ls", "-la", "/workspace"], check=True)
`,
{ output: { capture: "all" } },
);
console.log(listing.stdout); // Directory listing for /workspace
} finally {
await runtime.dispose();
}
Get started
npm install @rivet-dev/agentos