Bash
Run shell commands and arbitrary argument-vector operations through the agentOS execution lifecycle with controlled input, output, and exit status.
Run bash inside the VM. Shell commands are the simplest agentOS execution surface, and share the same execution lifecycle as JavaScript, TypeScript, Python, and package workflows.
- Hosted
v1.process.runtakes an executable and an argument array. Runshwith-cfor pipes, redirects, and globs. - Library
process.exec()runs a bash command line, andprocess.execFile()runs an executable whose arguments are never parsed by a shell. - In-VM only: Commands run inside the VM, never in the host shell.
For multi-step work, consider JavaScript or Python instead. One call returns real data structures instead of a chain of shell calls.
Run commands
import { createAgentOsClient } from "@rivet-dev/agentos";
const client = createAgentOsClient({ endpoint: "https://api.rivet.dev" });
const vm = client.agentOS.getOrCreate(["my-vm"]);
// Pipes, redirects, and globs need a shell, so run `sh -c`.
const piped = await vm.v1.process.run({
command: "sh",
args: ["-c", "echo 'hello world' | tr a-z A-Z"],
options: { captureStdio: true },
});
console.log(piped.stdout.trim()); // "HELLO WORLD"
// Arguments are passed verbatim, so nothing is parsed by a shell.
const raw = await vm.v1.process.run({
command: "echo",
args: ["$(whoami)"],
options: { captureStdio: true },
});
console.log(raw.stdout.trim(), raw.status.exitCode); // "$(whoami)" 0
import { AgentOs } from "@rivet-dev/agentos-core";
const runtime = await AgentOs.create();
try {
// No capture: you get the outcome and exit code only.
await runtime.process.exec("mkdir -p /workspace/demo");
// Capture "stderr" for diagnostics, "all" for both streams.
const listing = await runtime.process.exec("ls -la /workspace", {
output: { capture: "all" },
});
console.log(listing.outcome === "succeeded" ? listing.stdout : listing.error);
// Pipes, redirects, and globs work — it's a real shell.
const piped = await runtime.process.exec("echo 'hello world' | tr a-z A-Z", {
output: { capture: "all" },
});
console.log(piped.stdout?.trim()); // "HELLO WORLD"
// execFile sends each arg verbatim — no shell parses it, so no injection.
const raw = await runtime.process.execFile("echo", ["$(whoami)"], {
output: { capture: "all" },
});
console.log(raw.stdout?.trim()); // "$(whoami)" — printed literally, never run
} finally {
await runtime.dispose();
}
Background and interactive work
spawn starts a long-lived process and returns a handle. From there you get
stdin, a PTY, output replay, signals, and waiting.
import { createAgentOsClient } from "@rivet-dev/agentos";
const client = createAgentOsClient({ endpoint: "https://api.rivet.dev" });
const vm = client.agentOS.getOrCreate(["my-vm"]);
const shell = await vm.v1.process.spawn({ command: "sh", args: [] });
await vm.v1.process.stdin.write({ process: shell, data: "echo from the background\n" });
await vm.v1.process.stdin.close({ process: shell });
await vm.v1.process.wait({ process: shell });
const replay = await vm.v1.process.output.read({ process: shell });
console.log(replay.events.length);
import { AgentOs } from "@rivet-dev/agentos-core";
const runtime = await AgentOs.create();
try {
const shell = await runtime.process.spawn("sh", [], {
output: { retainEvents: true }, // buffer output so it can be replayed
});
await runtime.process.writeStdin(shell.pid, "echo from the background\n");
await runtime.process.resizePty(shell.pid, { cols: 120, rows: 40 });
await runtime.process.closeStdin(shell.pid);
const exit = await runtime.process.wait(shell.pid);
const replay = await runtime.process.readOutput(shell.pid);
console.log(exit.exitCode, replay.events.length); // 0 1
} finally {
await runtime.dispose();
}
Files and software
Shell commands see the same filesystem as JavaScript and Python. Common POSIX commands ship by default; more is projected through the software registry.
import { createAgentOsClient } from "@rivet-dev/agentos";
const client = createAgentOsClient({ endpoint: "https://api.rivet.dev" });
const vm = client.agentOS.getOrCreate(["my-vm"]);
await vm.v1.process.run({
command: "sh",
args: ["-c", "printf 'a\\nb\\nc\\n' > /workspace/out.txt"],
});
const lines = await vm.v1.process.run({
command: "sh",
args: ["-c", "cat /workspace/out.txt | wc -l"],
options: { captureStdio: true },
});
console.log(lines.stdout.trim()); // "3"
import { AgentOs } from "@rivet-dev/agentos-core";
const runtime = await AgentOs.create();
try {
// Files written by the shell are the same files agents, JavaScript, and
// Python see — one persistent VM filesystem.
await runtime.process.exec("printf 'a\\nb\\nc\\n' > /workspace/out.txt");
const lines = await runtime.process.exec("cat /workspace/out.txt | wc -l", {
output: { capture: "all" },
});
console.log(lines.stdout?.trim()); // "3"
} finally {
await runtime.dispose();
}
Host functions
Host functions appear as commands, so pipelines use trusted host capabilities without putting credentials inside the VM. Host functions are library-only.
import { AgentOs } from "@rivet-dev/agentos-core";
import { z } from "zod";
// The handler runs on the host, so the API key never enters the VM.
const vm = await AgentOs.create({
hostFunctions: {
weather: {
forecast: {
inputSchema: z
.object({ city: z.string() })
.describe("Get the weather forecast for a city"),
execute: async ({ city }) => {
const res = await fetch(
`https://api.weather.example/forecast?city=${city}&key=${process.env.WEATHER_API_KEY}`,
);
return res.json();
},
},
},
},
});
try {
// The collection is projected into the VM as an `agentos-weather` command,
// so it composes with pipes and redirects like any other program.
const result = await vm.process.exec(
"agentos-weather forecast --city Paris > /tmp/forecast.json && wc -c < /tmp/forecast.json",
{ output: { capture: "all" } },
);
console.log(result.outcome === "succeeded" ? result.stdout : result.error);
} finally {
await vm.dispose();
}
Permissions, limits, and timeouts
Every command inherits the VM permission policy and resource limits.
import { AgentOs } from "@rivet-dev/agentos-core";
const runtime = await AgentOs.create();
try {
// timeoutMs is a wall-clock deadline. It expires into a result rather than
// throwing; a denied operation gets the normal POSIX error.
const result = await runtime.process.exec("sleep 10", { timeoutMs: 1_000 });
console.log(result.outcome); // "timed_out"
} finally {
await runtime.dispose();
}
Embedded API
The library examples use AgentOs.create(). The host owns dispose(), and
files only outlive the VM when its root filesystem is stored in SQLite; see
Persistence.
Read more in the embedded API quickstart.