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.1in committed files. Release workflows apply real versions transiently withscripts/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.jsonscripts limited to Turbo orchestration; repo-specific commands belong injustfilerecipes 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.
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.
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_*, includingagentos_fs_schema_version. - Sidecar/core durable state owns
agentos_core_*, includingagentos_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_*, includingagentos_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.
-
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/waitand 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. Seewebsite/public/docs/docs/architecture/processes.mdandposix-syscalls.md, andcrates/kernel/CLAUDE.md. -
The projected
/opt/agentosfilesystem 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
.aospkgfiles (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.jsonis 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 (
urlfor hosted actors; trusted local path for embedded Core); they do not scannode_modulesor 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}/, ortoolchain/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:pnpm install --frozen-lockfile just tools-rebuild
just tools-rebuildrunsjust toolchain-build, copies the canonical output fromtoolchain/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.
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.
- The migration target is Node.js's evented networking invariants:
sidecar-owned nonblocking I/O, readiness-driven bounded work, real
Duplexbackpressure, 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
RequestFrameingress, stdout for non-heartbeatEventFrameegress, 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.
scripts/publishis 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 issecure-exec, a small function-style facade over@rivet-dev/agentos-corethat exposessecure-exec/typescript. Its top-level functions are one-shot conveniences, andcreateVm()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 debugtojust release). This still rebuilds every release artifact, but publishes larger, unoptimized debug native binaries. scripts/verify-fixed-versions.mjsmust pass in the committed tree.
- Docs are authored here and published on rivet.dev by the
rivet-dev/websiterepo. This repo ships two bundles, eachsidebar.jsonpluscontent/docs/**.mdx:docs/(agentOS, with snippets fromexamples/) andsecure-exec/docs/(Secure Exec, with snippets fromsecure-exec/examples/). Thesecure-execnpm package itself lives in the same directory (secure-exec/src,secure-exec/tests), not underpackages/..github/workflows/docs-sync.ymlsyncs both on merge tomain. Followdocs/CLAUDE.mdfor page, sidebar, snippet, and writing rules; they apply to both bundles. - Secure Exec docs cover only the
secure-execAPI. 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/agentosis the canonical agentOS site. Use it for URLs, schema IDs, and package metadata; neveragentos-sdk.dev. Secure Exec lives athttps://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.
- 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.
- 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.