Quickstart (Hosted)
Create durable agentOS VMs by ID and control them from the RivetKit client or plain HTTP.
Hosted agentOS VMs run as the prebuilt agentOS Rivet Actor, on Rivet Cloud or
on your own Rivet control plane. Create a VM by key, then control it from the
@rivet-dev/agentos client or plain HTTP. To run VMs inside your own Node.js
process instead, see the library quickstart.
Which one should I use?
| Library | Hosted | |
|---|---|---|
| Runs | Inside your process | Durable instance on Rivet Cloud or self-hosted |
| Lifecycle | Runs until dispose() or process exit | Sleeps when idle unless a process is running |
| Clients | Your backend | Your backend, or browsers with a scoped JWT |
| Preview URLs | None | Expiring preview URLs |
Create a VM
Create a project
Create a project in the Rivet dashboard and
choose agentOS as the product. Set RIVET_NAMESPACE and RIVET_TOKEN from the
project. To run on your own infrastructure, see
Self-Hosting.
Install the client
npm install @rivet-dev/agentos
The client is optional. Every action is also a plain HTTP call.
Create a VM and use it
import { createAgentOsClient } from "@rivet-dev/agentos";
const client = createAgentOsClient({ endpoint: "https://api.rivet.dev" });
// Creates the VM on first use and returns the same VM for the same key.
const vm = client.agentOS.getOrCreate(["my-vm"]);
const result = await vm.v1.process.run({
command: "sh",
args: ["-c", "echo hello && ls /home/agentos"],
options: { captureStdio: true },
});
console.log(result.stdout);
await vm.v1.filesystem.writeFile({
path: "/home/agentos/hello.txt",
content: "Hello, world!",
});
// Start a server and expose it through an expiring preview URL.
await vm.v1.filesystem.writeFile({
path: "/home/agentos/server.js",
content: `require("node:http").createServer((req, res) => res.end("hello")).listen(3000);`,
});
await vm.v1.process.spawn({ command: "node", args: ["/home/agentos/server.js"] });
const preview = await vm.v1.network.preview.create({ port: 3000, ttlMs: 60 * 60 * 1_000 });
curl -X PUT "https://api.rivet.dev/actors?namespace=$RIVET_NAMESPACE" \
-H "Authorization: Bearer $RIVET_TOKEN" \
--json '{
"name": "agentOS",
"key": "my-vm",
"runner_name_selector": "services",
"crash_policy": "sleep"
}'
# → { "actor": { "actor_id": "…" }, "created": true }
curl "https://api.rivet.dev/gateway/$VM_ID/action/v1.process.run" \
-H "x-rivet-token: $VM_TOKEN" \
--json '{"args": [{
"command": "sh",
"args": ["-c", "echo hello && ls /home/agentos"],
"options": { "captureStdio": true }
}]}'
curl "https://api.rivet.dev/gateway/$VM_ID/action/v1.process.spawn" \
-H "x-rivet-token: $VM_TOKEN" \
--json '{"args": [{"command": "node", "args": ["/home/agentos/server.js"]}]}'
curl "https://api.rivet.dev/gateway/$VM_ID/action/v1.network.preview.create" \
-H "x-rivet-token: $VM_TOKEN" \
--json '{"args": [{"port": 3000, "ttlMs": 3600000}]}'
# → { "output": { "path": "/preview/…/", "token": "…", … } }
Open the preview at /gateway/$VM_ID@$VM_TOKEN/request followed by the
returned path. A running server keeps the VM awake.
Configure the VM
Configuration is stored with the VM and versioned. Replace it with
v1.config.set, then restart the VM so filesystem and software changes take
effect.
const vm = client.agentOS.getOrCreate(["my-vm"]);
await vm.v1.config.set({
config: {
// Keep files across sleep and restarts, and mount durable storage at /data.
filesystem: {
root: { type: "durable" },
mounts: [{ path: "/data", backend: { type: "durable" } }],
},
// Environment variables for guest processes.
environment: { NODE_ENV: "production" },
// Deny network access except to one host.
permissions: {
network: {
default: "deny",
rules: [
{
mode: "allow",
operations: ["*"],
patterns: ["dns://api.example.com", "tcp://api.example.com:*"],
},
],
},
},
},
});
// Filesystem and software changes apply after a restart.
await vm.v1.vm.restart({});
curl "https://api.rivet.dev/gateway/$VM_ID/action/v1.config.set" \
-H "x-rivet-token: $VM_TOKEN" \
--json '{"args": [{"config": {
"filesystem": {
"root": { "type": "durable" },
"mounts": [{ "path": "/data", "backend": { "type": "durable" } }]
},
"environment": { "NODE_ENV": "production" },
"permissions": {
"network": {
"default": "deny",
"rules": [{
"mode": "allow",
"operations": ["*"],
"patterns": ["dns://api.example.com", "tcp://api.example.com:*"]
}]
}
}
}}]}'
curl "https://api.rivet.dev/gateway/$VM_ID/action/v1.vm.restart" \
-H "x-rivet-token: $VM_TOKEN" \
--json '{"args": [{}]}'
See Persistence, Permissions, and the Hosted HTTP API for every config field.
Give Clients Direct Access With JWTs
Keep your admin token on the backend and issue a JWT scoped to one VM. Browsers and other untrusted clients can then call that VM directly, and nothing else.
// Backend: issue a token for this VM only
const vmId = await vm.resolve();
const response = await fetch("https://api.rivet.dev/auth/tokens", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.RIVET_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
namespace: process.env.RIVET_NAMESPACE,
subject: "user-123",
duration: 3600,
grants: [
{ resource: "actor_gateway", target: { id: vmId }, operations: ["read"] },
],
}),
});
const { token } = await response.json();
// Client: call the VM directly
const userClient = createAgentOsClient({
endpoint: "https://api.rivet.dev",
namespace: "my-namespace",
token,
});
const userVm = userClient.getForId("agentOS", vmId);
curl -X POST "https://api.rivet.dev/auth/tokens" \
-H "Authorization: Bearer $RIVET_TOKEN" \
--json @- <<EOF
{
"namespace": "$RIVET_NAMESPACE",
"subject": "user-123",
"duration": 3600,
"grants": [{
"resource": "actor_gateway",
"target": { "id": "$VM_ID" },
"operations": ["read"]
}]
}
EOF
# → { "token": "…", "expires_ts": … }
Next steps
- See the Hosted HTTP API for configuration, events, errors, and every action.
- Install tools from the software registry.
- Schedule commands with Crons & Schedules.