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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
10 changes: 10 additions & 0 deletions docs/concepts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
127 changes: 127 additions & 0 deletions docs/concepts/durable-agents-disposable-compute.md
Original file line number Diff line number Diff line change
@@ -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-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-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-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-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 |
95 changes: 95 additions & 0 deletions docs/concepts/isolation-and-egress.md
Original file line number Diff line number Diff line change
@@ -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 |
Loading