Python
Execute Python source, files, modules, and package workflows in agentOS.
agentOS runs CPython 3.13 as a first-class VM execution engine. Python shares the VM filesystem, process tree, networking, permissions, and limits with agents, Bash, JavaScript, and installed software.
Letting an agent write code instead of chaining one tool call per step is called Code Mode. It has a few advantages over driving Bash:
- Fewer tokens: Ten chained operations cost one round trip, not ten.
- Real data processing: Comprehensions and
pandasinstead ofjqandawk. - The package ecosystem:
pip installwhatever the task needs. - Concurrency:
asyncioinstead of shell job control.
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.python.evaluate<number>("21 * 2");
console.log(result.outcome === "succeeded" ? result.value : result.error); // 42
} finally {
await runtime.dispose();
}
A value the JSON encoder can’t represent fails with
evaluation_serialization_failed rather than silently losing the value.
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.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 — "stderr" for
diagnostics, "all" for both streams. onStdout/onStderr stream live and work
independently of capture.
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.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, functions, imports, and modules alive in one
interpreter.
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 : result.error); // 42
// Delete an idle context when you are done with it. `contexts.reset()`
// is the other option: it keeps the id and drops only the interpreter state.
await runtime.contexts.delete("analysis");
} finally {
await runtime.dispose();
}
Only inline execute()/evaluate() retain memory. Files, modules, installs, and
Bash may pass the same id, but they run fresh. A context runs one operation at a
time — reusing a busy contextId fails immediately.
Files, modules, and async
import { AgentOs } from "@rivet-dev/agentos";
const runtime = await AgentOs.create();
try {
await runtime.python.executeFile("/workspace/report.py", {
args: ["--json"],
});
await runtime.python.executeModule("http.server", { args: ["8000"] }); // python -m
// Inline Python supports top-level await, `async for`, and `async with`.
await runtime.createContext("async");
const result = await runtime.python.evaluate<string>("await fetch_data()", {
contextId: "async",
});
console.log(result.outcome === "succeeded" ? result.value : result.error);
} finally {
await runtime.dispose();
}
Awaited work counts against the deadline; unawaited asyncio tasks are not
promised to survive between operations.
Install packages
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. Only one npm/Python
mutation runs at a time per VM; a concurrent install fails with execution_busy.
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 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 environment underneath
There is a real Linux environment behind all of this, shared by every language. Files and installed packages are immediately visible to Bash, JavaScript, agents, and other executions.
import { AgentOs } from "@rivet-dev/agentos";
const runtime = await AgentOs.create({ permissions: { network: "allow" } });
try {
// Filesystem: pathlib and os use the VM's persistent filesystem.
const files = 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(files.stdout); // "hello\n"
// Process trees: subprocess spawns real guest processes.
const processes = await runtime.python.execute(
`
import subprocess
subprocess.run(["ls", "-la", "/workspace"], check=True)
`,
{ output: { capture: "all" } },
);
console.log(processes.stdout); // Directory listing for /workspace
// Networking: sockets and HTTP clients go through the VM network policy.
const status = await runtime.python.execute(
`
import urllib.request
print(urllib.request.urlopen("https://example.com").status)
`,
{ output: { capture: "all" } },
);
console.log(status.stdout); // "200\n"
} finally {
await runtime.dispose();
}
See Filesystem, Processes & Shells, and Networking & Previews.
Bindings
Python calls bindings as normal commands via subprocess, so
host credentials stay outside the VM.
import { AgentOs, type Bindings } from "@rivet-dev/agentos";
import { z } from "zod";
// The handler runs on the host, so the API key never enters the VM.
const weather: Bindings = {
name: "weather",
description: "Weather data bindings",
bindings: {
forecast: {
description: "Get the weather forecast for a city",
inputSchema: z.object({ city: z.string() }),
execute: async ({ city }: { city: string }) => {
const res = await fetch(
`https://api.weather.example/forecast?city=${city}&key=${process.env.WEATHER_API_KEY}`,
);
return res.json();
},
},
},
};
// The collection is projected into the VM as an `agentos-weather` command.
const runtime = await AgentOs.create({ bindings: [weather] });
try {
const result = await runtime.python.execute(
`
import json
import subprocess
completed = subprocess.run(
["agentos-weather", "forecast", "--city", "Paris"],
check=True,
capture_output=True,
text=True,
)
print(json.loads(completed.stdout)["result"])
`,
{ output: { capture: "all" } },
);
console.log(result.outcome === "succeeded" ? result.stdout : result.error);
} finally {
await runtime.dispose();
}
Permissions, limits, and timeouts
Every operation inherits the VM permission policy and resource limits.
import { AgentOs } from "@rivet-dev/agentos";
const runtime = await AgentOs.create();
try {
// timeoutMs is a wall-clock deadline for the whole operation. It expires
// into a result rather than throwing, and never replaces the VM watchdogs.
const result = await runtime.python.execute("import time; time.sleep(10)", {
timeoutMs: 2_000,
});
console.log(result.outcome); // "timed_out"
} finally {
await runtime.dispose();
}