From 58f95f3c1e765ac2b9a6939784e8608f2254a176 Mon Sep 17 00:00:00 2001 From: mintaka Date: Wed, 26 Aug 2026 15:07:00 -0400 Subject: [PATCH 1/4] docs(concepts): durable agents / disposable compute + isolation & egress (RIG-2795) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two load-bearing runtime principles lived only in the `docs/designs/` ledger, not in `docs/concepts/` — the primary orientation surface. This captures them as first-class concept pages, grounded in the frozen decision records. ### Durable agents, disposable compute The agent and its session are durable and Server-owned (Postgres hot-tail + S3-compatible cold archive); the container/microVM it runs in is disposable and dies with the session. Resume reconstructs the transcript into a fresh sandbox — the agent never notices new hardware. Covers the container → microVM substrate move as an isolation upgrade that leaves the durability contract unchanged. (ledger DL-024, DL-063/084/085/086/087/088/089/093) ### Isolation and egress Model-written code is contained, not trusted: a per-agent sandbox (rootless-podman container today → hardware-virtualized microVM end state) with default-deny nftables egress and no server credential. (ledger DL-024/076, DL-235/259; `go/internal/runtime/egress.go`) Both pages are wired into `docs/concepts/README.md` and the top-level README's Agent concepts list. markdownlint clean. Spec-impact: none. Refs RIG-2795 Co-authored-by: Matt Wilkinson --- README.md | 4 + docs/concepts/README.md | 10 ++ .../durable-agents-disposable-compute.md | 127 ++++++++++++++++++ docs/concepts/isolation-and-egress.md | 95 +++++++++++++ 4 files changed, 236 insertions(+) create mode 100644 docs/concepts/durable-agents-disposable-compute.md create mode 100644 docs/concepts/isolation-and-egress.md diff --git a/README.md b/README.md index fabe0ccb..59f685ce 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,10 @@ design holds to. That model lives under - [No human clicks](./docs/concepts/no-human-clicks.md) and [read-only inspection](./docs/concepts/read-only-inspection.md) — agents stand up the org through tools; the human holds only the security boundary. +- [Durable agents, disposable compute](./docs/concepts/durable-agents-disposable-compute.md) + and [isolation and egress](./docs/concepts/isolation-and-egress.md) — the agent + and its session are durable and Server-owned; the container/microVM it runs in + is disposable and contained. ## Toolchain diff --git a/docs/concepts/README.md b/docs/concepts/README.md index 8d246a44..9ee2b188 100644 --- a/docs/concepts/README.md +++ b/docs/concepts/README.md @@ -31,6 +31,16 @@ concept; the tool and prompt material carry the detail, these carry the model. load-bearing premise any external-session integration maps onto, never replaces. +## The runtime + +- **[Durable agents, disposable compute](./durable-agents-disposable-compute.md)** + — the agent and its session are durable and Server-owned (Postgres + S3); + the container/microVM it runs in is disposable and dies with the session. + Resume rebuilds the compute and reconstructs the transcript into it. +- **[Isolation and egress](./isolation-and-egress.md)** — model-written code is + contained, not trusted: a per-agent sandbox (container today, microVM in the + end state) with default-deny egress and no server credential. + ## The tools - **[The agent tool set](./tools.md)** — the native tools an agent drives diff --git a/docs/concepts/durable-agents-disposable-compute.md b/docs/concepts/durable-agents-disposable-compute.md new file mode 100644 index 00000000..ee1329f0 --- /dev/null +++ b/docs/concepts/durable-agents-disposable-compute.md @@ -0,0 +1,127 @@ +# Durable agents, disposable compute + +A Compass agent is **durable**; the machine it runs on is **disposable**. The +agent — its identity, its place in the tree, and the full history of its work — +outlives any particular container or microVM it happens to be executing in. The +compute is cattle; the agent and its session are the thing of record. Getting +this split right is what lets Compass tear down, move, suspend, and rebuild the +expensive part (the sandbox) without ever losing the valuable part (the agent's +state). + +## The two things that persist, and the one that does not + +- **The agent is a stable, long-lived tree node.** Managers and their peers are + minted once and live as durable accounts — each with a home channel it is + always subscribed to — not spun up one-shot per task. An agent's identity, + its owner, its persona, and its position in the [agent + tree](./handle-vs-account.md) are model facts in the Server's store, not + properties of a running process. See [the comms model](./comms-model.md) for + why agents are stable nodes with home channels rather than ephemeral runs. +- **The session is durable and Server-owned.** Everything the agent has thought + and done — its streamed reasoning, tool calls, and edits — is persisted by the + Server as a durable transcript. The Server owns this store; the agent does not + custody it. (ledger DL-084, DL-093) +- **The compute is disposable.** The container or microVM the agent executes in + holds only an *ephemeral working copy* of the session file, and it **dies with + the container**. It custodies nothing of record: no durable state, and — by + the [isolation and egress](./isolation-and-egress.md) posture — no storage + credentials at all. (ledger DL-085, DL-089) + +The test for which bucket something falls in: **would it survive the sandbox +being destroyed right now?** The agent, its identity, and its transcript survive. +The process, the local session file, the checkout, and any in-memory state do +not — and nothing is allowed to depend on them surviving. + +## How the session stays durable without the agent holding storage + +The agent persists nothing durable itself. As a session runs, each committed +session entry is **tee'd upstream** to the Server as a frame: the agent writes +its container-local ephemeral file normally (so the agent runtime's own loader, +compaction, and rewrites work), and the same committed write is streamed up to +the Server, which owns durability. (ledger DL-089) + +The Server's durable store is **two-tier**: + +- a **Postgres hot tail** holding the recent, resume-relevant slice of the + transcript, and +- an **S3-compatible cold archive** that older, superseded segments flush out + to as verbatim log segments. + +So "the session streams out to durable object storage" is exactly right — the +durable home of a session's history is Server-owned Postgres plus S3-compatible +storage, never the sandbox's disk. (ledger DL-063, DL-093) + +The durable transcript and the live trace you watch in the session log are the +**same artifact seen two ways** — the agent emits committed entries once, and +the Server both persists them and projects the live block-level trace from that +one store. (ledger DL-088) + +## Resume: rebuild the compute, reattach the agent + +Because the durable state lives in the Server and the compute is disposable, +bringing an agent back is a **reconstruct-into-a-fresh-sandbox** operation, not a +"find the old machine" one: + +1. The old container/microVM is gone (torn down, evicted, crashed, or migrated). +2. On resume the Server **reconstructs the session** from its durable transcript + store and hands that reconstructed session body to the Runner. +3. The Runner materializes it into a **new** container/microVM, and the agent + picks up exactly where it left off. + +The agent never notices it is running on different hardware. The identity was +never in the box; it was always in the Server. (ledger DL-086, DL-087) + +## The compute substrate is moving: container → microVM + +Today an agent's sandbox is a **rootless-podman container**, one per agent, for +blast-radius isolation (ledger DL-024). The end-state substrate is a +**hardware-virtualized microVM** — the same disposable-compute role, hardened to +a VM-class boundary around model-written code. The migration path is: container +through Dogfood and trusted-tenant Beta, microVM in the end state; a host without +KVM degrades to the container runtime with an explicit capability log, never +silently. + +This move **does not change the durability contract on this page** — that is the +whole point of the split. Whether the sandbox is a container or a microVM, it is +still disposable, still custodies no durable state, and the agent still resumes +by the Server reconstructing its transcript into a fresh one. The substrate +change is an isolation upgrade (see [isolation and +egress](./isolation-and-egress.md)), not a change to what persists. + +The direction is already load-bearing in shipped decisions — the microVM's KVM +floor is what retired local agent execution and made the native app a thin client +against a headless, KVM-capable stack (ledger DL-235), and the self-host stack is +a host-level bring-up on a KVM-capable Linux machine (ledger DL-259). The microVM +Runner backend itself is designed in +[`microvm-runner.md`](../designs/infra/runtime/compass-elastic-session-runtime/microvm-runner.md) +(RIG-2394), under the hosted-platform end-state record +[`compass-elastic-session-runtime`](../designs/infra/runtime/compass-elastic-session-runtime/design.md) +(RIG-1717). + +## Why this split is the design, not an accident + +- **Disposable compute is what makes hosted scale possible.** If the agent's + state lived in the box, you could never evict an idle session, pack many + sessions onto a host, migrate one to another machine, or reclaim a crashed + sandbox without data loss. Because durable state is Server-owned, an idle + session's environment can be torn down entirely and rebuilt on activity — the + density play the hosted platform is built on. +- **Durable agents are what make supervision coherent.** A supervisor reasons + about a stable fleet of named agents with continuous histories, not a churn of + one-shot runs. The tree, the ownership trail, and the conversation history all + assume the agent outlives its current process. +- **The boundary is a security boundary too.** Keeping durable state and + credentials out of the sandbox means model-written code runs in a box that + holds nothing of record and cannot exfiltrate what it never had — see + [isolation and egress](./isolation-and-egress.md). + +## Quick reference + +| Thing | Durable? | Lives where | +| --- | --- | --- | +| Agent identity, owner, persona, tree position | yes | Server store (model fact) | +| Session transcript (reasoning, tools, edits) | yes | Server-owned: Postgres hot tail + S3-compatible cold archive | +| Home channel + its conversation history | yes | Server store | +| The container / microVM (the sandbox) | no | Runner host — disposable, rebuilt on resume | +| The container-local session file | no | dies with the sandbox (ephemeral working copy) | +| Storage / durability credentials | n/a | never in the sandbox — the agent holds none | diff --git a/docs/concepts/isolation-and-egress.md b/docs/concepts/isolation-and-egress.md new file mode 100644 index 00000000..97b8cd15 --- /dev/null +++ b/docs/concepts/isolation-and-egress.md @@ -0,0 +1,95 @@ +# Isolation and egress: the sandbox around model-written code + +Compass runs code an AI model wrote. The load-bearing safety assumption is +therefore the opposite of trust: **treat every agent as potentially compromised +and contain it**, rather than trusting it to behave. Two boundaries do that +containment — an **execution boundary** (each agent in its own sandbox) and a +**network boundary** (default-deny egress from that sandbox). This page is the +model behind both. + +## Each agent runs in its own sandbox + +Every agent executes in a **per-agent sandbox** on the Runner, isolated from the +host and from every other agent. The reason is **blast radius, not credential +avoidance**: the point is that a misbehaving or compromised agent can damage only +its own sandbox, not the host and not its neighbors. (ledger DL-024) + +The sandbox is [disposable compute](./durable-agents-disposable-compute.md) — it +custodies no durable state and no storage credentials, so destroying it loses +nothing of record. That is what makes aggressive containment cheap: the strong +move (tear the whole thing down) has no data cost. + +The substrate is on a path from **rootless-podman container** (today, through +Dogfood and trusted-tenant Beta) to a **hardware-virtualized microVM** (the +end state). Both fill the same role; the microVM raises the execution boundary +from a shared-kernel container to a VM-class boundary with its own guest kernel — +the right isolation for running untrusted model-written code, and for putting +multiple tenants on one host. A box without KVM degrades to the container +runtime with an explicit capability log, never silently. The microVM Runner +backend is designed in +[`microvm-runner.md`](../designs/infra/runtime/compass-elastic-session-runtime/microvm-runner.md) +(RIG-2394); its KVM floor is already baked into shipped decisions — it is what +made the native app a thin client against a headless KVM-capable stack (ledger +DL-235) and the self-host stack a KVM-capable host bring-up (ledger DL-259). + +## Default-deny egress: the agent reaches only what it is allowed to + +Isolating the process is not enough — a compromised agent with open network +access could still exfiltrate. So the sandbox's network is **firewalled +default-deny**: an agent can reach nothing on the network unless a host is +explicitly allowlisted. (`go/internal/runtime/egress.go`) + +- **Empty policy = pure default-deny.** With no allowlist, only loopback, + already-established flows, and DNS to the container's own resolver work — + nothing else. Reaching an LLM provider, a forge, or object storage requires + that host to be added to the allowlist. (`egress.go:29-31`) +- **The agent cannot widen its own firewall.** The sandbox is granted the + capability to *arm* nftables only so a root entrypoint can install the ruleset + at launch; the agent itself then runs as a **non-root user with an empty + capability set**, so it can neither flush nor edit the rules even though the + sandbox nominally holds the capability. The ruleset is armed *before* the agent + process starts. (`egress.go:6-10`) +- **The rules are built to resist evasion.** Both IPv4 and IPv6 addresses of an + allowlisted host are resolved and allowed, so a dual-stack host can't be + reached over the family you forgot to list; and DNS is allowlisted first, then + names are resolved from inside the sandbox, so resolution can't be the thing + that widens the allowlist. (`egress.go:12-19`) + +## The agent holds no server credential + +Containment extends to what the agent is *given*, not just what it can *reach*. +The agent is **egress-sealed and holds no Compass server token** — it cannot call +privileged server RPCs directly. Privileged operations an agent appears to +perform (spawning a peer, a forge write) are **relayed through the Server**, which +resolves the caller's identity and authority at the edge, rather than handed to +the agent as a credential it could misuse. (ledger DL-076) + +This is the same shape as the [handle/account split](./handle-vs-account.md) +(the agent never holds a per-agent forge seat) and the [no-human-clicks +security boundary](./no-human-clicks.md) (the human provides secret *values*; the +agent only declares needs): capability is kept out of the model's hands and +mediated by the Server. + +## Why this is a hard principle + +- **The threat model is "the agent is compromised."** Model-written code is + untrusted by construction. Every boundary here assumes the agent may try to + reach a host it shouldn't or damage what it can touch, and contains the damage + structurally instead of relying on the agent's good behavior. +- **Containment is cheap because compute is disposable.** Since the sandbox holds + nothing durable, the strongest response — destroy it — costs nothing. Isolation + and disposability reinforce each other: the box is safe to nuke *because* it is + empty, and it can be empty *because* durability lives in the Server. +- **It scales to multi-tenant.** A VM-class boundary per agent plus default-deny + egress is what lets many tenants share a host without one reaching another — + the isolation guarantee the hosted platform is sold on. + +## Quick reference + +| Boundary | Mechanism | The agent cannot | +| --- | --- | --- | +| Execution | per-agent sandbox (container today → microVM end state) | touch the host or another agent's sandbox | +| Network | default-deny nftables egress, allowlist-only | reach any host not explicitly allowed | +| Firewall control | rules armed by root at launch; agent runs non-root, empty caps | flush or edit its own egress ruleset | +| Server authority | egress-sealed, no server token; privileged calls relayed via Server | call privileged server RPCs directly | +| Durable state / secrets | Server-owned; none in the sandbox | exfiltrate durable data or credentials it never held | From ff1fec30ec976d1f38e8f8917d05cd85e6a1ac40 Mon Sep 17 00:00:00 2001 From: mintaka Date: Wed, 26 Aug 2026 15:21:57 -0400 Subject: [PATCH 2/4] docs(concepts): cite active ledger rows only (RIG-2795) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review found a citation to DL-063, which is not only superseded (by DL-084 → DL-093) but describes the REVERSED agent-direct-to-S3 model — the opposite of this page's thesis that the agent holds no storage credentials. Drop it; the active DL-093 alone supports the two-tier Server-owned store. Also collapse three lineage-paired citations (DL-084/DL-085/DL-086) to their active successors (DL-093/DL-089/DL-087) so no citation points a reader at a row stamped 'Superseded by'. Spec-impact: none. Refs RIG-2795 Co-authored-by: Matt Wilkinson --- docs/concepts/durable-agents-disposable-compute.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/concepts/durable-agents-disposable-compute.md b/docs/concepts/durable-agents-disposable-compute.md index ee1329f0..a5301b9b 100644 --- a/docs/concepts/durable-agents-disposable-compute.md +++ b/docs/concepts/durable-agents-disposable-compute.md @@ -20,12 +20,12 @@ state). - **The session is durable and Server-owned.** Everything the agent has thought and done — its streamed reasoning, tool calls, and edits — is persisted by the Server as a durable transcript. The Server owns this store; the agent does not - custody it. (ledger DL-084, DL-093) + custody it. (ledger DL-093) - **The compute is disposable.** The container or microVM the agent executes in holds only an *ephemeral working copy* of the session file, and it **dies with the container**. It custodies nothing of record: no durable state, and — by the [isolation and egress](./isolation-and-egress.md) posture — no storage - credentials at all. (ledger DL-085, DL-089) + credentials at all. (ledger DL-089) The test for which bucket something falls in: **would it survive the sandbox being destroyed right now?** The agent, its identity, and its transcript survive. @@ -49,7 +49,7 @@ The Server's durable store is **two-tier**: So "the session streams out to durable object storage" is exactly right — the durable home of a session's history is Server-owned Postgres plus S3-compatible -storage, never the sandbox's disk. (ledger DL-063, DL-093) +storage, never the sandbox's disk. (ledger DL-093) The durable transcript and the live trace you watch in the session log are the **same artifact seen two ways** — the agent emits committed entries once, and @@ -69,7 +69,7 @@ bringing an agent back is a **reconstruct-into-a-fresh-sandbox** operation, not picks up exactly where it left off. The agent never notices it is running on different hardware. The identity was -never in the box; it was always in the Server. (ledger DL-086, DL-087) +never in the box; it was always in the Server. (ledger DL-087) ## The compute substrate is moving: container → microVM From 4e6a8f7c76c5a6d61e0829b7ef59c0a3d4fcecd8 Mon Sep 17 00:00:00 2001 From: mintaka Date: Wed, 26 Aug 2026 21:19:39 -0400 Subject: [PATCH 3/4] =?UTF-8?q?docs(concepts):=20correct=20relay=20attribu?= =?UTF-8?q?tion=20=E2=80=94=20Runner=20forwards,=20Server=20resolves=20(RI?= =?UTF-8?q?G-2795)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The isolation-and-egress concept said privileged agent operations are "relayed through the Server," conflating the two roles. The Runner is the pure forwarder (asserts no identity); the Server resolves the caller's account from the session binding and executes. Corrected both the prose and the quick-reference table row to name both roles. Grounded: go/internal/runnerhub/relay_comms.go trust-model header ("The Runner is a pure forwarder … The SERVER resolves session_id -> agent account … and executes"); ledger DL-076. Co-authored-by: Matt Wilkinson --- docs/concepts/isolation-and-egress.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/concepts/isolation-and-egress.md b/docs/concepts/isolation-and-egress.md index 97b8cd15..083e9093 100644 --- a/docs/concepts/isolation-and-egress.md +++ b/docs/concepts/isolation-and-egress.md @@ -60,9 +60,11 @@ explicitly allowlisted. (`go/internal/runtime/egress.go`) Containment extends to what the agent is *given*, not just what it can *reach*. The agent is **egress-sealed and holds no Compass server token** — it cannot call privileged server RPCs directly. Privileged operations an agent appears to -perform (spawning a peer, a forge write) are **relayed through the Server**, which -resolves the caller's identity and authority at the edge, rather than handed to -the agent as a credential it could misuse. (ledger DL-076) +perform (spawning a peer, a forge write) are **forwarded by the Runner to the +Server** — the Runner asserts no identity and is a pure relay; the Server +resolves the caller's account and authority from the session binding at the +edge and executes the call — rather than handed to the agent as a credential it +could misuse. (ledger DL-076) This is the same shape as the [handle/account split](./handle-vs-account.md) (the agent never holds a per-agent forge seat) and the [no-human-clicks @@ -91,5 +93,5 @@ mediated by the Server. | Execution | per-agent sandbox (container today → microVM end state) | touch the host or another agent's sandbox | | Network | default-deny nftables egress, allowlist-only | reach any host not explicitly allowed | | Firewall control | rules armed by root at launch; agent runs non-root, empty caps | flush or edit its own egress ruleset | -| Server authority | egress-sealed, no server token; privileged calls relayed via Server | call privileged server RPCs directly | +| Server authority | egress-sealed, no server token; privileged calls forwarded by the Runner, resolved + executed by the Server | call privileged server RPCs directly | | Durable state / secrets | Server-owned; none in the sandbox | exfiltrate durable data or credentials it never held | From 61b813be98a842c66785f904955d69bbe44fd56d Mon Sep 17 00:00:00 2001 From: mintaka Date: Wed, 26 Aug 2026 21:25:52 -0400 Subject: [PATCH 4/4] docs(concepts): scope authority to the resolved account (RIG-2795) Review low: the session binding maps session_id -> account only; authority (owner-scoping etc.) is derived from the resolved account, not stored in the binding. Corrected the prose to say the Server resolves the account from the binding and executes under that account's authority. Grounded: go/internal/runnerhub/relay_comms.go (the binding records agent_account_id and resolves session_id -> agent account); DL-075 (owner-scope follows from the account). Co-authored-by: Matt Wilkinson --- docs/concepts/isolation-and-egress.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/concepts/isolation-and-egress.md b/docs/concepts/isolation-and-egress.md index 083e9093..c35cf921 100644 --- a/docs/concepts/isolation-and-egress.md +++ b/docs/concepts/isolation-and-egress.md @@ -62,9 +62,9 @@ The agent is **egress-sealed and holds no Compass server token** — it cannot c privileged server RPCs directly. Privileged operations an agent appears to perform (spawning a peer, a forge write) are **forwarded by the Runner to the Server** — the Runner asserts no identity and is a pure relay; the Server -resolves the caller's account and authority from the session binding at the -edge and executes the call — rather than handed to the agent as a credential it -could misuse. (ledger DL-076) +resolves the caller's account from the session binding at the edge and executes +the call under that account's authority — rather than handed to the agent as a +credential it could misuse. (ledger DL-076) This is the same shape as the [handle/account split](./handle-vs-account.md) (the agent never holds a per-agent forge seat) and the [no-human-clicks