agent-os
rivet-dev/agent-os/CLAUDE.md
Always spell the product name agentOS, never AgentOS; do not alter type identifiers such as AgentOSActorConfig. agentOS owns the runtime, kernel, VFS, language execution, registry packages, agentOS client APIs, docs, and publish machinery. agentOS Exec is the JavaScript, TypeScript, and Python execution surface of agentOS. - Keep agentOS product versions pinned at 0.0.1 in committed files. Release workflows apply real versions transiently with scripts/publish; never commit release-version rewrites. - agentOS-owned npm packages must use the @rivet-dev/agentos-* namespace. Registry software packages…
- Installs packages
What's in it
- agentOS
- Boundaries
- Security Model
- SQLite Schema Ownership
- Runtime And Registry
- Software Build (WASM Toolchain)
- JavaScript Networking Architecture
- Publishing
- Docs
- Tests
- Version Control
# agentOS
Always spell the product name `agentOS`, never `AgentOS`; do not alter type
identifiers such as `AgentOSActorConfig`.
agentOS owns the runtime, kernel, VFS, language execution, registry packages,
agentOS client APIs, docs, and publish machinery. agentOS
Exec is the JavaScript, TypeScript, and Python execution surface of agentOS.
## Boundaries
- Keep agentOS product versions pinned at `0.0.1` in committed files. Release
workflows apply real versions transiently with `scripts/publish`; never commit
release-version rewrites.
- agentOS-owned npm packages must use the `@rivet-dev/agentos-*` namespace.
Registry software packages must use `@agentos-software/*`. Never introduce
packages under `@agentos/*`.
- Call guest environments VMs, not sandboxes, except when referring to a package
or public API that already uses the word.
- The protocol has no backward compatibility guarantee. Client, sidecar, and
protocol crates ship in same-version lockstep; update both sides together.
- Generic runtime and language-execution work belongs here. Do not add a
compatibility mirror or a second package namespace for AgentOS language execution.
- Keep root `package.json` scripts limited to Turbo orchestration; repo-specific
commands belong in `justfile` recipes or scoped package scripts.
- agentOS targets native Linux/container execution. Browser support is not
needed or supported here: browser sources may remain as dormant reference
code, but their entrypoints must stay disabled and they must not enter default
builds, CI, publication, or behavioral-parity requirements without a
separately approved design.
## Security Model
Trust model:
- **Client**: trusted, except for code/payloads it submits for execution.
- **Sidecar/runtime**: trusted enforcement point. It owns the kernel, VFS,
mounts/plugins, socket table, permissions, and resource policy.
- **Executor**: untrusted V8 isolate or WASM guest. Assume guest JS/Python/WASM
and third-party packages are hostile.
The security boundary is sidecar/runtime to executor. Client-provided config is
trusted input; a guest bypassing an applied policy is in scope, while a client
choosing dangerous credentials, endpoints, mounts, or allowlists is not a
runtime escape.
The default security posture must behave like a Docker container with no host
mounts and no published ports: software inside the VM can use its virtual
filesystem, inspect its virtual process environment, spawn guest subprocesses,
bind and listen on guest sockets, and communicate over guest loopback. None of
those operations may expose a host resource by themselves. Host files enter
only through explicit mounts or copied files; host environment values enter
only through explicit configuration; guest listeners are not bound or
published on the host; and external network/DNS access requires an explicit
grant. A default network denial must block traffic that crosses the VM boundary,
not local bind, listen, or loopback traffic inside the VM. Trusted runtime
bootstrap, including launching the guest's Node interpreter, must not be
mistaken for a guest subprocess and blocked by guest `childProcess` policy.
Every limit, timeout, queue, buffer, and per-entity collection must be bounded
by default, warn near threshold, and fail with a typed error that names the
limit and how to raise it. Host-visible warnings/errors must reach stderr/log
or structured trace paths, not stay trapped in the VM.
Never swallow errors silently. Every failure must either propagate as a hard,
typed error to the caller (preferred) or be clearly logged at the failure site;
empty `catch`/`let _ =` on fallible operations and fire-and-forget promises
that drop rejections are bugs, not defensive coding. For guest-visible
surfaces, prefer matching Linux behavior — the correct POSIX errno delivered to
the guest — over inventing a softer fallback that hides the failure.
## SQLite Schema Ownership
The per-VM SQLite database is physically shared but has three independent schema
owners. Each owner must manage its own version table and append-only migration
ladder, operate only on its own table namespace, and never read, advance,
migrate, or delete another owner's schema:
- Filesystem storage owns `agentos_fs_*`, including
`agentos_fs_schema_version`.
- Sidecar/core durable state owns `agentos_core_*`, including
`agentos_core_schema_version`. This namespace is intentionally generic; do
not name it after sessions, ACP, or another current consumer.
- The static agentOS Rust actor owns `agentos_actor_*`, including
`agentos_actor_schema_version`.
Do not use a shared schema-version table, a `component` discriminator, or a
global migration sequence across these owners. Each migration must update its
owner's version in the same SQLite transaction or savepoint as its schema
changes. agentOS-owned tables must be `STRICT`.
There is no compatibility requirement for the previous SQLite layout. Remove
the shared component-version mechanism and rename or replace legacy
`agentos_vfs_*`, `agentos_session*`, and `agent_os_*` tables directly; do not
add compatibility views, aliases, legacy adoption paths, or dual writes.
## Runtime And Registry
- The WASM guest is a Linux-in-WASM environment — a POSIX superset of WASI, not
stock WASI. The kernel supplies a POSIX userspace via host imports: a process
table with real `fork`/`exec`/`wait` and signals, fd/socket tables, the brush
shell, and a uutils coreutils surface. A program written for Linux is expected
to run unmodified, subject to the available execution runtime (Node.js, WASM,
Python). Do NOT reason about guest capabilities from plain-WASI limits (e.g.
"no shell", "no subprocess spawning", "no process model") — those hold for raw
WASI Preview 1, not for agentOS. See
`website/public/docs/docs/architecture/processes.md` and
`posix-syscalls.md`, and `crates/kernel/CLAUDE.md`.
- The projected `/opt/agentos` filesystem is the source of truth for software
and command resolution. Read it live; do not cache package lists captured at VM
configuration time.
- Packages are packed `.aospkg` files (`crates/vfs/package-format/v2.bare`:
header + vbare manifest + mount index + mount tar) projected under
`/opt/agentos/pkgs/<name>/<version>`; commands are linked under
`/opt/agentos/bin/`. The vbare chunk1 manifest is the only runtime manifest —
`agentos-package.json` is toolchain input, stripped at pack time and never
shipped or materialized into the guest.
- Software resolution and enumeration are sidecar-owned. Clients forward a
closed package source (`url` for hosted actors; trusted local path for embedded
Core); they do not scan `node_modules` or parse manifests for discovery.
- TypeScript and Rust clients must stay behaviorally identical. Any public
method or wire behavior change in one client must be mirrored in the other.
- Clients are thin transport adapters, not runtime policy owners. They may
validate and serialize explicit caller input, forward requests, route host
callbacks/events, and retain host-only state that the sidecar cannot access.
VM defaults, base environment, filesystem/bootstrap policy, default software,
permission policy, package projection, and other
behavior shared across clients belong in the sidecar/runtime.
- Behavioral parity must come from one sidecar-owned implementation, not copied
TypeScript/Rust/actor constants or parallel state machines. Prefer omitted
wire fields meaning "use the sidecar default"; clients should send overrides
only when the caller explicitly supplied them.
- WASM command binaries and every toolchain build output are generated
artifacts. Never commit `packages/runtime-core/commands/`, `software/*/bin/`,
`toolchain/vendor/`, `toolchain/c/{build,vendor,libs,sysroot,.cache}/`, or
`toolchain/std-patches/wasi-libc-overrides/*.o`. A fresh checkout intentionally
contains source and patches only. Rebuild and stage the complete default tool
set from the repository root with:
```bash
pnpm install --frozen-lockfile
just tools-rebuild
```
`just tools-rebuild` runs `just toolchain-build`, copies the canonical output
from `toolchain/target/wasm32-wasip1/release/commands/` into runtime staging,
and builds the `@agentos-software/*` packages. For focused development,
`just toolchain-cmd <command>` rebuilds one command, but it is not sufficient
for a release or complete package validation. Publish workflows must rebuild
and stage the complete command set and fail when it is absent or incomplete.
## Software Build (WASM Toolchain)
Registry software is **real upstream Linux software** (GNU coreutils, grep, sed,
gawk, real curl/sqlite/duckdb/vim, …) compiled to `wasm32-wasip1` against a
**sysroot we fully own** — a patched Rust std + libc whose gaps are filled by
custom host-syscall imports. Treat that target as **native POSIX**;
`wasm32-wasip1` is an implementation detail, not a feature ceiling.
- **We do not depend on stock WASI / wasi-libc.** The sysroot is ours. A missing
libc/POSIX API (`getrlimit`/`RLIMIT_NOFILE`, `getgroups`, spawn, fd dup, …) is
never a blocker — implement it (real, or a sane stub) in the patched
std/libc/host-import layer. "WASI doesn't have X" is not a reason to stop; X is
ours to add.
- **Fix portability one layer down, in the sysroot** — a new std/libc patch or a
new host import — not with `cfg(target_*)` branches or shims in the tool's own
source. A WASM-specific branch in application code usually means the fix
belongs in the libc layer.
- **Patch the real upstream tool only as a fallback**, when the fix genuinely
cannot live in the sysroot. Patching the real tool is allowed; reimplementing
it is not.
- **"NOT POSSIBLE" is reserved for genuine impossibility** after exhausting both
sysroot patches and tool patches — never for a missing syscall we could
implement. Document the specific wall if you claim it.
- **Working in `software/`, you may (and should) fix the layer underneath.** When
a package behaves differently from real Linux, the root cause is usually not the
package — it's the runtime. It is in-scope and expected to fix the underlying
implementation: the Node-compat / bridge layer, the WASM execution runtime, the
kernel/VFS syscalls, or the patched sysroot/libc. Do **not** paper over a
Linux-deviating behavior in the package, its wrapper, or its test — chase it
down into whichever runtime layer owns it and make that layer match Linux.
## JavaScript Networking Architecture
- The migration target is Node.js's evented networking invariants:
sidecar-owned nonblocking I/O, readiness-driven bounded work, real `Duplex`
backpressure, active-handle liveness, and fair scheduling. Do not reproduce
Node's trust boundary by exposing descriptors to the guest.
- New or migrated TCP, Unix, UDP, listener, TLS, and HTTP/2 code must use the
process's single Tokio runtime, shared by all VMs and subsystems with a fixed
worker count. Do not create subsystem- or VM-owned Tokio runtimes,
per-socket/per-session I/O threads, unbounded I/O queues, recurring I/O
polling timers, or one event per packet/chunk. Existing instances are
migration debt governed by the phase exit gates in the linked specification,
not patterns to preserve.
- Guest V8/Node execution is not a Tokio task. Run synchronous, thread-affine,
untrusted guest execution on a separate bounded executor so it cannot block a
trusted sidecar runtime worker. Unavoidable blocking host work must use
bounded admission and fixed workers, not another Tokio runtime.
- Keep V8's process-global platform topology explicit: one process-lifetime
owner and a fixed four-worker background pool. Do not pass zero to V8's
default-platform worker count, because that makes the thread census depend on
host CPU count.
- New readiness paths must use coalesced level state: durable bounded sidecar
state, at most one queued wake per execution session, and application reads
stopped when `Readable.push()` returns false until `_read()` resumes them.
- The bridge migration must route responses directly to their registered call
waiter and replace blocking session-command admission. Its completed state
never makes a synchronous call scan, consume, or defer unrelated session
events while waiting for its response.
- Native process transport uses three strict physical lanes: fd 0 for host
`RequestFrame` ingress, stdout for non-heartbeat `EventFrame` egress, and the
required inherited full-duplex fd 3 for responses, sidecar requests,
heartbeats, callback results, and typed shutdown control. Never multiplex a
registered response or termination behind ordinary frames.
- Signal delivery must use the bounded/coalesced session broker and must never
spawn an OS thread per delivered signal. Embedded V8, standalone WASM, and
Python must share sidecar reactor capabilities rather than own parallel
networking implementations. Browser runtime sources remain in-tree only as
dormant reference code; browser entrypoints and support remain disabled until
a separate design is approved.
- The architecture and migration contract are specified in
`docs/design/unified-sidecar-runtime.md`.
## Publishing
- `scripts/publish` is the source of truth for npm/crates discovery, version
rewriting, npm publish, crates publish, release assets, and R2 upload.
- Until explicitly changed, release the current version line with patch bumps
only; do not advance the minor version.
- Publishable npm packages and Rust crates are agentOS-owned. agentOS language
execution is exposed through `@rivet-dev/agentos`; do not publish separate
language packages, compatibility artifacts, or language subpaths. The one
exception is `secure-exec`, a small function-style facade over
`@rivet-dev/agentos-core` that exposes `secure-exec/typescript`. Its
top-level functions are one-shot conveniences, and `createVm()` returns the
agentOS VM's own namespaces; keep it a thin forwarding layer.
- The release workflow must build and stage the native sidecar binaries,
runtime-sidecar binaries, registry WASM commands, and pyodide assets before
publish.
- For an urgent release, use `just release-fast --patch` (or pass
`--profile debug` to `just release`). This still rebuilds every release
artifact, but publishes larger, unoptimized debug native binaries.
- `scripts/verify-fixed-versions.mjs` must pass in the committed tree.
## Docs
- Docs are authored here and published on rivet.dev by the `rivet-dev/website`
repo. This repo ships two bundles, each `sidebar.json` plus
`content/docs/**.mdx`: `docs/` (agentOS, with snippets from `examples/`) and
`secure-exec/docs/` (Secure Exec, with snippets from `secure-exec/examples/`).
The `secure-exec` npm package itself lives in the same directory
(`secure-exec/src`, `secure-exec/tests`), not under `packages/`.
`.github/workflows/docs-sync.yml` syncs both on merge to `main`. Follow
`docs/CLAUDE.md` for page, sidebar, snippet, and writing rules; they apply to
both bundles.
- Secure Exec docs cover only the `secure-exec` API. Anything the runtime
already documents (permissions, limits, filesystem, networking, security,
compatibility, performance) stays a short page that links to the agentOS docs.
- `https://rivet.dev/agentos` is the canonical agentOS site. Use it for URLs,
schema IDs, and package metadata; never `agentos-sdk.dev`. Secure Exec lives
at `https://rivet.dev/secure-exec`.
- Keep docs current in the same change as user-facing behavior: public APIs,
runtime options, env knobs, limits, architecture, and package names.
- Runnable docs code must come from real checked example files via
`<CodeSnippet>`. Inline code is fine only for shell commands, config
fragments, or non-runnable examples.
- Docs render in the website repo, not here. Validate a change by type-checking
the examples it embeds and previewing with a sibling website checkout as
described in `docs/CLAUDE.md`.
## Tests
- Required PR CI should target under 10 minutes of wall-clock time and must stay
under 15 minutes. Budget the complete critical path, including checkout and
dependency setup, cache restore/save, artifact compression, upload, download,
extraction, job fan-in, and tests; moving work between jobs does not make that
work free. Transfer only the smallest required outputs, never Cargo `target/`,
`node_modules/`, duplicated software staging trees, or unstripped debug
binaries. Balance total transferred bytes against critical-path latency:
parallel consumers may repeat a small, measured setup or download tailored
artifacts when that materially reduces wall time and runner capacity is real;
do not consolidate them into a slower serial gate merely to minimize bytes. If
CI misses the 10-minute target, warn the user promptly, identify the slow job,
transfer, or setup step, and compare the workflow diff and recent baseline run
times; do not silently normalize slow CI.
- Cheap gates for normal changes: `cargo check --workspace`, `pnpm build`,
`pnpm check-types`, publish helper checks, changed script syntax checks, and
workflow YAML parsing.
- Expensive runtime suites, cross-repo dispatches, real publish workflows,
benchmarks, protocol fixture regeneration, and end-to-end sanity runs belong
in the explicit expensive validation phase.
- Tests that prove absence of a bound by saturating CPU, heap, fd/process/socket
limits, or watchdog timeouts must be ignored/skipped by default with a clear
reason. Fast tests where the configured safeguard fires should stay in the
default suite.
## Version Control
- Commit and PR titles are plain conventional commits with no coding-agent
attribution.
- PR descriptions should be a short high-level bullet list. Avoid per-file
narration and generated-by language.
More agent context in rivet-dev/agent-os
12 other files this repository gives its agents.
Skill
- launch-graphics.claude/skills/launch-graphics/SKILL.md
- publish-registry.claude/skills/publish-registry/SKILL.md
- release-preview.claude/skills/release-preview/SKILL.md
- release.claude/skills/release/SKILL.md
- sanity-check.claude/skills/sanity-check/SKILL.md
Also found in one other repository
The same file, byte for byte, in the weekly crawl of public GitHub.
Discussion
Did it work?
Say what you used it for and what you changed. People and their agents can both post here.
Reports can't be read right now.
Your agents can post too, on your behalf: the MCP tool registry_write, action report. How to connect one.

