From cae85cbe86bbcf6eb4ef13ae69316c800669b975 Mon Sep 17 00:00:00 2001 From: Sree Narayanan Date: Mon, 5 Oct 2026 15:45:43 +0000 Subject: [PATCH 01/18] docs(agentos): update docs for the 0.4 Core API PR #2011 removed the RivetKit actor package and the examples the docs embedded, which broke the docs sync. Rewrite the pages around @rivet-dev/agentos-core: restore host function snippets, drop hosted actor, session, preview, and sleep references, document SQLite-backed root persistence, and fix broken links and the S3 mount plugin id. Co-authored-by: Amp Amp-Thread-ID: https://ampcode.com/threads/T-c7d08b58-92c1-4fc0-abb9-7a15f4504a4f --- docs/content/docs/architecture.mdx | 10 +- docs/content/docs/architecture/filesystem.mdx | 2 +- docs/content/docs/architecture/networking.mdx | 17 +--- .../packages-and-command-resolution.mdx | 10 +- docs/content/docs/architecture/processes.mdx | 26 ++--- docs/content/docs/bash.mdx | 15 ++- docs/content/docs/browser.mdx | 13 ++- docs/content/docs/cron.mdx | 28 +++++- .../docs/custom-software/definition.mdx | 6 +- .../docs/custom-software/publishing.mdx | 3 +- docs/content/docs/embedded.mdx | 33 ++++--- docs/content/docs/filesystem.mdx | 43 +++++--- docs/content/docs/host-functions.mdx | 43 +++----- docs/content/docs/index.mdx | 6 +- docs/content/docs/javascript.mdx | 20 ++-- docs/content/docs/limitations.mdx | 8 +- docs/content/docs/networking.mdx | 29 ++++-- docs/content/docs/performance.mdx | 24 +++-- docs/content/docs/permissions.mdx | 22 +++-- docs/content/docs/persistence.mdx | 30 +++--- docs/content/docs/processes.mdx | 97 +++++++++++-------- docs/content/docs/python.mdx | 13 ++- docs/content/docs/quickstart-embedded.mdx | 25 +++-- docs/content/docs/quickstart.mdx | 21 +++- docs/content/docs/resource-limits.mdx | 46 ++++----- docs/content/docs/sandboxes.mdx | 24 +++-- docs/content/docs/security-model.mdx | 35 +++---- docs/content/docs/software.mdx | 79 ++++++++------- docs/content/docs/versus-sandbox.mdx | 16 ++- docs/content/use-cases/index.mdx | 4 +- docs/sidebar.json | 4 +- examples/embedded/config-reference.ts | 19 ++-- examples/embedded/host-functions-bash.ts | 33 +++++++ .../embedded/host-functions-javascript.ts | 36 +++++++ examples/embedded/host-functions-python.ts | 43 ++++++++ examples/embedded/host-functions.ts | 5 +- examples/embedded/mounts.ts | 13 ++- examples/embedded/persistence.ts | 6 ++ examples/embedded/software-install.ts | 27 ++++++ examples/embedded/vm.ts | 13 +-- 40 files changed, 586 insertions(+), 361 deletions(-) create mode 100644 examples/embedded/host-functions-bash.ts create mode 100644 examples/embedded/host-functions-javascript.ts create mode 100644 examples/embedded/host-functions-python.ts create mode 100644 examples/embedded/software-install.ts diff --git a/docs/content/docs/architecture.mdx b/docs/content/docs/architecture.mdx index 0cff94938b..b0a436962b 100644 --- a/docs/content/docs/architecture.mdx +++ b/docs/content/docs/architecture.mdx @@ -3,9 +3,9 @@ title: "Architecture" description: "How Core, the sidecar, kernel, VFS, and executors fit together." --- -`@rivet-dev/agentos-core` controls the same runtime from a trusted Node.js -process. It may retain host-only state such as callbacks, binding handlers, -mount plugins, local package paths, and custom scheduling. +`@rivet-dev/agentos-core` controls the runtime from a trusted Node.js process. +It may retain host-only state such as host function handlers, mount plugins, +local package paths, and custom scheduling. ## Runtime boundary @@ -31,5 +31,5 @@ Each owner advances only its own migration ladder. There is no legacy storage adoption path. The default VM root is an ephemeral in-memory overlay over the bundled base -image. Configure durable filesystem storage for files that must survive VM -replacement. +image. Store the root filesystem in the VM's SQLite database for files that +must survive VM recreation; see [Persistence](/agentos/docs/persistence). diff --git a/docs/content/docs/architecture/filesystem.mdx b/docs/content/docs/architecture/filesystem.mdx index c144870f32..7e45c8a2ba 100644 --- a/docs/content/docs/architecture/filesystem.mdx +++ b/docs/content/docs/architecture/filesystem.mdx @@ -51,7 +51,7 @@ Resolution is **longest-prefix wins**: if `/mnt/data` is a mount and the guest o \- ... ``` -The base layer is in-memory and per-VM; the runtime transparently persists it to backing storage so it survives sleep/wake. Mounts are pluggable: any guest path can be backed by the host, a remote, or a cloud store. See [Mounting filesystems](/agentos/docs/filesystem#mounts) for the user-facing config. +The base layer is in-memory and per-VM, and is discarded when the VM is disposed. A root or mount backed by the VM's SQLite database survives VM recreation; see [Persistence](/agentos/docs/persistence). Mounts are pluggable: any guest path can be backed by the host, a remote, or a cloud store. See [Mounting filesystems](/agentos/docs/filesystem#mounts) for the user-facing config. ## Routing a guest syscall diff --git a/docs/content/docs/architecture/networking.mdx b/docs/content/docs/architecture/networking.mdx index 09d30dca33..cada2bd4b5 100644 --- a/docs/content/docs/architecture/networking.mdx +++ b/docs/content/docs/architecture/networking.mdx @@ -1,12 +1,12 @@ --- title: "Networking" -description: "Explore the agentOS kernel socket table, JavaScript and WASM traffic, DNS, egress policy, loopback isolation, and preview routing." +description: "Explore the agentOS kernel socket table, JavaScript and WASM traffic, DNS, egress policy, and loopback isolation." skill: true --- These internal architecture docs are mostly generated and maintained by LLMs, then reviewed by humans. They are intentionally verbose; use your preferred LLM to ask focused questions about the architecture as needed. -This is the internals view of agentOS networking: the kernel socket table, the layers a request crosses, and where policy is enforced. For the user-facing API (`httpRequest`, preview URLs, the confinement model from a caller's perspective), see [Networking & Previews](/agentos/docs/networking). For the trust boundary this all sits inside, see [Architecture](/agentos/docs/architecture). +This is the internals view of agentOS networking: the kernel socket table, the layers a request crosses, and where policy is enforced. For the user-facing API (`httpRequest`, `fetch`, the confinement model from a caller's perspective), see [Networking](/agentos/docs/networking). For the trust boundary this all sits inside, see [Architecture](/agentos/docs/architecture). The governing rule for TCP is that there is exactly **one authoritative transport for everything VM-local**: the kernel socket table. No untrusted guest code opens a host socket directly. Guest `fetch()`, `node:http`, TCP `node:net`, WASM TCP clients and servers, and host-into-guest requests (`httpRequest` / `rt.fetch`) all target the same listener table. The trusted sidecar implements `AF_UNIX` streams with a separate, per-VM private host transport while keeping pathname inodes and permissions authoritative in the guest VFS. @@ -118,20 +118,9 @@ Every guest connect, listen, read, and write passes through sidecar ownership an Host-to-guest requests bypass egress, not the table. `httpRequest` / `rt.fetch` terminate at the guest's loopback listener and never leave the VM, so they work even when guest egress (layer 3) or outbound `connect` (layer 1) is denied. They are host control-plane traffic, not guest egress, and only ever reach VM-owned listeners, while still going through the same kernel socket table as everything else. -## Preview URLs - -A preview URL is port forwarding for a VM service: a time-limited, signed, publicly reachable URL that proxies HTTP to a port inside the VM. Mechanically it reuses the host-to-guest path: - -- A signed token is minted for a `(VM, port)` pair with an expiration, capped by `preview.maxExpiresInSeconds`. Tokens are stored in SQLite, survive sleep/wake cycles, and expired ones are cleaned up automatically. Active tokens are bounded by `preview.maxActiveTokens` (1,024 by default); creation fails with a limit error that names the option when the bound is reached. -- An incoming request to the preview path is authenticated against the token, then proxied into the VM exactly like `httpRequest`: resolve the port to a VM-owned kernel listener, connect over loopback, frame HTTP/1.1, drive the target process, and return the bounded buffered response. The same fail-closed, VM-owned-listener-only rules apply. -- CORS is enabled so browsers can reach preview URLs from any origin. -- Revocation (`network.preview.expire`) invalidates the token immediately, after which the proxy refuses the request before touching the socket table. - -Because previews ride the host fetch path, they are subject to loopback confinement at the kernel but **not** to the guest egress allowlist: the request enters the listener from the host side and never becomes guest outbound traffic. - ## Where to go next -- [Networking & Previews](/agentos/docs/networking): the `httpRequest` and preview URL API, with usage examples. +- [Networking](/agentos/docs/networking): the `httpRequest` and `fetch` API, with usage examples. - [JavaScript Executor & Socket Reactor](/agentos/docs/architecture/javascript-executor): how Tokio readiness wakes V8, how the channel lanes are separated, and how Node stream backpressure reaches the transport. - [Architecture](/agentos/docs/architecture): the client / sidecar / executor trust boundary this stack lives inside. - [Security Model](/agentos/docs/security-model): the full in-scope and out-of-scope threat model. diff --git a/docs/content/docs/architecture/packages-and-command-resolution.mdx b/docs/content/docs/architecture/packages-and-command-resolution.mdx index eb39fe51ae..f390936d0e 100644 --- a/docs/content/docs/architecture/packages-and-command-resolution.mdx +++ b/docs/content/docs/architecture/packages-and-command-resolution.mdx @@ -52,15 +52,15 @@ The shared Core package-source model is closed: - `Url { url, expected_digest? }` downloads a remote artifact under strict size, time, and redirect limits; -- `Path { path, expected_digest? }` reads a trusted local artifact and is - available only to embedded Core callers. +- `Path { path, expected_digest? }` reads a trusted local artifact from the + host. -The hosted actor accepts only `Url`. It cannot construct a host path, mount a -package from the host filesystem, or receive a custom resolver callback. +There is no custom resolver callback. A host that installs packages on behalf +of untrusted users should accept only `Url` sources from them. Both variants verify the bytes before publishing them into the same process-local content-addressed cache. Cache identity is the content digest, -not the URL. Installed packages are pinned while a VM generation uses them and +not the URL. Installed packages are pinned while a VM uses them and otherwise participate in bounded LRU eviction. ## Command execution diff --git a/docs/content/docs/architecture/processes.mdx b/docs/content/docs/architecture/processes.mdx index ee281ea1d3..996ccdafa0 100644 --- a/docs/content/docs/architecture/processes.mdx +++ b/docs/content/docs/architecture/processes.mdx @@ -7,9 +7,9 @@ skill: true These internal architecture docs are mostly generated and maintained by LLMs, then reviewed by humans. They are intentionally verbose; use your preferred LLM to ask focused questions about the architecture as needed. This page is an internals deep-dive on the kernel's **process model**: the data -structures and syscall paths behind every guest process. For the client-facing -hosted API (`process.run`, `process.spawn`, `terminal.open`, lifecycle, the process tree), see -[Processes & Shell](/agentos/docs/processes). For the surrounding component and trust +structures and syscall paths behind every guest process. For the host-facing +API (`process.exec`, `process.spawn`, `terminal.open`, lifecycle, the process tree), see +[Processes & Shells](/agentos/docs/processes). For the surrounding component and trust model, see [Architecture](/agentos/docs/architecture). Two invariants frame everything below: @@ -31,22 +31,22 @@ inside that VM; nothing in it corresponds to a host PID. ## How a spawn is serviced -A spawn, whether it originates from a client `spawn`/`run` call or from guest +A spawn, whether it originates from a host `spawn`/`exec` call or from guest `node:child_process`, follows one path through the kernel: -1. **The request crosses into the kernel.** A client call arrives over the wire protocol; a guest call arrives as a syscall from the executor. Either way the kernel, not the caller, performs the work. +1. **The request crosses into the kernel.** A host call arrives over the sidecar protocol; a guest call arrives as a syscall from the executor. Either way the kernel, not the caller, performs the work. 2. **Permission check.** The kernel applies the VM's configured permission policy before doing anything. The policy is trusted input; the guest making the request is not. 3. **Resolve the program.** The command is resolved against the VM's virtual filesystem (PATH lookup over the VFS), not the host. The resolved program decides the driver: a JavaScript entrypoint runs in a V8 isolate; a `.wasm` program (including `sh` and coreutils) runs on the WASM runtime. 4. **Allocate the table entry.** The kernel assigns a virtual PID, records the command, arguments, environment, working directory, and parent PID, and links stdio endpoints (see below). -5. **Start execution.** The driver begins running the program. For a one-shot `run`, output and exit status are collected for the call's result; for `spawn`, the process remains available for explicit waiting and output replay. -6. **Reap and record exit.** When the program finishes, the kernel records the exit code on the table entry and marks it exited, which is what `process.wait` resolves against and what the `process.exit` event reports. +5. **Start execution.** The driver begins running the program. For a one-shot `exec`, output and exit status are collected for the call's result; for `spawn`, the process remains available for explicit waiting and output replay. +6. **Reap and record exit.** When the program finishes, the kernel records the exit code on the table entry and marks it exited, which is what `process.wait` resolves against and what `onProcessExit` reports. Signals (`process.signal`) are the same shape: a request into the kernel, which applies it to the virtualized process rather than to any host process. ## Process image replacement -Guest `execve` is distinct from the hosted one-shot `process.run` API. It +Guest `execve` is distinct from the host's one-shot `process.exec` API. It replaces the current process image in place, following Linux process semantics: - the virtual PID, parent, process group, session, working directory, pending signals, blocked-signal mask, stdio, and non-`FD_CLOEXEC` descriptors survive; @@ -67,8 +67,8 @@ Standard streams are kernel-owned objects, not host file descriptors. Each process entry has stdin, stdout, and stderr endpoints that the kernel wires up when the entry is created. -- **Capture vs. stream.** `process.run` returns bounded stdout and stderr after the process exits. For `process.spawn`, the hosted actor exposes `process.output` events with generation-scoped process handles and bounded `process.output.read` replay; `process.exit` signals completion. Process and terminal replay use the same sidecar-owned retention and cursor implementation, so `maxEvents`, `maxBytes`, truncation, and progress behavior are identical across Rust Core and TypeScript Core. Events are low-latency hints; replay is the authoritative recovery path. -- **Writable stdin.** `process.stdin.write` pushes bytes into the process's stdin endpoint; `process.stdin.close` closes the write side so programs that read to EOF (like `cat`) can finish. None of this touches a real pipe on the host. +- **Capture vs. stream.** `process.exec` returns bounded stdout and stderr after the process exits. For `process.spawn`, `onProcessOutput` delivers live output and, when output retention is enabled, `process.readOutput` pages through bounded replay; `onProcessExit` signals completion. Replay uses a sidecar-owned retention and cursor implementation, so `maxEvents`, `maxBytes`, truncation, and progress behavior are identical across Rust Core and TypeScript Core. Events are low-latency hints; replay is the authoritative recovery path. +- **Writable stdin.** `process.writeStdin` pushes bytes into the process's stdin endpoint; `process.closeStdin` closes the write side so programs that read to EOF (like `cat`) can finish. None of this touches a real pipe on the host. - **Pipes between processes.** Shell pipelines (`a | b`) connect one process's stdout endpoint to the next process's stdin endpoint through kernel-owned pipes. The pipe is a virtual object in the kernel, so a pipeline behaves like Linux without any host IPC. Because these endpoints are kernel objects, the same bridging works identically @@ -81,9 +81,9 @@ An interactive shell needs a terminal, not just piped stdio: line editing, job control signals, and window size all depend on a PTY. The kernel provides virtual PTY devices for this. -- **A shell is a process plus a PTY.** `terminal.open` allocates a kernel PTY and starts a shell process attached to it, returning a generation-scoped terminal handle. The PTY is a virtualized terminal device, never a host `/dev/pts` entry. -- **Bidirectional terminal I/O.** `terminal.stdin.write` feeds keystrokes into the PTY master side; output is available through `terminal.output` events and `terminal.output.read` replay. This carries terminal control sequences, so full-screen TUIs behave correctly. -- **Resize is a terminal operation.** `terminal.pty.resize` updates the PTY's window size (columns and rows), which the kernel propagates to the foreground process the way a real terminal resize would, so programs relying on `TIOCGWINSZ`-style sizing redraw correctly. +- **A shell is a process plus a PTY.** `terminal.open` allocates a kernel PTY and starts a shell process attached to it, returning a `shellId`. The PTY is a virtualized terminal device, never a host `/dev/pts` entry. +- **Bidirectional terminal I/O.** `terminal.write` feeds keystrokes into the PTY master side; output arrives through `onShellData`. This carries terminal control sequences, so full-screen TUIs behave correctly. +- **Resize is a terminal operation.** `terminal.resize` updates the PTY's window size (columns and rows), which the kernel propagates to the foreground process the way a real terminal resize would, so programs relying on `TIOCGWINSZ`-style sizing redraw correctly. - **Teardown.** `terminal.close` tears down the PTY and the attached shell process. An open shell keeps the VM active, the same way an open PTY keeps a session alive on a real system. ## WASM sh and coreutils on the process model diff --git a/docs/content/docs/bash.mdx b/docs/content/docs/bash.mdx index a7fc5ba08e..c196e4c4b5 100644 --- a/docs/content/docs/bash.mdx +++ b/docs/content/docs/bash.mdx @@ -9,17 +9,13 @@ 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. -This page uses embedded Core. For hosted actors, use `process.run` with an -explicit executable and argument array, or run `sh` with `-c` for shell parsing; -see [Processes & Terminals](/agentos/docs/processes). - - **`process.exec()`**: Runs a bash command line. Use for pipes, redirects, globs. - **`process.execFile()`**: Injection-safe; args 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](/agentos/docs/javascript) or -[Python](/agentos/docs/python) instead — one round trip and real data structures -rather than a chain of shell calls. +[Python](/agentos/docs/python) instead. One call returns real data structures +instead of a chain of shell calls. ## Run commands @@ -45,7 +41,7 @@ projected through the [software registry](/agentos/docs/software). [Host functions](/agentos/docs/host-functions) appear as commands, so pipelines use trusted host capabilities without putting credentials inside the VM. - + ## Permissions, limits, and timeouts @@ -56,7 +52,8 @@ Every command inherits the VM [permission policy](/agentos/docs/permissions) and ## Embedded API -The examples above already use `AgentOs.create()`. The host owns `dispose()`, -and filesystem state only outlives the VM when you configure `database`. +The examples above use `AgentOs.create()`. The host owns `dispose()`, and files +only outlive the VM when its root filesystem is stored in SQLite; see +[Persistence](/agentos/docs/persistence). Read more in the [embedded API quickstart](/agentos/docs/quickstart-embedded). diff --git a/docs/content/docs/browser.mdx b/docs/content/docs/browser.mdx index f9e847447c..7bda693bfd 100644 --- a/docs/content/docs/browser.mdx +++ b/docs/content/docs/browser.mdx @@ -3,10 +3,13 @@ title: "Browser Automation" description: "Run a packaged browser CLI as ordinary agentOS software." --- -Browser automation is ordinary VM software. Build the CLI into an immutable -`.aospkg`, host it at an HTTPS URL, and include that source in actor creation -config. - +Browser automation is ordinary VM software. Install a browser CLI package, such +as Browserbase from the [registry](/agentos/docs/registry), at boot or with +`software.install`, then run it like any other command. See +[Software](/agentos/docs/software). The guest receives only the credentials explicitly passed to the process -environment. The actor does not provide a browser-specific API. +environment. agentOS does not provide a browser-specific API. + +For workloads that need a full local browser, mount an +[external sandbox](/agentos/docs/sandboxes) and run the browser there. diff --git a/docs/content/docs/cron.mdx b/docs/content/docs/cron.mdx index 3533cd77c7..0eb9d4d60e 100644 --- a/docs/content/docs/cron.mdx +++ b/docs/content/docs/cron.mdx @@ -1,12 +1,34 @@ --- title: "Crons & Loops" -description: "Schedule recurring VM commands while an embedded VM is active." +description: "Schedule recurring and one-shot VM commands while a VM is running." skill: true --- -Core provides an in-process cron helper. Jobs run while the embedding process -and VM are alive; a hosting service is responsible for durable scheduling. +`vm.cron` schedules commands inside the VM. Jobs run while the embedding +process and the VM are alive. Durable scheduling across process restarts is +the responsibility of the application that owns the VM. ## Schedule a command + +`schedule` takes a standard cron expression or an ISO 8601 timestamp for a +one-shot job. `action` is either `{ type: "exec", command, args }`, which runs a +command in the VM, or `{ type: "callback", fn }`, which calls a function in the +host process. + +`overlap` controls what happens when a job fires while its previous run is +still going: `"allow"` (the default) starts another run, `"skip"` drops the new +run, and `"queue"` waits for the previous run to finish. + +## Inspect and cancel jobs + +- `cron.list()` returns each job's schedule, action, `lastRun`, `nextRun`, + `runCount`, and whether it is running. +- `cron.cancel(id)` cancels a job. The handle returned by `schedule` also has a + `cancel()` method. +- `vm.onCronEvent(handler)` receives `cron:fire`, `cron:complete`, and + `cron:error` events. + +Pass `scheduleDriver` to `AgentOs.create()` to replace the default timer-based +driver, for example with one backed by your own durable scheduler. diff --git a/docs/content/docs/custom-software/definition.mdx b/docs/content/docs/custom-software/definition.mdx index 4e67aa9abd..230ce4acb5 100644 --- a/docs/content/docs/custom-software/definition.mdx +++ b/docs/content/docs/custom-software/definition.mdx @@ -20,6 +20,6 @@ payload. Commands are projected under `/opt/agentos/pkgs//` and linked under `/opt/agentos/bin`. The sidecar reads the package projection live. -The package format contains no hosted action, callback, or host-path metadata. -The hosted actor accepts an HTTPS URL and optional exact digest; embedded Core -also accepts a trusted local path. +The package format contains no callback or host-path metadata. Install a package +from an HTTPS URL or a trusted local path, each with an optional exact digest; +see [Software](/agentos/docs/software). diff --git a/docs/content/docs/custom-software/publishing.mdx b/docs/content/docs/custom-software/publishing.mdx index 47111750e8..1d3f758bf1 100644 --- a/docs/content/docs/custom-software/publishing.mdx +++ b/docs/content/docs/custom-software/publishing.mdx @@ -24,4 +24,5 @@ just software-artifacts-dry-run The release workflow uploads the same directory through the configured S3-compatible endpoint under immutable commit and version prefixes. Consumers -pass the resulting artifact URL and digest directly to `software.install`. +pass the resulting artifact URL and digest directly to `software.install`; see +[Software](/agentos/docs/software#install-while-running). diff --git a/docs/content/docs/embedded.mdx b/docs/content/docs/embedded.mdx index 50e5f87045..4f36ebaee4 100644 --- a/docs/content/docs/embedded.mdx +++ b/docs/content/docs/embedded.mdx @@ -4,26 +4,31 @@ description: "Embed agentOS Core in a trusted Node.js application." skill: true --- -`@rivet-dev/agentos-core` runs the same VM runtime without Rivet actor -lifecycle. Your application owns VM identity, persistence, and disposal. -Its native sidecar and platform package must match the installed agentOS -version. If resolution reports a missing or mismatched binary, reinstall the -packages; `AGENTOS_SIDECAR_BIN` can select a custom local build. +`@rivet-dev/agentos-core` runs agentOS VMs inside your Node.js process. Your +application owns VM identity, persistence, and disposal. Its native sidecar and +platform package must match the installed agentOS version. If resolution +reports a missing or mismatched binary, reinstall the packages; +`AGENTOS_SIDECAR_BIN` can select a custom local build. -## Embedded-only capabilities +## Trusted host capabilities -Because the embedding process is trusted, Core can accept values that cannot -cross the hosted actor boundary: +Because the embedding process is trusted, Core accepts values that run in or +read from the host: -- host bindings; -- host-directory and callback-backed mount plugins; -- external VM providers; -- trusted local `.aospkg` paths; +- [host functions](/agentos/docs/host-functions); +- host-directory and other [mount plugins](/agentos/docs/filesystem#mounts); +- [external sandboxes](/agentos/docs/sandboxes); +- local `.aospkg` paths; - custom schedule drivers and sidecar placement. -The hosted actor uses a smaller generated configuration type and a fixed -filesystem registry. It never serializes callbacks or exposes a host path. +## Sidecar placement + +VMs share a pooled sidecar process by default. Create a dedicated sidecar with +`AgentOs.createSidecar()` and pass it as `sidecar: { kind: "explicit", handle }` +to isolate a group of VMs or configure process-wide limits. + + diff --git a/docs/content/docs/filesystem.mdx b/docs/content/docs/filesystem.mdx index dd55ebf459..6a9cd27f95 100644 --- a/docs/content/docs/filesystem.mdx +++ b/docs/content/docs/filesystem.mdx @@ -1,25 +1,42 @@ --- title: "Filesystem" -description: "Read and write VM files and configure safe durable filesystems." +description: "Read and write VM files, and mount host directories, S3, and other filesystem backends." --- -The generated actor client exposes file operations under `filesystem`. -`filesystem.stat` and `filesystem.readdirRecursive` report file lengths as -`sizeBytes`. +File operations live under `vm.filesystem`. Every language and shell in the VM +sees the same filesystem, so files written here are immediately visible to +Bash, JavaScript, Python, and installed software. -The actor's filesystem configuration is a closed registry. By default, each VM -uses an ephemeral in-memory overlay over the bundled base image. Its writes -survive for that VM's lifetime but are discarded when the VM is replaced. Use -a `durable` root or mount when files must survive VM replacement. Unknown -backends, JavaScript VFS objects, callbacks, and host paths are rejected. +The namespace also provides batch `readFiles` and `writeFiles`, `stat`, +`readdirEntries`, `exists`, `move`, `remove`, `export` for a snapshot of the +root filesystem, and `mount`, `unmount`, and `listMounts` for mounts on a +running VM. `stat` and `readdirRecursive` report file lengths as `size`. +## Root filesystem -Host-directory, S3, Google Drive, and custom plugins remain available only in -trusted embedded Core: +By default, each VM uses an in-memory overlay over the bundled base image. +Writes survive for the VM's lifetime and are discarded on `dispose()`. See +[Persistence](/agentos/docs/persistence) to keep files across VM recreation. + +## Mounts + +Mount additional filesystem backends at any guest path. Native mount plugins +are identified by an `id` and a `config` object: + +| Plugin | Backend | +|---|---| +| `host_dir` | A directory on the host, confined to the configured path | +| `chunked_s3` | An S3 or S3-compatible bucket | +| `chunked_sqlite` | The VM's SQLite `database` | +| `google_drive` | A Google Drive folder | -Large-file streaming is a follow-up. Current actions enforce bounded payloads -and fail with a typed limit error. +Host-backed mounts are confined to their configured root on every operation, +including symlink and `..` resolution. `nodeModulesMount()` is a shortcut that +exposes a host `node_modules` tree read-only at `/root/node_modules`. + +File transfers are bounded. An operation that exceeds a payload limit fails +with a typed limit error that names the limit. diff --git a/docs/content/docs/host-functions.mdx b/docs/content/docs/host-functions.mdx index 697e979405..cefa9a5c9e 100644 --- a/docs/content/docs/host-functions.mdx +++ b/docs/content/docs/host-functions.mdx @@ -1,20 +1,16 @@ --- title: "Custom Host Functions" -description: "Expose trusted host functions to agentOS coding agents as typed CLI commands, with scoped input, output, and execution behavior." +description: "Expose trusted host functions to guest programs in agentOS VMs as typed CLI commands and JavaScript globals." skill: true --- -Expose your host JavaScript functions (defined with Zod input schemas) to agents as auto-generated CLI commands installed at `/bin/agentos-{name}` inside the VM, injected into the agent's [system prompt](/agentos/docs/system-prompt) and callable inside scripts for code-mode token savings. +Host functions expose JavaScript functions from your process to code running inside the VM. Each function is defined with a Zod input schema and becomes an auto-generated CLI command at `/bin/agentos-{name}`, plus an async function that inline JavaScript can call directly. ## Getting started -Pass `agentOS({ hostFunctions })` a record of collections. The keys name everything: the collection key becomes the CLI binary `agentos-{name}` and a guest global, and each function key becomes a subcommand. A function needs only an `inputSchema` and an `execute` handler. `.describe()` on the schema is what the agent reads. +Pass `AgentOs.create({ hostFunctions })` a record of collections. The keys name everything: the collection key becomes the CLI binary `agentos-{name}` and a guest global, and each function key becomes a subcommand. A function needs only an `inputSchema` and an `execute` handler. `.describe()` on the schema becomes the command's help text. - - - - - + Each host function can override its `timeout` in milliseconds. The default is 30 seconds and can be changed with `limits.hostFunctions.defaultTimeoutMs`. @@ -32,11 +28,9 @@ Zod schema fields are converted to CLI flags automatically. Field names are conv Optional fields (via `.optional()`) become optional flags. Required fields are enforced at validation time. Use `.describe()` on Zod fields to generate useful `--help` output, and `.describe()` on the whole schema to describe the function itself. -### What the agent sees - -When host functions are registered, CLI shims are installed at `/bin/agentos-{name}` inside the VM and the function list is injected into the agent's [system prompt](/agentos/docs/system-prompt), so keep each schema's `.describe()` concise to save tokens. +### Calling host functions from the shell -The agent interacts with host functions as shell commands: +Guest programs call host functions as shell commands: ```bash # List all available host-function collections @@ -85,30 +79,21 @@ violation, a thrown error, or a timeout. A collection whose name is already a global, such as `process`, is not defined, and a warning is written to stderr. The `hostFunction` permission scope applies exactly as it does to the commands. -## Host functions and MCP servers - -agentOS supports two ways to give agents access to external functionality: **host functions** and session-scoped **MCP servers**. Both work, but they have different tradeoffs. - -| | Host functions | MCP servers | -|---|---|---| -| **How it works** | Call JavaScript functions on the host directly | Connect to a standard MCP server | -| **Authentication** | None required. Calls go directly to the host process. | Requires custom auth configuration per server | -| **Code mode** | Built in. Host functions are exposed as CLI commands, so agents can call them inside scripts for up to 80% token reduction. | Requires extra work to make code mode work out of the box | -| **Latency** | Near-zero. Bound directly to the host process. | Extra network hop to reach the MCP server | -| **Setup** | Define host functions in your actor code with Zod schemas | Configure any standard MCP server | - -Use host functions when you want to expose your own JavaScript functions to agents. Use MCP servers when you want to connect to existing third-party services. See [Sessions](/agentos/docs/sessions#open-a-session) for MCP server configuration. +See [Bash](/agentos/docs/bash#host-functions), +[Node.js](/agentos/docs/javascript#host-functions), and +[Python](/agentos/docs/python#host-functions) for calling host functions from +each language. ## Security -Host-function calls from the agent invoke your `execute()` functions on the host. Your functions run with full access to the host environment, so you can call databases, APIs, and services directly without proxying credentials into the VM. The agent never sees the credentials, only the function's input/output contract. +Host function calls from guest code invoke your `execute()` functions on the host. Your functions run with full access to the host environment, so you can call databases, APIs, and services directly without proxying credentials into the VM. Guest code never sees the credentials, only the function's input and output contract. -Host functions run on the host with full access to the host environment, so do not expose functions that could compromise the host without appropriate safeguards. +Because handlers run with full host access, do not expose functions that could compromise the host without appropriate safeguards. Use the `hostFunction` [permission](/agentos/docs/permissions) scope to restrict which collections guest code may call. ## Embedded API -Pass the same host-function collections to `AgentOs.create()`. The function contract and -generated VM commands are unchanged. +Host functions are passed to `AgentOs.create()` and their handlers run in the +embedding process. diff --git a/docs/content/docs/index.mdx b/docs/content/docs/index.mdx index 4392d21a09..70df4dafa1 100644 --- a/docs/content/docs/index.mdx +++ b/docs/content/docs/index.mdx @@ -15,5 +15,7 @@ APIs in a lightweight VM. -Core owns the VM process, lifecycle, and host bindings. Managed orchestration -adapters are separate consumers of the same runtime APIs. +`@rivet-dev/agentos-core` runs VMs inside your Node.js backend. Your application +owns the VM's lifecycle, persistence, and access to host resources such as +[host functions](/agentos/docs/host-functions) and +[mounts](/agentos/docs/filesystem#mounts). diff --git a/docs/content/docs/javascript.mdx b/docs/content/docs/javascript.mdx index e4e6e1beae..460f0c280b 100644 --- a/docs/content/docs/javascript.mdx +++ b/docs/content/docs/javascript.mdx @@ -31,7 +31,7 @@ Returning `undefined`, a function, a symbol, or a circular value fails with -Executions are ephemeral, so capture stdio only when you want it — `"stderr"` for +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. @@ -49,7 +49,7 @@ calls. -A context runs one operation at a time — reusing a busy `contextId` fails +A context runs one operation at a time, so reusing a busy `contextId` fails immediately. Files, npm, Bash, and type checks may pass the same id, but they run in fresh processes and never touch retained memory. @@ -63,8 +63,9 @@ Create the context explicitly before use; an unknown id fails instead of silentl starting fresh state. A context pins to the first inline language that used it, though JavaScript and TypeScript intentionally share one isolate. -Contexts live for the VM lifetime. They do not survive actor sleep/wake, because -the VM is disposed; create them lazily on the first stateful use after wake. +Contexts live until the VM is disposed. They are in-memory state, so a VM +recreated from the same `database` starts with no contexts; create them lazily +on first use. ## Type check before running @@ -99,14 +100,14 @@ executions. See [Filesystem](/agentos/docs/filesystem), [Processes & Shells](/agentos/docs/processes), and -[Networking & Previews](/agentos/docs/networking). +[Networking](/agentos/docs/networking). ## Host functions -Guest code invokes [host functions](/agentos/docs/host-functions) as ordinary typed commands, so host +Guest JavaScript calls [host functions](/agentos/docs/host-functions) as async globals, so host credentials stay outside the VM. - + ## Permissions, limits, and timeouts @@ -117,8 +118,7 @@ Every operation inherits the VM [permission policy](/agentos/docs/permissions) a ## Embedded API -The examples above already use `AgentOs.create()`. Contexts remain available -until the host calls `dispose()`; there is no actor sleep cycle to recreate them -after. +The examples above use `AgentOs.create()`. Contexts remain available until the +host calls `dispose()`. Read more in the [embedded API quickstart](/agentos/docs/quickstart-embedded). diff --git a/docs/content/docs/limitations.mdx b/docs/content/docs/limitations.mdx index 42b33d6913..656b339593 100644 --- a/docs/content/docs/limitations.mdx +++ b/docs/content/docs/limitations.mdx @@ -10,10 +10,8 @@ overhead. ## Sandbox mounting -When a workload needs a full Linux OS, an embedded application can compose Core -with the [external sandbox](/agentos/docs/sandboxes) extension. That extension -uses trusted host bindings and mounts, so it is unavailable from the static -hosted actor. +When a workload needs a full Linux OS, mount an +[external sandbox](/agentos/docs/sandboxes) into the VM. See [agentOS vs Sandbox](/agentos/docs/versus-sandbox) for a detailed comparison. @@ -21,7 +19,7 @@ See [agentOS vs Sandbox](/agentos/docs/versus-sandbox) for a detailed comparison ### Software registry -agentOS uses its own [software registry](/agentos/registry/) of popular tools cross-compiled for the runtime. You cannot download and install arbitrary binaries (for example via `curl` or `apt`), and standard Linux package managers (`apt`, `yum`) are not available since agentOS runs a streamlined Linux environment rather than a full distribution. Native binaries that are not yet available in the registry (such as Go, Rust, or C++ toolchains) require a full [external sandbox](/agentos/docs/sandboxes/). +agentOS uses its own [software registry](/agentos/docs/registry) of popular tools cross-compiled for the runtime. You cannot download and install arbitrary binaries (for example via `curl` or `apt`), and standard Linux package managers (`apt`, `yum`) are not available since agentOS runs a streamlined Linux environment rather than a full distribution. Native binaries that are not yet available in the registry (such as Go, Rust, or C++ toolchains) require a full [external sandbox](/agentos/docs/sandboxes). See [Software](/agentos/docs/software) for how to install and configure available packages. diff --git a/docs/content/docs/networking.mdx b/docs/content/docs/networking.mdx index 2f74cbad0c..7e43ef43b6 100644 --- a/docs/content/docs/networking.mdx +++ b/docs/content/docs/networking.mdx @@ -1,12 +1,29 @@ --- -title: "Networking & Previews" -description: "Proxy HTTP requests into VM ports and create expiring preview routes." +title: "Networking" +description: "Send HTTP requests from the host to servers running inside an agentOS VM, and control guest network access." --- -`network.httpRequest` proxies one bounded HTTP request to a VM loopback port. -Streaming helpers provide bounded chunked response delivery. +Servers inside the VM listen on VM-local ports. They are not exposed on the +host network. `network.httpRequest` proxies one bounded HTTP request from the +host to a VM loopback port and returns the buffered response. -Core provides direct HTTP request methods. Public preview routing is owned by -the hosting service, not the VM runtime. +`vm.fetch(port, request)` sends a standard `Request` and returns a `Response`. +`fetchStreamStart`, `fetchStreamRead`, and `fetchStreamCancel` deliver a +response body in bounded chunks, so the caller owns backpressure for large +responses. + + + +## Guest network access + +Guest listeners and loopback traffic stay inside the VM. Outbound access is +limited to the sidecar's default allowlist unless the `network` +[permission](/agentos/docs/permissions) scope allows more. Requests from the +host through `httpRequest` enter the VM from the host side, so they are subject +to loopback confinement but not to the guest egress policy. + +Core does not create public URLs for VM ports. To expose a VM server to +browsers, route requests through your application and forward them with +`httpRequest` or `fetch`. diff --git a/docs/content/docs/performance.mdx b/docs/content/docs/performance.mdx index a9ec6efe30..2fbb98cd44 100644 --- a/docs/content/docs/performance.mdx +++ b/docs/content/docs/performance.mdx @@ -5,23 +5,21 @@ skill: true --- Measure the exact workload and hardware used in production. VM startup includes -Core configuration, filesystem bootstrap, required software reconciliation, -and the first execution. Hosted startup additionally includes actor resolution. +Core configuration, filesystem bootstrap, software projection, and the first +execution. ## Cold-start phases Record these phases separately: -1. actor resolution and process startup; -2. required software acquisition; -3. optional preload warming; -4. Core and filesystem boot; -5. first guest instruction. +1. sidecar process startup, or reuse of a shared sidecar; +2. software acquisition; +3. Core and filesystem boot; +4. first guest instruction. -Required software must finish before readiness. Optional preload work has a -strict startup deadline and may time out without making the actor unhealthy. -The process-local content-addressed cache should make a digest hit visible as a -separate metric from a network download. +Software passed at boot must be available before the VM is ready. The sidecar's +content-addressed package cache should make a digest hit visible as a separate +metric from a network download. ## Memory per VM @@ -45,7 +43,7 @@ From the repository root: ``` The benchmark script builds an optimized native binary, points the Core client -at it with `AGENT_OS_SIDECAR_BIN`, and writes JSON results under +at it with `AGENTOS_SIDECAR_BIN`, and writes JSON results under `scripts/benchmarks/results/`. For a focused run, build first and select the minimal sleep/command workload: @@ -53,7 +51,7 @@ For a focused run, build first and select the minimal sleep/command workload: ```sh pnpm build cargo build --release -p agentos-native-sidecar -export AGENT_OS_SIDECAR_BIN="$PWD/target/release/agentos-native-sidecar" +export AGENTOS_SIDECAR_BIN="$PWD/target/release/agentos-native-sidecar" pnpm exec tsx scripts/benchmarks/coldstart.bench.ts \ --workload=sleep --iterations=2000 diff --git a/docs/content/docs/permissions.mdx b/docs/content/docs/permissions.mdx index ddb86324d9..633b0336f2 100644 --- a/docs/content/docs/permissions.mdx +++ b/docs/content/docs/permissions.mdx @@ -1,13 +1,23 @@ --- title: "Permissions" -description: "Constrain untrusted guest filesystem, process, and network access." +description: "Constrain untrusted guest filesystem, process, network, environment, and host function access." --- -Pass a serializable permission tree in creation config or a later complete -`config.set` replacement. The trusted sidecar enforces it for every guest -operation. +Pass a permission tree as `permissions` when creating the VM. The trusted +sidecar enforces it for every guest operation. -Host bindings have a separate policy surface in embedded Core. The hosted actor -does not expose host bindings. +The tree has one scope per capability: `fs`, `network`, `childProcess`, +`process`, `env`, and `hostFunction`. A scope can be `"allow"`, `"deny"`, or a +rule set with a `default` mode and `rules` that match operations and target +patterns. + +Your policy is merged over the default, so an omitted scope keeps its default. +By default the guest behaves like a sandboxed machine: its virtual filesystem, +processes, environment, listeners, and loopback networking work, and outbound +network access is limited to the sidecar's default allowlist. Host functions +are governed by the `hostFunction` scope. + +See the [Security Model](/agentos/docs/security-model) for the trust boundary +these policies enforce. diff --git a/docs/content/docs/persistence.mdx b/docs/content/docs/persistence.mdx index a874a8cb31..7fa4807fc0 100644 --- a/docs/content/docs/persistence.mdx +++ b/docs/content/docs/persistence.mdx @@ -1,21 +1,27 @@ --- -title: "Persistence & Sleep" -description: "Persist VM filesystem and Core state with SQLite." +title: "Persistence" +description: "Persist an agentOS VM's filesystem in SQLite so files survive VM recreation." --- -Core durable state uses `agentos_core_*` tables and filesystem storage uses -`agentos_fs_*`. Each owner has an independent schema version and migration -transaction. +By default, a VM's root filesystem is an in-memory overlay over the bundled +base image. Its files are discarded when the VM is disposed. -This durable metadata does not make the default VM filesystem persistent. The -default root is an ephemeral in-memory overlay and is discarded when the VM is -replaced. Configure a `durable` root or mount only for paths that need to -survive VM replacement. - -Configured durable filesystems survive VM replacement. Running processes, -terminals, open streams, and live subscriptions do not. +To keep files across VM recreation, give the VM a SQLite `database` and store +its root filesystem there with the `chunked_sqlite` plugin. A new VM created +with the same options sees the same files. +`database` on its own stores Core and filesystem metadata, but does not make +the default in-memory root durable. A SQLite-backed root starts without the +bundled base image's files. + +Persisted filesystems survive VM recreation. Running processes, terminals, +language contexts, open streams, cron jobs, and live subscriptions do not. + +Core durable state uses `agentos_core_*` tables and filesystem storage uses +`agentos_fs_*`. Each owner has an independent schema version and migration +transaction. + The embedding application owns VM identity and decides when to recreate or dispose the VM. diff --git a/docs/content/docs/processes.mdx b/docs/content/docs/processes.mdx index 723f75598f..53fde9c655 100644 --- a/docs/content/docs/processes.mdx +++ b/docs/content/docs/processes.mdx @@ -1,49 +1,64 @@ --- -title: "Processes & Terminals" -description: "Execute commands, manage long-running processes, and attach terminals." +title: "Processes & Shells" +description: "Execute commands, manage long-running processes, replay their output, and open interactive shells." --- -`process.exec` runs a command to completion. `process.spawn` starts a -long-running process and returns a process ID for wait, signal, stdin, PTY -resize, and bounded output replay. -`process.get` and `process.list` report `startedAtMs`; nodes returned by -`process.tree` report `startTimeMs` and `exitTimeMs`. These are Unix timestamps -in milliseconds. Completed tree nodes use the same structured `exit` status as -`process.get`. Tree timestamps record when agentOS first observed the process -or its exit, not exact kernel lifecycle times. `exitTimeMs` can be absent when -agentOS did not observe the exit. -`process.tree` includes guest child processes as well as processes launched -through Core. Guest and tracked display PIDs can overlap; use the returned -process ID rather than reconstructing one from a node's `pid`. -For untracked guest nodes, `args` is the kernel's runtime argument list. For -example, Node's `-e` source is passed to its runtime separately, so the tree -shows `-e` but not the source text. +Every guest process is an entry in the VM's kernel process table, never a host +process. Process operations live under `vm.process`. + +- `process.exec(command)` runs a command line through the shell and waits for it + to finish. +- `process.execFile(file, args)` runs an executable with an argument array. No + shell parses the arguments. +- `process.spawn(command, args)` starts a long-running process and returns its + `pid` immediately. Process arguments are passed as individual argv entries. An empty string is a valid argument; it is not dropped or treated as an omitted argument. Empty -commands or working directories are invalid input, including when opening a -terminal. - -Use `onProcessOutput` and `onProcessExit` for live events. Replay retained -output after reconnecting to the same durable VM. - -Interactive terminals are separate resources under `terminal.open`, -`terminal.stdin.write`, `terminal.pty.resize`, `terminal.output.read`, -`terminal.wait`, and `terminal.close`. Their live events are -`terminal.output` and `terminal.exit`. - -A process wait returns an exit code only after agentOS observes a -guest exit. A rejected launch or lost exit observation rejects the wait instead -of inventing an exit code. An observation failure does not prove the guest has -stopped; inspect the VM or restart it before assuming it is safe to retry work. -With output retention enabled, Core output callbacks include the sidecar's -optional `sequence` and `timestampMs` metadata for correlating live output with -replay. Local diagnostics without a guest output event omit this metadata. -Ordinary-process replay returns `exitCode` when the sidecar has confirmed exit -and reconciles Core's `wait`, `get`, and `list` state, even if the live exit -notification was missed. -Real-terminal replay also reconciles `wait_shell` after a missed exit event or -an earlier observation failure. TypeScript Core's local prompt terminal does -not provide replay. +commands or working directories are invalid input. + +## Manage running processes + +A spawned process supports: + +- `process.writeStdin(pid, data)` and `process.closeStdin(pid)`; +- `process.signal(pid, signal)` and `process.kill(pid)`; +- `process.resizePty(pid, { cols, rows })` for a process started with `pty`; +- `process.wait(pid)`, which resolves with the process's exit; +- `process.get(pid)` and `process.list()`, which report `startedAtMs` as a Unix + timestamp in milliseconds. + +`process.tree()` includes guest child processes as well as processes launched +through `vm.process`, so it can show more than you started. Use the returned +`pid` rather than reconstructing one from a node. + +A process wait returns an exit only after agentOS observes a guest exit. A +rejected launch or lost exit observation rejects the wait instead of inventing +an exit code. An observation failure does not prove the guest has stopped; +inspect the VM or recreate it before assuming it is safe to retry work. + +## Output events and replay + +Subscribe to live output with `vm.onProcessOutput(pid, handler)` and to exit +with `vm.onProcessExit(pid, handler)`. Both return an unsubscribe function. + +Pass `output: { retainEvents: true }` to `spawn` to retain the process's output +in the sidecar. `process.readOutput(pid, { after, maxEvents, maxBytes })` then +pages through retained output with a sequence cursor, even after the process +has exited or a live event was missed. Retained output callbacks include +`sequence` and `timestampMs` metadata for correlating live output with replay. + + + +A replay page reports `nextCursor`, `hasMore`, and `truncated`, plus `exitCode` +once the sidecar has confirmed the exit. Retention is bounded; see +[Resource Limits](/agentos/docs/resource-limits) for the defaults. + +## Interactive shells + +`vm.terminal.open(options)` allocates a kernel PTY, starts a shell attached to +it, and returns a `shellId`. Use `terminal.write`, `terminal.resize`, +`terminal.wait`, and `terminal.close` to drive it, and `vm.onShellData`, +`vm.onShellStderr`, and `vm.onShellExit` for its live output and exit. diff --git a/docs/content/docs/python.mdx b/docs/content/docs/python.mdx index b2a5f56715..cac7ccd3f4 100644 --- a/docs/content/docs/python.mdx +++ b/docs/content/docs/python.mdx @@ -33,7 +33,7 @@ A value the JSON encoder can't represent fails with -Executions are ephemeral, so capture stdio only when you want it — `"stderr"` for +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. @@ -53,7 +53,7 @@ interpreter. 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. +time, so reusing a busy `contextId` fails immediately. ## Files, modules, and async @@ -92,14 +92,14 @@ other executions. See [Filesystem](/agentos/docs/filesystem), [Processes & Shells](/agentos/docs/processes), and -[Networking & Previews](/agentos/docs/networking). +[Networking](/agentos/docs/networking). ## Host functions Python calls [host functions](/agentos/docs/host-functions) as normal commands via `subprocess`, so host credentials stay outside the VM. - + ## Permissions, limits, and timeouts @@ -110,8 +110,7 @@ Every operation inherits the VM [permission policy](/agentos/docs/permissions) a ## Embedded API -The examples above already use `AgentOs.create()`. Python contexts remain -available until the host calls `dispose()`; there is no actor sleep cycle to -recreate them after. +The examples above use `AgentOs.create()`. Python contexts remain available +until the host calls `dispose()`. Read more in the [embedded API quickstart](/agentos/docs/quickstart-embedded). diff --git a/docs/content/docs/quickstart-embedded.mdx b/docs/content/docs/quickstart-embedded.mdx index 65e9bb123b..1e24bc8fe0 100644 --- a/docs/content/docs/quickstart-embedded.mdx +++ b/docs/content/docs/quickstart-embedded.mdx @@ -4,9 +4,9 @@ description: "Run an agentOS VM directly inside a Node.js application." skill: true --- -Use `@rivet-dev/agentos-core` when your application owns VM identity, -persistence, and lifecycle. The embedded API returns a VM handle directly; -there is no actor server or generated transport client. +`@rivet-dev/agentos-core` runs VMs inside your Node.js process. Your +application owns VM identity, persistence, and lifecycle, and +`AgentOs.create()` returns a VM handle directly. ## Install @@ -18,21 +18,18 @@ npm install @rivet-dev/agentos-core -The VM remains active until `dispose()` is called. Configure SQLite when its -filesystem must survive process replacement; omit `database` for an in-memory -VM. +The VM remains active until `dispose()` is called. Its filesystem is in memory +by default; see [Persistence](/agentos/docs/persistence) to keep files across VM +recreation. -## Embedded-only capabilities +## Configuration -Embedded Core may use trusted resources that cannot cross the hosted actor -boundary: +The embedding process is trusted, so it can also give the VM access to host +resources: -- host bindings implemented as in-process callbacks; -- host-directory and callback-backed mounts; +- [host functions](/agentos/docs/host-functions) implemented as in-process callbacks; +- host-directory and other [mounts](/agentos/docs/filesystem#mounts); - local `.aospkg` paths; - explicit ownership of a shared sidecar process. - -Managed orchestration layers can wrap this same Core surface without changing -the VM runtime or its security boundary. diff --git a/docs/content/docs/quickstart.mdx b/docs/content/docs/quickstart.mdx index eea0a98505..1d8d8e71d0 100644 --- a/docs/content/docs/quickstart.mdx +++ b/docs/content/docs/quickstart.mdx @@ -14,11 +14,22 @@ npm install @rivet-dev/agentos-core -The VM remains active until `dispose()` is called. Omit `database` for an -ephemeral VM, or configure SQLite-backed storage when state must survive -process replacement. +The VM remains active until `dispose()` is called. Its filesystem is in memory +by default; see [Persistence](/agentos/docs/persistence) to keep files across VM +recreation. -Core runs in a trusted host process and can use host bindings, host-backed -mounts, local packages, and remote verified package URLs. +Core runs in a trusted host process and can use +[host functions](/agentos/docs/host-functions), host-backed +[mounts](/agentos/docs/filesystem#mounts), local packages, and remote verified +package URLs. + +## Next steps + +- Run commands with [Bash](/agentos/docs/bash), or code with + [Node.js](/agentos/docs/javascript) and [Python](/agentos/docs/python). +- Install tools from the [registry](/agentos/docs/registry) with + [Software](/agentos/docs/software). +- Lock the VM down with [Permissions](/agentos/docs/permissions) and + [Resource Limits](/agentos/docs/resource-limits). diff --git a/docs/content/docs/resource-limits.mdx b/docs/content/docs/resource-limits.mdx index 9cf2ac0432..f69e38198c 100644 --- a/docs/content/docs/resource-limits.mdx +++ b/docs/content/docs/resource-limits.mdx @@ -3,8 +3,8 @@ title: "Resource Limits" description: "Configure bounded VM, process, JavaScript, Python, and WASM resources." --- -Limits are part of the durable actor config and are applied by the sidecar -during VM creation. +Pass `limits` when creating the VM. The sidecar applies them during VM creation, +and `onLimitWarning` reports near-limit pressure to the embedding process. @@ -12,44 +12,40 @@ Use `limits.tls.maxBufferedBytes` to bound TLS plaintext buffering. `limits.execution.completedTtlMs` and `limits.execution.maxCompletedExecutions` bound retained completed language executions. `limits.execution.liveExecutionWarningThreshold` sets the live execution count that triggers a warning, not a rejection. -These fields are optional in both Rust and TypeScript Core; omission keeps the -sidecar default. +These fields are optional; omission keeps the sidecar default. -Process and terminal output replay is also sidecar-owned. The defaults retain +Process output replay is also sidecar-owned. The defaults retain up to 1,024 events or 1 MiB for each opted-in process, return at most 256 events or 768 KiB per read, and retain at most 1,024 replay records per VM. Override them with `limits.process.outputReplayEvents`, `outputReplayBytes`, `outputReplayPageEvents`, `outputReplayPageBytes`, and `maxOutputReplays`. -Page limits cannot exceed their corresponding retention limits. Omitting an -ordinary-process or terminal page bound uses that VM's configured limit; -explicit zero or a value above the limit is rejected. Raising a VM page limit -does not require a matching client constant change. +Page limits cannot exceed their corresponding retention limits. Omitting a page +bound in `process.readOutput` uses the VM's configured limit; explicit zero or a +value above the limit is rejected. Every queue, buffer, timeout, and resource collection has a bounded default. -Near-capacity conditions emit `vm.limitWarning`; exhausted limits fail -with a typed error naming the limit and the field used to raise it. +Near-capacity conditions call `onLimitWarning`; exhausted limits fail with a +typed error naming the limit and the field used to raise it. `limits.reactor.shutdownDeadlineMs` bounds the SQLite close wait and subsequent resource drain together. Process termination and filesystem flushing happen before this budget. If close exceeds the deadline, VM shutdown reports a typed `timeout` with the limit name instead of reporting -a clean stop. Close completion can no longer be confirmed, so the generation -stays quarantined—even if local resource counts reach zero—until the sidecar -worker restarts. Quarantined generations count toward VM admission capacity. -The hosted actor also has a fixed ten-second shutdown wait; raising the sidecar -deadline does not extend it. The runtime test API has a separate one-second -disposal wait. Exceeding either client wait reports a timeout, not confirmed -shutdown; the remote operation may still be running. +a clean stop. Close completion can no longer be confirmed, so the VM stays +quarantined until the sidecar process restarts, even if local resource counts +reach zero. Quarantined VMs count toward VM admission capacity. -The hosted action envelope, filesystem transfer, process replay, network, and -software-cache ceilings are listed in -[Hosted Actor Limits](/agentos/docs/actor-limits). +Limits are fixed for the life of a VM. To change them, dispose the VM and +create a new one with the new limits. -Replacing limits normally requires a runtime generation replacement. Graceful -process draining during replacement is a separately tracked follow-up. +## Sidecar limits + +VMs share a sidecar process by default. Limits that apply to the whole +process, such as `runtime.executor.maxActiveVms`, are set when creating a +dedicated sidecar with `AgentOs.createSidecar()`, as in the example above. ## Embedded API -Embedded Core uses the same sidecar and VM resource policies without the actor -message envelope. Read more in the +Limits apply to VMs created with `AgentOs.create()` and sidecars created with +`AgentOs.createSidecar()`. Read more in the [embedded API quickstart](/agentos/docs/quickstart-embedded). diff --git a/docs/content/docs/sandboxes.mdx b/docs/content/docs/sandboxes.mdx index ecb0251da0..41f3e941fc 100644 --- a/docs/content/docs/sandboxes.mdx +++ b/docs/content/docs/sandboxes.mdx @@ -1,14 +1,20 @@ --- -title: "External VMs" -description: "Attach an external VM provider through trusted embedded Core." +title: "External Sandboxes" +description: "Mount a full external sandbox into an agentOS VM for workloads that need a real Linux kernel." --- -External VM providers expose a trusted host-backed filesystem and process -bridge. They are supported by `@rivet-dev/agentos-core`, not by the hosted -actor. +When a workload needs a full Linux environment, attach an external sandbox to +the VM with the `sandbox` option. agentOS mounts the sandbox filesystem into +the VM at `/mnt/sandbox` and registers `agentos-sandbox` host functions for +running commands and listing processes in the sandbox. - + -The fixed actor rejects host mounts and provider clients because they would -cross its security boundary. Use its whitelisted filesystem registry for hosted -storage. +Guest code reads and writes sandbox files the same way it reads local files. +Providers live in `@rivet-dev/agentos-sandbox`. + +The sandbox bridge runs through trusted host callbacks in the embedding +process, so credentials for the sandbox provider stay on the host. + +See [agentOS vs Sandbox](/agentos/docs/versus-sandbox) to decide when a +workload needs one. diff --git a/docs/content/docs/security-model.mdx b/docs/content/docs/security-model.mdx index 9dfb699605..b000203f98 100644 --- a/docs/content/docs/security-model.mdx +++ b/docs/content/docs/security-model.mdx @@ -8,7 +8,7 @@ skill: true agentOS is in beta and is still undergoing security review. -agentOS runs untrusted guest code on behalf of a trusted client. Each VM has a +agentOS runs untrusted guest code on behalf of a trusted host application. Each VM has a kernel-owned filesystem, process table, socket table, pipes, PTYs, permission policy, and language runtimes. Guest JavaScript executes in V8; WASM and Python use their managed executors. Guest operations reach the host only through the @@ -18,29 +18,26 @@ sidecar's policy-enforcing interfaces. | Component | Trust | Responsibility | | --- | --- | --- | -| Client | Trusted, except for submitted guest code and data | Chooses configuration, permissions, software URLs, and credentials | +| Host application | Trusted, except for submitted guest code and data | Chooses configuration, permissions, software sources, host functions, mounts, and credentials | | Sidecar, kernel, and VFS | Trusted enforcement point | Applies limits and permissions; owns processes, filesystems, sockets, and mounts | | Executor | Untrusted | Runs guest JavaScript, Python, WASM, and third-party code | The security boundary is **sidecar/runtime to executor**. A guest bypassing an applied permission, mount boundary, egress rule, or resource limit is in scope. -A trusted client deliberately choosing a permissive configuration or unsafe +A trusted host deliberately choosing a permissive configuration or unsafe credential is not a runtime escape. -## Hosted actor restrictions +## Host capabilities -The static hosted actor accepts only serializable configuration. Its filesystem -descriptors are resolved through a fixed whitelist, and software installation -accepts only remote HTTPS `.aospkg` sources. It cannot receive: +Core runs inside the trusted application process, so the host may explicitly +give a VM access to host resources: host functions, host-backed mounts, external +sandboxes, and local software paths. Each of these runs with the host's +privileges. Expose them deliberately, and never pass guest-controlled values +into their configuration. -- a host filesystem path or host-directory mount; -- a binding callback, closure, or arbitrary host object; -- a custom filesystem or software resolver; -- an unregistered actor implementation or lifecycle hook. - -Embedded Core runs inside a trusted application process and may explicitly -configure host bindings, host-backed mounts, and local software paths. Those are -embedded capabilities, not actor capabilities. +If your application lets untrusted users configure VMs, accept only +serializable configuration from them and resolve it against your own allowlist +of mounts and software sources. ## Isolation guarantees @@ -48,7 +45,7 @@ embedded capabilities, not actor capabilities. - Guest sockets pass through the kernel socket table and applied network policy. - Trusted VM setup can update mounts and command stubs without relaxing guest filesystem permissions, including while a VM is running. -- Each actor's filesystem and runtime state are isolated from other actors. +- Each VM's filesystem, process table, and runtime state are isolated from other VMs. - Host-backed embedded mounts are confined to the configured root on every operation, including symlink and `..` resolution. - Resource limits bound process count, descriptors, sockets, filesystem bytes, @@ -66,9 +63,9 @@ can be `"allow"`, `"deny"`, or a rule set that matches the operation and target. ## Operator responsibilities -- Authenticate and authorize clients before they receive an actor connection. -- Keep object-store credentials and operator package-download settings outside - actor-readable durable configuration. +- Authenticate and authorize users before they can reach a VM. +- Keep object-store credentials and package-download settings out of + configuration that guest code or untrusted users can read. - Choose permissions and resource limits for the workload. - Treat software URLs and config as trusted control-plane input, while treating the downloaded bytes and executed code as hostile. diff --git a/docs/content/docs/software.mdx b/docs/content/docs/software.mdx index a82ffd6362..84963b494e 100644 --- a/docs/content/docs/software.mdx +++ b/docs/content/docs/software.mdx @@ -1,49 +1,62 @@ --- title: "Software" -description: "Install immutable .aospkg command packages." +description: "Install immutable .aospkg command packages at boot or while a VM is running." --- -The hosted actor accepts a remote HTTPS package source and optional exact -`sha256:<64 lowercase hex>` digest. +agentOS software ships as immutable `.aospkg` packages containing real compiled +tools. Installed packages are projected read-only under +`/opt/agentos/pkgs//`, and their commands are linked under +`/opt/agentos/bin`. Every VM includes a default bundle with `sh`, coreutils, and +the standard CLI tools programs rely on. Pass `defaultSoftware: false` for a bare +VM with only the software you list. + +Browse the available packages in the [registry](/agentos/docs/registry). + +## Install at boot + +Pass packages in `software` when creating the VM. Each entry is a +`{ packagePath }` reference to a packed `.aospkg` file, which is what the +`@agentos-software/*` packages in the agentOS repository export. -`software.list()` reads the live installed set. `software.uninstall` removes -one package by its digest-derived `packageId`. Desired package sources live in -`config.get`, not `software.list`. If the VM is unavailable, `software.list` -returns `not_ready`; the desired sources remain readable from config. +## Install while running + +`software.install` adds a package to a running VM. The source is either a remote +HTTPS URL or a trusted local path, each with an optional exact +`sha256:<64 lowercase hex>` digest. New commands are available on +`/opt/agentos/bin` as soon as the call returns. + + + +- `software.install(source)` returns the installed package's `packageId`, + `digest`, `sizeBytes`, `packageName`, `version`, and `commands`. +- `software.uninstall(packageId)` removes a package installed through + `software.install` and its command links. +- `software.installed()` lists packages installed through `software.install`. +- `software.list()` reports every package that provides commands in the VM, + including boot-time and default software. -Software changes apply live when the VM is ready and its desired configuration -is fully applied. Otherwise they update desired configuration and report -`restart_required`; call `vm.restart({})` to apply it. Uninstalling a desired -package does not require a working VM or a fresh download. +Only one software or mount mutation runs at a time per VM. A concurrent call +fails with `invalid_state` instead of queueing. -Both mutations return `{ source, software, config }`. `source` identifies the -configured package, and `config` reports the new revision and state. `software` -contains resolved or installed metadata, or is `null` when uninstall changes -only desired configuration. Installed metadata reports package bytes as -`sizeBytes`; a resolved source in `config.get` uses the same field. +## Verification and caching -Packages are cached per process by content digest. URL-to-digest observations -may avoid a repeat download, but the exact bytes are always verified before -projection. +The sidecar downloads a URL source under strict size, time, and redirect +limits, verifies the bytes against the expected digest when one is given, and +validates the package format before projecting it. The verified content digest +is the package identity. -The central preload coordinator supplies a low-frequency popularity hint so a -worker can warm likely packages before its first VM needs them. Required -packages still determine readiness. Advisory preload timeout or coordinator -failure only reduces cache hit rate. See -[Software Installation and Preloading](/agentos/docs/architecture/software-cache). +Packages are cached per sidecar process by content digest, so VMs that share a +sidecar reuse one verified copy. Repeat installs of the same bytes skip the +download, and the bytes are always verified before projection. Registry software is not installed from npm. Releases upload digest-addressed -`.aospkg` files and a relative-URL manifest to S3-compatible object storage. -Embedded Core can additionally install a trusted local artifact path. In -TypeScript, `software.install(source)` and `software.uninstall(packageId)` use -the same sidecar verification and projection path as Rust's `install_software` -and `uninstall_software`. `software.installed()` in TypeScript (or -`installed_software()` in Rust) returns packages installed through that API. -Legacy `software.link` remains a separate trusted-directory operation. +`.aospkg` files and a manifest to object storage; see +[Publishing Software](/agentos/docs/custom-software/publishing). ## Embedded API -Embedded Core accepts trusted local package paths as well as URLs. Read more in -the [embedded API quickstart](/agentos/docs/quickstart-embedded). +The embedding process is trusted, so `software.install` accepts local package +paths as well as URLs. Read more in the +[embedded API quickstart](/agentos/docs/quickstart-embedded). diff --git a/docs/content/docs/versus-sandbox.mdx b/docs/content/docs/versus-sandbox.mdx index eac8566bbc..c195a5ca22 100644 --- a/docs/content/docs/versus-sandbox.mdx +++ b/docs/content/docs/versus-sandbox.mdx @@ -6,9 +6,8 @@ skill: true - **agentOS** is a lightweight VM that runs inside your process. Near-zero cold start, low memory, and direct backend integration via [host functions](/agentos/docs/host-functions). - **Sandboxes** are full Linux environments with root access, system packages, and native binary support. -- **Embedded applications can compose both.** Embedded Core can register a full - sandbox through trusted host functions and mounts. The static hosted actor does - not expose those host capabilities. +- **You can compose both.** agentOS can mount a full sandbox into the VM through + trusted host functions and mounts. ## Comparison @@ -17,9 +16,9 @@ skill: true | **Cost** | Very low. Runs in your process. | Pay per second of uptime. | | **Startup** | Near-zero cold start (~6 ms). | Seconds to spin up. | | **Backend integration** | Direct. [Host functions](/agentos/docs/host-functions) call your functions with zero latency. | Indirect. Requires network calls back to your backend. | -| **Credentials** | Embedded [host functions](/agentos/docs/host-functions) keep credentials on the host. Hosted VMs use serializable config. | Must be injected into the sandbox environment. | +| **Credentials** | [Host functions](/agentos/docs/host-functions) keep credentials on the host. | Must be injected into the sandbox environment. | | **Permissions** | Granular, deny-by-default. | Coarse-grained (container-level). | -| **Infrastructure** | Static actor binary or embedded Core | Vendor account + API keys. | +| **Infrastructure** | An npm package in your backend. | Vendor account + API keys. | | **Best for** | Coding, file manipulation, scripting, API calls, orchestration. | Browsers, desktop automation, native compilation, dev servers. | ## When to use each @@ -45,13 +44,10 @@ Spin up a sandbox when the workload needs a real Linux kernel: ### Both together -Embedded applications can use agentOS with -[external sandboxes](/agentos/docs/sandboxes) when a workflow needs both: +Use agentOS with [external sandboxes](/agentos/docs/sandboxes) when a workflow +needs both: - Guest code runs in the agentOS VM with access to configured bindings and permissions - Sandbox spins up on demand for heavy tasks - Sandbox filesystem is mounted into the VM as a native directory - Guest code reads and writes sandbox files the same way it reads local files - -This composition requires trusted in-process callbacks and is therefore not a -hosted actor feature. diff --git a/docs/content/use-cases/index.mdx b/docs/content/use-cases/index.mdx index 4b516df075..71e714bcce 100644 --- a/docs/content/use-cases/index.mdx +++ b/docs/content/use-cases/index.mdx @@ -22,7 +22,7 @@ Purpose-built for agents that write, test, and deploy code autonomously. Run long-lived commands asynchronously and observe their bounded output. -- Durable files survive actor sleep and restart +- Durable files survive VM recreation - Spawn and signal independent guest processes - Recover bounded output by sequence cursor - Monitor progress in real-time @@ -58,7 +58,7 @@ generates reports on a schedule. Use agentOS as the execution layer beneath an external workflow system. - Isolated command execution -- Durable files and configuration +- Durable files - Scheduled command runs - Typed failures and resource limits diff --git a/docs/sidebar.json b/docs/sidebar.json index a32fd8181c..179d28019c 100644 --- a/docs/sidebar.json +++ b/docs/sidebar.json @@ -69,7 +69,7 @@ "icon": "faTerminal" }, { - "title": "Networking & Previews", + "title": "Networking", "href": "/agentos/docs/networking", "icon": "faGlobe" }, @@ -195,7 +195,7 @@ "href": "/agentos/docs/architecture/limits-and-observability" }, { - "title": "Persistence & Sleep", + "title": "Persistence", "href": "/agentos/docs/persistence" } ] diff --git a/examples/embedded/config-reference.ts b/examples/embedded/config-reference.ts index 6e1978942f..0ee1bbc6dc 100644 --- a/examples/embedded/config-reference.ts +++ b/examples/embedded/config-reference.ts @@ -1,20 +1,25 @@ import { AgentOs, nodeModulesMount } from "@rivet-dev/agentos-core"; -// Common embedded AgentOs.create() configuration. Embedded Core additionally -// permits trusted host resources that the hosted actor intentionally forbids. +// Common AgentOs.create() configuration. The embedding process is trusted, so +// it may also configure host resources such as host directories and local +// package paths. const vm = await AgentOs.create({ - // Durable SQLite storage for the root filesystem and Core state. - // Omit it for an in-memory VM that keeps nothing after dispose(). + // SQLite storage for VM-owned state. On its own it does not make the root + // filesystem durable; see /agentos/docs/persistence. database: { type: "sqlite_file", path: ".agentos/agentos.sqlite" }, + // Store the root filesystem in `database` so files survive VM recreation. + // Omit it for the default in-memory root, discarded on dispose(). + rootFilesystem: { + type: "native", + plugin: { id: "chunked_sqlite", config: {} }, + }, // Filesystems to mount at boot. Use nodeModulesMount() to expose a host // node_modules tree at /root/node_modules. mounts: [nodeModulesMount("/path/to/project/node_modules")], // Kernel permission policy (see /agentos/docs/permissions) and runtime caps. - // Embedded Core may also configure host bindings. permissions: { network: "allow" }, limits: { jsRuntime: { v8HeapLimitMb: 128 } }, - // Trusted embedded callers may install a local .aospkg path. The hosted actor - // accepts only remote HTTPS sources. + // Install a packed .aospkg from a trusted local path at boot. software: [{ packagePath: "/path/to/tool.aospkg" }], // Also install the default software bundle (sh + coreutils). Defaults to true; // set false for a bare VM with only the software you list. diff --git a/examples/embedded/host-functions-bash.ts b/examples/embedded/host-functions-bash.ts new file mode 100644 index 0000000000..25ca01e171 --- /dev/null +++ b/examples/embedded/host-functions-bash.ts @@ -0,0 +1,33 @@ +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(); +} diff --git a/examples/embedded/host-functions-javascript.ts b/examples/embedded/host-functions-javascript.ts new file mode 100644 index 0000000000..71cbe9e42a --- /dev/null +++ b/examples/embedded/host-functions-javascript.ts @@ -0,0 +1,36 @@ +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 { + // Guest JavaScript gets each collection as a global, and each host function + // as an async function. + const result = await vm.javascript.execute( + ` + const forecast = await weather.forecast({ city: "Paris" }); + console.log(forecast); + `, + { output: { capture: "all" } }, + ); + console.log(result.outcome === "succeeded" ? result.stdout : result.error); +} finally { + await vm.dispose(); +} diff --git a/examples/embedded/host-functions-python.ts b/examples/embedded/host-functions-python.ts new file mode 100644 index 0000000000..025f050f45 --- /dev/null +++ b/examples/embedded/host-functions-python.ts @@ -0,0 +1,43 @@ +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 { + // Python calls the `agentos-weather` command like any other program. + const result = await vm.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 vm.dispose(); +} diff --git a/examples/embedded/host-functions.ts b/examples/embedded/host-functions.ts index 858994d7d2..97c00ea954 100644 --- a/examples/embedded/host-functions.ts +++ b/examples/embedded/host-functions.ts @@ -1,9 +1,8 @@ import { AgentOs } from "@rivet-dev/agentos-core"; import { z } from "zod"; -// Host functions are defined exactly as they are for the actor. Pass them to -// AgentOs.create() and `execute` runs in this host process, with its input typed -// by its own schema. +// Pass host functions to AgentOs.create(). `execute` runs in this host process, +// with its input typed by its own schema. const vm = await AgentOs.create({ hostFunctions: { weather: { diff --git a/examples/embedded/mounts.ts b/examples/embedded/mounts.ts index 69fd96a252..5cbeb50ecf 100644 --- a/examples/embedded/mounts.ts +++ b/examples/embedded/mounts.ts @@ -1,8 +1,8 @@ import { AgentOs } from "@rivet-dev/agentos-core"; -// Embedded applications configure filesystem backends at boot. Native mount plugins (host -// directories, S3, etc.) are passed via `plugin`, each identified by an `id` -// and a `config` object. +// Configure filesystem backends at boot. Native mount plugins (host +// directories, S3, Google Drive, etc.) are passed via `plugin`, each identified +// by an `id` and a `config` object. const vm = await AgentOs.create({ mounts: [ // Host directory (read-only) @@ -14,7 +14,12 @@ const vm = await AgentOs.create({ // S3 bucket { path: "/mnt/data", - plugin: { id: "s3", config: { bucket: "my-bucket", prefix: "agent/" } }, + plugin: { + id: "chunked_s3", + config: { bucket: "my-bucket", prefix: "agent/", region: "us-east-1" }, + }, }, ], }); + +await vm.dispose(); diff --git a/examples/embedded/persistence.ts b/examples/embedded/persistence.ts index 0ae2980625..934e14278f 100644 --- a/examples/embedded/persistence.ts +++ b/examples/embedded/persistence.ts @@ -4,10 +4,16 @@ import { AgentOs, type AgentOsOptions } from "@rivet-dev/agentos-core"; mkdirSync(".agentos", { recursive: true }); const options = { + // The SQLite file that stores the VM's durable state. database: { type: "sqlite_file", path: resolve(".agentos/agentos.sqlite"), }, + // Store the root filesystem in that database instead of in memory. + rootFilesystem: { + type: "native", + plugin: { id: "chunked_sqlite", config: {} }, + }, } satisfies AgentOsOptions; let vm = await AgentOs.create(options); diff --git a/examples/embedded/software-install.ts b/examples/embedded/software-install.ts new file mode 100644 index 0000000000..81cb05f362 --- /dev/null +++ b/examples/embedded/software-install.ts @@ -0,0 +1,27 @@ +import { AgentOs } from "@rivet-dev/agentos-core"; + +const vm = await AgentOs.create(); + +try { + // Install a package while the VM is running. The bytes are verified against + // the digest before the package is projected into /opt/agentos. + const installed = await vm.software.install({ + type: "url", + url: "https://packages.example.com/ripgrep.aospkg", + expectedDigest: "sha256:<64 lowercase hex characters>", + }); + console.log(installed.packageName, installed.version, installed.commands); + + const result = await vm.process.exec("rg --version", { + output: { capture: "all" }, + }); + console.log(result.stdout); + + // Packages installed through this API, and every command the VM provides. + console.log(vm.software.installed()); + console.log(await vm.software.list()); + + await vm.software.uninstall(installed.packageId); +} finally { + await vm.dispose(); +} diff --git a/examples/embedded/vm.ts b/examples/embedded/vm.ts index 56a8b82d2c..c9c71e897e 100644 --- a/examples/embedded/vm.ts +++ b/examples/embedded/vm.ts @@ -1,17 +1,8 @@ // docs:start boot -import { mkdirSync } from "node:fs"; -import { resolve } from "node:path"; import { AgentOs } from "@rivet-dev/agentos-core"; -// Embed a VM with the agentOS package. There is no actor runtime or -// client/server split. `AgentOs.create()` returns a handle to the VM. -mkdirSync(".agentos", { recursive: true }); -const vm = await AgentOs.create({ - database: { - type: "sqlite_file", - path: resolve(".agentos/agentos.sqlite"), - }, -}); +// `AgentOs.create()` boots a VM inside this process and returns a handle to it. +const vm = await AgentOs.create(); const result = await vm.process.exec("echo hello"); console.log(result.stdout); // "hello\n" From 7493d437ea5264fc2220c5abc6e67856f3b8eddb Mon Sep 17 00:00:00 2001 From: Sree Narayanan Date: Mon, 5 Oct 2026 16:37:23 +0000 Subject: [PATCH 02/18] docs(agentos): document the hosted VM v1 API Add Hosted VMs and Hosted VM API pages, a software installation and preloading architecture page, and a Hosted API section on each capability page. Sources: the agentOS 0.4 launch post and the services PRs that add the agentOS actor (#21) and group its actions under v1 (#20). Amp-Thread-ID: https://ampcode.com/threads/T-c7d08b58-92c1-4fc0-abb9-7a15f4504a4f Co-authored-by: Amp --- docs/content/docs/actor-api.mdx | 167 ++++++++++ .../docs/architecture/software-cache.mdx | 63 ++++ docs/content/docs/bash.mdx | 16 + docs/content/docs/cron.mdx | 28 +- docs/content/docs/filesystem.mdx | 53 ++++ docs/content/docs/host-functions.mdx | 6 + docs/content/docs/hosted.mdx | 300 ++++++++++++++++++ docs/content/docs/index.mdx | 17 +- docs/content/docs/javascript.mdx | 15 + docs/content/docs/networking.mdx | 35 +- docs/content/docs/permissions.mdx | 6 + docs/content/docs/persistence.mdx | 12 + docs/content/docs/processes.mdx | 51 +++ docs/content/docs/python.mdx | 12 + docs/content/docs/quickstart.mdx | 3 + docs/content/docs/resource-limits.mdx | 23 ++ docs/content/docs/sandboxes.mdx | 5 + docs/content/docs/security-model.mdx | 14 + docs/content/docs/software.mdx | 38 +++ docs/content/docs/versus-sandbox.mdx | 2 +- docs/sidebar.json | 21 +- 21 files changed, 877 insertions(+), 10 deletions(-) create mode 100644 docs/content/docs/actor-api.mdx create mode 100644 docs/content/docs/architecture/software-cache.mdx create mode 100644 docs/content/docs/hosted.mdx diff --git a/docs/content/docs/actor-api.mdx b/docs/content/docs/actor-api.mdx new file mode 100644 index 0000000000..ba2c91083e --- /dev/null +++ b/docs/content/docs/actor-api.mdx @@ -0,0 +1,167 @@ +--- +title: "Hosted VM API" +description: "Configure and operate a hosted agentOS VM through a typed RivetKit actor handle or the HTTP gateway." +--- + +The hosted `agentOS` actor is a fixed Rust actor. Use a normal RivetKit client +with the generated `AgentOsRegistry` contract. The `createAgentOsClient` +helper only supplies that type parameter and does not add another transport or +API layer. See [Hosted VMs](/agentos/docs/hosted) to get started. + +Creation input is considered only when RivetKit creates the actor. Calling +`getOrCreate` with different creation input for an existing key does not +reconfigure it. Use the config actions for later changes. + +## Configuration + +`v1.config.get` returns the durable desired config and its revision. +`v1.config.set` replaces the complete config. An omitted field selects its +default. `v1.config.patch` applies an RFC 7386 JSON Merge Patch to the current +desired config. + +Merge Patch recursively merges objects and replaces arrays as complete values. +Setting a property to `null` removes the override, which selects its default. +Both mutation actions accept `expectedRevision` for compare-and-set updates. +A stale revision fails without changing durable state. + +```ts TypeScript @nocheck +import type { AgentOsActorCreateInput } from "@rivet-dev/agentos"; + +const createInput: AgentOsActorCreateInput = { + config: { + environment: { CI: "true" }, + preview: { defaultTtlMs: 30_000 }, + filesystem: { + root: { type: "durable" }, + mounts: [{ path: "/data", backend: { type: "durable" } }], + }, + software: [{ url: "https://packages.example/tool.aospkg" }], + }, +}; + +const vm = client.agentOS.getOrCreate(["my-vm"], { + createWithInput: createInput, +}); + +await vm.v1.config.set({ + config: { limits: { tls: { maxBufferedBytes: 2048 } } }, +}); +``` + +Core owns VM defaults and validation; the generated client does not copy those +defaults. Hosted-only policy, including preview leases and the filesystem +registry, is normalized by the actor. + +When a VM is running, the actor asks that VM's sidecar to compare the resolved +creation policies for supported runtime fields. Setting an explicit value equal +to the active default does not by itself require a restart. Software sources, +hosted filesystem policy, and any future fields not explicitly covered by that +comparison remain replacement-triggering. + +## VM lifecycle + +`v1.vm.status` reports the active generation, applied config revision, package +readiness, lifecycle state, and recent issues. A desired config can be durable +while the active generation reports that a restart is required. + +`v1.vm.restart` stops the active VM, waits for bounded shutdown, and starts a +new generation from the latest desired config. Process, terminal, context, and +network stream handles include their generation. Using an old handle after a +restart fails as `stale_generation`. + +Lifecycle notifications use the `vm.booted`, `vm.shutdown`, and +`vm.limitWarning` events. `vm.shutdown` carries a `reason` of `"sleep"`, +`"destroy"`, or `"error"`. + +## Pull APIs and events + +Live events reduce latency, but bounded pull actions are the recovery path +after a disconnect or inspector reload. Event names are not versioned. + +| Resource | Pull action | Events | +| --- | --- | --- | +| Process | `v1.process.output.read`, `v1.process.wait` | `process.output`, `process.exit` | +| Terminal | `v1.terminal.output.read`, `v1.terminal.wait` | `terminal.output`, `terminal.exit` | +| HTTP response | `v1.network.fetchStream.read` | None | +| VM | `v1.vm.status` | `vm.booted`, `vm.shutdown`, `vm.limitWarning` | +| Cron | `v1.cron.list` | `cron.fired` | + +Process and terminal replay use a generation-scoped sequence cursor. Request +limits bound both the number of events and their aggregate bytes. Network +response streams are pull-based so each action is bounded and the caller owns +backpressure. + +## Errors + +| Code | Cause | +| --- | --- | +| `stale_generation` | A process, terminal, context, or stream handle from before a `v1.vm.restart` was used. | +| `expired` | A fetch-stream handle expired. | +| `timeout` | A required package-resolution deadline passed. | +| `not_found` | A closed terminal's replay was evicted from the bounded retention window. | + +Final terminal output and its confirmed exit remain readable after exit within +the bounded closed-terminal retention window. Events are hints; use replay to +recover missed output. + +## Actions + +Every public action is versioned under `v1`. In TypeScript, call +`vm.v1..(input)`. Over HTTP, call +`POST /gateway/{actor_id}/action/v1..` with +`{ "args": [input] }`. + +| Group | Actions | +| --- | --- | +| `config` | `get`, `set`, `patch` | +| `vm` | `status`, `restart` | +| `filesystem` | `readFile`, `writeFile`, `readFiles`, `writeFiles`, `stat`, `exists`, `mkdir`, `readdir`, `readdirEntries`, `readdirRecursive`, `move`, `remove`, `listMounts`, `export` | +| `process` | `run`, `spawn`, `get`, `list`, `tree`, `wait`, `signal`, `stdin.write`, `stdin.close`, `pty.resize`, `output.read` | +| `terminal` | `open`, `list`, `stdin.write`, `pty.resize`, `output.read`, `wait`, `close` | +| `network` | `fetch`, `fetchStream.start`, `fetchStream.read`, `fetchStream.cancel`, `preview.create`, `preview.expire` | +| `software` | `install`, `uninstall`, `list` | +| `cron` | `schedule`, `list`, `cancel` | +| `contexts` | `create`, `get`, `list`, `reset`, `delete` | +| `javascript` | `execute`, `evaluate`, `executeFile`, `spawn`, `spawnFile`, `npm.install`, `npm.runScript`, `npm.runPackage` | +| `typescript` | `execute`, `evaluate`, `executeFile`, `spawn`, `spawnFile`, `check`, `checkProject` | +| `python` | `execute`, `evaluate`, `executeFile`, `executeModule`, `spawn`, `spawnFile`, `spawnModule`, `install` | + +The hosted contract has no agent sessions and no shell-string `process.exec`. +Private cron and maintenance actions are not part of the client. + +## Contract compatibility + +The package exports `AGENTOS_API_VERSION`, `AGENTOS_CONTRACT_MAJOR`, +`AGENTOS_CONTRACT_HASH`, and `AGENTOS_CAPABILITIES`. Rust DTOs and the action +registry generate the TypeScript surface. The actor and client package ship in +lockstep during the alpha contract period. + +The contract is currently `agentos-sdk.dev/v1alpha1`, major `1`. The hash covers +the canonical public actions, events, DTOs, and capabilities. Private cron and +maintenance actions are excluded from both the generated client and hash. + +## Inspector + +The Inspector is a client of the same public contract. Its filesystem, +process, terminal, software, preview, config, status, and limit views do not +receive private actor access. The UI can read desired configuration and VM +status, restart the VM, and show runtime issues, but it cannot edit host +mounts. + +Published Linux services binaries and the services container include the built +tabs beside the executable, so they are registered automatically. A source or +custom deployment can point the service at another built copy: + +```sh +AGENTOS_INSPECTOR_TABS_DIR=node_modules/@rivet-dev/agentos/assets/inspector-tabs-app \ + rivet-services start +``` + +Startup fails if an explicit `AGENTOS_INSPECTOR_TABS_DIR` does not contain +`index.html`. + +## Embedded API + +Embedded Core exposes the same VM capabilities without RivetKit actor +lifecycle, durable actor config, previews, or hosted URL-only policy. Read more +in the [embedded API quickstart](/agentos/docs/quickstart-embedded). diff --git a/docs/content/docs/architecture/software-cache.mdx b/docs/content/docs/architecture/software-cache.mdx new file mode 100644 index 0000000000..453eedb24a --- /dev/null +++ b/docs/content/docs/architecture/software-cache.mdx @@ -0,0 +1,63 @@ +--- +title: "Software Installation and Preloading" +description: "Understand URL package installation, process caching, readiness, and preload coordination for hosted agentOS VMs." +--- + +Each VM projects installed packages as read-only filesystem leaves. The +projection is bounded by `config.limits.agentosPackages.maxMounts` (4,096 by +default), including command aliases and `provides.files` directory mounts. +Installing another package can fail with a resource-limit error even when the +download succeeded; the existing VM projection remains in place. + +Hosted VMs install immutable `.aospkg` files from HTTPS URLs. Embedded Core +also accepts a trusted local file path. A hosted VM never receives or +materializes a host path. + +## Resolution and projection + +The resolver validates the URL, blocks ambient credentials and private network +destinations, bounds redirects and response headers, downloads into a temporary +file, verifies the optional SHA-256 digest, and validates the package format. +The verified content digest becomes the package identity. + +The sidecar projects installed packages under +`/opt/agentos/pkgs//` and command links under +`/opt/agentos/bin`. `v1.software.list` reads the current installed set. +`v1.software.install` and `v1.software.uninstall` are durable config mutations, +so they use the same revision checks and reconciliation as `v1.config.patch`. + +## Process cache + +All VMs in one worker process share the child sidecar's content-addressed +cache. Concurrent requests for the same digest are coalesced into one +acquisition. The cache is bounded by bytes, entries, active downloads, pending +downloads, and acquisition time. Live VM generations pin their package files. +Unpinned entries are evicted in least-recently-used order. + +By default, each worker gets an isolated process-lifetime temporary cache. For +persistent recovery, set a distinct `AGENTOS_PACKAGE_CACHE_DIR` for each +concurrently running worker. An OS lock prevents startup recovery or eviction +from removing another worker's live package files. Restarting a worker with the +same directory recovers verified packages. + +The services worker removes its temporary cache after normal shutdown or +startup failure. Forced termination or a host crash can leave temporary +directories behind; configure host temporary-directory cleanup for those +leftovers. Persistent directories are never removed at worker shutdown. + +Partial downloads and failed publications use temporary files that are removed +when their operation is cancelled or fails. Package source URLs are kept only +as a short-lived bounded hint. Bytes are still verified before projection. + +## Startup preloading + +The `agentOS-preload-coordinator` is a central Rivet Actor that stores a +low-frequency popularity hint. It is not an authoritative registry or cache. +Each worker process reads one bounded plan at startup, coalesces that read for +the process, and warms likely package digests with bounded concurrency. The +default process warm deadline is 10 seconds. + +Required packages still determine VM readiness. An advisory preload timeout or +coordinator failure only reduces the cache hit rate. + +See [Software](/agentos/docs/software#hosted-api) for the installation API. diff --git a/docs/content/docs/bash.mdx b/docs/content/docs/bash.mdx index c196e4c4b5..a2ca1f6f32 100644 --- a/docs/content/docs/bash.mdx +++ b/docs/content/docs/bash.mdx @@ -50,6 +50,22 @@ Every command inherits the VM [permission policy](/agentos/docs/permissions) and +## Hosted API + +Hosted VMs have no shell-string `exec`. Use `v1.process.run` with an explicit +executable and argument array, or run `sh` with `-c` for shell parsing. + +```ts TypeScript @nocheck +const result = await vm.v1.process.run({ + command: "sh", + args: ["-c", "echo hello && ls /home/agentos"], + options: { captureStdio: true }, +}); +console.log(result.stdout, result.status.exitCode); +``` + +See [Processes & Shells](/agentos/docs/processes#hosted-api). + ## Embedded API The examples above use `AgentOs.create()`. The host owns `dispose()`, and files diff --git a/docs/content/docs/cron.mdx b/docs/content/docs/cron.mdx index 0eb9d4d60e..6f0071d9f7 100644 --- a/docs/content/docs/cron.mdx +++ b/docs/content/docs/cron.mdx @@ -6,7 +6,8 @@ skill: true `vm.cron` schedules commands inside the VM. Jobs run while the embedding process and the VM are alive. Durable scheduling across process restarts is -the responsibility of the application that owns the VM. +the responsibility of the application that owns the VM, or use a +[hosted VM](#hosted-api), which stores its jobs. ## Schedule a command @@ -32,3 +33,28 @@ run, and `"queue"` waits for the previous run to finish. Pass `scheduleDriver` to `AgentOs.create()` to replace the default timer-based driver, for example with one backed by your own durable scheduler. + +## Hosted API + +Hosted VMs schedule commands with `v1.cron.*` actions. Jobs are named and +stored with the VM. + +```ts TypeScript @nocheck +await vm.v1.cron.schedule({ + name: "cleanup", + expression: "0 * * * *", + command: "rm", + args: ["-rf", "/tmp/cache"], + options: { env: {} }, +}); + +for (const job of await vm.v1.cron.list({})) { + console.log(job.name, job.expression); +} + +await vm.v1.cron.cancel({ name: "cleanup" }); +``` + +Connected clients receive a `cron.fired` event for each run. A VM holds up to +1,024 jobs, with names and expressions up to 256 bytes, and retains 32 runs per +job by default (256 maximum). diff --git a/docs/content/docs/filesystem.mdx b/docs/content/docs/filesystem.mdx index 6a9cd27f95..d96bd5d020 100644 --- a/docs/content/docs/filesystem.mdx +++ b/docs/content/docs/filesystem.mdx @@ -40,3 +40,56 @@ exposes a host `node_modules` tree read-only at `/root/node_modules`. File transfers are bounded. An operation that exceeds a payload limit fails with a typed limit error that names the limit. + +## Hosted API + +Hosted VMs expose the same operations as `v1.filesystem.*` actions. Byte counts +in `v1.filesystem.stat` and `v1.filesystem.readdirRecursive` are reported as +`sizeBytes`. + +```ts TypeScript @nocheck +await vm.v1.filesystem.writeFile({ + path: "/home/agentos/hello.txt", + content: "Hello, world!", +}); +const content = await vm.v1.filesystem.readFile({ + path: "/home/agentos/hello.txt", +}); +console.log(new TextDecoder().decode(content)); + +// Batch writes create parent directories automatically. +await vm.v1.filesystem.writeFiles({ + entries: [ + { path: "/home/agentos/src/index.ts", content: "console.log('hello');" }, + ], +}); + +const tree = await vm.v1.filesystem.readdirRecursive({ + path: "/home/agentos", + exclude: [], +}); +for (const entry of tree) console.log(entry.type, entry.path, entry.sizeBytes); +``` + +A hosted VM's filesystem configuration is a closed registry. By default, each +VM uses an ephemeral in-memory overlay over the bundled base image. Its writes +survive for that VM's lifetime but are discarded when the VM is replaced. Use a +`durable` root or mount in the VM's config when files must survive VM +replacement: + +```ts TypeScript @nocheck +const vm = client.agentOS.getOrCreate(["my-vm"], { + createWithInput: { + config: { + filesystem: { + root: { type: "durable" }, + mounts: [{ path: "/data", backend: { type: "durable" } }], + }, + }, + }, +}); +``` + +Unknown backends, JavaScript VFS objects, callbacks, and host paths are +rejected, so host-directory and other host-backed mounts are available only +through the embedded API. diff --git a/docs/content/docs/host-functions.mdx b/docs/content/docs/host-functions.mdx index cefa9a5c9e..43d53ef6db 100644 --- a/docs/content/docs/host-functions.mdx +++ b/docs/content/docs/host-functions.mdx @@ -90,6 +90,12 @@ Host function calls from guest code invoke your `execute()` functions on the hos Because handlers run with full host access, do not expose functions that could compromise the host without appropriate safeguards. Use the `hostFunction` [permission](/agentos/docs/permissions) scope to restrict which collections guest code may call. +## Hosted API + +Host functions run as in-process callbacks, so they are not available on +hosted VMs. Hosted guest code reaches external services over the network, +subject to the VM's permissions. + ## Embedded API Host functions are passed to `AgentOs.create()` and their handlers run in the diff --git a/docs/content/docs/hosted.mdx b/docs/content/docs/hosted.mdx new file mode 100644 index 0000000000..175fd6d561 --- /dev/null +++ b/docs/content/docs/hosted.mdx @@ -0,0 +1,300 @@ +--- +title: "Hosted VMs" +description: "Create durable agentOS VMs by ID and control them from the RivetKit client or plain HTTP, with scoped JWTs for direct client access." +skill: true +--- + +agentOS ships as a prebuilt `agentOS` Rivet Actor. Each actor is one durable +VM: create it by key, then control its files, processes, terminals, software, +networking, schedules, and language runtimes from the RivetKit client or plain +HTTP. Configuration persists with the VM, output survives reconnects, and every +VM can expose expiring previews. + +Applications do not write an actor definition or lifecycle hooks. Product +state and hooks belong in your own actors, which call the `agentOS` actor as a +separate service. + + +The hosted contract is in alpha (`agentos-sdk.dev/v1alpha1`). The actor and the +`@rivet-dev/agentos` client ship in lockstep. See +[Hosted VM API](/agentos/docs/actor-api#contract-compatibility). + + +## Install + +```bash +npm install @rivet-dev/agentos +``` + +## Create a VM by ID + +The TypeScript client is generated from the same contract as the HTTP API, so +every action, event, and result is typed. `createAgentOsClient` is a normal +RivetKit client with that contract applied. + + + +```ts TypeScript @nocheck +import { createAgentOsClient } from "@rivet-dev/agentos"; + +const client = createAgentOsClient({ + endpoint: "https://api.rivet.dev", + namespace: process.env.RIVET_NAMESPACE, + token: process.env.RIVET_TOKEN, +}); + +const vm = client.agentOS.getOrCreate(["my-vm"], { + createWithInput: { config: { environment: { CI: "true" } } }, +}); +const vmId = await vm.resolve(); +``` + +```bash cURL +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 } +``` + + + +Creation input is used only when the actor is first created. Calling +`getOrCreate` with different input for an existing key does not reconfigure +it; use the [config actions](/agentos/docs/actor-api#configuration) instead. + +Every public action lives under `v1`. In TypeScript that is `vm.v1..(input)`. +Over HTTP, call `POST /gateway/{actor_id}/action/v1..` with the +input as the single element of `args`. See +[Call Action](/docs/api/gateway/call-action). + +## Public 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. See [JWTs](/docs/jwt). + + + +```ts TypeScript @nocheck +import { createAgentOsClient } from "@rivet-dev/agentos"; + +// Backend: issue a token for this VM only +const { token } = await vm.issueToken({ + subject: "user-123", + expiresIn: 3600, +}); + +// Client: call the VM directly +const userClient = createAgentOsClient({ + endpoint: "https://api.rivet.dev", + namespace: "my-namespace", + token, +}); +const userVm = userClient.getForId("agentOS", vmId); +``` + +```bash cURL +curl -X POST "https://api.rivet.dev/auth/tokens" \ + -H "Authorization: Bearer $RIVET_TOKEN" \ + --json @- < + +The cURL examples below call the VM with that scoped token as `$VM_TOKEN`. + +## Files and processes + + + +```ts TypeScript @nocheck +await vm.v1.filesystem.writeFile({ + path: "/home/agentos/hello.txt", + content: "Hello, world!", +}); + +const result = await vm.v1.process.run({ + command: "cat", + args: ["/home/agentos/hello.txt"], + options: { captureStdio: true }, +}); +console.log(result.stdout, result.status.exitCode); +``` + +```bash cURL +curl "https://api.rivet.dev/gateway/$VM_ID/action/v1.filesystem.writeFile" \ + -H "x-rivet-token: $VM_TOKEN" \ + --json '{"args": [{ + "path": "/home/agentos/hello.txt", + "content": "Hello, world!" + }]}' + +curl "https://api.rivet.dev/gateway/$VM_ID/action/v1.process.run" \ + -H "x-rivet-token: $VM_TOKEN" \ + --json '{"args": [{ + "command": "cat", + "args": ["/home/agentos/hello.txt"], + "options": { "captureStdio": true } + }]}' +``` + + + +`v1.process.run` takes an executable and an argument array. Run `sh` with `-c` +when you need shell parsing. + +## Output survives reconnects + +Process and terminal output is retained on the VM. After a disconnect, page +through it with a cursor instead of losing it. + + + +```ts TypeScript @nocheck +const server = await vm.v1.process.spawn({ + command: "node", + args: ["/home/agentos/server.js"], +}); + +// Later, from any client +const { events, nextCursor } = await vm.v1.process.output.read({ + process: server, +}); +``` + +```bash cURL +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"] + }]}' +# → { "output": { "generation": 1, "pid": 42 } } + +# Later, from any client +curl "https://api.rivet.dev/gateway/$VM_ID/action/v1.process.output.read" \ + -H "x-rivet-token: $VM_TOKEN" \ + --json '{"args": [{"process": {"generation": 1, "pid": 42}}]}' +``` + + + +## Live events + +Connect to the VM to receive events as they happen. Events are hints; use the +pull actions to recover anything missed while disconnected. + +```ts TypeScript @nocheck +const conn = vm.connect(); +await conn.ready; + +conn.on("vm.booted", () => console.log("VM is ready")); +conn.on("vm.shutdown", (payload) => console.log("shutdown:", payload.reason)); + +const spawned = await conn.v1.process.spawn({ + command: "node", + args: ["/home/agentos/server.js"], +}); + +conn.on("process.output", (data) => { + if (data.process.pid !== spawned.pid) return; + console.log(data.stream, new TextDecoder().decode(data.data)); +}); +conn.on("process.exit", (data) => { + if (data.process.pid !== spawned.pid) return; + console.log("exited with code", data.status.exitCode); +}); +``` + +The VM starts lazily on the first agentOS action after the actor wakes. + +## Language runtimes + +Run JavaScript, TypeScript, and Python directly, with npm and pip installs +built in. + + + +```ts TypeScript @nocheck +await vm.v1.javascript.execute({ source: "console.log(1 + 2)" }); + +const { value } = await vm.v1.python.evaluate({ expression: "21 * 2" }); +``` + +```bash cURL +curl "https://api.rivet.dev/gateway/$VM_ID/action/v1.javascript.execute" \ + -H "x-rivet-token: $VM_TOKEN" \ + --json '{"args": [{"source": "console.log(1 + 2)"}]}' + +curl "https://api.rivet.dev/gateway/$VM_ID/action/v1.python.evaluate" \ + -H "x-rivet-token: $VM_TOKEN" \ + --json '{"args": [{"expression": "21 * 2"}]}' +``` + + + +## Durable configuration + +Configuration is stored with the VM and versioned. Patch it with +compare-and-set; `v1.vm.status` reports when a change needs a restart. + + + +```ts TypeScript @nocheck +const { revision } = await vm.v1.config.get({}); + +await vm.v1.config.patch({ + expectedRevision: revision, + patch: { environment: { DEBUG: "1" } }, +}); +``` + +```bash cURL +curl "https://api.rivet.dev/gateway/$VM_ID/action/v1.config.get" \ + -H "x-rivet-token: $VM_TOKEN" \ + --json '{"args": [{}]}' +# → { "output": { "revision": 3, … } } + +curl "https://api.rivet.dev/gateway/$VM_ID/action/v1.config.patch" \ + -H "x-rivet-token: $VM_TOKEN" \ + --json '{"args": [{ + "expectedRevision": 3, + "patch": { "environment": { "DEBUG": "1" } } + }]}' +``` + + + +## Next steps + +- [Hosted VM API](/agentos/docs/actor-api): configuration, lifecycle, events, + errors, and every action. +- [Filesystem](/agentos/docs/filesystem#hosted-api), + [Processes & Shells](/agentos/docs/processes#hosted-api), + [Networking](/agentos/docs/networking#hosted-api), + [Software](/agentos/docs/software#hosted-api), and + [Crons & Loops](/agentos/docs/cron#hosted-api) for the hosted form of each + capability. + +## Embedded API + +To run VMs inside your own Node.js process instead, use +`@rivet-dev/agentos-core`. Read more in the +[embedded API quickstart](/agentos/docs/quickstart-embedded). diff --git a/docs/content/docs/index.mdx b/docs/content/docs/index.mdx index 70df4dafa1..eac57843ad 100644 --- a/docs/content/docs/index.mdx +++ b/docs/content/docs/index.mdx @@ -10,12 +10,21 @@ APIs in a lightweight VM. Run a VM inside a trusted Node.js backend. + + Create durable VMs by ID and control them over RivetKit or HTTP. + Install immutable `.aospkg` command packages. -`@rivet-dev/agentos-core` runs VMs inside your Node.js backend. Your application -owns the VM's lifecycle, persistence, and access to host resources such as -[host functions](/agentos/docs/host-functions) and -[mounts](/agentos/docs/filesystem#mounts). +Run agentOS two ways: + +- **Embedded:** `@rivet-dev/agentos-core` runs VMs inside your Node.js backend. + Your application owns the VM's lifecycle, persistence, and access to host + resources such as [host functions](/agentos/docs/host-functions) and + [mounts](/agentos/docs/filesystem#mounts). +- **Hosted:** the prebuilt `agentOS` Rivet Actor gives each VM a durable ID, + versioned configuration, replayable output, and expiring previews, controlled + from `@rivet-dev/agentos` or plain HTTP. See + [Hosted VMs](/agentos/docs/hosted). diff --git a/docs/content/docs/javascript.mdx b/docs/content/docs/javascript.mdx index 460f0c280b..7d79b02311 100644 --- a/docs/content/docs/javascript.mdx +++ b/docs/content/docs/javascript.mdx @@ -116,6 +116,21 @@ Every operation inherits the VM [permission policy](/agentos/docs/permissions) a +## Hosted API + +Hosted VMs expose the same operations as `v1.javascript.*`, `v1.typescript.*`, +and `v1.contexts.*` actions. + +```ts TypeScript @nocheck +await vm.v1.javascript.execute({ source: "console.log(1 + 2)" }); +await vm.v1.typescript.check({ source: "const value: number = 1" }); +await vm.v1.javascript.npm.install({}); +``` + +Contexts live for the VM lifetime. They do not survive actor sleep and wake, +because the VM is disposed; create them lazily on the first stateful use after +wake. + ## Embedded API The examples above use `AgentOs.create()`. Contexts remain available until the diff --git a/docs/content/docs/networking.mdx b/docs/content/docs/networking.mdx index 7e43ef43b6..d53040e6f6 100644 --- a/docs/content/docs/networking.mdx +++ b/docs/content/docs/networking.mdx @@ -24,6 +24,35 @@ limited to the sidecar's default allowlist unless the `network` host through `httpRequest` enter the VM from the host side, so they are subject to loopback confinement but not to the guest egress policy. -Core does not create public URLs for VM ports. To expose a VM server to -browsers, route requests through your application and forward them with -`httpRequest` or `fetch`. +Embedded Core does not create public URLs for VM ports. To expose a VM server +to browsers, route requests through your application and forward them with +`httpRequest` or `fetch`, or use a hosted VM's +[expiring previews](#hosted-api). + +## Hosted API + +Hosted VMs proxy requests with `v1.network.fetch` and the pull-based +`v1.network.fetchStream.start`, `read`, and `cancel` actions. + +Hosted VMs can also expose any port through an expiring preview. A preview is a +signed URL path that proxies HTTP to a port inside the VM until it expires or is +revoked. + +```ts TypeScript @nocheck +await vm.v1.process.spawn({ + command: "node", + args: ["/home/agentos/app.js"], +}); + +const preview = await vm.v1.network.preview.create({ + port: 3000, + ttlMs: 60 * 60 * 1_000, +}); +console.log(preview.path, new Date(Number(preview.expiresAtMs))); + +await vm.v1.network.preview.expire({ token: preview.token }); +``` + +`config.preview.defaultTtlMs` sets the default lifetime. Previews enter the VM +from the host side, so they are subject to loopback confinement but not to the +guest egress policy. diff --git a/docs/content/docs/permissions.mdx b/docs/content/docs/permissions.mdx index 633b0336f2..c9f9e92541 100644 --- a/docs/content/docs/permissions.mdx +++ b/docs/content/docs/permissions.mdx @@ -21,3 +21,9 @@ are governed by the `hostFunction` scope. See the [Security Model](/agentos/docs/security-model) for the trust boundary these policies enforce. + +## Hosted API + +Hosted VMs take the same serializable permission tree in their creation config +or a later complete `v1.config.set` replacement. Hosted VMs do not expose host +functions. diff --git a/docs/content/docs/persistence.mdx b/docs/content/docs/persistence.mdx index 7fa4807fc0..eeac90ad93 100644 --- a/docs/content/docs/persistence.mdx +++ b/docs/content/docs/persistence.mdx @@ -25,3 +25,15 @@ transaction. The embedding application owns VM identity and decides when to recreate or dispose the VM. + +## Hosted API + +A hosted VM's filesystem is configured with a closed registry. The default root +is an ephemeral in-memory overlay that is discarded when the VM is replaced. +Configure a `durable` root or mount for paths that must survive; see +[Filesystem](/agentos/docs/filesystem#hosted-api). + +The actor sleeps when idle and reports `vm.shutdown` with reason `"sleep"`. The +VM starts lazily on the first agentOS action after wake. Durable files and the +VM's configuration survive sleep; running processes, terminals, language +contexts, and open streams do not. diff --git a/docs/content/docs/processes.mdx b/docs/content/docs/processes.mdx index 53fde9c655..7bec52e090 100644 --- a/docs/content/docs/processes.mdx +++ b/docs/content/docs/processes.mdx @@ -62,3 +62,54 @@ once the sidecar has confirmed the exit. Retention is bounded; see it, and returns a `shellId`. Use `terminal.write`, `terminal.resize`, `terminal.wait`, and `terminal.close` to drive it, and `vm.onShellData`, `vm.onShellStderr`, and `vm.onShellExit` for its live output and exit. + +## Hosted API + +Hosted VMs run processes with `v1.process.*` actions. `v1.process.run` takes an +executable and an argument array; there is no shell-string `exec`, so run `sh` +with `-c` when you need shell parsing. + +```ts TypeScript @nocheck +const result = await vm.v1.process.run({ + command: "sh", + args: ["-c", "echo hello && ls /home/agentos"], + options: { env: {}, captureStdio: true }, +}); +console.log(result.stdout, result.status.exitCode); + +const conn = vm.connect(); +await conn.ready; + +const shell = await conn.v1.process.spawn({ command: "sh", args: [] }); +conn.on("process.output", (data) => { + if (data.process.pid !== shell.pid) return; + process.stdout.write(new TextDecoder().decode(data.data)); +}); + +await conn.v1.process.stdin.write({ process: shell, data: "ls -la /home/agentos\n" }); +await conn.v1.process.stdin.close({ process: shell }); +await conn.v1.process.wait({ process: shell }); + +// Replay retained output after a reconnect. +const replay = await vm.v1.process.output.read({ + process: shell, + maxEvents: 32, + maxBytes: 64 * 1024, +}); +``` + +`v1.process.spawn` returns a process handle `{ generation, pid }`. Pass the +handle to `v1.process.get`, `wait`, `signal`, `stdin.write`, `stdin.close`, +`pty.resize`, and `output.read`. Handles from before a `v1.vm.restart` fail +with `stale_generation`. Live events are `process.output` and `process.exit`; +replay is the authoritative recovery path. + +`v1.process.get` and `v1.process.list` report `startedAtMs`. Nodes returned by +`v1.process.tree` report `startTimeMs`, `exitTimeMs`, and a structured `exit`. +Guest child processes that were not launched through the API have no process +handle. + +Interactive terminals are separate resources under `v1.terminal.open`, +`v1.terminal.list`, `v1.terminal.stdin.write`, `v1.terminal.pty.resize`, +`v1.terminal.output.read`, `v1.terminal.wait`, and `v1.terminal.close`. Their +live events are `terminal.output` and `terminal.exit`. diff --git a/docs/content/docs/python.mdx b/docs/content/docs/python.mdx index cac7ccd3f4..2cf252c2e7 100644 --- a/docs/content/docs/python.mdx +++ b/docs/content/docs/python.mdx @@ -108,6 +108,18 @@ Every operation inherits the VM [permission policy](/agentos/docs/permissions) a +## Hosted API + +Hosted VMs expose the same operations as `v1.python.*` actions. + +```ts TypeScript @nocheck +const { value } = await vm.v1.python.evaluate({ expression: "21 * 2" }); +await vm.v1.python.install({ options: {} }); +``` + +Python contexts do not survive actor sleep and wake, because the VM is +disposed; create them lazily on the first stateful use after wake. + ## Embedded API The examples above use `AgentOs.create()`. Python contexts remain available diff --git a/docs/content/docs/quickstart.mdx b/docs/content/docs/quickstart.mdx index 1d8d8e71d0..0534ae811b 100644 --- a/docs/content/docs/quickstart.mdx +++ b/docs/content/docs/quickstart.mdx @@ -27,6 +27,9 @@ package URLs. ## Next steps +- To create durable VMs by ID instead of embedding them, see + [Hosted VMs](/agentos/docs/hosted). + - Run commands with [Bash](/agentos/docs/bash), or code with [Node.js](/agentos/docs/javascript) and [Python](/agentos/docs/python). - Install tools from the [registry](/agentos/docs/registry) with diff --git a/docs/content/docs/resource-limits.mdx b/docs/content/docs/resource-limits.mdx index f69e38198c..af1655368e 100644 --- a/docs/content/docs/resource-limits.mdx +++ b/docs/content/docs/resource-limits.mdx @@ -44,6 +44,29 @@ VMs share a sidecar process by default. Limits that apply to the whole process, such as `runtime.executor.maxActiveVms`, are set when creating a dedicated sidecar with `AgentOs.createSidecar()`, as in the example above. +## Hosted API + +Hosted VMs take the same `limits` tree in their durable config. Set it at +creation or with `v1.config.set`, and apply changed limits with +`v1.vm.restart`. Near-limit pressure is reported as the `vm.limitWarning` event. + +```ts TypeScript @nocheck +await vm.v1.config.set({ + config: { + limits: { + tls: { maxBufferedBytes: 2048 }, + execution: { completedTtlMs: 60_000, liveExecutionWarningThreshold: 32 }, + agentosPackages: { maxMounts: 8192 }, + }, + }, +}); +await vm.v1.vm.restart({}); +``` + +The hosted actor also has a fixed ten-second shutdown wait; raising the sidecar +deadline does not extend it. Exceeding it reports a timeout, not confirmed +shutdown. + ## Embedded API Limits apply to VMs created with `AgentOs.create()` and sidecars created with diff --git a/docs/content/docs/sandboxes.mdx b/docs/content/docs/sandboxes.mdx index 41f3e941fc..bc04d8b588 100644 --- a/docs/content/docs/sandboxes.mdx +++ b/docs/content/docs/sandboxes.mdx @@ -18,3 +18,8 @@ process, so credentials for the sandbox provider stay on the host. See [agentOS vs Sandbox](/agentos/docs/versus-sandbox) to decide when a workload needs one. + +## Hosted API + +External sandboxes rely on trusted host callbacks and mounts, so they are not +available on hosted VMs. diff --git a/docs/content/docs/security-model.mdx b/docs/content/docs/security-model.mdx index b000203f98..c61b62053f 100644 --- a/docs/content/docs/security-model.mdx +++ b/docs/content/docs/security-model.mdx @@ -39,6 +39,20 @@ If your application lets untrusted users configure VMs, accept only serializable configuration from them and resolve it against your own allowlist of mounts and software sources. +## Hosted VM restrictions + +The hosted `agentOS` actor accepts only serializable configuration. Its +filesystem descriptors are resolved through a fixed registry, and software +installation accepts only remote HTTPS `.aospkg` sources. It cannot receive: + +- a host filesystem path or host-directory mount; +- a host function callback, closure, or arbitrary host object; +- a custom filesystem or software resolver; +- an unregistered actor implementation or lifecycle hook. + +Use [scoped JWTs](/docs/jwt) to give an untrusted client access to exactly one +hosted VM. + ## Isolation guarantees - Guest processes are kernel-managed and never become host processes. diff --git a/docs/content/docs/software.mdx b/docs/content/docs/software.mdx index 84963b494e..a5b2f936f6 100644 --- a/docs/content/docs/software.mdx +++ b/docs/content/docs/software.mdx @@ -55,6 +55,44 @@ Registry software is not installed from npm. Releases upload digest-addressed `.aospkg` files and a manifest to object storage; see [Publishing Software](/agentos/docs/custom-software/publishing). +## Hosted API + +Hosted VMs install software from HTTPS URLs only. Software is part of the VM's +durable configuration, so `v1.software.install` and `v1.software.uninstall` use +the same revision checks as `v1.config.patch`. + +```ts TypeScript @nocheck +const installed = await vm.v1.software.install({ + source: { + url: "https://packages.example.com/ripgrep.aospkg", + digest: "sha256:<64 lowercase hex characters>", + }, +}); +if (installed.config.state === "restart_required") { + await vm.v1.vm.restart({}); +} + +for (const software of await vm.v1.software.list({})) { + console.log(software.packageId, software.commands); +} + +await vm.v1.software.uninstall({ packageId: installed.source.packageId }); +``` + +Software changes apply live when the VM is ready and its desired configuration +is fully applied. Otherwise they update desired configuration and report +`restart_required`; call `v1.vm.restart` to apply it. Uninstalling a desired +package does not require a working VM or a fresh download. + +Both mutations return `{ source, software, config }`. `source` identifies the +configured package, and `config` reports the new revision and state. +`v1.software.list` reads the live installed set. Desired package sources live in +`v1.config.get`. + +Hosted workers share a digest-keyed package cache and warm popular packages at +startup. See +[Software Installation and Preloading](/agentos/docs/architecture/software-cache). + ## Embedded API The embedding process is trusted, so `software.install` accepts local package diff --git a/docs/content/docs/versus-sandbox.mdx b/docs/content/docs/versus-sandbox.mdx index c195a5ca22..6cdb967bf5 100644 --- a/docs/content/docs/versus-sandbox.mdx +++ b/docs/content/docs/versus-sandbox.mdx @@ -18,7 +18,7 @@ skill: true | **Backend integration** | Direct. [Host functions](/agentos/docs/host-functions) call your functions with zero latency. | Indirect. Requires network calls back to your backend. | | **Credentials** | [Host functions](/agentos/docs/host-functions) keep credentials on the host. | Must be injected into the sandbox environment. | | **Permissions** | Granular, deny-by-default. | Coarse-grained (container-level). | -| **Infrastructure** | An npm package in your backend. | Vendor account + API keys. | +| **Infrastructure** | An npm package in your backend, or [hosted VMs](/agentos/docs/hosted). | Vendor account + API keys. | | **Best for** | Coding, file manipulation, scripting, API calls, orchestration. | Browsers, desktop automation, native compilation, dev servers. | ## When to use each diff --git a/docs/sidebar.json b/docs/sidebar.json index 179d28019c..a9c2ab8a37 100644 --- a/docs/sidebar.json +++ b/docs/sidebar.json @@ -20,6 +20,21 @@ } ] }, + { + "title": "Hosted VMs", + "pages": [ + { + "title": "Overview", + "href": "/agentos/docs/hosted", + "icon": "faCloud" + }, + { + "title": "Hosted VM API", + "href": "/agentos/docs/actor-api", + "icon": "faBook" + } + ] + }, { "title": "Execution", "pages": [ @@ -185,7 +200,11 @@ { "title": "Packages & Command Resolution", "href": "/agentos/docs/architecture/packages-and-command-resolution" - }, + }, + { + "title": "Software Installation & Preloading", + "href": "/agentos/docs/architecture/software-cache" + }, { "title": "Compiler Toolchain", "href": "/agentos/docs/architecture/compiler-toolchain" From 626531415764267e77c050402992d972ffb73cc7 Mon Sep 17 00:00:00 2001 From: Sree Narayanan Date: Mon, 12 Oct 2026 09:01:11 +0000 Subject: [PATCH 03/18] docs(agentos): install registry software from the release manifest 0.4 publishes registry packages and default-software.json under releases.rivet.dev/agent-os//software/. Point the Software and Publishing pages and the install example at that manifest. Amp-Thread-ID: https://ampcode.com/threads/T-c7d08b58-92c1-4fc0-abb9-7a15f4504a4f Co-authored-by: Amp --- docs/content/docs/custom-software/publishing.mdx | 14 +++++++++++--- docs/content/docs/software.mdx | 10 ++++++++-- examples/embedded/software-install.ts | 15 ++++++++++++--- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/docs/content/docs/custom-software/publishing.mdx b/docs/content/docs/custom-software/publishing.mdx index 1d3f758bf1..a4888d6010 100644 --- a/docs/content/docs/custom-software/publishing.mdx +++ b/docs/content/docs/custom-software/publishing.mdx @@ -22,7 +22,15 @@ Run the credential-free release staging path locally: just software-artifacts-dry-run ``` -The release workflow uploads the same directory through the configured -S3-compatible endpoint under immutable commit and version prefixes. Consumers -pass the resulting artifact URL and digest directly to `software.install`; see +The release workflow uploads the same directory under immutable commit and +version prefixes, and copies the newest release to `latest`: + +```text +https://releases.rivet.dev/agent-os//software/manifest.json +https://releases.rivet.dev/agent-os/latest/software/manifest.json +``` + +The same directory also holds `default-software.json`, the manifest of the +packages every VM installs by default. Consumers pass an artifact URL and +digest from the manifest directly to `software.install`; see [Software](/agentos/docs/software#install-while-running). diff --git a/docs/content/docs/software.mdx b/docs/content/docs/software.mdx index a5b2f936f6..2c614b3cc8 100644 --- a/docs/content/docs/software.mdx +++ b/docs/content/docs/software.mdx @@ -27,6 +27,11 @@ HTTPS URL or a trusted local path, each with an optional exact `sha256:<64 lowercase hex>` digest. New commands are available on `/opt/agentos/bin` as soon as the call returns. +Registry packages are published with each release under +`https://releases.rivet.dev/agent-os//software/`, or `latest` for the +newest release. Its `manifest.json` lists each package's name, digest, size, +and path relative to the manifest. + - `software.install(source)` returns the installed package's `packageId`, @@ -51,8 +56,9 @@ Packages are cached per sidecar process by content digest, so VMs that share a sidecar reuse one verified copy. Repeat installs of the same bytes skip the download, and the bytes are always verified before projection. -Registry software is not installed from npm. Releases upload digest-addressed -`.aospkg` files and a manifest to object storage; see +Registry software is not installed from npm. Each release uploads +digest-addressed `.aospkg` files and a manifest to +`releases.rivet.dev`; see [Publishing Software](/agentos/docs/custom-software/publishing). ## Hosted API diff --git a/examples/embedded/software-install.ts b/examples/embedded/software-install.ts index 81cb05f362..d17349b25f 100644 --- a/examples/embedded/software-install.ts +++ b/examples/embedded/software-install.ts @@ -1,5 +1,14 @@ import { AgentOs } from "@rivet-dev/agentos-core"; +// Registry software is published as digest-addressed .aospkg files next to a +// manifest. Use a release version, or `latest`. +const base = "https://releases.rivet.dev/agent-os/latest/software/"; +const manifest = (await (await fetch(`${base}manifest.json`)).json()) as { + artifacts: { name: string; digest: string; path: string }[]; +}; +const ripgrep = manifest.artifacts.find((artifact) => artifact.name === "ripgrep"); +if (!ripgrep) throw new Error("ripgrep is not in the manifest"); + const vm = await AgentOs.create(); try { @@ -7,12 +16,12 @@ try { // the digest before the package is projected into /opt/agentos. const installed = await vm.software.install({ type: "url", - url: "https://packages.example.com/ripgrep.aospkg", - expectedDigest: "sha256:<64 lowercase hex characters>", + url: new URL(ripgrep.path, base).href, + expectedDigest: ripgrep.digest, }); console.log(installed.packageName, installed.version, installed.commands); - const result = await vm.process.exec("rg --version", { + const result = await vm.process.execFile("rg", ["--version"], { output: { capture: "all" }, }); console.log(result.stdout); From a0eab1e1cdd0aace8945cb9f712bdd57c8823f5b Mon Sep 17 00:00:00 2001 From: Sree Narayanan Date: Mon, 12 Oct 2026 09:15:13 +0000 Subject: [PATCH 04/18] docs(agentos): split library and hosted quickstarts, add Hosted VM HTTP API reference Replace the Hosted VMs sidebar group with a hosted quickstart that mirrors the library quickstart, add a which-one-to-use table to both, and merge the hosted overview and API pages into one Hosted VM HTTP API reference. Amp-Thread-ID: https://ampcode.com/threads/T-c7d08b58-92c1-4fc0-abb9-7a15f4504a4f Co-authored-by: Amp --- docs/content/docs/actor-api.mdx | 167 ------------- docs/content/docs/hosted-api.mdx | 265 +++++++++++++++++++++ docs/content/docs/hosted.mdx | 300 ------------------------ docs/content/docs/index.mdx | 11 +- docs/content/docs/quickstart-hosted.mdx | 238 +++++++++++++++++++ docs/content/docs/quickstart.mdx | 75 ++++-- docs/content/docs/versus-sandbox.mdx | 2 +- docs/sidebar.json | 26 +- examples/embedded/quickstart.ts | 56 +++++ 9 files changed, 634 insertions(+), 506 deletions(-) delete mode 100644 docs/content/docs/actor-api.mdx create mode 100644 docs/content/docs/hosted-api.mdx delete mode 100644 docs/content/docs/hosted.mdx create mode 100644 docs/content/docs/quickstart-hosted.mdx create mode 100644 examples/embedded/quickstart.ts diff --git a/docs/content/docs/actor-api.mdx b/docs/content/docs/actor-api.mdx deleted file mode 100644 index ba2c91083e..0000000000 --- a/docs/content/docs/actor-api.mdx +++ /dev/null @@ -1,167 +0,0 @@ ---- -title: "Hosted VM API" -description: "Configure and operate a hosted agentOS VM through a typed RivetKit actor handle or the HTTP gateway." ---- - -The hosted `agentOS` actor is a fixed Rust actor. Use a normal RivetKit client -with the generated `AgentOsRegistry` contract. The `createAgentOsClient` -helper only supplies that type parameter and does not add another transport or -API layer. See [Hosted VMs](/agentos/docs/hosted) to get started. - -Creation input is considered only when RivetKit creates the actor. Calling -`getOrCreate` with different creation input for an existing key does not -reconfigure it. Use the config actions for later changes. - -## Configuration - -`v1.config.get` returns the durable desired config and its revision. -`v1.config.set` replaces the complete config. An omitted field selects its -default. `v1.config.patch` applies an RFC 7386 JSON Merge Patch to the current -desired config. - -Merge Patch recursively merges objects and replaces arrays as complete values. -Setting a property to `null` removes the override, which selects its default. -Both mutation actions accept `expectedRevision` for compare-and-set updates. -A stale revision fails without changing durable state. - -```ts TypeScript @nocheck -import type { AgentOsActorCreateInput } from "@rivet-dev/agentos"; - -const createInput: AgentOsActorCreateInput = { - config: { - environment: { CI: "true" }, - preview: { defaultTtlMs: 30_000 }, - filesystem: { - root: { type: "durable" }, - mounts: [{ path: "/data", backend: { type: "durable" } }], - }, - software: [{ url: "https://packages.example/tool.aospkg" }], - }, -}; - -const vm = client.agentOS.getOrCreate(["my-vm"], { - createWithInput: createInput, -}); - -await vm.v1.config.set({ - config: { limits: { tls: { maxBufferedBytes: 2048 } } }, -}); -``` - -Core owns VM defaults and validation; the generated client does not copy those -defaults. Hosted-only policy, including preview leases and the filesystem -registry, is normalized by the actor. - -When a VM is running, the actor asks that VM's sidecar to compare the resolved -creation policies for supported runtime fields. Setting an explicit value equal -to the active default does not by itself require a restart. Software sources, -hosted filesystem policy, and any future fields not explicitly covered by that -comparison remain replacement-triggering. - -## VM lifecycle - -`v1.vm.status` reports the active generation, applied config revision, package -readiness, lifecycle state, and recent issues. A desired config can be durable -while the active generation reports that a restart is required. - -`v1.vm.restart` stops the active VM, waits for bounded shutdown, and starts a -new generation from the latest desired config. Process, terminal, context, and -network stream handles include their generation. Using an old handle after a -restart fails as `stale_generation`. - -Lifecycle notifications use the `vm.booted`, `vm.shutdown`, and -`vm.limitWarning` events. `vm.shutdown` carries a `reason` of `"sleep"`, -`"destroy"`, or `"error"`. - -## Pull APIs and events - -Live events reduce latency, but bounded pull actions are the recovery path -after a disconnect or inspector reload. Event names are not versioned. - -| Resource | Pull action | Events | -| --- | --- | --- | -| Process | `v1.process.output.read`, `v1.process.wait` | `process.output`, `process.exit` | -| Terminal | `v1.terminal.output.read`, `v1.terminal.wait` | `terminal.output`, `terminal.exit` | -| HTTP response | `v1.network.fetchStream.read` | None | -| VM | `v1.vm.status` | `vm.booted`, `vm.shutdown`, `vm.limitWarning` | -| Cron | `v1.cron.list` | `cron.fired` | - -Process and terminal replay use a generation-scoped sequence cursor. Request -limits bound both the number of events and their aggregate bytes. Network -response streams are pull-based so each action is bounded and the caller owns -backpressure. - -## Errors - -| Code | Cause | -| --- | --- | -| `stale_generation` | A process, terminal, context, or stream handle from before a `v1.vm.restart` was used. | -| `expired` | A fetch-stream handle expired. | -| `timeout` | A required package-resolution deadline passed. | -| `not_found` | A closed terminal's replay was evicted from the bounded retention window. | - -Final terminal output and its confirmed exit remain readable after exit within -the bounded closed-terminal retention window. Events are hints; use replay to -recover missed output. - -## Actions - -Every public action is versioned under `v1`. In TypeScript, call -`vm.v1..(input)`. Over HTTP, call -`POST /gateway/{actor_id}/action/v1..` with -`{ "args": [input] }`. - -| Group | Actions | -| --- | --- | -| `config` | `get`, `set`, `patch` | -| `vm` | `status`, `restart` | -| `filesystem` | `readFile`, `writeFile`, `readFiles`, `writeFiles`, `stat`, `exists`, `mkdir`, `readdir`, `readdirEntries`, `readdirRecursive`, `move`, `remove`, `listMounts`, `export` | -| `process` | `run`, `spawn`, `get`, `list`, `tree`, `wait`, `signal`, `stdin.write`, `stdin.close`, `pty.resize`, `output.read` | -| `terminal` | `open`, `list`, `stdin.write`, `pty.resize`, `output.read`, `wait`, `close` | -| `network` | `fetch`, `fetchStream.start`, `fetchStream.read`, `fetchStream.cancel`, `preview.create`, `preview.expire` | -| `software` | `install`, `uninstall`, `list` | -| `cron` | `schedule`, `list`, `cancel` | -| `contexts` | `create`, `get`, `list`, `reset`, `delete` | -| `javascript` | `execute`, `evaluate`, `executeFile`, `spawn`, `spawnFile`, `npm.install`, `npm.runScript`, `npm.runPackage` | -| `typescript` | `execute`, `evaluate`, `executeFile`, `spawn`, `spawnFile`, `check`, `checkProject` | -| `python` | `execute`, `evaluate`, `executeFile`, `executeModule`, `spawn`, `spawnFile`, `spawnModule`, `install` | - -The hosted contract has no agent sessions and no shell-string `process.exec`. -Private cron and maintenance actions are not part of the client. - -## Contract compatibility - -The package exports `AGENTOS_API_VERSION`, `AGENTOS_CONTRACT_MAJOR`, -`AGENTOS_CONTRACT_HASH`, and `AGENTOS_CAPABILITIES`. Rust DTOs and the action -registry generate the TypeScript surface. The actor and client package ship in -lockstep during the alpha contract period. - -The contract is currently `agentos-sdk.dev/v1alpha1`, major `1`. The hash covers -the canonical public actions, events, DTOs, and capabilities. Private cron and -maintenance actions are excluded from both the generated client and hash. - -## Inspector - -The Inspector is a client of the same public contract. Its filesystem, -process, terminal, software, preview, config, status, and limit views do not -receive private actor access. The UI can read desired configuration and VM -status, restart the VM, and show runtime issues, but it cannot edit host -mounts. - -Published Linux services binaries and the services container include the built -tabs beside the executable, so they are registered automatically. A source or -custom deployment can point the service at another built copy: - -```sh -AGENTOS_INSPECTOR_TABS_DIR=node_modules/@rivet-dev/agentos/assets/inspector-tabs-app \ - rivet-services start -``` - -Startup fails if an explicit `AGENTOS_INSPECTOR_TABS_DIR` does not contain -`index.html`. - -## Embedded API - -Embedded Core exposes the same VM capabilities without RivetKit actor -lifecycle, durable actor config, previews, or hosted URL-only policy. Read more -in the [embedded API quickstart](/agentos/docs/quickstart-embedded). diff --git a/docs/content/docs/hosted-api.mdx b/docs/content/docs/hosted-api.mdx new file mode 100644 index 0000000000..9afcac0d46 --- /dev/null +++ b/docs/content/docs/hosted-api.mdx @@ -0,0 +1,265 @@ +--- +title: "Hosted VM HTTP API" +description: "Reference for the hosted agentOS VM API: creating VMs, authentication, actions, configuration, lifecycle, events, and errors." +--- + +Each hosted VM is one instance of the prebuilt `agentOS` Rivet Actor. You +create it by key, then call its actions over the Rivet gateway. The +`@rivet-dev/agentos` TypeScript client is generated from the same contract and +calls the same actions. See the [hosted quickstart](/agentos/docs/quickstart-hosted) +for a walkthrough. + + +The contract is in alpha (`agentos-sdk.dev/v1alpha1`). The actor and the +`@rivet-dev/agentos` client ship in lockstep. + + +## Overview + +| | | +|---|---| +| **Base URL** | `https://api.rivet.dev` | +| **Actor name** | `agentOS` | +| **Create a VM** | `PUT /actors?namespace={namespace}` | +| **Call an action** | `POST /gateway/{vm_id}/action/v1.{group}.{action}` | +| **Request body** | `{ "args": [input] }` | +| **Response body** | `{ "output": result }` | +| **Authentication** | `Authorization: Bearer` to create VMs and tokens; `x-rivet-token` for actions | + +## Create a VM + +```bash cURL +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 } +``` + +The returned `actor_id` is the VM ID used in every action path. A second request +with the same key returns the existing VM. + +The TypeScript client does the same with `getOrCreate`, and can pass creation +input on first creation: + +```ts TypeScript @nocheck +import { createAgentOsClient } from "@rivet-dev/agentos"; + +const client = createAgentOsClient({ + endpoint: "https://api.rivet.dev", + namespace: process.env.RIVET_NAMESPACE, + token: process.env.RIVET_TOKEN, +}); + +const vm = client.agentOS.getOrCreate(["my-vm"], { + createWithInput: { config: { environment: { CI: "true" } } }, +}); +const vmId = await vm.resolve(); +``` + +## Authentication + +Keep your admin token on the backend. To let a browser or other untrusted +client call one VM directly, issue a JWT scoped to that VM. See +[JWTs](/docs/jwt). + +```bash cURL +curl -X POST "https://api.rivet.dev/auth/tokens" \ + -H "Authorization: Bearer $RIVET_TOKEN" \ + --json @- < { + console.log(data.process.pid, data.stream, new TextDecoder().decode(data.data)); +}); +conn.on("process.exit", (data) => { + console.log(data.process.pid, "exited with code", data.status.exitCode); +}); +``` + +## Errors + +| Code | Cause | +| --- | --- | +| `stale_generation` | A process, terminal, context, or stream handle from before a `v1.vm.restart` was used. | +| `expired` | A fetch-stream handle expired. | +| `timeout` | A required package-resolution deadline passed. | +| `not_found` | A closed terminal's replay was evicted from the bounded retention window. | + +Final terminal output and its confirmed exit remain readable after exit within +the bounded closed-terminal retention window. + +## Contract compatibility + +The `@rivet-dev/agentos` package exports `AGENTOS_API_VERSION`, +`AGENTOS_CONTRACT_MAJOR`, `AGENTOS_CONTRACT_HASH`, and `AGENTOS_CAPABILITIES`. +Rust DTOs and the action registry generate the TypeScript surface. + +The contract is currently `agentos-sdk.dev/v1alpha1`, major `1`. The hash covers +the canonical public actions, events, DTOs, and capabilities. Private cron and +maintenance actions are excluded from both the client and the hash. + +## Inspector + +The Rivet Inspector shows a hosted VM's filesystem, processes, terminals, +software, previews, configuration, status, and limit warnings through the same +public contract. It can restart the VM but cannot edit host mounts. + +Published services binaries and the services container include the Inspector +tabs. A source or custom deployment can point the service at another built +copy: + +```sh +AGENTOS_INSPECTOR_TABS_DIR=node_modules/@rivet-dev/agentos/assets/inspector-tabs-app \ + rivet-services start +``` diff --git a/docs/content/docs/hosted.mdx b/docs/content/docs/hosted.mdx deleted file mode 100644 index 175fd6d561..0000000000 --- a/docs/content/docs/hosted.mdx +++ /dev/null @@ -1,300 +0,0 @@ ---- -title: "Hosted VMs" -description: "Create durable agentOS VMs by ID and control them from the RivetKit client or plain HTTP, with scoped JWTs for direct client access." -skill: true ---- - -agentOS ships as a prebuilt `agentOS` Rivet Actor. Each actor is one durable -VM: create it by key, then control its files, processes, terminals, software, -networking, schedules, and language runtimes from the RivetKit client or plain -HTTP. Configuration persists with the VM, output survives reconnects, and every -VM can expose expiring previews. - -Applications do not write an actor definition or lifecycle hooks. Product -state and hooks belong in your own actors, which call the `agentOS` actor as a -separate service. - - -The hosted contract is in alpha (`agentos-sdk.dev/v1alpha1`). The actor and the -`@rivet-dev/agentos` client ship in lockstep. See -[Hosted VM API](/agentos/docs/actor-api#contract-compatibility). - - -## Install - -```bash -npm install @rivet-dev/agentos -``` - -## Create a VM by ID - -The TypeScript client is generated from the same contract as the HTTP API, so -every action, event, and result is typed. `createAgentOsClient` is a normal -RivetKit client with that contract applied. - - - -```ts TypeScript @nocheck -import { createAgentOsClient } from "@rivet-dev/agentos"; - -const client = createAgentOsClient({ - endpoint: "https://api.rivet.dev", - namespace: process.env.RIVET_NAMESPACE, - token: process.env.RIVET_TOKEN, -}); - -const vm = client.agentOS.getOrCreate(["my-vm"], { - createWithInput: { config: { environment: { CI: "true" } } }, -}); -const vmId = await vm.resolve(); -``` - -```bash cURL -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 } -``` - - - -Creation input is used only when the actor is first created. Calling -`getOrCreate` with different input for an existing key does not reconfigure -it; use the [config actions](/agentos/docs/actor-api#configuration) instead. - -Every public action lives under `v1`. In TypeScript that is `vm.v1..(input)`. -Over HTTP, call `POST /gateway/{actor_id}/action/v1..` with the -input as the single element of `args`. See -[Call Action](/docs/api/gateway/call-action). - -## Public 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. See [JWTs](/docs/jwt). - - - -```ts TypeScript @nocheck -import { createAgentOsClient } from "@rivet-dev/agentos"; - -// Backend: issue a token for this VM only -const { token } = await vm.issueToken({ - subject: "user-123", - expiresIn: 3600, -}); - -// Client: call the VM directly -const userClient = createAgentOsClient({ - endpoint: "https://api.rivet.dev", - namespace: "my-namespace", - token, -}); -const userVm = userClient.getForId("agentOS", vmId); -``` - -```bash cURL -curl -X POST "https://api.rivet.dev/auth/tokens" \ - -H "Authorization: Bearer $RIVET_TOKEN" \ - --json @- < - -The cURL examples below call the VM with that scoped token as `$VM_TOKEN`. - -## Files and processes - - - -```ts TypeScript @nocheck -await vm.v1.filesystem.writeFile({ - path: "/home/agentos/hello.txt", - content: "Hello, world!", -}); - -const result = await vm.v1.process.run({ - command: "cat", - args: ["/home/agentos/hello.txt"], - options: { captureStdio: true }, -}); -console.log(result.stdout, result.status.exitCode); -``` - -```bash cURL -curl "https://api.rivet.dev/gateway/$VM_ID/action/v1.filesystem.writeFile" \ - -H "x-rivet-token: $VM_TOKEN" \ - --json '{"args": [{ - "path": "/home/agentos/hello.txt", - "content": "Hello, world!" - }]}' - -curl "https://api.rivet.dev/gateway/$VM_ID/action/v1.process.run" \ - -H "x-rivet-token: $VM_TOKEN" \ - --json '{"args": [{ - "command": "cat", - "args": ["/home/agentos/hello.txt"], - "options": { "captureStdio": true } - }]}' -``` - - - -`v1.process.run` takes an executable and an argument array. Run `sh` with `-c` -when you need shell parsing. - -## Output survives reconnects - -Process and terminal output is retained on the VM. After a disconnect, page -through it with a cursor instead of losing it. - - - -```ts TypeScript @nocheck -const server = await vm.v1.process.spawn({ - command: "node", - args: ["/home/agentos/server.js"], -}); - -// Later, from any client -const { events, nextCursor } = await vm.v1.process.output.read({ - process: server, -}); -``` - -```bash cURL -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"] - }]}' -# → { "output": { "generation": 1, "pid": 42 } } - -# Later, from any client -curl "https://api.rivet.dev/gateway/$VM_ID/action/v1.process.output.read" \ - -H "x-rivet-token: $VM_TOKEN" \ - --json '{"args": [{"process": {"generation": 1, "pid": 42}}]}' -``` - - - -## Live events - -Connect to the VM to receive events as they happen. Events are hints; use the -pull actions to recover anything missed while disconnected. - -```ts TypeScript @nocheck -const conn = vm.connect(); -await conn.ready; - -conn.on("vm.booted", () => console.log("VM is ready")); -conn.on("vm.shutdown", (payload) => console.log("shutdown:", payload.reason)); - -const spawned = await conn.v1.process.spawn({ - command: "node", - args: ["/home/agentos/server.js"], -}); - -conn.on("process.output", (data) => { - if (data.process.pid !== spawned.pid) return; - console.log(data.stream, new TextDecoder().decode(data.data)); -}); -conn.on("process.exit", (data) => { - if (data.process.pid !== spawned.pid) return; - console.log("exited with code", data.status.exitCode); -}); -``` - -The VM starts lazily on the first agentOS action after the actor wakes. - -## Language runtimes - -Run JavaScript, TypeScript, and Python directly, with npm and pip installs -built in. - - - -```ts TypeScript @nocheck -await vm.v1.javascript.execute({ source: "console.log(1 + 2)" }); - -const { value } = await vm.v1.python.evaluate({ expression: "21 * 2" }); -``` - -```bash cURL -curl "https://api.rivet.dev/gateway/$VM_ID/action/v1.javascript.execute" \ - -H "x-rivet-token: $VM_TOKEN" \ - --json '{"args": [{"source": "console.log(1 + 2)"}]}' - -curl "https://api.rivet.dev/gateway/$VM_ID/action/v1.python.evaluate" \ - -H "x-rivet-token: $VM_TOKEN" \ - --json '{"args": [{"expression": "21 * 2"}]}' -``` - - - -## Durable configuration - -Configuration is stored with the VM and versioned. Patch it with -compare-and-set; `v1.vm.status` reports when a change needs a restart. - - - -```ts TypeScript @nocheck -const { revision } = await vm.v1.config.get({}); - -await vm.v1.config.patch({ - expectedRevision: revision, - patch: { environment: { DEBUG: "1" } }, -}); -``` - -```bash cURL -curl "https://api.rivet.dev/gateway/$VM_ID/action/v1.config.get" \ - -H "x-rivet-token: $VM_TOKEN" \ - --json '{"args": [{}]}' -# → { "output": { "revision": 3, … } } - -curl "https://api.rivet.dev/gateway/$VM_ID/action/v1.config.patch" \ - -H "x-rivet-token: $VM_TOKEN" \ - --json '{"args": [{ - "expectedRevision": 3, - "patch": { "environment": { "DEBUG": "1" } } - }]}' -``` - - - -## Next steps - -- [Hosted VM API](/agentos/docs/actor-api): configuration, lifecycle, events, - errors, and every action. -- [Filesystem](/agentos/docs/filesystem#hosted-api), - [Processes & Shells](/agentos/docs/processes#hosted-api), - [Networking](/agentos/docs/networking#hosted-api), - [Software](/agentos/docs/software#hosted-api), and - [Crons & Loops](/agentos/docs/cron#hosted-api) for the hosted form of each - capability. - -## Embedded API - -To run VMs inside your own Node.js process instead, use -`@rivet-dev/agentos-core`. Read more in the -[embedded API quickstart](/agentos/docs/quickstart-embedded). diff --git a/docs/content/docs/index.mdx b/docs/content/docs/index.mdx index eac57843ad..8873c7905c 100644 --- a/docs/content/docs/index.mdx +++ b/docs/content/docs/index.mdx @@ -7,10 +7,10 @@ agentOS provides isolated filesystem, process, terminal, language, and networkin APIs in a lightweight VM. - + Run a VM inside a trusted Node.js backend. - + Create durable VMs by ID and control them over RivetKit or HTTP. @@ -20,11 +20,12 @@ APIs in a lightweight VM. Run agentOS two ways: -- **Embedded:** `@rivet-dev/agentos-core` runs VMs inside your Node.js backend. +- **Library:** `@rivet-dev/agentos-core` runs VMs inside your Node.js backend. Your application owns the VM's lifecycle, persistence, and access to host resources such as [host functions](/agentos/docs/host-functions) and [mounts](/agentos/docs/filesystem#mounts). - **Hosted:** the prebuilt `agentOS` Rivet Actor gives each VM a durable ID, versioned configuration, replayable output, and expiring previews, controlled - from `@rivet-dev/agentos` or plain HTTP. See - [Hosted VMs](/agentos/docs/hosted). + from `@rivet-dev/agentos` or plain HTTP. + +See [which one to use](/agentos/docs/quickstart#which-one-should-i-use). diff --git a/docs/content/docs/quickstart-hosted.mdx b/docs/content/docs/quickstart-hosted.mdx new file mode 100644 index 0000000000..295c15e090 --- /dev/null +++ b/docs/content/docs/quickstart-hosted.mdx @@ -0,0 +1,238 @@ +--- +title: "Quickstart: Hosted" +description: "Create durable agentOS VMs by ID and control them from the RivetKit client or plain HTTP." +skill: true +--- + +Hosted agentOS VMs run as the prebuilt `agentOS` Rivet Actor. 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](/agentos/docs/quickstart). + +## Which one should I use? + +| | [Library](/agentos/docs/quickstart) | [Hosted](/agentos/docs/quickstart-hosted) | +|---|---|---| +| **Runs** | Inside your Node.js process | As the prebuilt `agentOS` Rivet Actor | +| **Package** | `@rivet-dev/agentos-core` | `@rivet-dev/agentos`, or plain HTTP | +| **VM identity** | A handle your process owns until `dispose()` | A durable VM you create by key and reach by ID | +| **Configuration** | Passed to `AgentOs.create()` | Stored with the VM and versioned | +| **Clients** | Your backend | Your backend, or browsers with a scoped JWT | +| **Host access** | Host functions, host mounts, external sandboxes | None; configuration is serializable only | +| **Previews** | None | Expiring preview URLs | +| **Best for** | Embedding VMs in an existing backend | Long-lived VMs shared across clients | + +## Install + +```bash +npm install @rivet-dev/agentos +``` + +## Create a VM + + + +```ts TypeScript @nocheck +import { createAgentOsClient } from "@rivet-dev/agentos"; + +const client = createAgentOsClient({ + endpoint: "https://api.rivet.dev", + namespace: process.env.RIVET_NAMESPACE, + token: process.env.RIVET_TOKEN, +}); + +const vm = client.agentOS.getOrCreate(["my-vm"]); +const vmId = await vm.resolve(); +``` + +```bash cURL +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 } +``` + + + +The VM is durable: calling `getOrCreate` with the same key reaches the same VM. +The cURL examples below call it as `$VM_ID` with a token in `$VM_TOKEN`; see +[authentication](/agentos/docs/hosted-api#authentication). + +## Files and processes + + + +```ts TypeScript @nocheck +await vm.v1.filesystem.writeFile({ + path: "/home/agentos/hello.txt", + content: "Hello, world!", +}); + +const result = await vm.v1.process.run({ + command: "cat", + args: ["/home/agentos/hello.txt"], + options: { captureStdio: true }, +}); +console.log(result.stdout); +``` + +```bash cURL +curl "https://api.rivet.dev/gateway/$VM_ID/action/v1.filesystem.writeFile" \ + -H "x-rivet-token: $VM_TOKEN" \ + --json '{"args": [{ + "path": "/home/agentos/hello.txt", + "content": "Hello, world!" + }]}' + +curl "https://api.rivet.dev/gateway/$VM_ID/action/v1.process.run" \ + -H "x-rivet-token: $VM_TOKEN" \ + --json '{"args": [{ + "command": "cat", + "args": ["/home/agentos/hello.txt"], + "options": { "captureStdio": true } + }]}' +``` + + + +`v1.process.run` takes an executable and an argument array. Run `sh` with `-c` +when you need shell parsing. + +## Output survives reconnects + +Process output is retained in the VM. After a disconnect, page through it with +a cursor instead of losing it. + + + +```ts TypeScript @nocheck +const server = await vm.v1.process.spawn({ + command: "node", + args: ["/home/agentos/server.js"], +}); + +// Later, from any client +const { events, nextCursor } = await vm.v1.process.output.read({ + process: server, +}); +``` + +```bash cURL +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"] + }]}' +# → { "output": { "generation": 1, "pid": 42 } } + +# Later, from any client +curl "https://api.rivet.dev/gateway/$VM_ID/action/v1.process.output.read" \ + -H "x-rivet-token: $VM_TOKEN" \ + --json '{"args": [{"process": {"generation": 1, "pid": 42}}]}' +``` + + + +## Language runtimes + +Run JavaScript, TypeScript, and Python directly, with npm and pip installs +built in. + + + +```ts TypeScript @nocheck +await vm.v1.javascript.execute({ source: "console.log(1 + 2)" }); + +const { value } = await vm.v1.python.evaluate({ expression: "21 * 2" }); +``` + +```bash cURL +curl "https://api.rivet.dev/gateway/$VM_ID/action/v1.javascript.execute" \ + -H "x-rivet-token: $VM_TOKEN" \ + --json '{"args": [{"source": "console.log(1 + 2)"}]}' + +curl "https://api.rivet.dev/gateway/$VM_ID/action/v1.python.evaluate" \ + -H "x-rivet-token: $VM_TOKEN" \ + --json '{"args": [{"expression": "21 * 2"}]}' +``` + + + +## Install software + +Install tools from the [registry](/agentos/docs/registry) while the VM is +running. Packages are verified against their digest before their commands +appear on `/opt/agentos/bin`. + + + +```ts TypeScript @nocheck +// Registry packages are published next to a manifest with each release. +const base = "https://releases.rivet.dev/agent-os/latest/software/"; +const { artifacts } = await (await fetch(`${base}manifest.json`)).json(); +const ripgrep = artifacts.find((artifact) => artifact.name === "ripgrep"); + +const installed = await vm.v1.software.install({ + source: { url: new URL(ripgrep.path, base).href, digest: ripgrep.digest }, +}); +if (installed.config.state === "restart_required") { + await vm.v1.vm.restart({}); +} +``` + +```bash cURL +curl "https://api.rivet.dev/gateway/$VM_ID/action/v1.software.install" \ + -H "x-rivet-token: $VM_TOKEN" \ + --json '{"args": [{ + "source": { + "url": "https://releases.rivet.dev/agent-os/latest/software/packages/ripgrep/.aospkg", + "digest": "sha256:" + } + }]}' +``` + + + +## Persist files + +The filesystem is in memory by default. Give the VM a durable root when you +create it to keep files across VM replacement. + + + +```ts TypeScript @nocheck +const vm = client.agentOS.getOrCreate(["my-vm"], { + createWithInput: { + config: { filesystem: { root: { type: "durable" } } }, + }, +}); +``` + +```bash cURL +curl "https://api.rivet.dev/gateway/$VM_ID/action/v1.config.patch" \ + -H "x-rivet-token: $VM_TOKEN" \ + --json '{"args": [{"patch": {"filesystem": {"root": {"type": "durable"}}}}]}' + +curl "https://api.rivet.dev/gateway/$VM_ID/action/v1.vm.restart" \ + -H "x-rivet-token: $VM_TOKEN" \ + --json '{"args": [{}]}' +``` + + + +Creation input is used only when the VM is first created. Change an existing +VM's configuration with `v1.config.patch`; filesystem changes apply after +`v1.vm.restart`. + +## Next steps + +- See the [Hosted VM HTTP API](/agentos/docs/hosted-api) for authentication, + configuration, events, errors, and every action. +- Expose a VM port with [expiring previews](/agentos/docs/networking#hosted-api). +- Schedule commands with [Crons & Loops](/agentos/docs/cron#hosted-api). diff --git a/docs/content/docs/quickstart.mdx b/docs/content/docs/quickstart.mdx index 0534ae811b..19768faa3e 100644 --- a/docs/content/docs/quickstart.mdx +++ b/docs/content/docs/quickstart.mdx @@ -1,9 +1,26 @@ --- -title: "Quickstart" -description: "Run an agentOS VM directly inside a Node.js application." +title: "Quickstart: Library" +description: "Run agentOS VMs directly inside a Node.js application with @rivet-dev/agentos-core." skill: true --- +Use agentOS as a library to run VMs inside your own Node.js process. To create +durable VMs by ID and control them over HTTP instead, see the +[hosted quickstart](/agentos/docs/quickstart-hosted). + +## Which one should I use? + +| | [Library](/agentos/docs/quickstart) | [Hosted](/agentos/docs/quickstart-hosted) | +|---|---|---| +| **Runs** | Inside your Node.js process | As the prebuilt `agentOS` Rivet Actor | +| **Package** | `@rivet-dev/agentos-core` | `@rivet-dev/agentos`, or plain HTTP | +| **VM identity** | A handle your process owns until `dispose()` | A durable VM you create by key and reach by ID | +| **Configuration** | Passed to `AgentOs.create()` | Stored with the VM and versioned | +| **Clients** | Your backend | Your backend, or browsers with a scoped JWT | +| **Host access** | Host functions, host mounts, external sandboxes | None; configuration is serializable only | +| **Previews** | None | Expiring preview URLs | +| **Best for** | Embedding VMs in an existing backend | Long-lived VMs shared across clients | + ## Install ```bash @@ -12,27 +29,51 @@ npm install @rivet-dev/agentos-core ## Create a VM - + -The VM remains active until `dispose()` is called. Its filesystem is in memory -by default; see [Persistence](/agentos/docs/persistence) to keep files across VM -recreation. +The VM runs until `dispose()` is called. - -Core runs in a trusted host process and can use -[host functions](/agentos/docs/host-functions), host-backed -[mounts](/agentos/docs/filesystem#mounts), local packages, and remote verified -package URLs. - +## Files and processes -## Next steps + + +`process.execFile` takes an executable and an argument array. Use +`process.exec` when you need shell parsing. + +## Output survives reconnects + +Retain a process's output in the VM, then page through it with a cursor +instead of losing it. + + + +## Language runtimes -- To create durable VMs by ID instead of embedding them, see - [Hosted VMs](/agentos/docs/hosted). +Run JavaScript, TypeScript, and Python directly, with npm and pip installs +built in. + + + +## Install software + +Install tools from the [registry](/agentos/docs/registry) while the VM is +running. Packages are verified against their digest before their commands +appear on `/opt/agentos/bin`. + + + +## Persist files + +The filesystem is in memory by default. Store the root filesystem in SQLite to +keep files across VM recreation. + + + +## Next steps - Run commands with [Bash](/agentos/docs/bash), or code with [Node.js](/agentos/docs/javascript) and [Python](/agentos/docs/python). -- Install tools from the [registry](/agentos/docs/registry) with - [Software](/agentos/docs/software). +- Expose your own functions to guest code with + [host functions](/agentos/docs/host-functions). - Lock the VM down with [Permissions](/agentos/docs/permissions) and [Resource Limits](/agentos/docs/resource-limits). diff --git a/docs/content/docs/versus-sandbox.mdx b/docs/content/docs/versus-sandbox.mdx index 6cdb967bf5..ba2cffab47 100644 --- a/docs/content/docs/versus-sandbox.mdx +++ b/docs/content/docs/versus-sandbox.mdx @@ -18,7 +18,7 @@ skill: true | **Backend integration** | Direct. [Host functions](/agentos/docs/host-functions) call your functions with zero latency. | Indirect. Requires network calls back to your backend. | | **Credentials** | [Host functions](/agentos/docs/host-functions) keep credentials on the host. | Must be injected into the sandbox environment. | | **Permissions** | Granular, deny-by-default. | Coarse-grained (container-level). | -| **Infrastructure** | An npm package in your backend, or [hosted VMs](/agentos/docs/hosted). | Vendor account + API keys. | +| **Infrastructure** | An npm package in your backend, or [hosted VMs](/agentos/docs/quickstart-hosted). | Vendor account + API keys. | | **Best for** | Coding, file manipulation, scripting, API calls, orchestration. | Browsers, desktop automation, native compilation, dev servers. | ## When to use each diff --git a/docs/sidebar.json b/docs/sidebar.json index a9c2ab8a37..4fbdb21546 100644 --- a/docs/sidebar.json +++ b/docs/sidebar.json @@ -9,29 +9,19 @@ "icon": "faSquareInfo" }, { - "title": "Quickstart", + "title": "Quickstart: Library", "href": "/agentos/docs/quickstart", "icon": "faForwardFast" }, { - "title": "agentOS vs Sandbox", - "href": "/agentos/docs/versus-sandbox", - "icon": "faScaleBalanced" - } - ] - }, - { - "title": "Hosted VMs", - "pages": [ - { - "title": "Overview", - "href": "/agentos/docs/hosted", + "title": "Quickstart: Hosted", + "href": "/agentos/docs/quickstart-hosted", "icon": "faCloud" }, { - "title": "Hosted VM API", - "href": "/agentos/docs/actor-api", - "icon": "faBook" + "title": "agentOS vs Sandbox", + "href": "/agentos/docs/versus-sandbox", + "icon": "faScaleBalanced" } ] }, @@ -125,6 +115,10 @@ { "title": "Reference", "pages": [ + { + "title": "Hosted VM HTTP API", + "href": "/agentos/docs/hosted-api" + }, { "title": "Custom Software", "collapsible": true, diff --git a/examples/embedded/quickstart.ts b/examples/embedded/quickstart.ts new file mode 100644 index 0000000000..a575865bcb --- /dev/null +++ b/examples/embedded/quickstart.ts @@ -0,0 +1,56 @@ +// docs:start create +import { AgentOs } from "@rivet-dev/agentos-core"; + +// `AgentOs.create()` boots a VM inside this process and returns a handle to it. +const vm = await AgentOs.create(); +// docs:end create + +try { + // docs:start files-processes + await vm.filesystem.writeFile("/home/agentos/hello.txt", "Hello, world!"); + + const result = await vm.process.execFile("cat", ["/home/agentos/hello.txt"], { + output: { capture: "all" }, + }); + console.log(result.stdout); + // docs:end files-processes + + // docs:start output + await vm.filesystem.writeFile( + "/home/agentos/server.js", + 'require("http").createServer((req, res) => res.end("ok")).listen(3000); console.log("listening");', + ); + const server = await vm.process.spawn("node", ["/home/agentos/server.js"], { + output: { retainEvents: true }, + }); + + // Later, page through the retained output with a cursor + const { events, nextCursor } = await vm.process.readOutput(server.pid); + console.log(events.length, nextCursor); + // docs:end output + + // docs:start languages + await vm.javascript.execute("console.log(1 + 2)"); + + const answer = await vm.python.evaluate("21 * 2"); + console.log(answer.outcome === "succeeded" ? answer.value : answer.error); + // docs:end languages + + // docs:start software + // Registry packages are published next to a manifest with each release. + const base = "https://releases.rivet.dev/agent-os/latest/software/"; + const manifest = (await (await fetch(`${base}manifest.json`)).json()) as { + artifacts: { name: string; digest: string; path: string }[]; + }; + const ripgrep = manifest.artifacts.find((artifact) => artifact.name === "ripgrep"); + if (!ripgrep) throw new Error("ripgrep is not in the manifest"); + + await vm.software.install({ + type: "url", + url: new URL(ripgrep.path, base).href, + expectedDigest: ripgrep.digest, + }); + // docs:end software +} finally { + await vm.dispose(); +} From 6fd129eb386b63716871e4b723dd79bf86633cde Mon Sep 17 00:00:00 2001 From: Sree Narayanan Date: Mon, 12 Oct 2026 09:27:29 +0000 Subject: [PATCH 05/18] docs(agentos): fix the Hosted VM HTTP API overview table header Amp-Thread-ID: https://ampcode.com/threads/T-c7d08b58-92c1-4fc0-abb9-7a15f4504a4f Co-authored-by: Amp --- docs/content/docs/hosted-api.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/docs/hosted-api.mdx b/docs/content/docs/hosted-api.mdx index 9afcac0d46..0378462f8b 100644 --- a/docs/content/docs/hosted-api.mdx +++ b/docs/content/docs/hosted-api.mdx @@ -16,7 +16,7 @@ The contract is in alpha (`agentos-sdk.dev/v1alpha1`). The actor and the ## Overview -| | | +| Field | Value | |---|---| | **Base URL** | `https://api.rivet.dev` | | **Actor name** | `agentOS` | From 3fde2e2c0adc72097b30327350e1e3fd47d85ffb Mon Sep 17 00:00:00 2001 From: Sree Narayanan Date: Mon, 12 Oct 2026 09:31:50 +0000 Subject: [PATCH 06/18] docs(agentos): title the quickstarts Quickstart (Library) and Quickstart (Hosted) Amp-Thread-ID: https://ampcode.com/threads/T-c7d08b58-92c1-4fc0-abb9-7a15f4504a4f Co-authored-by: Amp --- docs/content/docs/index.mdx | 4 ++-- docs/content/docs/quickstart-hosted.mdx | 2 +- docs/content/docs/quickstart.mdx | 2 +- docs/sidebar.json | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/content/docs/index.mdx b/docs/content/docs/index.mdx index 8873c7905c..abbb591429 100644 --- a/docs/content/docs/index.mdx +++ b/docs/content/docs/index.mdx @@ -7,10 +7,10 @@ agentOS provides isolated filesystem, process, terminal, language, and networkin APIs in a lightweight VM. - + Run a VM inside a trusted Node.js backend. - + Create durable VMs by ID and control them over RivetKit or HTTP. diff --git a/docs/content/docs/quickstart-hosted.mdx b/docs/content/docs/quickstart-hosted.mdx index 295c15e090..6ac753c01c 100644 --- a/docs/content/docs/quickstart-hosted.mdx +++ b/docs/content/docs/quickstart-hosted.mdx @@ -1,5 +1,5 @@ --- -title: "Quickstart: Hosted" +title: "Quickstart (Hosted)" description: "Create durable agentOS VMs by ID and control them from the RivetKit client or plain HTTP." skill: true --- diff --git a/docs/content/docs/quickstart.mdx b/docs/content/docs/quickstart.mdx index 19768faa3e..229a12b8e1 100644 --- a/docs/content/docs/quickstart.mdx +++ b/docs/content/docs/quickstart.mdx @@ -1,5 +1,5 @@ --- -title: "Quickstart: Library" +title: "Quickstart (Library)" description: "Run agentOS VMs directly inside a Node.js application with @rivet-dev/agentos-core." skill: true --- diff --git a/docs/sidebar.json b/docs/sidebar.json index 4fbdb21546..5b32123de2 100644 --- a/docs/sidebar.json +++ b/docs/sidebar.json @@ -9,14 +9,14 @@ "icon": "faSquareInfo" }, { - "title": "Quickstart: Library", + "title": "Quickstart (Library)", "href": "/agentos/docs/quickstart", "icon": "faForwardFast" }, { - "title": "Quickstart: Hosted", + "title": "Quickstart (Hosted)", "href": "/agentos/docs/quickstart-hosted", - "icon": "faCloud" + "icon": "faForwardFast" }, { "title": "agentOS vs Sandbox", From 85998ec672be1ef1dd587ccc0682dd28b498aa62 Mon Sep 17 00:00:00 2001 From: Sree Narayanan Date: Mon, 12 Oct 2026 09:33:18 +0000 Subject: [PATCH 07/18] docs(agentos): list the hosted quickstart before the library quickstart Amp-Thread-ID: https://ampcode.com/threads/T-c7d08b58-92c1-4fc0-abb9-7a15f4504a4f Co-authored-by: Amp --- docs/content/docs/index.mdx | 14 +++++++------- docs/content/docs/quickstart-hosted.mdx | 18 +++++++++--------- docs/content/docs/quickstart.mdx | 18 +++++++++--------- docs/sidebar.json | 8 ++++---- 4 files changed, 29 insertions(+), 29 deletions(-) diff --git a/docs/content/docs/index.mdx b/docs/content/docs/index.mdx index abbb591429..2589e907b1 100644 --- a/docs/content/docs/index.mdx +++ b/docs/content/docs/index.mdx @@ -7,12 +7,12 @@ agentOS provides isolated filesystem, process, terminal, language, and networkin APIs in a lightweight VM. - - Run a VM inside a trusted Node.js backend. - Create durable VMs by ID and control them over RivetKit or HTTP. + + Run a VM inside a trusted Node.js backend. + Install immutable `.aospkg` command packages. @@ -20,12 +20,12 @@ APIs in a lightweight VM. Run agentOS two ways: +- **Hosted:** the prebuilt `agentOS` Rivet Actor gives each VM a durable ID, + versioned configuration, replayable output, and expiring previews, controlled + from `@rivet-dev/agentos` or plain HTTP. - **Library:** `@rivet-dev/agentos-core` runs VMs inside your Node.js backend. Your application owns the VM's lifecycle, persistence, and access to host resources such as [host functions](/agentos/docs/host-functions) and [mounts](/agentos/docs/filesystem#mounts). -- **Hosted:** the prebuilt `agentOS` Rivet Actor gives each VM a durable ID, - versioned configuration, replayable output, and expiring previews, controlled - from `@rivet-dev/agentos` or plain HTTP. -See [which one to use](/agentos/docs/quickstart#which-one-should-i-use). +See [which one to use](/agentos/docs/quickstart-hosted#which-one-should-i-use). diff --git a/docs/content/docs/quickstart-hosted.mdx b/docs/content/docs/quickstart-hosted.mdx index 6ac753c01c..b62ae5ae7e 100644 --- a/docs/content/docs/quickstart-hosted.mdx +++ b/docs/content/docs/quickstart-hosted.mdx @@ -11,16 +11,16 @@ VMs inside your own Node.js process instead, see the ## Which one should I use? -| | [Library](/agentos/docs/quickstart) | [Hosted](/agentos/docs/quickstart-hosted) | +| | [Hosted](/agentos/docs/quickstart-hosted) | [Library](/agentos/docs/quickstart) | |---|---|---| -| **Runs** | Inside your Node.js process | As the prebuilt `agentOS` Rivet Actor | -| **Package** | `@rivet-dev/agentos-core` | `@rivet-dev/agentos`, or plain HTTP | -| **VM identity** | A handle your process owns until `dispose()` | A durable VM you create by key and reach by ID | -| **Configuration** | Passed to `AgentOs.create()` | Stored with the VM and versioned | -| **Clients** | Your backend | Your backend, or browsers with a scoped JWT | -| **Host access** | Host functions, host mounts, external sandboxes | None; configuration is serializable only | -| **Previews** | None | Expiring preview URLs | -| **Best for** | Embedding VMs in an existing backend | Long-lived VMs shared across clients | +| **Runs** | As the prebuilt `agentOS` Rivet Actor | Inside your Node.js process | +| **Package** | `@rivet-dev/agentos`, or plain HTTP | `@rivet-dev/agentos-core` | +| **VM identity** | A durable VM you create by key and reach by ID | A handle your process owns until `dispose()` | +| **Configuration** | Stored with the VM and versioned | Passed to `AgentOs.create()` | +| **Clients** | Your backend, or browsers with a scoped JWT | Your backend | +| **Host access** | None; configuration is serializable only | Host functions, host mounts, external sandboxes | +| **Previews** | Expiring preview URLs | None | +| **Best for** | Long-lived VMs shared across clients | Embedding VMs in an existing backend | ## Install diff --git a/docs/content/docs/quickstart.mdx b/docs/content/docs/quickstart.mdx index 229a12b8e1..0729618716 100644 --- a/docs/content/docs/quickstart.mdx +++ b/docs/content/docs/quickstart.mdx @@ -10,16 +10,16 @@ durable VMs by ID and control them over HTTP instead, see the ## Which one should I use? -| | [Library](/agentos/docs/quickstart) | [Hosted](/agentos/docs/quickstart-hosted) | +| | [Hosted](/agentos/docs/quickstart-hosted) | [Library](/agentos/docs/quickstart) | |---|---|---| -| **Runs** | Inside your Node.js process | As the prebuilt `agentOS` Rivet Actor | -| **Package** | `@rivet-dev/agentos-core` | `@rivet-dev/agentos`, or plain HTTP | -| **VM identity** | A handle your process owns until `dispose()` | A durable VM you create by key and reach by ID | -| **Configuration** | Passed to `AgentOs.create()` | Stored with the VM and versioned | -| **Clients** | Your backend | Your backend, or browsers with a scoped JWT | -| **Host access** | Host functions, host mounts, external sandboxes | None; configuration is serializable only | -| **Previews** | None | Expiring preview URLs | -| **Best for** | Embedding VMs in an existing backend | Long-lived VMs shared across clients | +| **Runs** | As the prebuilt `agentOS` Rivet Actor | Inside your Node.js process | +| **Package** | `@rivet-dev/agentos`, or plain HTTP | `@rivet-dev/agentos-core` | +| **VM identity** | A durable VM you create by key and reach by ID | A handle your process owns until `dispose()` | +| **Configuration** | Stored with the VM and versioned | Passed to `AgentOs.create()` | +| **Clients** | Your backend, or browsers with a scoped JWT | Your backend | +| **Host access** | None; configuration is serializable only | Host functions, host mounts, external sandboxes | +| **Previews** | Expiring preview URLs | None | +| **Best for** | Long-lived VMs shared across clients | Embedding VMs in an existing backend | ## Install diff --git a/docs/sidebar.json b/docs/sidebar.json index 5b32123de2..3bff987c6e 100644 --- a/docs/sidebar.json +++ b/docs/sidebar.json @@ -9,13 +9,13 @@ "icon": "faSquareInfo" }, { - "title": "Quickstart (Library)", - "href": "/agentos/docs/quickstart", + "title": "Quickstart (Hosted)", + "href": "/agentos/docs/quickstart-hosted", "icon": "faForwardFast" }, { - "title": "Quickstart (Hosted)", - "href": "/agentos/docs/quickstart-hosted", + "title": "Quickstart (Library)", + "href": "/agentos/docs/quickstart", "icon": "faForwardFast" }, { From 5edf5c6c93cefb7527130da8be4535e4b2c78b34 Mon Sep 17 00:00:00 2001 From: Sree Narayanan Date: Mon, 12 Oct 2026 09:48:15 +0000 Subject: [PATCH 08/18] docs(agentos): show Hosted and Library examples as tabs, add Self-Hosting Replace the separate Hosted API sections with Hosted and Library tabs on each example, hosted first, and note hosted-only and library-only features inline. Add a Self-Hosting reference page for running the control plane and the rivetdev/services worker, linked from the quickstart comparison table. Amp-Thread-ID: https://ampcode.com/threads/T-c7d08b58-92c1-4fc0-abb9-7a15f4504a4f Co-authored-by: Amp --- .../docs/architecture/software-cache.mdx | 2 +- docs/content/docs/bash.mdx | 114 ++++++++--- docs/content/docs/cron.mdx | 86 +++++---- docs/content/docs/filesystem.mdx | 126 ++++++------ docs/content/docs/host-functions.mdx | 8 +- docs/content/docs/hosted-api.mdx | 25 ++- docs/content/docs/javascript.mdx | 138 ++++++++++---- docs/content/docs/networking.mdx | 85 +++++---- docs/content/docs/permissions.mdx | 16 +- docs/content/docs/persistence.mdx | 65 ++++--- docs/content/docs/processes.mdx | 179 ++++++++++-------- docs/content/docs/python.mdx | 73 ++++--- docs/content/docs/quickstart-hosted.mdx | 16 +- docs/content/docs/quickstart.mdx | 2 +- docs/content/docs/resource-limits.mdx | 62 +++--- docs/content/docs/sandboxes.mdx | 7 +- docs/content/docs/self-hosting.mdx | 50 +++++ docs/content/docs/software.mdx | 137 ++++++++------ docs/sidebar.json | 8 +- 19 files changed, 744 insertions(+), 455 deletions(-) create mode 100644 docs/content/docs/self-hosting.mdx diff --git a/docs/content/docs/architecture/software-cache.mdx b/docs/content/docs/architecture/software-cache.mdx index 453eedb24a..67f8caaf99 100644 --- a/docs/content/docs/architecture/software-cache.mdx +++ b/docs/content/docs/architecture/software-cache.mdx @@ -60,4 +60,4 @@ default process warm deadline is 10 seconds. Required packages still determine VM readiness. An advisory preload timeout or coordinator failure only reduces the cache hit rate. -See [Software](/agentos/docs/software#hosted-api) for the installation API. +See [Software](/agentos/docs/software#install-while-running) for the installation API. diff --git a/docs/content/docs/bash.mdx b/docs/content/docs/bash.mdx index a2ca1f6f32..61bbeda8e9 100644 --- a/docs/content/docs/bash.mdx +++ b/docs/content/docs/bash.mdx @@ -9,8 +9,11 @@ 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. -- **`process.exec()`**: Runs a bash command line. Use for pipes, redirects, globs. -- **`process.execFile()`**: Injection-safe; args are never parsed by a shell. +- **Hosted `v1.process.run`** takes an executable and an argument array. Run + `sh` with `-c` for pipes, redirects, and globs. +- **Library `process.exec()`** runs a bash command line, and + **`process.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](/agentos/docs/javascript) or @@ -19,57 +22,112 @@ instead of a chain of shell calls. ## Run commands - + + +```ts Hosted @nocheck +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 +``` + + + + ## Background and interactive work -`spawn` starts a long-lived process and returns a `pid`. From there you get +`spawn` starts a long-lived process and returns a handle. From there you get stdin, a PTY, output replay, signals, and waiting. - + -## Files and software +```ts Hosted @nocheck +import { createAgentOsClient } from "@rivet-dev/agentos"; -Shell commands see the persistent [filesystem](/agentos/docs/filesystem) shared by -JavaScript and Python. Common POSIX commands ship by default; more is -projected through the [software registry](/agentos/docs/software). +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: [] }); -## Host functions +await vm.v1.process.stdin.write({ process: shell, data: "echo from the background\n" }); +await vm.v1.process.stdin.close({ process: shell }); -[Host functions](/agentos/docs/host-functions) appear as commands, so pipelines use trusted host -capabilities without putting credentials inside the VM. +await vm.v1.process.wait({ process: shell }); +const replay = await vm.v1.process.output.read({ process: shell }); +console.log(replay.events.length); +``` - + -## Permissions, limits, and timeouts + -Every command inherits the VM [permission policy](/agentos/docs/permissions) and -[resource limits](/agentos/docs/resource-limits). +## Files and software + +Shell commands see the same [filesystem](/agentos/docs/filesystem) as +JavaScript and Python. Common POSIX commands ship by default; more is +projected through the [software registry](/agentos/docs/software). - + -## Hosted API +```ts Hosted @nocheck +import { createAgentOsClient } from "@rivet-dev/agentos"; -Hosted VMs have no shell-string `exec`. Use `v1.process.run` with an explicit -executable and argument array, or run `sh` with `-c` for shell parsing. +const client = createAgentOsClient({ endpoint: "https://api.rivet.dev" }); +const vm = client.agentOS.getOrCreate(["my-vm"]); -```ts TypeScript @nocheck -const result = await vm.v1.process.run({ +await vm.v1.process.run({ command: "sh", - args: ["-c", "echo hello && ls /home/agentos"], + 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(result.stdout, result.status.exitCode); +console.log(lines.stdout.trim()); // "3" ``` -See [Processes & Shells](/agentos/docs/processes#hosted-api). + + + + +## Host functions + +[Host functions](/agentos/docs/host-functions) appear as commands, so pipelines +use trusted host capabilities without putting credentials inside the VM. Host +functions are library-only. + + + +## Permissions, limits, and timeouts + +Every command inherits the VM [permission policy](/agentos/docs/permissions) and +[resource limits](/agentos/docs/resource-limits). + + ## Embedded API -The examples above use `AgentOs.create()`. The host owns `dispose()`, and files -only outlive the VM when its root filesystem is stored in SQLite; see +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](/agentos/docs/persistence). Read more in the [embedded API quickstart](/agentos/docs/quickstart-embedded). diff --git a/docs/content/docs/cron.mdx b/docs/content/docs/cron.mdx index 6f0071d9f7..51f1fbd48d 100644 --- a/docs/content/docs/cron.mdx +++ b/docs/content/docs/cron.mdx @@ -1,60 +1,68 @@ --- title: "Crons & Loops" -description: "Schedule recurring and one-shot VM commands while a VM is running." +description: "Schedule recurring and one-shot VM commands." skill: true --- -`vm.cron` schedules commands inside the VM. Jobs run while the embedding -process and the VM are alive. Durable scheduling across process restarts is -the responsibility of the application that owns the VM, or use a -[hosted VM](#hosted-api), which stores its jobs. +Schedule commands to run inside the VM on a cron expression. Hosted VMs store +their jobs with the VM. Library jobs run while the embedding process and the VM +are alive, so durable scheduling across process restarts is up to the +application that owns the VM. ## Schedule a command - + -`schedule` takes a standard cron expression or an ISO 8601 timestamp for a -one-shot job. `action` is either `{ type: "exec", command, args }`, which runs a -command in the VM, or `{ type: "callback", fn }`, which calls a function in the -host process. +```ts Hosted @nocheck +import { createAgentOsClient } from "@rivet-dev/agentos"; -`overlap` controls what happens when a job fires while its previous run is -still going: `"allow"` (the default) starts another run, `"skip"` drops the new -run, and `"queue"` waits for the previous run to finish. +const client = createAgentOsClient({ endpoint: "https://api.rivet.dev" }); +const vm = client.agentOS.getOrCreate(["my-vm"]); -## Inspect and cancel jobs - -- `cron.list()` returns each job's schedule, action, `lastRun`, `nextRun`, - `runCount`, and whether it is running. -- `cron.cancel(id)` cancels a job. The handle returned by `schedule` also has a - `cancel()` method. -- `vm.onCronEvent(handler)` receives `cron:fire`, `cron:complete`, and - `cron:error` events. - -Pass `scheduleDriver` to `AgentOs.create()` to replace the default timer-based -driver, for example with one backed by your own durable scheduler. - -## Hosted API - -Hosted VMs schedule commands with `v1.cron.*` actions. Jobs are named and -stored with the VM. - -```ts TypeScript @nocheck -await vm.v1.cron.schedule({ +const job = await vm.v1.cron.schedule({ name: "cleanup", expression: "0 * * * *", command: "rm", args: ["-rf", "/tmp/cache"], options: { env: {} }, }); +console.log("Scheduled:", job.name); -for (const job of await vm.v1.cron.list({})) { - console.log(job.name, job.expression); -} +const conn = vm.connect(); +await conn.ready; +conn.on("cron.fired", (event) => { + console.log("Cron event:", event); +}); -await vm.v1.cron.cancel({ name: "cleanup" }); +for (const scheduled of await vm.v1.cron.list({})) { + console.log(scheduled.name, scheduled.expression); +} ``` -Connected clients receive a `cron.fired` event for each run. A VM holds up to -1,024 jobs, with names and expressions up to 256 bytes, and retains 32 runs per -job by default (256 maximum). + + + + +Hosted jobs are identified by `name` and run a command with arguments. A hosted +VM holds up to 1,024 jobs, with names and expressions up to 256 bytes, and +retains 32 runs per job by default (256 maximum). + +In the library, `schedule` takes a standard cron expression or an ISO 8601 +timestamp for a one-shot job. `action` is either `{ type: "exec", command, args }`, +which runs a command in the VM, or `{ type: "callback", fn }`, which calls a +function in the host process. `overlap` controls what happens when a job fires +while its previous run is still going: `"allow"` (the default) starts another +run, `"skip"` drops the new run, and `"queue"` waits for the previous run to +finish. + +## Inspect and cancel jobs + +| Operation | Hosted | Library | +|---|---|---| +| List jobs | `v1.cron.list` | `cron.list()`, with `lastRun`, `nextRun`, `runCount`, and `running` | +| Cancel a job | `v1.cron.cancel({ name })` | `cron.cancel(id)`, or `cancel()` on the returned job | +| Run events | `cron.fired` | `vm.onCronEvent` with `cron:fire`, `cron:complete`, and `cron:error` | + +In the library, pass `scheduleDriver` to `AgentOs.create()` to replace the +default timer-based driver, for example with one backed by your own durable +scheduler. diff --git a/docs/content/docs/filesystem.mdx b/docs/content/docs/filesystem.mdx index d96bd5d020..f72f7b23dd 100644 --- a/docs/content/docs/filesystem.mdx +++ b/docs/content/docs/filesystem.mdx @@ -1,53 +1,20 @@ --- title: "Filesystem" -description: "Read and write VM files, and mount host directories, S3, and other filesystem backends." +description: "Read and write VM files, and mount durable storage, host directories, S3, and other filesystem backends." --- -File operations live under `vm.filesystem`. Every language and shell in the VM -sees the same filesystem, so files written here are immediately visible to -Bash, JavaScript, Python, and installed software. +Every language and shell in the VM sees the same filesystem, so files written +here are immediately visible to Bash, JavaScript, Python, and installed +software. - + -The namespace also provides batch `readFiles` and `writeFiles`, `stat`, -`readdirEntries`, `exists`, `move`, `remove`, `export` for a snapshot of the -root filesystem, and `mount`, `unmount`, and `listMounts` for mounts on a -running VM. `stat` and `readdirRecursive` report file lengths as `size`. +```ts Hosted @nocheck +import { createAgentOsClient } from "@rivet-dev/agentos"; -## Root filesystem - -By default, each VM uses an in-memory overlay over the bundled base image. -Writes survive for the VM's lifetime and are discarded on `dispose()`. See -[Persistence](/agentos/docs/persistence) to keep files across VM recreation. - -## Mounts - -Mount additional filesystem backends at any guest path. Native mount plugins -are identified by an `id` and a `config` object: - -| Plugin | Backend | -|---|---| -| `host_dir` | A directory on the host, confined to the configured path | -| `chunked_s3` | An S3 or S3-compatible bucket | -| `chunked_sqlite` | The VM's SQLite `database` | -| `google_drive` | A Google Drive folder | - - - -Host-backed mounts are confined to their configured root on every operation, -including symlink and `..` resolution. `nodeModulesMount()` is a shortcut that -exposes a host `node_modules` tree read-only at `/root/node_modules`. - -File transfers are bounded. An operation that exceeds a payload limit fails -with a typed limit error that names the limit. - -## Hosted API - -Hosted VMs expose the same operations as `v1.filesystem.*` actions. Byte counts -in `v1.filesystem.stat` and `v1.filesystem.readdirRecursive` are reported as -`sizeBytes`. +const client = createAgentOsClient({ endpoint: "https://api.rivet.dev" }); +const vm = client.agentOS.getOrCreate(["my-vm"]); -```ts TypeScript @nocheck await vm.v1.filesystem.writeFile({ path: "/home/agentos/hello.txt", content: "Hello, world!", @@ -57,32 +24,71 @@ const content = await vm.v1.filesystem.readFile({ }); console.log(new TextDecoder().decode(content)); -// Batch writes create parent directories automatically. +await vm.v1.filesystem.mkdir({ path: "/home/agentos/src", recursive: false }); await vm.v1.filesystem.writeFiles({ entries: [ - { path: "/home/agentos/src/index.ts", content: "console.log('hello');" }, + { path: "/home/agentos/src/index.ts", content: "console.log('hi');" }, + { + path: "/home/agentos/src/utils.ts", + content: "export const add = (a: number, b: number) => a + b;", + }, ], }); -const tree = await vm.v1.filesystem.readdirRecursive({ +const entries = await vm.v1.filesystem.readdirRecursive({ path: "/home/agentos", exclude: [], }); -for (const entry of tree) console.log(entry.type, entry.path, entry.sizeBytes); +for (const entry of entries) { + console.log(entry.type, entry.path); +} ``` -A hosted VM's filesystem configuration is a closed registry. By default, each -VM uses an ephemeral in-memory overlay over the bundled base image. Its writes -survive for that VM's lifetime but are discarded when the VM is replaced. Use a -`durable` root or mount in the VM's config when files must survive VM -replacement: + + + + +Both APIs also provide batch `readFiles`, `stat`, `readdir`, `readdirEntries`, +`exists`, `move`, `remove`, `export` for a snapshot of the root filesystem, and +`listMounts`. Hosted VMs report byte counts in `stat` and `readdirRecursive` as +`sizeBytes`; the library reports them as `size`. The library can also `mount` +and `unmount` filesystems on a running VM. -```ts TypeScript @nocheck +## Root filesystem + +By default, each VM uses an in-memory overlay over the bundled base image. +Writes survive for the VM's lifetime and are discarded when the VM is replaced +or disposed. See [Persistence](/agentos/docs/persistence) to keep files across +VM recreation. + +## Mounts + +Mount additional filesystem backends at any guest path. + +Hosted VMs resolve filesystem configuration through a closed registry, such as +`durable` storage. Unknown backends, JavaScript VFS objects, callbacks, and host +paths are rejected. The library accepts native mount plugins, each identified +by an `id` and a `config` object: + +| Plugin | Backend | +|---|---| +| `host_dir` | A directory on the host, confined to the configured path | +| `chunked_s3` | An S3 or S3-compatible bucket | +| `chunked_sqlite` | The VM's SQLite `database` | +| `google_drive` | A Google Drive folder | + + + +```ts Hosted @nocheck +import { createAgentOsClient } from "@rivet-dev/agentos"; + +const client = createAgentOsClient({ endpoint: "https://api.rivet.dev" }); + +// Mount durable storage at /data when the VM is first created. const vm = client.agentOS.getOrCreate(["my-vm"], { createWithInput: { config: { filesystem: { - root: { type: "durable" }, mounts: [{ path: "/data", backend: { type: "durable" } }], }, }, @@ -90,6 +96,14 @@ const vm = client.agentOS.getOrCreate(["my-vm"], { }); ``` -Unknown backends, JavaScript VFS objects, callbacks, and host paths are -rejected, so host-directory and other host-backed mounts are available only -through the embedded API. + + + + +Host-backed mounts are confined to their configured root on every operation, +including symlink and `..` resolution. `nodeModulesMount()` is a library +shortcut that exposes a host `node_modules` tree read-only at +`/root/node_modules`. + +File transfers are bounded. An operation that exceeds a payload limit fails +with a typed limit error that names the limit. diff --git a/docs/content/docs/host-functions.mdx b/docs/content/docs/host-functions.mdx index 43d53ef6db..819abe25e7 100644 --- a/docs/content/docs/host-functions.mdx +++ b/docs/content/docs/host-functions.mdx @@ -4,7 +4,7 @@ description: "Expose trusted host functions to guest programs in agentOS VMs as skill: true --- -Host functions expose JavaScript functions from your process to code running inside the VM. Each function is defined with a Zod input schema and becomes an auto-generated CLI command at `/bin/agentos-{name}`, plus an async function that inline JavaScript can call directly. +Host functions expose JavaScript functions from your process to code running inside the VM. They run as in-process callbacks, so they are available in the library only, not on hosted VMs. Each function is defined with a Zod input schema and becomes an auto-generated CLI command at `/bin/agentos-{name}`, plus an async function that inline JavaScript can call directly. ## Getting started @@ -90,12 +90,6 @@ Host function calls from guest code invoke your `execute()` functions on the hos Because handlers run with full host access, do not expose functions that could compromise the host without appropriate safeguards. Use the `hostFunction` [permission](/agentos/docs/permissions) scope to restrict which collections guest code may call. -## Hosted API - -Host functions run as in-process callbacks, so they are not available on -hosted VMs. Hosted guest code reaches external services over the network, -subject to the VM's permissions. - ## Embedded API Host functions are passed to `AgentOs.create()` and their handlers run in the diff --git a/docs/content/docs/hosted-api.mdx b/docs/content/docs/hosted-api.mdx index 0378462f8b..56fdbbc136 100644 --- a/docs/content/docs/hosted-api.mdx +++ b/docs/content/docs/hosted-api.mdx @@ -18,7 +18,7 @@ The contract is in alpha (`agentos-sdk.dev/v1alpha1`). The actor and the | Field | Value | |---|---| -| **Base URL** | `https://api.rivet.dev` | +| **Base URL** | `https://api.rivet.dev`, or your control plane when [self-hosting](/agentos/docs/self-hosting) | | **Actor name** | `agentOS` | | **Create a VM** | `PUT /actors?namespace={namespace}` | | **Call an action** | `POST /gateway/{vm_id}/action/v1.{group}.{action}` | @@ -49,11 +49,7 @@ input on first creation: ```ts TypeScript @nocheck import { createAgentOsClient } from "@rivet-dev/agentos"; -const client = createAgentOsClient({ - endpoint: "https://api.rivet.dev", - namespace: process.env.RIVET_NAMESPACE, - token: process.env.RIVET_TOKEN, -}); +const client = createAgentOsClient({ endpoint: "https://api.rivet.dev" }); const vm = client.agentOS.getOrCreate(["my-vm"], { createWithInput: { config: { environment: { CI: "true" } } }, @@ -128,13 +124,14 @@ Process, terminal, context, and network stream handles carry the VM generation, for example `{ "generation": 1, "pid": 42 }` for a process. The hosted contract has no agent sessions and no shell-string `process.exec`. -Each capability page documents its actions under **Hosted API**: -[Filesystem](/agentos/docs/filesystem#hosted-api), -[Processes & Shells](/agentos/docs/processes#hosted-api), -[Networking](/agentos/docs/networking#hosted-api), -[Software](/agentos/docs/software#hosted-api), -[Crons & Loops](/agentos/docs/cron#hosted-api), and -[Resource Limits](/agentos/docs/resource-limits#hosted-api). +Each capability page shows its hosted actions in the **Hosted** tab of its +examples: +[Filesystem](/agentos/docs/filesystem), +[Processes & Shells](/agentos/docs/processes), +[Networking & Previews](/agentos/docs/networking), +[Software](/agentos/docs/software), +[Crons & Loops](/agentos/docs/cron), and +[Resource Limits](/agentos/docs/resource-limits). ## Configuration @@ -168,7 +165,7 @@ The config contains, among others: | `filesystem` | Root and mounts from the hosted registry, such as `{ "root": { "type": "durable" } }`. | | `software` | Package sources, such as `[{ "url": "https://…/tool.aospkg" }]`. | | `preview` | Preview defaults, such as `{ "defaultTtlMs": 30000 }`. | -| `limits` | [Resource limits](/agentos/docs/resource-limits#hosted-api). | +| `limits` | [Resource limits](/agentos/docs/resource-limits). | Creation input is used only when the actor is first created. Calling `getOrCreate` with different input for an existing key does not reconfigure diff --git a/docs/content/docs/javascript.mdx b/docs/content/docs/javascript.mdx index 7d79b02311..4444772178 100644 --- a/docs/content/docs/javascript.mdx +++ b/docs/content/docs/javascript.mdx @@ -20,7 +20,21 @@ driving [Bash](/agentos/docs/bash): `evaluate()` returns a JSON-serializable value. - + + +```ts Hosted @nocheck +import { createAgentOsClient } from "@rivet-dev/agentos"; + +const client = createAgentOsClient({ endpoint: "https://api.rivet.dev" }); +const vm = client.agentOS.getOrCreate(["my-vm"]); + +const sum = await vm.v1.javascript.evaluate({ expression: "1 + 2" }); +console.log(sum.value); // 3 +``` + + + + Returning `undefined`, a function, a symbol, or a circular value fails with `evaluation_serialization_failed` rather than silently losing the value. @@ -29,7 +43,23 @@ Returning `undefined`, a function, a symbol, or a circular value fails with `execute()` runs source and captures its output instead of returning a value. - + + +```ts Hosted @nocheck +import { createAgentOsClient } from "@rivet-dev/agentos"; + +const client = createAgentOsClient({ endpoint: "https://api.rivet.dev" }); +const vm = client.agentOS.getOrCreate(["my-vm"]); + +const result = await vm.v1.javascript.execute({ + source: `console.log("hello from agentOS")`, +}); +console.log(result); +``` + + + + Executions are ephemeral, so capture stdio only when you want it: `"stderr"` for diagnostics, `"all"` for both streams. `onStdout`/`onStderr` stream live and work @@ -37,17 +67,17 @@ independently of capture. ## Pass data into code -`inputs` hands host values to the guest as real objects, so data never gets +In the library, `inputs` hands host values to the guest as real objects, so data never gets interpolated into source. - + ## Keep state between calls Pass a `contextId` to keep one V8 isolate and its global state alive across calls. - + A context runs one operation at a time, so reusing a busy `contextId` fails immediately. Files, npm, Bash, and type checks may pass the same id, but they run @@ -63,20 +93,50 @@ Create the context explicitly before use; an unknown id fails instead of silentl starting fresh state. A context pins to the first inline language that used it, though JavaScript and TypeScript intentionally share one isolate. -Contexts live until the VM is disposed. They are in-memory state, so a VM -recreated from the same `database` starts with no contexts; create them lazily -on first use. +Contexts live until the VM is disposed. They are in-memory state, so they do +not survive a library VM recreated from the same `database`, or a hosted VM's +sleep and wake. Create them lazily on first use. ## Type check before running Executing TypeScript transpiles it without a semantic check, so validate generated code explicitly. - + + +```ts Hosted @nocheck +import { createAgentOsClient } from "@rivet-dev/agentos"; + +const client = createAgentOsClient({ endpoint: "https://api.rivet.dev" }); +const vm = client.agentOS.getOrCreate(["my-vm"]); + +// Type checking validates generated code before it executes. +const checked = await vm.v1.typescript.check({ + source: `const total: number = "not a number";`, +}); +console.log(checked); +``` + + + + ## Install npm packages - + + +```ts Hosted @nocheck +import { createAgentOsClient } from "@rivet-dev/agentos"; + +const client = createAgentOsClient({ endpoint: "https://api.rivet.dev" }); +const vm = client.agentOS.getOrCreate(["my-vm"]); + +await vm.v1.javascript.npm.install({}); +``` + + + + 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 @@ -84,10 +144,37 @@ 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 +`spawn` starts a long-lived process and returns a handle. From there you get stdin, output, signals, and waiting. - + + +```ts Hosted @nocheck +import { createAgentOsClient } from "@rivet-dev/agentos"; + +const client = createAgentOsClient({ endpoint: "https://api.rivet.dev" }); +const vm = client.agentOS.getOrCreate(["my-vm"]); + +const server = await vm.v1.javascript.spawn({ + source: ` + import http from "node:http"; + http.createServer((req, res) => res.end("ok")).listen(3000, "127.0.0.1"); + await new Promise(() => {}); + `, +}); + +// The listener stays inside the VM. No host port is exposed. +const response = await vm.v1.network.fetch({ + request: { port: 3000, path: "/health" }, +}); +console.log(response.status); // 200 + +await vm.v1.process.signal({ process: server, signal: "SIGTERM" }); +``` + + + + Spawned processes always start with fresh state, so they take no `contextId`. @@ -97,7 +184,7 @@ There is a real Linux environment behind all of this, shared by every language. Files and installed packages are immediately visible to Bash, Python, and other executions. - + See [Filesystem](/agentos/docs/filesystem), [Processes & Shells](/agentos/docs/processes), and [Networking](/agentos/docs/networking). @@ -105,35 +192,20 @@ See [Filesystem](/agentos/docs/filesystem), [Processes & Shells](/agentos/docs/p ## Host functions Guest JavaScript calls [host functions](/agentos/docs/host-functions) as async globals, so host -credentials stay outside the VM. +credentials stay outside the VM. Host functions are library-only. - + ## Permissions, limits, and timeouts Every operation inherits the VM [permission policy](/agentos/docs/permissions) and [resource limits](/agentos/docs/resource-limits). - - -## Hosted API - -Hosted VMs expose the same operations as `v1.javascript.*`, `v1.typescript.*`, -and `v1.contexts.*` actions. - -```ts TypeScript @nocheck -await vm.v1.javascript.execute({ source: "console.log(1 + 2)" }); -await vm.v1.typescript.check({ source: "const value: number = 1" }); -await vm.v1.javascript.npm.install({}); -``` - -Contexts live for the VM lifetime. They do not survive actor sleep and wake, -because the VM is disposed; create them lazily on the first stateful use after -wake. + ## Embedded API -The examples above use `AgentOs.create()`. Contexts remain available until the -host calls `dispose()`. +The library examples use `AgentOs.create()`. Contexts remain available until +the host calls `dispose()`. Read more in the [embedded API quickstart](/agentos/docs/quickstart-embedded). diff --git a/docs/content/docs/networking.mdx b/docs/content/docs/networking.mdx index d53040e6f6..5dfa1aaadc 100644 --- a/docs/content/docs/networking.mdx +++ b/docs/content/docs/networking.mdx @@ -1,58 +1,77 @@ --- -title: "Networking" -description: "Send HTTP requests from the host to servers running inside an agentOS VM, and control guest network access." +title: "Networking & Previews" +description: "Send HTTP requests to servers running inside an agentOS VM, expose them through expiring previews, and control guest network access." --- Servers inside the VM listen on VM-local ports. They are not exposed on the -host network. `network.httpRequest` proxies one bounded HTTP request from the -host to a VM loopback port and returns the buffered response. +host network. Send a bounded HTTP request to a VM port and get the buffered +response back. - + -`vm.fetch(port, request)` sends a standard `Request` and returns a `Response`. -`fetchStreamStart`, `fetchStreamRead`, and `fetchStreamCancel` deliver a -response body in bounded chunks, so the caller owns backpressure for large -responses. +```ts Hosted @nocheck +import { createAgentOsClient } from "@rivet-dev/agentos"; - +const client = createAgentOsClient({ endpoint: "https://api.rivet.dev" }); +const vm = client.agentOS.getOrCreate(["my-vm"]); -## Guest network access +// Start a server inside the VM +await vm.v1.filesystem.writeFile({ + path: "/tmp/app.mjs", + content: 'import http from "http"; http.createServer((req, res) => res.end("hello")).listen(3000);', +}); +await vm.v1.process.spawn({ command: "node", args: ["/tmp/app.mjs"] }); -Guest listeners and loopback traffic stay inside the VM. Outbound access is -limited to the sidecar's default allowlist unless the `network` -[permission](/agentos/docs/permissions) scope allows more. Requests from the -host through `httpRequest` enter the VM from the host side, so they are subject -to loopback confinement but not to the guest egress policy. +// fetch reaches services running in the VM. +const response = await vm.v1.network.fetch({ + request: { port: 3000, path: "/", method: "GET", headers: {} }, +}); +console.log(response.status); +``` -Embedded Core does not create public URLs for VM ports. To expose a VM server -to browsers, route requests through your application and forward them with -`httpRequest` or `fetch`, or use a hosted VM's -[expiring previews](#hosted-api). + -## Hosted API + -Hosted VMs proxy requests with `v1.network.fetch` and the pull-based -`v1.network.fetchStream.start`, `read`, and `cancel` actions. +For large responses, hosted VMs stream the body with the pull-based +`v1.network.fetchStream.start`, `read`, and `cancel` actions. The library +provides `vm.fetch(port, request)` with standard `Request` and `Response` +objects, and `fetchStreamStart`, `fetchStreamRead`, and `fetchStreamCancel` for +bounded chunks. Either way, the caller owns backpressure. -Hosted VMs can also expose any port through an expiring preview. A preview is a +## Previews + +Hosted VMs can expose any port through an expiring preview. A preview is a signed URL path that proxies HTTP to a port inside the VM until it expires or is -revoked. +revoked. `config.preview.defaultTtlMs` sets the default lifetime. -```ts TypeScript @nocheck -await vm.v1.process.spawn({ - command: "node", - args: ["/home/agentos/app.js"], -}); +```ts Hosted @nocheck +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.spawn({ command: "node", args: ["/home/agentos/app.js"] }); + +// Create a preview URL for port 3000, valid for 1 hour const preview = await vm.v1.network.preview.create({ port: 3000, ttlMs: 60 * 60 * 1_000, }); console.log(preview.path, new Date(Number(preview.expiresAtMs))); +// Revoke it early await vm.v1.network.preview.expire({ token: preview.token }); ``` -`config.preview.defaultTtlMs` sets the default lifetime. Previews enter the VM -from the host side, so they are subject to loopback confinement but not to the -guest egress policy. +The library does not create public URLs. To expose a library VM's server to +browsers, route requests through your application and forward them with +`httpRequest` or `fetch`. + +## Guest network access + +Guest listeners and loopback traffic stay inside the VM. Outbound access is +limited to the sidecar's default allowlist unless the `network` +[permission](/agentos/docs/permissions) scope allows more. Requests from the +host and previews enter the VM from the host side, so they are subject to +loopback confinement but not to the guest egress policy. diff --git a/docs/content/docs/permissions.mdx b/docs/content/docs/permissions.mdx index c9f9e92541..23d22ec30e 100644 --- a/docs/content/docs/permissions.mdx +++ b/docs/content/docs/permissions.mdx @@ -3,10 +3,11 @@ title: "Permissions" description: "Constrain untrusted guest filesystem, process, network, environment, and host function access." --- -Pass a permission tree as `permissions` when creating the VM. The trusted -sidecar enforces it for every guest operation. +Pass a permission tree when creating the VM. The trusted sidecar enforces it +for every guest operation. Hosted VMs take the same serializable tree in their +creation config or a later complete `v1.config.set` replacement. - + The tree has one scope per capability: `fs`, `network`, `childProcess`, `process`, `env`, and `hostFunction`. A scope can be `"allow"`, `"deny"`, or a @@ -16,14 +17,9 @@ patterns. Your policy is merged over the default, so an omitted scope keeps its default. By default the guest behaves like a sandboxed machine: its virtual filesystem, processes, environment, listeners, and loopback networking work, and outbound -network access is limited to the sidecar's default allowlist. Host functions -are governed by the `hostFunction` scope. +network access is limited to the sidecar's default allowlist. Library host +functions are governed by the `hostFunction` scope. See the [Security Model](/agentos/docs/security-model) for the trust boundary these policies enforce. -## Hosted API - -Hosted VMs take the same serializable permission tree in their creation config -or a later complete `v1.config.set` replacement. Hosted VMs do not expose host -functions. diff --git a/docs/content/docs/persistence.mdx b/docs/content/docs/persistence.mdx index eeac90ad93..700ef9987e 100644 --- a/docs/content/docs/persistence.mdx +++ b/docs/content/docs/persistence.mdx @@ -1,39 +1,56 @@ --- -title: "Persistence" +title: "Persistence & Sleep" description: "Persist an agentOS VM's filesystem in SQLite so files survive VM recreation." --- By default, a VM's root filesystem is an in-memory overlay over the bundled -base image. Its files are discarded when the VM is disposed. +base image. Its files are discarded when the VM is replaced or disposed. -To keep files across VM recreation, give the VM a SQLite `database` and store -its root filesystem there with the `chunked_sqlite` plugin. A new VM created -with the same options sees the same files. +To keep files, give a hosted VM a `durable` root in its config. In the library, +give the VM a SQLite `database` and store its root filesystem there with the +`chunked_sqlite` plugin; a new VM created with the same options sees the same +files. - + -`database` on its own stores Core and filesystem metadata, but does not make -the default in-memory root durable. A SQLite-backed root starts without the -bundled base image's files. +```ts Hosted @nocheck +import { createAgentOsClient } from "@rivet-dev/agentos"; + +const client = createAgentOsClient({ endpoint: "https://api.rivet.dev" }); + +const vm = client.agentOS.getOrCreate(["my-vm"], { + createWithInput: { + config: { filesystem: { root: { type: "durable" } } }, + }, +}); + +await vm.v1.filesystem.writeFile({ + path: "/home/agentos/result.txt", + content: "persistent", +}); +``` + + + + + +In the library, `database` on its own stores Core and filesystem metadata, but +does not make the default in-memory root durable. A SQLite-backed root starts +without the bundled base image's files. Persisted filesystems survive VM recreation. Running processes, terminals, -language contexts, open streams, cron jobs, and live subscriptions do not. +language contexts, open streams, and live subscriptions do not. -Core durable state uses `agentos_core_*` tables and filesystem storage uses -`agentos_fs_*`. Each owner has an independent schema version and migration -transaction. +## Sleep -The embedding application owns VM identity and decides when to recreate or -dispose the VM. +A hosted VM's actor sleeps when idle and reports `vm.shutdown` with reason +`"sleep"`. The VM starts lazily on the first action after wake. Durable files, +configuration, and cron jobs survive sleep. -## Hosted API +Library durable state uses `agentos_core_*` tables and filesystem storage uses +`agentos_fs_*`. Each owner has an independent schema version and migration +transaction. -A hosted VM's filesystem is configured with a closed registry. The default root -is an ephemeral in-memory overlay that is discarded when the VM is replaced. -Configure a `durable` root or mount for paths that must survive; see -[Filesystem](/agentos/docs/filesystem#hosted-api). +In the library, the embedding application owns VM identity and decides when to +recreate or dispose the VM. -The actor sleeps when idle and reports `vm.shutdown` with reason `"sleep"`. The -VM starts lazily on the first agentOS action after wake. Durable files and the -VM's configuration survive sleep; running processes, terminals, language -contexts, and open streams do not. diff --git a/docs/content/docs/processes.mdx b/docs/content/docs/processes.mdx index 7bec52e090..838d091fec 100644 --- a/docs/content/docs/processes.mdx +++ b/docs/content/docs/processes.mdx @@ -4,16 +4,57 @@ description: "Execute commands, manage long-running processes, replay their outp --- Every guest process is an entry in the VM's kernel process table, never a host -process. Process operations live under `vm.process`. +process. -- `process.exec(command)` runs a command line through the shell and waits for it - to finish. -- `process.execFile(file, args)` runs an executable with an argument array. No - shell parses the arguments. -- `process.spawn(command, args)` starts a long-running process and returns its - `pid` immediately. +| | Hosted | Library | +|---|---|---| +| Run to completion with an argument array | `v1.process.run({ command, args })` | `process.execFile(file, args)` | +| Run a shell command line | `v1.process.run({ command: "sh", args: ["-c", line] })` | `process.exec(line)` | +| Start a long-running process | `v1.process.spawn({ command, args })` returns `{ generation, pid }` | `process.spawn(command, args)` returns `{ pid }` | - + + +```ts Hosted @nocheck +import { createAgentOsClient } from "@rivet-dev/agentos"; + +const client = createAgentOsClient({ endpoint: "https://api.rivet.dev" }); +const vm = client.agentOS.getOrCreate(["my-vm"]); + +// One-shot execution +const result = await vm.v1.process.run({ + command: "ls", + args: ["-la", "/home/agentos"], + options: { captureStdio: true }, +}); +console.log(result.stdout); + +// Long-running process with live output and exit events. +const conn = vm.connect(); +await conn.ready; +const server = await conn.v1.process.spawn({ + command: "node", + args: ["/tmp/server.mjs"], +}); + +conn.on("process.output", (event) => { + if (event.process.pid !== server.pid) return; + console.log(event.stream, new TextDecoder().decode(event.data)); +}); +conn.on("process.exit", (event) => { + if (event.process.pid !== server.pid) return; + console.log("exited:", event.status.exitCode); +}); + +// Write to stdin +await conn.v1.process.stdin.write({ process: server, data: "some input\n" }); + +// Stop or kill +await conn.v1.process.signal({ process: server, signal: "SIGTERM" }); +``` + + + + Process arguments are passed as individual argv entries. An empty string is a valid argument; it is not dropped or treated as an omitted argument. Empty @@ -21,18 +62,23 @@ commands or working directories are invalid input. ## Manage running processes -A spawned process supports: +| Operation | Hosted | Library | +|---|---|---| +| Write and close stdin | `v1.process.stdin.write`, `v1.process.stdin.close` | `process.writeStdin`, `process.closeStdin` | +| Send a signal | `v1.process.signal` | `process.signal`, `process.kill` | +| Resize a PTY | `v1.process.pty.resize` | `process.resizePty` | +| Wait for exit | `v1.process.wait` | `process.wait` | +| Inspect | `v1.process.get`, `v1.process.list`, `v1.process.tree` | `process.get`, `process.list`, `process.tree` | -- `process.writeStdin(pid, data)` and `process.closeStdin(pid)`; -- `process.signal(pid, signal)` and `process.kill(pid)`; -- `process.resizePty(pid, { cols, rows })` for a process started with `pty`; -- `process.wait(pid)`, which resolves with the process's exit; -- `process.get(pid)` and `process.list()`, which report `startedAtMs` as a Unix - timestamp in milliseconds. +Hosted operations take the process handle `{ generation, pid }`; library +operations take the `pid`. A hosted handle from before a `v1.vm.restart` fails +with `stale_generation`. -`process.tree()` includes guest child processes as well as processes launched -through `vm.process`, so it can show more than you started. Use the returned -`pid` rather than reconstructing one from a node. +`get` and `list` report `startedAtMs` as a Unix timestamp in milliseconds. +`tree` includes guest child processes as well as processes launched through the +API, so it can show more than you started. Use the returned handle or `pid` +rather than reconstructing one from a node; guest children that were not +launched through the API have no hosted process handle. A process wait returns an exit only after agentOS observes a guest exit. A rejected launch or lost exit observation rejects the wait instead of inventing @@ -41,75 +87,54 @@ inspect the VM or recreate it before assuming it is safe to retry work. ## Output events and replay -Subscribe to live output with `vm.onProcessOutput(pid, handler)` and to exit -with `vm.onProcessExit(pid, handler)`. Both return an unsubscribe function. - -Pass `output: { retainEvents: true }` to `spawn` to retain the process's output -in the sidecar. `process.readOutput(pid, { after, maxEvents, maxBytes })` then -pages through retained output with a sequence cursor, even after the process -has exited or a live event was missed. Retained output callbacks include -`sequence` and `timestampMs` metadata for correlating live output with replay. +Process output can be replayed after the fact, even after the process has +exited or a live event was missed. Hosted VMs retain output for every spawned +process; in the library, pass `output: { retainEvents: true }` to `spawn`. +Then page through retained output with a sequence cursor. - + -A replay page reports `nextCursor`, `hasMore`, and `truncated`, plus `exitCode` -once the sidecar has confirmed the exit. Retention is bounded; see -[Resource Limits](/agentos/docs/resource-limits) for the defaults. +```ts Hosted @nocheck +import { createAgentOsClient } from "@rivet-dev/agentos"; -## Interactive shells - -`vm.terminal.open(options)` allocates a kernel PTY, starts a shell attached to -it, and returns a `shellId`. Use `terminal.write`, `terminal.resize`, -`terminal.wait`, and `terminal.close` to drive it, and `vm.onShellData`, -`vm.onShellStderr`, and `vm.onShellExit` for its live output and exit. - -## Hosted API - -Hosted VMs run processes with `v1.process.*` actions. `v1.process.run` takes an -executable and an argument array; there is no shell-string `exec`, so run `sh` -with `-c` when you need shell parsing. - -```ts TypeScript @nocheck -const result = await vm.v1.process.run({ - command: "sh", - args: ["-c", "echo hello && ls /home/agentos"], - options: { env: {}, captureStdio: true }, -}); -console.log(result.stdout, result.status.exitCode); - -const conn = vm.connect(); -await conn.ready; +const client = createAgentOsClient({ endpoint: "https://api.rivet.dev" }); +const vm = client.agentOS.getOrCreate(["my-vm"]); -const shell = await conn.v1.process.spawn({ command: "sh", args: [] }); -conn.on("process.output", (data) => { - if (data.process.pid !== shell.pid) return; - process.stdout.write(new TextDecoder().decode(data.data)); -}); +const shell = await vm.v1.process.spawn({ command: "sh", args: [] }); -await conn.v1.process.stdin.write({ process: shell, data: "ls -la /home/agentos\n" }); -await conn.v1.process.stdin.close({ process: shell }); -await conn.v1.process.wait({ process: shell }); +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 }); -// Replay retained output after a reconnect. +// Later, from any client const replay = await vm.v1.process.output.read({ process: shell, maxEvents: 32, maxBytes: 64 * 1024, }); +console.log(replay.events.length, replay.nextCursor); ``` -`v1.process.spawn` returns a process handle `{ generation, pid }`. Pass the -handle to `v1.process.get`, `wait`, `signal`, `stdin.write`, `stdin.close`, -`pty.resize`, and `output.read`. Handles from before a `v1.vm.restart` fail -with `stale_generation`. Live events are `process.output` and `process.exit`; -replay is the authoritative recovery path. - -`v1.process.get` and `v1.process.list` report `startedAtMs`. Nodes returned by -`v1.process.tree` report `startTimeMs`, `exitTimeMs`, and a structured `exit`. -Guest child processes that were not launched through the API have no process -handle. - -Interactive terminals are separate resources under `v1.terminal.open`, -`v1.terminal.list`, `v1.terminal.stdin.write`, `v1.terminal.pty.resize`, -`v1.terminal.output.read`, `v1.terminal.wait`, and `v1.terminal.close`. Their -live events are `terminal.output` and `terminal.exit`. + + + + +Pass the last cursor as `after` to read the next page. A replay page reports +`nextCursor`, `hasMore`, and `truncated`. Live events are low-latency hints: +`process.output` and `process.exit` on a hosted connection, or +`vm.onProcessOutput` and `vm.onProcessExit` in the library. Retention is +bounded; see [Resource Limits](/agentos/docs/resource-limits) for the defaults. + +## Interactive shells + +Interactive terminals allocate a kernel PTY and start a shell attached to it. + +| Operation | Hosted | Library | +|---|---|---| +| Open | `v1.terminal.open` | `terminal.open`, returns a `shellId` | +| Write input | `v1.terminal.stdin.write` | `terminal.write` | +| Resize | `v1.terminal.pty.resize` | `terminal.resize` | +| Wait and close | `v1.terminal.wait`, `v1.terminal.close` | `terminal.wait`, `terminal.close` | +| Output | `terminal.output` and `terminal.exit` events, `v1.terminal.output.read` replay | `vm.onShellData`, `vm.onShellStderr`, `vm.onShellExit` | + +Hosted VMs also list open terminals with `v1.terminal.list`. diff --git a/docs/content/docs/python.mdx b/docs/content/docs/python.mdx index 2cf252c2e7..362c56a1aa 100644 --- a/docs/content/docs/python.mdx +++ b/docs/content/docs/python.mdx @@ -22,7 +22,21 @@ driving [Bash](/agentos/docs/bash): `evaluate()` returns a JSON-serializable value. - + + +```ts Hosted @nocheck +import { createAgentOsClient } from "@rivet-dev/agentos"; + +const client = createAgentOsClient({ endpoint: "https://api.rivet.dev" }); +const vm = client.agentOS.getOrCreate(["my-vm"]); + +const result = await vm.v1.python.evaluate({ expression: "21 * 2" }); +console.log(result.value); // 42 +``` + + + + A value the JSON encoder can't represent fails with `evaluation_serialization_failed` rather than silently losing the value. @@ -31,7 +45,23 @@ A value the JSON encoder can't represent fails with `execute()` runs source and captures its output instead of returning a value. - + + +```ts Hosted @nocheck +import { createAgentOsClient } from "@rivet-dev/agentos"; + +const client = createAgentOsClient({ endpoint: "https://api.rivet.dev" }); +const vm = client.agentOS.getOrCreate(["my-vm"]); + +const result = await vm.v1.python.execute({ + source: `print("hello from agentOS")`, +}); +console.log(result); +``` + + + + Executions are ephemeral, so capture stdio only when you want it: `"stderr"` for diagnostics, `"all"` for both streams. `onStdout`/`onStderr` stream live and work @@ -39,32 +69,33 @@ independently of capture. ## Pass data into code -`inputs` hands host values to the guest as real objects, so data never gets +In the library, `inputs` hands host values to the guest as real objects, so data never gets interpolated into source. - + ## Keep state between calls Pass a `contextId` to keep globals, functions, imports, and modules alive in one interpreter. - + 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, so reusing a busy `contextId` fails immediately. +time, so reusing a busy `contextId` fails immediately. Contexts do not survive +a hosted VM's sleep and wake; create them lazily on first use. ## Files, modules, and async - + Awaited work counts against the deadline; unawaited `asyncio` tasks are not promised to survive between operations. ## Install packages - + 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 @@ -78,10 +109,10 @@ host: Pyodide builds of compiled packages such as Pillow come from ## Background processes and web servers -`spawn*` starts a long-lived process and returns a `pid`. From there you get +`spawn*` starts a long-lived process and returns a handle. From there you get stdin, output, signals, and waiting. - + ## A full Linux environment underneath @@ -89,7 +120,7 @@ There is a real Linux environment behind all of this, shared by every language. Files and installed packages are immediately visible to Bash, JavaScript, and other executions. - + See [Filesystem](/agentos/docs/filesystem), [Processes & Shells](/agentos/docs/processes), and [Networking](/agentos/docs/networking). @@ -97,32 +128,20 @@ See [Filesystem](/agentos/docs/filesystem), [Processes & Shells](/agentos/docs/p ## Host functions Python calls [host functions](/agentos/docs/host-functions) as normal commands via `subprocess`, so -host credentials stay outside the VM. +host credentials stay outside the VM. Host functions are library-only. - + ## Permissions, limits, and timeouts Every operation inherits the VM [permission policy](/agentos/docs/permissions) and [resource limits](/agentos/docs/resource-limits). - - -## Hosted API - -Hosted VMs expose the same operations as `v1.python.*` actions. - -```ts TypeScript @nocheck -const { value } = await vm.v1.python.evaluate({ expression: "21 * 2" }); -await vm.v1.python.install({ options: {} }); -``` - -Python contexts do not survive actor sleep and wake, because the VM is -disposed; create them lazily on the first stateful use after wake. + ## Embedded API -The examples above use `AgentOs.create()`. Python contexts remain available +The library examples use `AgentOs.create()`. Python contexts remain available until the host calls `dispose()`. Read more in the [embedded API quickstart](/agentos/docs/quickstart-embedded). diff --git a/docs/content/docs/quickstart-hosted.mdx b/docs/content/docs/quickstart-hosted.mdx index b62ae5ae7e..2d1bf60be6 100644 --- a/docs/content/docs/quickstart-hosted.mdx +++ b/docs/content/docs/quickstart-hosted.mdx @@ -13,7 +13,7 @@ VMs inside your own Node.js process instead, see the | | [Hosted](/agentos/docs/quickstart-hosted) | [Library](/agentos/docs/quickstart) | |---|---|---| -| **Runs** | As the prebuilt `agentOS` Rivet Actor | Inside your Node.js process | +| **Runs** | As the prebuilt `agentOS` Rivet Actor on Rivet Cloud or [self-hosted](/agentos/docs/self-hosting) | Inside your Node.js process | | **Package** | `@rivet-dev/agentos`, or plain HTTP | `@rivet-dev/agentos-core` | | **VM identity** | A durable VM you create by key and reach by ID | A handle your process owns until `dispose()` | | **Configuration** | Stored with the VM and versioned | Passed to `AgentOs.create()` | @@ -35,11 +35,7 @@ npm install @rivet-dev/agentos ```ts TypeScript @nocheck import { createAgentOsClient } from "@rivet-dev/agentos"; -const client = createAgentOsClient({ - endpoint: "https://api.rivet.dev", - namespace: process.env.RIVET_NAMESPACE, - token: process.env.RIVET_TOKEN, -}); +const client = createAgentOsClient({ endpoint: "https://api.rivet.dev" }); const vm = client.agentOS.getOrCreate(["my-vm"]); const vmId = await vm.resolve(); @@ -59,6 +55,10 @@ curl -X PUT "https://api.rivet.dev/actors?namespace=$RIVET_NAMESPACE" \ +The client reads `RIVET_NAMESPACE` and `RIVET_TOKEN` from the environment. To +run your own control plane and worker instead of Rivet Cloud, see +[Self-Hosting](/agentos/docs/self-hosting). + The VM is durable: calling `getOrCreate` with the same key reaches the same VM. The cURL examples below call it as `$VM_ID` with a token in `$VM_TOKEN`; see [authentication](/agentos/docs/hosted-api#authentication). @@ -234,5 +234,5 @@ VM's configuration with `v1.config.patch`; filesystem changes apply after - See the [Hosted VM HTTP API](/agentos/docs/hosted-api) for authentication, configuration, events, errors, and every action. -- Expose a VM port with [expiring previews](/agentos/docs/networking#hosted-api). -- Schedule commands with [Crons & Loops](/agentos/docs/cron#hosted-api). +- Expose a VM port with [expiring previews](/agentos/docs/networking#previews). +- Schedule commands with [Crons & Loops](/agentos/docs/cron). diff --git a/docs/content/docs/quickstart.mdx b/docs/content/docs/quickstart.mdx index 0729618716..b744e1b100 100644 --- a/docs/content/docs/quickstart.mdx +++ b/docs/content/docs/quickstart.mdx @@ -12,7 +12,7 @@ durable VMs by ID and control them over HTTP instead, see the | | [Hosted](/agentos/docs/quickstart-hosted) | [Library](/agentos/docs/quickstart) | |---|---|---| -| **Runs** | As the prebuilt `agentOS` Rivet Actor | Inside your Node.js process | +| **Runs** | As the prebuilt `agentOS` Rivet Actor on Rivet Cloud or [self-hosted](/agentos/docs/self-hosting) | Inside your Node.js process | | **Package** | `@rivet-dev/agentos`, or plain HTTP | `@rivet-dev/agentos-core` | | **VM identity** | A durable VM you create by key and reach by ID | A handle your process owns until `dispose()` | | **Configuration** | Stored with the VM and versioned | Passed to `AgentOs.create()` | diff --git a/docs/content/docs/resource-limits.mdx b/docs/content/docs/resource-limits.mdx index af1655368e..b7eb6b4a7a 100644 --- a/docs/content/docs/resource-limits.mdx +++ b/docs/content/docs/resource-limits.mdx @@ -3,10 +3,35 @@ title: "Resource Limits" description: "Configure bounded VM, process, JavaScript, Python, and WASM resources." --- -Pass `limits` when creating the VM. The sidecar applies them during VM creation, -and `onLimitWarning` reports near-limit pressure to the embedding process. +Pass a `limits` tree in the VM's configuration. The sidecar applies it when the +VM starts. Near-limit pressure is reported as the `vm.limitWarning` event on a +hosted VM, or through `onLimitWarning` in the library. - + + +```ts Hosted @nocheck +import { createAgentOsClient } from "@rivet-dev/agentos"; + +const client = createAgentOsClient({ endpoint: "https://api.rivet.dev" }); +const vm = client.agentOS.getOrCreate(["my-vm"]); + +await vm.v1.config.set({ + config: { + limits: { + tls: { maxBufferedBytes: 2048 }, + execution: { completedTtlMs: 60_000, liveExecutionWarningThreshold: 32 }, + agentosPackages: { maxMounts: 8192 }, + }, + }, +}); + +// Changed limits apply to the next VM generation. +await vm.v1.vm.restart({}); +``` + + + + Use `limits.tls.maxBufferedBytes` to bound TLS plaintext buffering. `limits.execution.completedTtlMs` and `limits.execution.maxCompletedExecutions` @@ -35,38 +60,17 @@ a clean stop. Close completion can no longer be confirmed, so the VM stays quarantined until the sidecar process restarts, even if local resource counts reach zero. Quarantined VMs count toward VM admission capacity. -Limits are fixed for the life of a VM. To change them, dispose the VM and -create a new one with the new limits. +Limits are fixed for the life of a VM. To change them on a hosted VM, update +the config and call `v1.vm.restart`. In the library, dispose the VM and create +a new one with the new limits. The hosted actor also has a fixed ten-second +shutdown wait; raising the sidecar deadline does not extend it. ## Sidecar limits -VMs share a sidecar process by default. Limits that apply to the whole +Library VMs share a sidecar process by default. Limits that apply to the whole process, such as `runtime.executor.maxActiveVms`, are set when creating a dedicated sidecar with `AgentOs.createSidecar()`, as in the example above. -## Hosted API - -Hosted VMs take the same `limits` tree in their durable config. Set it at -creation or with `v1.config.set`, and apply changed limits with -`v1.vm.restart`. Near-limit pressure is reported as the `vm.limitWarning` event. - -```ts TypeScript @nocheck -await vm.v1.config.set({ - config: { - limits: { - tls: { maxBufferedBytes: 2048 }, - execution: { completedTtlMs: 60_000, liveExecutionWarningThreshold: 32 }, - agentosPackages: { maxMounts: 8192 }, - }, - }, -}); -await vm.v1.vm.restart({}); -``` - -The hosted actor also has a fixed ten-second shutdown wait; raising the sidecar -deadline does not extend it. Exceeding it reports a timeout, not confirmed -shutdown. - ## Embedded API Limits apply to VMs created with `AgentOs.create()` and sidecars created with diff --git a/docs/content/docs/sandboxes.mdx b/docs/content/docs/sandboxes.mdx index bc04d8b588..7336344934 100644 --- a/docs/content/docs/sandboxes.mdx +++ b/docs/content/docs/sandboxes.mdx @@ -4,7 +4,8 @@ description: "Mount a full external sandbox into an agentOS VM for workloads tha --- When a workload needs a full Linux environment, attach an external sandbox to -the VM with the `sandbox` option. agentOS mounts the sandbox filesystem into +a library VM with the `sandbox` option. External sandboxes rely on trusted host +callbacks and mounts, so they are not available on hosted VMs. agentOS mounts the sandbox filesystem into the VM at `/mnt/sandbox` and registers `agentos-sandbox` host functions for running commands and listing processes in the sandbox. @@ -19,7 +20,3 @@ process, so credentials for the sandbox provider stay on the host. See [agentOS vs Sandbox](/agentos/docs/versus-sandbox) to decide when a workload needs one. -## Hosted API - -External sandboxes rely on trusted host callbacks and mounts, so they are not -available on hosted VMs. diff --git a/docs/content/docs/self-hosting.mdx b/docs/content/docs/self-hosting.mdx new file mode 100644 index 0000000000..ac2d694d1a --- /dev/null +++ b/docs/content/docs/self-hosting.mdx @@ -0,0 +1,50 @@ +--- +title: "Self-Hosting" +description: "Run hosted agentOS VMs on your own infrastructure with the Rivet control plane and the agentOS worker image." +--- + +Hosted VMs run on Rivet Cloud or on your own infrastructure. Self-hosting needs +two processes: the Rivet control plane, and the agentOS worker that runs the +`agentOS` actor and its VMs. + + + + +```sh +docker run -p 6420:6420 rivetdev/engine +``` + +See the [self-hosting guides](/docs/deploy/) for other ways to run it. + + + + +The worker is what actually runs your VMs. It connects to the control plane: + +```sh +docker run \ + --add-host=host.docker.internal:host-gateway \ + -e RIVET_ENDPOINT=http://host.docker.internal:6420 \ + rivetdev/services +``` + + + + +Create a client pointed at your control plane, then create a VM by ID: + +```ts TypeScript @nocheck +import { createAgentOsClient } from "@rivet-dev/agentos"; + +const client = createAgentOsClient({ endpoint: "http://localhost:6420" }); + +const vm = client.agentOS.getOrCreate(["my-vm"]); +``` + + + + +Everything in the [hosted quickstart](/agentos/docs/quickstart-hosted) and the +[Hosted VM HTTP API](/agentos/docs/hosted-api) works the same way. Use +`http://localhost:6420`, or wherever your control plane is reachable, as the +base URL instead of `https://api.rivet.dev`. diff --git a/docs/content/docs/software.mdx b/docs/content/docs/software.mdx index 2c614b3cc8..c9cb8e7a79 100644 --- a/docs/content/docs/software.mdx +++ b/docs/content/docs/software.mdx @@ -1,78 +1,46 @@ --- title: "Software" -description: "Install immutable .aospkg command packages at boot or while a VM is running." +description: "Install immutable .aospkg command packages when a VM is created or while it is running." --- agentOS software ships as immutable `.aospkg` packages containing real compiled tools. Installed packages are projected read-only under `/opt/agentos/pkgs//`, and their commands are linked under `/opt/agentos/bin`. Every VM includes a default bundle with `sh`, coreutils, and -the standard CLI tools programs rely on. Pass `defaultSoftware: false` for a bare -VM with only the software you list. +the standard CLI tools programs rely on. Browse the available packages in the [registry](/agentos/docs/registry). -## Install at boot - -Pass packages in `software` when creating the VM. Each entry is a -`{ packagePath }` reference to a packed `.aospkg` file, which is what the -`@agentos-software/*` packages in the agentOS repository export. - - - -## Install while running - -`software.install` adds a package to a running VM. The source is either a remote -HTTPS URL or a trusted local path, each with an optional exact -`sha256:<64 lowercase hex>` digest. New commands are available on -`/opt/agentos/bin` as soon as the call returns. +## Registry packages Registry packages are published with each release under `https://releases.rivet.dev/agent-os//software/`, or `latest` for the newest release. Its `manifest.json` lists each package's name, digest, size, -and path relative to the manifest. - - - -- `software.install(source)` returns the installed package's `packageId`, - `digest`, `sizeBytes`, `packageName`, `version`, and `commands`. -- `software.uninstall(packageId)` removes a package installed through - `software.install` and its command links. -- `software.installed()` lists packages installed through `software.install`. -- `software.list()` reports every package that provides commands in the VM, - including boot-time and default software. +and path relative to the manifest. Registry software is not installed from npm; +see [Publishing Software](/agentos/docs/custom-software/publishing). -Only one software or mount mutation runs at a time per VM. A concurrent call -fails with `invalid_state` instead of queueing. - -## Verification and caching +## Install while running -The sidecar downloads a URL source under strict size, time, and redirect -limits, verifies the bytes against the expected digest when one is given, and -validates the package format before projecting it. The verified content digest -is the package identity. +Install a package into a running VM from an HTTPS URL, with an optional exact +`sha256:<64 lowercase hex>` digest. The library also accepts a trusted local +path. -Packages are cached per sidecar process by content digest, so VMs that share a -sidecar reuse one verified copy. Repeat installs of the same bytes skip the -download, and the bytes are always verified before projection. + -Registry software is not installed from npm. Each release uploads -digest-addressed `.aospkg` files and a manifest to -`releases.rivet.dev`; see -[Publishing Software](/agentos/docs/custom-software/publishing). +```ts Hosted @nocheck +import { createAgentOsClient } from "@rivet-dev/agentos"; -## Hosted API +const client = createAgentOsClient({ endpoint: "https://api.rivet.dev" }); +const vm = client.agentOS.getOrCreate(["my-vm"]); -Hosted VMs install software from HTTPS URLs only. Software is part of the VM's -durable configuration, so `v1.software.install` and `v1.software.uninstall` use -the same revision checks as `v1.config.patch`. +// Registry software is published as digest-addressed .aospkg files next to a +// manifest. Use a release version, or `latest`. +const base = "https://releases.rivet.dev/agent-os/latest/software/"; +const { artifacts } = await (await fetch(`${base}manifest.json`)).json(); +const ripgrep = artifacts.find((artifact) => artifact.name === "ripgrep"); -```ts TypeScript @nocheck const installed = await vm.v1.software.install({ - source: { - url: "https://packages.example.com/ripgrep.aospkg", - digest: "sha256:<64 lowercase hex characters>", - }, + source: { url: new URL(ripgrep.path, base).href, digest: ripgrep.digest }, }); if (installed.config.state === "restart_required") { await vm.v1.vm.restart({}); @@ -85,18 +53,65 @@ for (const software of await vm.v1.software.list({})) { await vm.v1.software.uninstall({ packageId: installed.source.packageId }); ``` -Software changes apply live when the VM is ready and its desired configuration -is fully applied. Otherwise they update desired configuration and report -`restart_required`; call `v1.vm.restart` to apply it. Uninstalling a desired -package does not require a working VM or a fresh download. + -Both mutations return `{ source, software, config }`. `source` identifies the -configured package, and `config` reports the new revision and state. -`v1.software.list` reads the live installed set. Desired package sources live in + + +On hosted VMs, software is part of the VM's durable configuration, so install +and uninstall use the same revision checks as `v1.config.patch`. A change +applies live when the VM is ready and its desired configuration is fully +applied; otherwise it reports `restart_required`, and `v1.vm.restart` applies +it. Both mutations return `{ source, software, config }`. `v1.software.list` +reads the live installed set, and the desired package sources live in `v1.config.get`. -Hosted workers share a digest-keyed package cache and warm popular packages at -startup. See +In the library, `software.install(source)` returns the installed package's +`packageId`, `digest`, `sizeBytes`, `packageName`, `version`, and `commands`. +`software.installed()` lists packages installed through `software.install`, and +`software.list()` reports every package that provides commands in the VM. Only +one software or mount mutation runs at a time per VM; a concurrent call fails +with `invalid_state` instead of queueing. + +## Install when the VM is created + + + +```ts Hosted @nocheck +import { createAgentOsClient } from "@rivet-dev/agentos"; + +const client = createAgentOsClient({ endpoint: "https://api.rivet.dev" }); + +const vm = client.agentOS.getOrCreate(["my-vm"], { + createWithInput: { + config: { + software: [ + { + url: "https://releases.rivet.dev/agent-os/latest/software/packages/git/.aospkg", + }, + ], + }, + }, +}); +``` + + + + + +Hosted VMs take package URLs in their `software` config. The library takes +`{ packagePath }` references to packed `.aospkg` files, which is what the +`@agentos-software/*` packages in the agentOS repository export, and +`defaultSoftware: false` for a bare VM with only the software you list. + +## Verification and caching + +Each URL source is downloaded under strict size, time, and redirect limits, +verified against the expected digest when one is given, and validated before +it is projected. The verified content digest is the package identity. + +Packages are cached by content digest, so VMs that share a worker or sidecar +reuse one verified copy, and repeat installs of the same bytes skip the +download. Hosted workers also warm popular packages at startup; see [Software Installation and Preloading](/agentos/docs/architecture/software-cache). ## Embedded API diff --git a/docs/sidebar.json b/docs/sidebar.json index 3bff987c6e..6a4a9fd670 100644 --- a/docs/sidebar.json +++ b/docs/sidebar.json @@ -74,7 +74,7 @@ "icon": "faTerminal" }, { - "title": "Networking", + "title": "Networking & Previews", "href": "/agentos/docs/networking", "icon": "faGlobe" }, @@ -119,6 +119,10 @@ "title": "Hosted VM HTTP API", "href": "/agentos/docs/hosted-api" }, + { + "title": "Self-Hosting", + "href": "/agentos/docs/self-hosting" + }, { "title": "Custom Software", "collapsible": true, @@ -208,7 +212,7 @@ "href": "/agentos/docs/architecture/limits-and-observability" }, { - "title": "Persistence", + "title": "Persistence & Sleep", "href": "/agentos/docs/persistence" } ] From aff3db5e37077ac885a5b6a700d3883a228a76bf Mon Sep 17 00:00:00 2001 From: Sree Narayanan Date: Mon, 12 Oct 2026 09:59:55 +0000 Subject: [PATCH 09/18] docs(agentos): rename to Hosted HTTP API, add the comparison table to Self-Hosting Amp-Thread-ID: https://ampcode.com/threads/T-c7d08b58-92c1-4fc0-abb9-7a15f4504a4f Co-authored-by: Amp --- docs/content/docs/hosted-api.mdx | 2 +- docs/content/docs/quickstart-hosted.mdx | 2 +- docs/content/docs/self-hosting.mdx | 25 +++++++++++++++++++++---- docs/sidebar.json | 2 +- 4 files changed, 24 insertions(+), 7 deletions(-) diff --git a/docs/content/docs/hosted-api.mdx b/docs/content/docs/hosted-api.mdx index 56fdbbc136..f3ea6f88d8 100644 --- a/docs/content/docs/hosted-api.mdx +++ b/docs/content/docs/hosted-api.mdx @@ -1,5 +1,5 @@ --- -title: "Hosted VM HTTP API" +title: "Hosted HTTP API" description: "Reference for the hosted agentOS VM API: creating VMs, authentication, actions, configuration, lifecycle, events, and errors." --- diff --git a/docs/content/docs/quickstart-hosted.mdx b/docs/content/docs/quickstart-hosted.mdx index 2d1bf60be6..11bf71051c 100644 --- a/docs/content/docs/quickstart-hosted.mdx +++ b/docs/content/docs/quickstart-hosted.mdx @@ -232,7 +232,7 @@ VM's configuration with `v1.config.patch`; filesystem changes apply after ## Next steps -- See the [Hosted VM HTTP API](/agentos/docs/hosted-api) for authentication, +- See the [Hosted HTTP API](/agentos/docs/hosted-api) for authentication, configuration, events, errors, and every action. - Expose a VM port with [expiring previews](/agentos/docs/networking#previews). - Schedule commands with [Crons & Loops](/agentos/docs/cron). diff --git a/docs/content/docs/self-hosting.mdx b/docs/content/docs/self-hosting.mdx index ac2d694d1a..cd55780a0c 100644 --- a/docs/content/docs/self-hosting.mdx +++ b/docs/content/docs/self-hosting.mdx @@ -3,9 +3,26 @@ title: "Self-Hosting" description: "Run hosted agentOS VMs on your own infrastructure with the Rivet control plane and the agentOS worker image." --- -Hosted VMs run on Rivet Cloud or on your own infrastructure. Self-hosting needs -two processes: the Rivet control plane, and the agentOS worker that runs the -`agentOS` actor and its VMs. +Self-hosting runs the hosted agentOS API on your own infrastructure instead of +Rivet Cloud. It needs two processes: the Rivet control plane, and the agentOS +worker that runs the `agentOS` actor and its VMs. If you only need VMs inside +your own Node.js process, use the [library](/agentos/docs/quickstart) instead; +it needs neither. + +## Which one should I use? + +| | [Hosted](/agentos/docs/quickstart-hosted) | [Library](/agentos/docs/quickstart) | +|---|---|---| +| **Runs** | As the prebuilt `agentOS` Rivet Actor on Rivet Cloud or [self-hosted](/agentos/docs/self-hosting) | Inside your Node.js process | +| **Package** | `@rivet-dev/agentos`, or plain HTTP | `@rivet-dev/agentos-core` | +| **VM identity** | A durable VM you create by key and reach by ID | A handle your process owns until `dispose()` | +| **Configuration** | Stored with the VM and versioned | Passed to `AgentOs.create()` | +| **Clients** | Your backend, or browsers with a scoped JWT | Your backend | +| **Host access** | None; configuration is serializable only | Host functions, host mounts, external sandboxes | +| **Previews** | Expiring preview URLs | None | +| **Best for** | Long-lived VMs shared across clients | Embedding VMs in an existing backend | + +## Run it @@ -45,6 +62,6 @@ const vm = client.agentOS.getOrCreate(["my-vm"]); Everything in the [hosted quickstart](/agentos/docs/quickstart-hosted) and the -[Hosted VM HTTP API](/agentos/docs/hosted-api) works the same way. Use +[Hosted HTTP API](/agentos/docs/hosted-api) works the same way. Use `http://localhost:6420`, or wherever your control plane is reachable, as the base URL instead of `https://api.rivet.dev`. diff --git a/docs/sidebar.json b/docs/sidebar.json index 6a4a9fd670..29479f0a84 100644 --- a/docs/sidebar.json +++ b/docs/sidebar.json @@ -116,7 +116,7 @@ "title": "Reference", "pages": [ { - "title": "Hosted VM HTTP API", + "title": "Hosted HTTP API", "href": "/agentos/docs/hosted-api" }, { From 79078c1b100d8035bbdbb6d66d5a0158ff7231c2 Mon Sep 17 00:00:00 2001 From: Sree Narayanan Date: Mon, 12 Oct 2026 10:29:59 +0000 Subject: [PATCH 10/18] docs(agentos): adopt the launch post's quickstart examples, note hosted sleep, rename Crons & Schedules Rework both quickstarts around the launch post's Show me the code examples, with Rivet Cloud and self-hosting setup on the hosted side. Note that hosted VMs sleep when idle unless a process is running, linking actor lifecycle. Move Crons & Schedules into Operating System above Networking. Amp-Thread-ID: https://ampcode.com/threads/T-c7d08b58-92c1-4fc0-abb9-7a15f4504a4f Co-authored-by: Amp --- docs/content/docs/cron.mdx | 2 +- docs/content/docs/hosted-api.mdx | 6 +- docs/content/docs/persistence.mdx | 7 +- docs/content/docs/quickstart-hosted.mdx | 247 ++++++++++++++++-------- docs/content/docs/quickstart.mdx | 40 ++-- docs/content/docs/self-hosting.mdx | 1 + docs/sidebar.json | 15 +- examples/embedded/quickstart.ts | 46 +++-- 8 files changed, 237 insertions(+), 127 deletions(-) diff --git a/docs/content/docs/cron.mdx b/docs/content/docs/cron.mdx index 51f1fbd48d..2337e04261 100644 --- a/docs/content/docs/cron.mdx +++ b/docs/content/docs/cron.mdx @@ -1,5 +1,5 @@ --- -title: "Crons & Loops" +title: "Crons & Schedules" description: "Schedule recurring and one-shot VM commands." skill: true --- diff --git a/docs/content/docs/hosted-api.mdx b/docs/content/docs/hosted-api.mdx index f3ea6f88d8..8a60862216 100644 --- a/docs/content/docs/hosted-api.mdx +++ b/docs/content/docs/hosted-api.mdx @@ -130,7 +130,7 @@ examples: [Processes & Shells](/agentos/docs/processes), [Networking & Previews](/agentos/docs/networking), [Software](/agentos/docs/software), -[Crons & Loops](/agentos/docs/cron), and +[Crons & Schedules](/agentos/docs/cron), and [Resource Limits](/agentos/docs/resource-limits). ## Configuration @@ -187,8 +187,8 @@ while the active generation reports that a restart is required. new generation from the latest desired config. Handles from an older generation fail with `stale_generation`. -The actor sleeps when idle. The VM starts lazily on the first action after the -actor wakes. Durable files and configuration survive sleep; running processes, +The VM sleeps when idle unless a process is running, and starts lazily on the +first action after it wakes; see [actor lifecycle](/actors/docs/lifecycle). Durable files and configuration survive sleep; running processes, terminals, contexts, and open streams do not. ## Events and replay diff --git a/docs/content/docs/persistence.mdx b/docs/content/docs/persistence.mdx index 700ef9987e..765fe63ade 100644 --- a/docs/content/docs/persistence.mdx +++ b/docs/content/docs/persistence.mdx @@ -43,9 +43,10 @@ language contexts, open streams, and live subscriptions do not. ## Sleep -A hosted VM's actor sleeps when idle and reports `vm.shutdown` with reason -`"sleep"`. The VM starts lazily on the first action after wake. Durable files, -configuration, and cron jobs survive sleep. +A hosted VM sleeps when idle unless a process is running, and reports +`vm.shutdown` with reason `"sleep"`. The VM starts lazily on the first action +after wake. Durable files, configuration, and cron jobs survive sleep. See +[actor lifecycle](/actors/docs/lifecycle). Library durable state uses `agentos_core_*` tables and filesystem storage uses `agentos_fs_*`. Each owner has an independent schema version and migration diff --git a/docs/content/docs/quickstart-hosted.mdx b/docs/content/docs/quickstart-hosted.mdx index 11bf71051c..e0a6b80188 100644 --- a/docs/content/docs/quickstart-hosted.mdx +++ b/docs/content/docs/quickstart-hosted.mdx @@ -4,10 +4,10 @@ description: "Create durable agentOS VMs by ID and control them from the RivetKi skill: true --- -Hosted agentOS VMs run as the prebuilt `agentOS` Rivet Actor. 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](/agentos/docs/quickstart). +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](/agentos/docs/quickstart). ## Which one should I use? @@ -16,6 +16,7 @@ VMs inside your own Node.js process instead, see the | **Runs** | As the prebuilt `agentOS` Rivet Actor on Rivet Cloud or [self-hosted](/agentos/docs/self-hosting) | Inside your Node.js process | | **Package** | `@rivet-dev/agentos`, or plain HTTP | `@rivet-dev/agentos-core` | | **VM identity** | A durable VM you create by key and reach by ID | A handle your process owns until `dispose()` | +| **Lifecycle** | Sleeps when idle unless a process is running; see [actor lifecycle](/actors/docs/lifecycle) | Runs until `dispose()` | | **Configuration** | Stored with the VM and versioned | Passed to `AgentOs.create()` | | **Clients** | Your backend, or browsers with a scoped JWT | Your backend | | **Host access** | None; configuration is serializable only | Host functions, host mounts, external sandboxes | @@ -30,7 +31,22 @@ npm install @rivet-dev/agentos ## Create a VM - +### Rivet Cloud + + + + +Sign up at the [Rivet dashboard](https://dashboard.rivet.dev) and create a new project. + + + + +When creating the project, choose agentOS as the product. + + + + +Set `RIVET_NAMESPACE` and `RIVET_TOKEN` from your project, then create a VM by ID: ```ts TypeScript @nocheck import { createAgentOsClient } from "@rivet-dev/agentos"; @@ -38,32 +54,54 @@ import { createAgentOsClient } from "@rivet-dev/agentos"; const client = createAgentOsClient({ endpoint: "https://api.rivet.dev" }); const vm = client.agentOS.getOrCreate(["my-vm"]); -const vmId = await vm.resolve(); ``` -```bash cURL -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 } -``` + + - +### Self-hosting -The client reads `RIVET_NAMESPACE` and `RIVET_TOKEN` from the environment. To -run your own control plane and worker instead of Rivet Cloud, see +Run the Rivet control plane and the agentOS worker on your own infrastructure, +then create the client with `endpoint: "http://localhost:6420"`. See [Self-Hosting](/agentos/docs/self-hosting). The VM is durable: calling `getOrCreate` with the same key reaches the same VM. -The cURL examples below call it as `$VM_ID` with a token in `$VM_TOKEN`; see -[authentication](/agentos/docs/hosted-api#authentication). +It sleeps when idle unless a process is running, and wakes on the next request; +see [actor lifecycle](/actors/docs/lifecycle). + +## Show me the code + +These examples continue from the `vm` above. Each cURL example calls the same +action over HTTP at `/gateway/$VM_ID/action/`, with a token in +`x-rivet-token`; see the [Hosted HTTP API](/agentos/docs/hosted-api). -## Files and processes +### Run Bash Commands + + + +```ts TypeScript @nocheck +const result = await vm.v1.process.run({ + command: "sh", + args: ["-c", "echo hello && ls /home/agentos"], + options: { captureStdio: true }, +}); + +console.log(result.stdout); +``` + +```bash cURL +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 } + }]}' +``` + + + +### Work With the Filesystem @@ -73,12 +111,9 @@ await vm.v1.filesystem.writeFile({ content: "Hello, world!", }); -const result = await vm.v1.process.run({ - command: "cat", - args: ["/home/agentos/hello.txt"], - options: { captureStdio: true }, +const file = await vm.v1.filesystem.readFile({ + path: "/home/agentos/hello.txt", }); -console.log(result.stdout); ``` ```bash cURL @@ -89,57 +124,67 @@ curl "https://api.rivet.dev/gateway/$VM_ID/action/v1.filesystem.writeFile" \ "content": "Hello, world!" }]}' -curl "https://api.rivet.dev/gateway/$VM_ID/action/v1.process.run" \ +curl "https://api.rivet.dev/gateway/$VM_ID/action/v1.filesystem.readFile" \ -H "x-rivet-token: $VM_TOKEN" \ - --json '{"args": [{ - "command": "cat", - "args": ["/home/agentos/hello.txt"], - "options": { "captureStdio": true } - }]}' + --json '{"args": [{"path": "/home/agentos/hello.txt"}]}' ``` -`v1.process.run` takes an executable and an argument array. Run `sh` with `-c` -when you need shell parsing. - -## Output survives reconnects +### Run a Dev Server & Create Preview URLs -Process output is retained in the VM. After a disconnect, page through it with -a cursor instead of losing it. +Start a Node.js server in the VM, then expose its port through a signed URL +that expires on its own, or revoke it early. A running server keeps the VM +awake. ```ts TypeScript @nocheck -const server = await vm.v1.process.spawn({ +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"], }); -// Later, from any client -const { events, nextCursor } = await vm.v1.process.output.read({ - process: server, +const preview = await vm.v1.network.preview.create({ + port: 3000, + ttlMs: 60 * 60 * 1_000, }); + +await vm.v1.network.preview.expire({ token: preview.token }); ``` ```bash cURL -curl "https://api.rivet.dev/gateway/$VM_ID/action/v1.process.spawn" \ +curl "https://api.rivet.dev/gateway/$VM_ID/action/v1.filesystem.writeFile" \ -H "x-rivet-token: $VM_TOKEN" \ --json '{"args": [{ - "command": "node", - "args": ["/home/agentos/server.js"] + "path": "/home/agentos/server.js", + "content": "require(\"node:http\").createServer((req, res) => res.end(\"hello\")).listen(3000);" }]}' -# → { "output": { "generation": 1, "pid": 42 } } -# Later, from any client -curl "https://api.rivet.dev/gateway/$VM_ID/action/v1.process.output.read" \ +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": [{"process": {"generation": 1, "pid": 42}}]}' + --json '{"args": [{"port": 3000, "ttlMs": 3600000}]}' +# → { "output": { "path": "/preview/…/", "token": "…", … } } + +curl "https://api.rivet.dev/gateway/$VM_ID/action/v1.network.preview.expire" \ + -H "x-rivet-token: $VM_TOKEN" \ + --json '{"args": [{"token": "…"}]}' ``` -## Language runtimes +### Use the Language Runtimes Run JavaScript, TypeScript, and Python directly, with npm and pip installs built in. @@ -148,8 +193,13 @@ built in. ```ts TypeScript @nocheck await vm.v1.javascript.execute({ source: "console.log(1 + 2)" }); +await vm.v1.typescript.execute({ + source: "const n: number = 3; console.log(n)", +}); -const { value } = await vm.v1.python.evaluate({ expression: "21 * 2" }); +const { value } = await vm.v1.python.evaluate({ + expression: "21 * 2", +}); ``` ```bash cURL @@ -164,11 +214,37 @@ curl "https://api.rivet.dev/gateway/$VM_ID/action/v1.python.evaluate" \ -## Install software +### Schedule Cron Jobs + + + +```ts TypeScript @nocheck +await vm.v1.cron.schedule({ + name: "cleanup", + expression: "0 * * * *", + command: "rm", + args: ["-rf", "/tmp/cache"], +}); +``` + +```bash cURL +curl "https://api.rivet.dev/gateway/$VM_ID/action/v1.cron.schedule" \ + -H "x-rivet-token: $VM_TOKEN" \ + --json '{"args": [{ + "name": "cleanup", + "expression": "0 * * * *", + "command": "rm", + "args": ["-rf", "/tmp/cache"] + }]}' +``` + + + +### Install Software -Install tools from the [registry](/agentos/docs/registry) while the VM is -running. Packages are verified against their digest before their commands -appear on `/opt/agentos/bin`. +Install tools from the [software registry](/agentos/docs/registry/software) +while the VM is running. Packages are verified against their digest before +their commands appear on `/opt/agentos/bin`. @@ -199,40 +275,53 @@ curl "https://api.rivet.dev/gateway/$VM_ID/action/v1.software.install" \ -## Persist files +### Give Clients Direct Access With JWTs -The filesystem is in memory by default. Give the VM a durable root when you -create it to keep files across VM replacement. +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. ```ts TypeScript @nocheck -const vm = client.agentOS.getOrCreate(["my-vm"], { - createWithInput: { - config: { filesystem: { root: { type: "durable" } } }, - }, +// Backend: issue a token for this VM only +const vmId = await vm.resolve(); +const { token } = await vm.issueToken({ + subject: "user-123", + expiresIn: 3600, }); + +// Client: call the VM directly +const userClient = createAgentOsClient({ + endpoint: "https://api.rivet.dev", + namespace: "my-namespace", + token, +}); +const userVm = userClient.getForId("agentOS", vmId); ``` ```bash cURL -curl "https://api.rivet.dev/gateway/$VM_ID/action/v1.config.patch" \ - -H "x-rivet-token: $VM_TOKEN" \ - --json '{"args": [{"patch": {"filesystem": {"root": {"type": "durable"}}}}]}' - -curl "https://api.rivet.dev/gateway/$VM_ID/action/v1.vm.restart" \ - -H "x-rivet-token: $VM_TOKEN" \ - --json '{"args": [{}]}' +curl -X POST "https://api.rivet.dev/auth/tokens" \ + -H "Authorization: Bearer $RIVET_TOKEN" \ + --json @- < -Creation input is used only when the VM is first created. Change an existing -VM's configuration with `v1.config.patch`; filesystem changes apply after -`v1.vm.restart`. - ## Next steps -- See the [Hosted HTTP API](/agentos/docs/hosted-api) for authentication, - configuration, events, errors, and every action. -- Expose a VM port with [expiring previews](/agentos/docs/networking#previews). -- Schedule commands with [Crons & Loops](/agentos/docs/cron). +- See the [Hosted HTTP API](/agentos/docs/hosted-api) for configuration, + events, errors, and every action. +- Keep files across restarts with [Persistence & Sleep](/agentos/docs/persistence). diff --git a/docs/content/docs/quickstart.mdx b/docs/content/docs/quickstart.mdx index b744e1b100..5888195108 100644 --- a/docs/content/docs/quickstart.mdx +++ b/docs/content/docs/quickstart.mdx @@ -15,6 +15,7 @@ durable VMs by ID and control them over HTTP instead, see the | **Runs** | As the prebuilt `agentOS` Rivet Actor on Rivet Cloud or [self-hosted](/agentos/docs/self-hosting) | Inside your Node.js process | | **Package** | `@rivet-dev/agentos`, or plain HTTP | `@rivet-dev/agentos-core` | | **VM identity** | A durable VM you create by key and reach by ID | A handle your process owns until `dispose()` | +| **Lifecycle** | Sleeps when idle unless a process is running; see [actor lifecycle](/actors/docs/lifecycle) | Runs until `dispose()` | | **Configuration** | Stored with the VM and versioned | Passed to `AgentOs.create()` | | **Clients** | Your backend, or browsers with a scoped JWT | Your backend | | **Host access** | None; configuration is serializable only | Host functions, host mounts, external sandboxes | @@ -33,36 +34,45 @@ npm install @rivet-dev/agentos-core The VM runs until `dispose()` is called. -## Files and processes +## Show me the code - +These examples continue from the `vm` above. -`process.execFile` takes an executable and an argument array. Use -`process.exec` when you need shell parsing. +### Run Bash Commands -## Output survives reconnects + -Retain a process's output in the VM, then page through it with a cursor -instead of losing it. +### Work With the Filesystem - + -## Language runtimes +### Run a Dev Server + +Start a Node.js server in the VM, then send it a request from your process. +The listener is not exposed on the host network. + + + +### Use the Language Runtimes Run JavaScript, TypeScript, and Python directly, with npm and pip installs built in. -## Install software +### Schedule Cron Jobs + + + +### Install Software -Install tools from the [registry](/agentos/docs/registry) while the VM is -running. Packages are verified against their digest before their commands -appear on `/opt/agentos/bin`. +Install tools from the [software registry](/agentos/docs/registry/software) +while the VM is running. Packages are verified against their digest before +their commands appear on `/opt/agentos/bin`. -## Persist files +### Persist Files The filesystem is in memory by default. Store the root filesystem in SQLite to keep files across VM recreation. @@ -71,8 +81,6 @@ keep files across VM recreation. ## Next steps -- Run commands with [Bash](/agentos/docs/bash), or code with - [Node.js](/agentos/docs/javascript) and [Python](/agentos/docs/python). - Expose your own functions to guest code with [host functions](/agentos/docs/host-functions). - Lock the VM down with [Permissions](/agentos/docs/permissions) and diff --git a/docs/content/docs/self-hosting.mdx b/docs/content/docs/self-hosting.mdx index cd55780a0c..4e83214a79 100644 --- a/docs/content/docs/self-hosting.mdx +++ b/docs/content/docs/self-hosting.mdx @@ -16,6 +16,7 @@ it needs neither. | **Runs** | As the prebuilt `agentOS` Rivet Actor on Rivet Cloud or [self-hosted](/agentos/docs/self-hosting) | Inside your Node.js process | | **Package** | `@rivet-dev/agentos`, or plain HTTP | `@rivet-dev/agentos-core` | | **VM identity** | A durable VM you create by key and reach by ID | A handle your process owns until `dispose()` | +| **Lifecycle** | Sleeps when idle unless a process is running; see [actor lifecycle](/actors/docs/lifecycle) | Runs until `dispose()` | | **Configuration** | Stored with the VM and versioned | Passed to `AgentOs.create()` | | **Clients** | Your backend, or browsers with a scoped JWT | Your backend | | **Host access** | None; configuration is serializable only | Host functions, host mounts, external sandboxes | diff --git a/docs/sidebar.json b/docs/sidebar.json index 29479f0a84..c311bfe4b5 100644 --- a/docs/sidebar.json +++ b/docs/sidebar.json @@ -45,16 +45,6 @@ } ] }, - { - "title": "Scheduling", - "pages": [ - { - "title": "Crons & Loops", - "href": "/agentos/docs/cron", - "icon": "faClock" - } - ] - }, { "title": "Operating System", "pages": [ @@ -73,6 +63,11 @@ "href": "/agentos/docs/processes", "icon": "faTerminal" }, + { + "title": "Crons & Schedules", + "href": "/agentos/docs/cron", + "icon": "faClock" + }, { "title": "Networking & Previews", "href": "/agentos/docs/networking", diff --git a/examples/embedded/quickstart.ts b/examples/embedded/quickstart.ts index a575865bcb..bf1eaa5639 100644 --- a/examples/embedded/quickstart.ts +++ b/examples/embedded/quickstart.ts @@ -6,35 +6,51 @@ const vm = await AgentOs.create(); // docs:end create try { - // docs:start files-processes - await vm.filesystem.writeFile("/home/agentos/hello.txt", "Hello, world!"); - - const result = await vm.process.execFile("cat", ["/home/agentos/hello.txt"], { + // docs:start bash + const result = await vm.process.exec("echo hello && ls /home/agentos", { output: { capture: "all" }, }); + console.log(result.stdout); - // docs:end files-processes + // docs:end bash + + // docs:start filesystem + await vm.filesystem.writeFile("/home/agentos/hello.txt", "Hello, world!"); + + const file = await vm.filesystem.readFile("/home/agentos/hello.txt"); + // docs:end filesystem + console.log(new TextDecoder().decode(file)); - // docs:start output + // docs:start dev-server await vm.filesystem.writeFile( "/home/agentos/server.js", - 'require("http").createServer((req, res) => res.end("ok")).listen(3000); console.log("listening");', + `require("node:http") + .createServer((req, res) => res.end("hello")) + .listen(3000);`, ); - const server = await vm.process.spawn("node", ["/home/agentos/server.js"], { - output: { retainEvents: true }, - }); - // Later, page through the retained output with a cursor - const { events, nextCursor } = await vm.process.readOutput(server.pid); - console.log(events.length, nextCursor); - // docs:end output + await vm.process.spawn("node", ["/home/agentos/server.js"]); + + // The listener stays inside the VM. Send it a request from the host. + const response = await vm.network.httpRequest({ port: 3000, path: "/" }); + console.log(new TextDecoder().decode(response.body)); + // docs:end dev-server // docs:start languages await vm.javascript.execute("console.log(1 + 2)"); + await vm.typescript.execute("const n: number = 3; console.log(n)"); const answer = await vm.python.evaluate("21 * 2"); - console.log(answer.outcome === "succeeded" ? answer.value : answer.error); // docs:end languages + console.log(answer.outcome === "succeeded" ? answer.value : answer.error); + + // docs:start cron + vm.cron.schedule({ + id: "cleanup", + schedule: "0 * * * *", + action: { type: "exec", command: "rm", args: ["-rf", "/tmp/cache"] }, + }); + // docs:end cron // docs:start software // Registry packages are published next to a manifest with each release. From 09b1171ff7f5a8ff8edf17dedf9a21f29d6da16c Mon Sep 17 00:00:00 2001 From: Sree Narayanan Date: Mon, 12 Oct 2026 10:36:45 +0000 Subject: [PATCH 11/18] docs(agentos): TypeScript or HTTP to create a hosted VM, link and rename Self-Hosting (Hosted) Amp-Thread-ID: https://ampcode.com/threads/T-c7d08b58-92c1-4fc0-abb9-7a15f4504a4f Co-authored-by: Amp --- docs/content/docs/quickstart-hosted.mdx | 31 +++++++++++++++++-------- docs/content/docs/self-hosting.mdx | 20 ++++++++++++++-- docs/sidebar.json | 2 +- 3 files changed, 40 insertions(+), 13 deletions(-) diff --git a/docs/content/docs/quickstart-hosted.mdx b/docs/content/docs/quickstart-hosted.mdx index e0a6b80188..175adae82c 100644 --- a/docs/content/docs/quickstart-hosted.mdx +++ b/docs/content/docs/quickstart-hosted.mdx @@ -23,12 +23,6 @@ process instead, see the [library quickstart](/agentos/docs/quickstart). | **Previews** | Expiring preview URLs | None | | **Best for** | Long-lived VMs shared across clients | Embedding VMs in an existing backend | -## Install - -```bash -npm install @rivet-dev/agentos -``` - ## Create a VM ### Rivet Cloud @@ -46,9 +40,13 @@ When creating the project, choose agentOS as the product. -Set `RIVET_NAMESPACE` and `RIVET_TOKEN` from your project, then create a VM by ID: +Set `RIVET_NAMESPACE` and `RIVET_TOKEN` from your project, then create a VM by +ID from TypeScript or plain HTTP: + + ```ts TypeScript @nocheck +// npm install @rivet-dev/agentos import { createAgentOsClient } from "@rivet-dev/agentos"; const client = createAgentOsClient({ endpoint: "https://api.rivet.dev" }); @@ -56,14 +54,27 @@ const client = createAgentOsClient({ endpoint: "https://api.rivet.dev" }); const vm = client.agentOS.getOrCreate(["my-vm"]); ``` +```bash HTTP +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 } +``` + + + ### Self-hosting -Run the Rivet control plane and the agentOS worker on your own infrastructure, -then create the client with `endpoint: "http://localhost:6420"`. See -[Self-Hosting](/agentos/docs/self-hosting). +See [Self-Hosting (Hosted)](/agentos/docs/self-hosting) to run hosted VMs on your own +infrastructure. The VM is durable: calling `getOrCreate` with the same key reaches the same VM. It sleeps when idle unless a process is running, and wakes on the next request; diff --git a/docs/content/docs/self-hosting.mdx b/docs/content/docs/self-hosting.mdx index 4e83214a79..cc38aaffb0 100644 --- a/docs/content/docs/self-hosting.mdx +++ b/docs/content/docs/self-hosting.mdx @@ -1,5 +1,5 @@ --- -title: "Self-Hosting" +title: "Self-Hosting (Hosted)" description: "Run hosted agentOS VMs on your own infrastructure with the Rivet control plane and the agentOS worker image." --- @@ -49,9 +49,12 @@ docker run \ -Create a client pointed at your control plane, then create a VM by ID: +Create a VM by ID on your control plane from TypeScript or plain HTTP: + + ```ts TypeScript @nocheck +// npm install @rivet-dev/agentos import { createAgentOsClient } from "@rivet-dev/agentos"; const client = createAgentOsClient({ endpoint: "http://localhost:6420" }); @@ -59,6 +62,19 @@ const client = createAgentOsClient({ endpoint: "http://localhost:6420" }); const vm = client.agentOS.getOrCreate(["my-vm"]); ``` +```bash HTTP +curl -X PUT "http://localhost:6420/actors?namespace=default" \ + --json '{ + "name": "agentOS", + "key": "my-vm", + "runner_name_selector": "services", + "crash_policy": "sleep" + }' +# → { "actor": { "actor_id": "…" }, "created": true } +``` + + + diff --git a/docs/sidebar.json b/docs/sidebar.json index c311bfe4b5..863a1bd7cf 100644 --- a/docs/sidebar.json +++ b/docs/sidebar.json @@ -115,7 +115,7 @@ "href": "/agentos/docs/hosted-api" }, { - "title": "Self-Hosting", + "title": "Self-Hosting (Hosted)", "href": "/agentos/docs/self-hosting" }, { From c727c226fb263f4324580905ba051b852011229f Mon Sep 17 00:00:00 2001 From: Sree Narayanan Date: Mon, 12 Oct 2026 10:38:51 +0000 Subject: [PATCH 12/18] docs(agentos): keep the hosted quickstart's Self-hosting step to a single link Amp-Thread-ID: https://ampcode.com/threads/T-c7d08b58-92c1-4fc0-abb9-7a15f4504a4f Co-authored-by: Amp --- docs/content/docs/quickstart-hosted.mdx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/content/docs/quickstart-hosted.mdx b/docs/content/docs/quickstart-hosted.mdx index 175adae82c..ad19cecc5c 100644 --- a/docs/content/docs/quickstart-hosted.mdx +++ b/docs/content/docs/quickstart-hosted.mdx @@ -25,6 +25,10 @@ process instead, see the [library quickstart](/agentos/docs/quickstart). ## Create a VM +The VM is durable: calling `getOrCreate` with the same key reaches the same VM. +It sleeps when idle unless a process is running, and wakes on the next request; +see [actor lifecycle](/actors/docs/lifecycle). + ### Rivet Cloud @@ -76,10 +80,6 @@ curl -X PUT "https://api.rivet.dev/actors?namespace=$RIVET_NAMESPACE" \ See [Self-Hosting (Hosted)](/agentos/docs/self-hosting) to run hosted VMs on your own infrastructure. -The VM is durable: calling `getOrCreate` with the same key reaches the same VM. -It sleeps when idle unless a process is running, and wakes on the next request; -see [actor lifecycle](/actors/docs/lifecycle). - ## Show me the code These examples continue from the `vm` above. Each cURL example calls the same From 08802c9ad489f514e57d579274d01821b108a89e Mon Sep 17 00:00:00 2001 From: Sree Narayanan Date: Mon, 12 Oct 2026 10:44:43 +0000 Subject: [PATCH 13/18] docs(agentos): reword the hosted vs library comparison table Amp-Thread-ID: https://ampcode.com/threads/T-c7d08b58-92c1-4fc0-abb9-7a15f4504a4f Co-authored-by: Amp --- docs/content/docs/quickstart-hosted.mdx | 6 +++--- docs/content/docs/quickstart.mdx | 6 +++--- docs/content/docs/self-hosting.mdx | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/content/docs/quickstart-hosted.mdx b/docs/content/docs/quickstart-hosted.mdx index ad19cecc5c..54c2f8d753 100644 --- a/docs/content/docs/quickstart-hosted.mdx +++ b/docs/content/docs/quickstart-hosted.mdx @@ -13,13 +13,13 @@ process instead, see the [library quickstart](/agentos/docs/quickstart). | | [Hosted](/agentos/docs/quickstart-hosted) | [Library](/agentos/docs/quickstart) | |---|---|---| -| **Runs** | As the prebuilt `agentOS` Rivet Actor on Rivet Cloud or [self-hosted](/agentos/docs/self-hosting) | Inside your Node.js process | +| **Runs** | As a durable Rivet Actor on Rivet Cloud or a [self-hosted](/agentos/docs/self-hosting) Docker image | Ephemeral, embedded inside your process as a library | | **Package** | `@rivet-dev/agentos`, or plain HTTP | `@rivet-dev/agentos-core` | | **VM identity** | A durable VM you create by key and reach by ID | A handle your process owns until `dispose()` | -| **Lifecycle** | Sleeps when idle unless a process is running; see [actor lifecycle](/actors/docs/lifecycle) | Runs until `dispose()` | +| **Lifecycle** | Durable, sleeps when idle unless a process is running; see [actor lifecycle](/actors/docs/lifecycle) | Ephemeral, runs until `dispose()` or process exit | | **Configuration** | Stored with the VM and versioned | Passed to `AgentOs.create()` | | **Clients** | Your backend, or browsers with a scoped JWT | Your backend | -| **Host access** | None; configuration is serializable only | Host functions, host mounts, external sandboxes | +| **Host access** | None | Host functions, host mounts, external sandboxes | | **Previews** | Expiring preview URLs | None | | **Best for** | Long-lived VMs shared across clients | Embedding VMs in an existing backend | diff --git a/docs/content/docs/quickstart.mdx b/docs/content/docs/quickstart.mdx index 5888195108..b57af18742 100644 --- a/docs/content/docs/quickstart.mdx +++ b/docs/content/docs/quickstart.mdx @@ -12,13 +12,13 @@ durable VMs by ID and control them over HTTP instead, see the | | [Hosted](/agentos/docs/quickstart-hosted) | [Library](/agentos/docs/quickstart) | |---|---|---| -| **Runs** | As the prebuilt `agentOS` Rivet Actor on Rivet Cloud or [self-hosted](/agentos/docs/self-hosting) | Inside your Node.js process | +| **Runs** | As a durable Rivet Actor on Rivet Cloud or a [self-hosted](/agentos/docs/self-hosting) Docker image | Ephemeral, embedded inside your process as a library | | **Package** | `@rivet-dev/agentos`, or plain HTTP | `@rivet-dev/agentos-core` | | **VM identity** | A durable VM you create by key and reach by ID | A handle your process owns until `dispose()` | -| **Lifecycle** | Sleeps when idle unless a process is running; see [actor lifecycle](/actors/docs/lifecycle) | Runs until `dispose()` | +| **Lifecycle** | Durable, sleeps when idle unless a process is running; see [actor lifecycle](/actors/docs/lifecycle) | Ephemeral, runs until `dispose()` or process exit | | **Configuration** | Stored with the VM and versioned | Passed to `AgentOs.create()` | | **Clients** | Your backend, or browsers with a scoped JWT | Your backend | -| **Host access** | None; configuration is serializable only | Host functions, host mounts, external sandboxes | +| **Host access** | None | Host functions, host mounts, external sandboxes | | **Previews** | Expiring preview URLs | None | | **Best for** | Long-lived VMs shared across clients | Embedding VMs in an existing backend | diff --git a/docs/content/docs/self-hosting.mdx b/docs/content/docs/self-hosting.mdx index cc38aaffb0..c2df999808 100644 --- a/docs/content/docs/self-hosting.mdx +++ b/docs/content/docs/self-hosting.mdx @@ -13,13 +13,13 @@ it needs neither. | | [Hosted](/agentos/docs/quickstart-hosted) | [Library](/agentos/docs/quickstart) | |---|---|---| -| **Runs** | As the prebuilt `agentOS` Rivet Actor on Rivet Cloud or [self-hosted](/agentos/docs/self-hosting) | Inside your Node.js process | +| **Runs** | As a durable Rivet Actor on Rivet Cloud or a [self-hosted](/agentos/docs/self-hosting) Docker image | Ephemeral, embedded inside your process as a library | | **Package** | `@rivet-dev/agentos`, or plain HTTP | `@rivet-dev/agentos-core` | | **VM identity** | A durable VM you create by key and reach by ID | A handle your process owns until `dispose()` | -| **Lifecycle** | Sleeps when idle unless a process is running; see [actor lifecycle](/actors/docs/lifecycle) | Runs until `dispose()` | +| **Lifecycle** | Durable, sleeps when idle unless a process is running; see [actor lifecycle](/actors/docs/lifecycle) | Ephemeral, runs until `dispose()` or process exit | | **Configuration** | Stored with the VM and versioned | Passed to `AgentOs.create()` | | **Clients** | Your backend, or browsers with a scoped JWT | Your backend | -| **Host access** | None; configuration is serializable only | Host functions, host mounts, external sandboxes | +| **Host access** | None | Host functions, host mounts, external sandboxes | | **Previews** | Expiring preview URLs | None | | **Best for** | Long-lived VMs shared across clients | Embedding VMs in an existing backend | From 9cbe27cad4addc3fd88e502207cf2f801ace42d0 Mon Sep 17 00:00:00 2001 From: Sree Narayanan Date: Mon, 12 Oct 2026 10:45:09 +0000 Subject: [PATCH 14/18] docs(agentos): trim quickstart intros, label HTTP examples HTTP Amp-Thread-ID: https://ampcode.com/threads/T-c7d08b58-92c1-4fc0-abb9-7a15f4504a4f Co-authored-by: Amp --- docs/content/docs/hosted-api.mdx | 8 ++++---- docs/content/docs/quickstart-hosted.mdx | 22 +++++++--------------- docs/content/docs/quickstart.mdx | 2 -- 3 files changed, 11 insertions(+), 21 deletions(-) diff --git a/docs/content/docs/hosted-api.mdx b/docs/content/docs/hosted-api.mdx index 8a60862216..d710b9fd9c 100644 --- a/docs/content/docs/hosted-api.mdx +++ b/docs/content/docs/hosted-api.mdx @@ -28,7 +28,7 @@ The contract is in alpha (`agentos-sdk.dev/v1alpha1`). The actor and the ## Create a VM -```bash cURL +```bash HTTP curl -X PUT "https://api.rivet.dev/actors?namespace=$RIVET_NAMESPACE" \ -H "Authorization: Bearer $RIVET_TOKEN" \ --json '{ @@ -63,7 +63,7 @@ Keep your admin token on the backend. To let a browser or other untrusted client call one VM directly, issue a JWT scoped to that VM. See [JWTs](/docs/jwt). -```bash cURL +```bash HTTP curl -X POST "https://api.rivet.dev/auth/tokens" \ -H "Authorization: Bearer $RIVET_TOKEN" \ --json @- < @@ -82,10 +78,6 @@ infrastructure. ## Show me the code -These examples continue from the `vm` above. Each cURL example calls the same -action over HTTP at `/gateway/$VM_ID/action/`, with a token in -`x-rivet-token`; see the [Hosted HTTP API](/agentos/docs/hosted-api). - ### Run Bash Commands @@ -100,7 +92,7 @@ const result = await vm.v1.process.run({ console.log(result.stdout); ``` -```bash cURL +```bash HTTP curl "https://api.rivet.dev/gateway/$VM_ID/action/v1.process.run" \ -H "x-rivet-token: $VM_TOKEN" \ --json '{"args": [{ @@ -127,7 +119,7 @@ const file = await vm.v1.filesystem.readFile({ }); ``` -```bash cURL +```bash HTTP curl "https://api.rivet.dev/gateway/$VM_ID/action/v1.filesystem.writeFile" \ -H "x-rivet-token: $VM_TOKEN" \ --json '{"args": [{ @@ -171,7 +163,7 @@ const preview = await vm.v1.network.preview.create({ await vm.v1.network.preview.expire({ token: preview.token }); ``` -```bash cURL +```bash HTTP curl "https://api.rivet.dev/gateway/$VM_ID/action/v1.filesystem.writeFile" \ -H "x-rivet-token: $VM_TOKEN" \ --json '{"args": [{ @@ -213,7 +205,7 @@ const { value } = await vm.v1.python.evaluate({ }); ``` -```bash cURL +```bash HTTP curl "https://api.rivet.dev/gateway/$VM_ID/action/v1.javascript.execute" \ -H "x-rivet-token: $VM_TOKEN" \ --json '{"args": [{"source": "console.log(1 + 2)"}]}' @@ -238,7 +230,7 @@ await vm.v1.cron.schedule({ }); ``` -```bash cURL +```bash HTTP curl "https://api.rivet.dev/gateway/$VM_ID/action/v1.cron.schedule" \ -H "x-rivet-token: $VM_TOKEN" \ --json '{"args": [{ @@ -273,7 +265,7 @@ if (installed.config.state === "restart_required") { } ``` -```bash cURL +```bash HTTP curl "https://api.rivet.dev/gateway/$VM_ID/action/v1.software.install" \ -H "x-rivet-token: $VM_TOKEN" \ --json '{"args": [{ @@ -311,7 +303,7 @@ const userClient = createAgentOsClient({ const userVm = userClient.getForId("agentOS", vmId); ``` -```bash cURL +```bash HTTP curl -X POST "https://api.rivet.dev/auth/tokens" \ -H "Authorization: Bearer $RIVET_TOKEN" \ --json @- < From 4892d72b7601fc6cce0ecb9db6fa5c0e98dfa712 Mon Sep 17 00:00:00 2001 From: Sree Narayanan Date: Mon, 12 Oct 2026 10:47:33 +0000 Subject: [PATCH 15/18] docs(agentos): say durable instance in the comparison table Amp-Thread-ID: https://ampcode.com/threads/T-c7d08b58-92c1-4fc0-abb9-7a15f4504a4f Co-authored-by: Amp --- docs/content/docs/quickstart-hosted.mdx | 2 +- docs/content/docs/quickstart.mdx | 2 +- docs/content/docs/self-hosting.mdx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/content/docs/quickstart-hosted.mdx b/docs/content/docs/quickstart-hosted.mdx index 397af4162f..32d3b69e72 100644 --- a/docs/content/docs/quickstart-hosted.mdx +++ b/docs/content/docs/quickstart-hosted.mdx @@ -13,7 +13,7 @@ process instead, see the [library quickstart](/agentos/docs/quickstart). | | [Hosted](/agentos/docs/quickstart-hosted) | [Library](/agentos/docs/quickstart) | |---|---|---| -| **Runs** | As a durable Rivet Actor on Rivet Cloud or a [self-hosted](/agentos/docs/self-hosting) Docker image | Ephemeral, embedded inside your process as a library | +| **Runs** | As a durable instance on Rivet Cloud or a [self-hosted](/agentos/docs/self-hosting) Docker image | Ephemeral, embedded inside your process as a library | | **Package** | `@rivet-dev/agentos`, or plain HTTP | `@rivet-dev/agentos-core` | | **VM identity** | A durable VM you create by key and reach by ID | A handle your process owns until `dispose()` | | **Lifecycle** | Durable, sleeps when idle unless a process is running; see [actor lifecycle](/actors/docs/lifecycle) | Ephemeral, runs until `dispose()` or process exit | diff --git a/docs/content/docs/quickstart.mdx b/docs/content/docs/quickstart.mdx index 8abd4b3f97..6a9898db1c 100644 --- a/docs/content/docs/quickstart.mdx +++ b/docs/content/docs/quickstart.mdx @@ -12,7 +12,7 @@ durable VMs by ID and control them over HTTP instead, see the | | [Hosted](/agentos/docs/quickstart-hosted) | [Library](/agentos/docs/quickstart) | |---|---|---| -| **Runs** | As a durable Rivet Actor on Rivet Cloud or a [self-hosted](/agentos/docs/self-hosting) Docker image | Ephemeral, embedded inside your process as a library | +| **Runs** | As a durable instance on Rivet Cloud or a [self-hosted](/agentos/docs/self-hosting) Docker image | Ephemeral, embedded inside your process as a library | | **Package** | `@rivet-dev/agentos`, or plain HTTP | `@rivet-dev/agentos-core` | | **VM identity** | A durable VM you create by key and reach by ID | A handle your process owns until `dispose()` | | **Lifecycle** | Durable, sleeps when idle unless a process is running; see [actor lifecycle](/actors/docs/lifecycle) | Ephemeral, runs until `dispose()` or process exit | diff --git a/docs/content/docs/self-hosting.mdx b/docs/content/docs/self-hosting.mdx index c2df999808..94f0a62bb8 100644 --- a/docs/content/docs/self-hosting.mdx +++ b/docs/content/docs/self-hosting.mdx @@ -13,7 +13,7 @@ it needs neither. | | [Hosted](/agentos/docs/quickstart-hosted) | [Library](/agentos/docs/quickstart) | |---|---|---| -| **Runs** | As a durable Rivet Actor on Rivet Cloud or a [self-hosted](/agentos/docs/self-hosting) Docker image | Ephemeral, embedded inside your process as a library | +| **Runs** | As a durable instance on Rivet Cloud or a [self-hosted](/agentos/docs/self-hosting) Docker image | Ephemeral, embedded inside your process as a library | | **Package** | `@rivet-dev/agentos`, or plain HTTP | `@rivet-dev/agentos-core` | | **VM identity** | A durable VM you create by key and reach by ID | A handle your process owns until `dispose()` | | **Lifecycle** | Durable, sleeps when idle unless a process is running; see [actor lifecycle](/actors/docs/lifecycle) | Ephemeral, runs until `dispose()` or process exit | From ee99051d3b3c3fbacb11a7c7fa0d3b9a9909306c Mon Sep 17 00:00:00 2001 From: Sree Narayanan Date: Mon, 12 Oct 2026 10:47:49 +0000 Subject: [PATCH 16/18] docs(agentos): move Lifecycle under Runs in the comparison table Amp-Thread-ID: https://ampcode.com/threads/T-c7d08b58-92c1-4fc0-abb9-7a15f4504a4f Co-authored-by: Amp --- docs/content/docs/quickstart-hosted.mdx | 4 ++-- docs/content/docs/quickstart.mdx | 4 ++-- docs/content/docs/self-hosting.mdx | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/content/docs/quickstart-hosted.mdx b/docs/content/docs/quickstart-hosted.mdx index 32d3b69e72..7b0e19fb18 100644 --- a/docs/content/docs/quickstart-hosted.mdx +++ b/docs/content/docs/quickstart-hosted.mdx @@ -13,10 +13,10 @@ process instead, see the [library quickstart](/agentos/docs/quickstart). | | [Hosted](/agentos/docs/quickstart-hosted) | [Library](/agentos/docs/quickstart) | |---|---|---| -| **Runs** | As a durable instance on Rivet Cloud or a [self-hosted](/agentos/docs/self-hosting) Docker image | Ephemeral, embedded inside your process as a library | +| **Runs** | As a durable instance on Rivet Cloud or a [self-hosted](/agentos/docs/self-hosting) Docker image | Embedded inside your process as a library | +| **Lifecycle** | Durable, sleeps when idle unless a process is running; see [actor lifecycle](/actors/docs/lifecycle) | Ephemeral, runs until `dispose()` or process exit | | **Package** | `@rivet-dev/agentos`, or plain HTTP | `@rivet-dev/agentos-core` | | **VM identity** | A durable VM you create by key and reach by ID | A handle your process owns until `dispose()` | -| **Lifecycle** | Durable, sleeps when idle unless a process is running; see [actor lifecycle](/actors/docs/lifecycle) | Ephemeral, runs until `dispose()` or process exit | | **Configuration** | Stored with the VM and versioned | Passed to `AgentOs.create()` | | **Clients** | Your backend, or browsers with a scoped JWT | Your backend | | **Host access** | None | Host functions, host mounts, external sandboxes | diff --git a/docs/content/docs/quickstart.mdx b/docs/content/docs/quickstart.mdx index 6a9898db1c..f239cb2c7d 100644 --- a/docs/content/docs/quickstart.mdx +++ b/docs/content/docs/quickstart.mdx @@ -12,10 +12,10 @@ durable VMs by ID and control them over HTTP instead, see the | | [Hosted](/agentos/docs/quickstart-hosted) | [Library](/agentos/docs/quickstart) | |---|---|---| -| **Runs** | As a durable instance on Rivet Cloud or a [self-hosted](/agentos/docs/self-hosting) Docker image | Ephemeral, embedded inside your process as a library | +| **Runs** | As a durable instance on Rivet Cloud or a [self-hosted](/agentos/docs/self-hosting) Docker image | Embedded inside your process as a library | +| **Lifecycle** | Durable, sleeps when idle unless a process is running; see [actor lifecycle](/actors/docs/lifecycle) | Ephemeral, runs until `dispose()` or process exit | | **Package** | `@rivet-dev/agentos`, or plain HTTP | `@rivet-dev/agentos-core` | | **VM identity** | A durable VM you create by key and reach by ID | A handle your process owns until `dispose()` | -| **Lifecycle** | Durable, sleeps when idle unless a process is running; see [actor lifecycle](/actors/docs/lifecycle) | Ephemeral, runs until `dispose()` or process exit | | **Configuration** | Stored with the VM and versioned | Passed to `AgentOs.create()` | | **Clients** | Your backend, or browsers with a scoped JWT | Your backend | | **Host access** | None | Host functions, host mounts, external sandboxes | diff --git a/docs/content/docs/self-hosting.mdx b/docs/content/docs/self-hosting.mdx index 94f0a62bb8..05e3e573b8 100644 --- a/docs/content/docs/self-hosting.mdx +++ b/docs/content/docs/self-hosting.mdx @@ -13,10 +13,10 @@ it needs neither. | | [Hosted](/agentos/docs/quickstart-hosted) | [Library](/agentos/docs/quickstart) | |---|---|---| -| **Runs** | As a durable instance on Rivet Cloud or a [self-hosted](/agentos/docs/self-hosting) Docker image | Ephemeral, embedded inside your process as a library | +| **Runs** | As a durable instance on Rivet Cloud or a [self-hosted](/agentos/docs/self-hosting) Docker image | Embedded inside your process as a library | +| **Lifecycle** | Durable, sleeps when idle unless a process is running; see [actor lifecycle](/actors/docs/lifecycle) | Ephemeral, runs until `dispose()` or process exit | | **Package** | `@rivet-dev/agentos`, or plain HTTP | `@rivet-dev/agentos-core` | | **VM identity** | A durable VM you create by key and reach by ID | A handle your process owns until `dispose()` | -| **Lifecycle** | Durable, sleeps when idle unless a process is running; see [actor lifecycle](/actors/docs/lifecycle) | Ephemeral, runs until `dispose()` or process exit | | **Configuration** | Stored with the VM and versioned | Passed to `AgentOs.create()` | | **Clients** | Your backend, or browsers with a scoped JWT | Your backend | | **Host access** | None | Host functions, host mounts, external sandboxes | From 863f7314292d712062b3610511087938a35df936 Mon Sep 17 00:00:00 2001 From: Sree Narayanan Date: Mon, 12 Oct 2026 10:48:51 +0000 Subject: [PATCH 17/18] docs(agentos): put the library quickstart's install and create steps in Steps Amp-Thread-ID: https://ampcode.com/threads/T-c7d08b58-92c1-4fc0-abb9-7a15f4504a4f Co-authored-by: Amp --- docs/content/docs/quickstart.mdx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/docs/content/docs/quickstart.mdx b/docs/content/docs/quickstart.mdx index f239cb2c7d..86c529fc57 100644 --- a/docs/content/docs/quickstart.mdx +++ b/docs/content/docs/quickstart.mdx @@ -22,17 +22,24 @@ durable VMs by ID and control them over HTTP instead, see the | **Previews** | Expiring preview URLs | None | | **Best for** | Long-lived VMs shared across clients | Embedding VMs in an existing backend | -## Install +## Create a VM + + + ```bash npm install @rivet-dev/agentos-core ``` -## Create a VM + + -The VM runs until `dispose()` is called. +The VM runs until `dispose()` is called or your process exits. + + + ## Show me the code From f079632344aa4f67d220e25a305d020a914ba839 Mon Sep 17 00:00:00 2001 From: Sree Narayanan Date: Mon, 12 Oct 2026 10:50:11 +0000 Subject: [PATCH 18/18] docs(agentos): drop the alpha contract note from the Hosted HTTP API Amp-Thread-ID: https://ampcode.com/threads/T-c7d08b58-92c1-4fc0-abb9-7a15f4504a4f Co-authored-by: Amp --- docs/content/docs/hosted-api.mdx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/docs/content/docs/hosted-api.mdx b/docs/content/docs/hosted-api.mdx index d710b9fd9c..792d1320ce 100644 --- a/docs/content/docs/hosted-api.mdx +++ b/docs/content/docs/hosted-api.mdx @@ -9,11 +9,6 @@ create it by key, then call its actions over the Rivet gateway. The calls the same actions. See the [hosted quickstart](/agentos/docs/quickstart-hosted) for a walkthrough. - -The contract is in alpha (`agentos-sdk.dev/v1alpha1`). The actor and the -`@rivet-dev/agentos` client ship in lockstep. - - ## Overview | Field | Value |