Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
657 changes: 657 additions & 0 deletions .agent/specs/overlay-filesystem-api-spec.md

Large diffs are not rendered by default.

936 changes: 936 additions & 0 deletions .agent/specs/overlay-metadata-proposal.md

Large diffs are not rendered by default.

9 changes: 8 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ The registry software packages depend on `@rivet-dev/agent-os-registry-types` (i

## Architecture

- **The VM base filesystem artifact is derived from Alpine Linux, but runtime source should stay generic.** `packages/core/src/` must not hardcode Alpine-specific defaults or import Alpine-named helpers. The runtime consumes `packages/core/fixtures/base-filesystem.json` as the default root layer.
- **Base filesystem rebuild flow:** first capture a fresh Alpine snapshot with `pnpm --dir packages/core snapshot:alpine-defaults`, which writes `packages/core/fixtures/alpine-defaults.json`. Then run `pnpm --dir packages/core build:base-filesystem`, which rewrites the required AgentOs-specific values (for example `HOSTNAME=agent-os` and `/etc/hostname`) and emits `packages/core/fixtures/base-filesystem.json`. AgentOs uses that built artifact as the lower layer of an overlay-backed root filesystem.
- **The default VM filesystem model should be Docker-like.** The root filesystem should be a layered overlay view with one writable upper layer on top of one or more immutable lower snapshot layers. The base filesystem artifact is the initial lower layer; additional frozen lower layers may be stacked beneath the writable upper if needed. Do not design the default VM root as a pile of ad hoc post-boot mutations.
- **Everything runs inside the VM.** Agent processes, servers, network requests -- all spawned inside the secure-exec kernel, never on the host. This is a hard rule with no exceptions.
- The `AgentOs` class wraps a secure-exec `Kernel` and proxies its API directly
- **All public methods on AgentOs must accept and return JSON-serializable data.** No object references (Session, ManagedProcess, ShellHandle) in the public API. Reference resources by ID (session ID, PID, shell ID). This keeps the API flat and portable across serialization boundaries (HTTP, RPC, IPC).
Expand All @@ -80,11 +83,15 @@ The registry software packages depend on `@rivet-dev/agent-os-registry-types` (i

- The old `fs-sqlite` and `fs-postgres` packages were deleted. They are replaced by the secure-exec `SqliteMetadataStore` and the `ChunkedVFS` composition layer.
- File system drivers live in `registry/file-system/` (see Registry section above). They implement the `FsBlockStore` interface and are passed via `type: "custom"` mount.
- The Rivet actor integration (in the Rivet repo at `rivetkit-typescript/packages/rivetkit/src/agent-os/`) uses `ChunkedVFS(InMemoryMetadataStore + InMemoryBlockStore)` as a temporary in-memory solution. A persistent backend (actor KV-backed metadata + actor storage-backed blocks) is planned.
- The Rivet actor integration (in the Rivet repo at `rivetkit-typescript/packages/rivetkit/src/agent-os/`) currently uses `ChunkedVFS(InMemoryMetadataStore + InMemoryBlockStore)` as legacy temporary infrastructure. This is not an acceptable long-term model for filesystem correctness. Filesystem semantics must move to durable metadata and block storage rather than transient in-memory state.

## Filesystem Conventions

- **OS-level content uses mounts, not post-boot writes.** If agentOS needs custom directories in the VM (e.g., `/etc/agentos/`), mount a pre-populated filesystem at boot — don't create the kernel and then write files into it afterward. This keeps the root filesystem clean and makes OS-provided paths read-only so agents can't tamper with them.
- **Filesystem semantics must be durable.** Any state that changes filesystem behavior — including overlay deletes, whiteouts, tombstones, copy-up state, directory entries, inode metadata, or file contents — must be represented in durable filesystem or metadata storage. Do not implement correctness-critical filesystem behavior with in-memory side tables, in-memory whiteout sets, or other transient hacks.
- **Overlay filesystem behavior must match Linux OverlayFS as closely as possible, including mount-boundary semantics.** Treat the kernel OverlayFS docs as normative. OverlayFS overlays directory trees, not the mount table: the merged hierarchy is its own standalone mount, not a bind mount over underlying mounts. Do not design root overlay logic that "sees through" or absorbs unrelated mounted filesystems. Mounted filesystems remain separate mount boundaries, and cross-mount operations must keep normal mount semantics (`EXDEV`, separate identity, separate read-only rules). If we want overlay behavior inside a mounted filesystem such as an S3-backed or host-backed mount, that mounted filesystem must implement the layered metadata semantics itself rather than relying on the parent/root overlay to compose across the mount boundary.
- **User-facing filesystem APIs should distinguish mounts from layers.** Mounts are separate mounted filesystems presented to the kernel VFS. Layers are overlay-building blocks used to construct a layered filesystem. Do not collapse those into one generic concept. A plain mounted `VirtualFileSystem` is not automatically a valid overlay layer. Overlay construction should consume explicit layer handles: one writable upper layer plus zero or more immutable lower snapshot layers.
- **Middle layers in a Docker-like stack should be frozen layers, not extra writable uppers.** Linux OverlayFS supports one writable upper per overlay mount. Additional stacked layers should be represented as immutable snapshot/materialized lower layers. They may share the same layer-handle interface as the upper layer, but their state must mark them frozen/read-only. Any live whiteouts, opaque markers, or copy-up bookkeeping belong only to the active writable upper; once a layer is sealed into a reusable lower snapshot, it must be materialized into an ordinary read-only tree.
- **Never interfere with the user's filesystem or code.** Don't write config files, instruction files, or metadata into the user's working directory or project tree. Use dedicated OS paths (`/etc/`, `/var/`, etc.) or CLI flags instead. If an agent framework requires a file in the project directory (e.g., OpenCode's context paths), prefer absolute paths to OS-managed locations over creating files in cwd.
- **Agent prompt injection must be non-destructive.** Each agent has its own mechanism for loading instructions (CLI flags, env vars, config files). When injecting OS instructions: preserve the agent's existing user-provided instructions (CLAUDE.md, AGENTS.md, etc.), append rather than replace, and always provide `skipOsInstructions` opt-out. User configuration is never clobbered — user env vars override ours via spread order.

Expand Down
3 changes: 3 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# TODO

- Add OCI import/export support for overlay filesystem layers and snapshots after phase 1. The phase-1 API should only guarantee the bundled base filesystem artifact and the internal snapshot export/import format.
93 changes: 93 additions & 0 deletions docs/filesystem.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
---
title: Filesystem
description: Layered filesystem model for AgentOs virtual machines.
icon: "folder"
---

`AgentOs` gives each VM a Linux-like filesystem with a layered root, predictable defaults, and optional mounted filesystems for custom behavior.

## Layering model

Each VM starts with three filesystem concepts working together:

- **Base filesystem** — the default root layout and OS files that every VM starts with.
- **Writable overlay** — the per-VM writable layer that records changes without mutating the base filesystem.
- **Mounted filesystems** — additional filesystems mounted at specific paths such as `/etc/agentos` or user-provided mount points.

This means a fresh VM always starts from the same known root layout, while each VM still gets an isolated writable view of that root.

## How the overlay works

The default root filesystem uses copy-on-write overlay behavior.

- **Reads** fall through to the base filesystem when a path has not been changed in the VM.
- **Writes** go to the writable overlay. If a file already exists in the base filesystem, it is copied into the writable layer before the change is applied.
- **Deletes** hide lower-layer entries from the VM's view, so a deleted base file stays deleted for that VM even though the shared base layer is unchanged.
- **New files and directories** are created only in the writable layer.

This keeps startup fast, makes the default root deterministic, and lets each VM change its filesystem freely without affecting other VMs.

## Default filesystem

The default base filesystem is intended to mimic Alpine Linux as closely as practical for agent workloads.

- It uses an Alpine-like directory layout such as `/bin`, `/etc`, `/home`, `/tmp`, `/usr`, and `/var`.
- It includes a default user home at `/home/user`.
- It preserves Alpine-like environment and shell defaults such as `PATH`, `PAGER`, prompt shape, and common `/etc` files.
- It keeps familiar symlinks and permissions where they matter for normal shell and tool behavior.

AgentOs normalizes the small pieces that should be stable across VMs, such as the hostname being `agent-os`, while keeping the rest of the base layout as close as possible to Alpine.

## Root filesystem config

`AgentOs.create()` now accepts a `rootFilesystem` option that configures the root overlay directly.

- `mode: "ephemeral"` keeps the default writable root behavior.
- `mode: "read-only"` keeps the merged root visible and mounts it read-only from the start of VM boot.
- `disableDefaultBaseLayer: true` removes the bundled base snapshot from the lower stack.
- `lowers` lets you add one or more snapshot exports as lower layers, ordered highest-precedence first.

If you omit `rootFilesystem`, AgentOs still creates the default overlay-backed root with the bundled base snapshot as its deepest lower.

If you set `disableDefaultBaseLayer: true` and do not provide any `lowers`, the VM starts from a minimal synthetic root that contains only the boot-critical POSIX directories and runtime command stubs.

## Mounts and precedence

Mounted filesystems replace a subtree at the mount point.

- A mount at `/data` makes `/data` resolve to that mounted filesystem instead of the default root layer.
- Mounts are useful for read-only instruction files, host-backed directories, persistent stores, or custom virtual filesystems.
- The overlay-backed root still applies everywhere that is not covered by a mount.

In practice, the path lookup order is:

1. Mounted filesystem at the path, if one exists.
2. Writable overlay for the default root.
3. Base filesystem for the default root.

For `mode: "read-only"`, step 2 is omitted entirely: the VM reads directly from the merged lower stack.

Mount entries can also be declarative overlay mounts. Those use a `LayerStore` plus one or more snapshot handles to build an isolated overlay at the mount path, instead of requiring you to prebuild a `VirtualFileSystem` yourself.

## Root snapshots

`AgentOs.snapshotRootFilesystem()` exports the current visible root tree as a reusable snapshot descriptor.

You can feed that snapshot back into a later VM through `rootFilesystem.lowers`, or import it into a `LayerStore` for declarative overlay mounts.

## Why this design exists

- It gives agents a familiar Linux-style root filesystem.
- It keeps the default VM state deterministic across runs.
- It avoids mutating the shared base filesystem at boot time.
- It makes it easy to add read-only or custom filesystems at specific paths.
- It keeps per-VM writes isolated and disposable.

## Rebuilding the base filesystem

The default root is generated in two steps:

1. Capture a fresh Alpine snapshot.
2. Build the AgentOs base filesystem artifact from that snapshot, applying the small AgentOs-specific normalizations.

The runtime itself consumes the built base filesystem artifact, not the Alpine snapshot directly.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"test:watch": "npx turbo watch test",
"check-types": "npx turbo check-types",
"lint": "pnpm biome check .",
"fmt": "pnpm biome check --write --diagnostic-level=error ."
"fmt": "pnpm biome check --write --diagnostic-level=error .",
"shell": "pnpm --filter @rivet-dev/agent-os-shell shell"
},
"devDependencies": {
"@biomejs/biome": "^2.3",
Expand All @@ -22,6 +23,7 @@
"@rivet-dev/agent-os-codex-agent": "workspace:*",
"@rivet-dev/agent-os-core": "workspace:*",
"@rivet-dev/agent-os-pi": "workspace:*",
"@rivet-dev/agent-os-pi-rust": "workspace:*",
"@types/node": "^22.19.15",
"turbo": "^2.5.6",
"typescript": "^5.9.2"
Expand Down
Loading