Skip to content

design(runtime): host runner tier + living tier spec (RIG-3070) - #1002

Open
rigel-mintaka wants to merge 6 commits into
mainfrom
compass/rig-3070-host-runner-tier
Open

design(runtime): host runner tier + living tier spec (RIG-3070)#1002
rigel-mintaka wants to merge 6 commits into
mainfrom
compass/rig-3070-host-runner-tier

Conversation

@rigel-mintaka

@rigel-mintaka rigel-mintaka commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Designs a third runner tier that runs an agent as an ordinary process on the
user's own machine, plus the agent-driven config-import review that makes it
worth something on day one. Both halves exist to shorten one path: getting a
new user's existing coding-agent setup working under Compass.

Design-only. No Go changes; no behaviour moves in this PR.

What the measurement changed

The original framing was "we probably need a file/directory upload, or tell
users to put their config in a git repo". Neither is needed — the transport is
already built. compass agent-config push --dir <path> tars+gzips a local
directory and PutAgentConfigs it (go/cmd/compass/agent_config.go:30-51),
and the bundle grammar is already the shape a migrating user has on disk:
skills/, extensions/, mcp/, settings/, rules/, agents/,
prompts/, profiles/ plus AGENTS.md and models.yml
(go/cmd/compass/bundle.go:23-90).

So the real gap is not ingress. It is that a user who imports their corpus
cannot tell what overlaps or is superseded by Compass's built-ins, and a
silently shadowed skill looks exactly like a successful import.

Secrets moved the same way: keyring:// is already an accepted SecretSpec
provider (WithProvider, go/internal/secrets/resolver.go:83-85) and
production pins nothing today (go/server/serve.go:528), so the host tier's
secret story is one option at one callsite rather than a subsystem.

The tier sits on an axis that already exists

DL-325 already rules that the security boundary follows the trust model, not
the deployment
(docs/designs/DECISIONS.md:158): microVM is required for
untrusted multi-tenant, podman is a permanent supported self-host tier. A host
tier is a third point on that axis, not a new doctrine — so this AMENDS
DL-325 rather than superseding it.

Tier Boundary Egress Trust model
host (not built) none — a process on the operator's machine explicitly unenforced single-tenant, operator's own box
podman rootless container, shared kernel enforced (nftables in the container netns) self-host entry tier, no /dev/kvm
microVM hardware virtualization enforced, armed in-guest required for untrusted multi-tenant

Host mode is not the preferred steady state, and that stays guidance
rather than a gate. The counterfactual for a host-tier user is not a contained
Compass user — it is someone still on their previous CLI agent at the same
exposure, learning nothing about Compass.

Three things the review loop corrected

The first draft was internally inconsistent in ways that would have shipped a
tier that could not launch:

  • The AsUser rule broke Launch on the tier's own target machine. The
    fleet agent uid is the baked constant 1000
    (go/internal/agentuid/agentuid.go:13, into SpecDefaults at
    go/cmd/compass-runner/main.go:153) and every provision exec passes it
    (go/internal/runtime/agent.go:203-204,249-250,343-344,359-360). Now the
    host tier derives Workspace.UID from os.Geteuid(); the strict rejection
    of any other uid stays.
  • The egress fail-loud was inverted. It triggered on a non-empty
    allowlist, but an empty EgressPolicy is pure default-deny — the
    strictest posture (go/internal/runtime/egress.go:29-31) — and
    --egress-allow defaults to "". It would have rejected a looser policy
    and silently discarded the tightest. It now triggers on the policy's
    presence.
  • The plan omitted the agent transport. The agent hard-freezes
    /run/compass/agent.sock and /run/compass/agent-config
    (packages/compass-agent/src/cli.ts:91, config-reader.ts:53), both
    delivered by bind-mount (go/internal/runner/host.go:198,214) and both
    pinned by contract tests whose comment reads "A drift is a silent
    unconfigured boot". A host process has no bind mounts. Adds the host
    provision leg and the path override.

Egress never reuses EgressArmedInGuest()
(go/internal/runtime/agent.go:298-306) — that marker means the backend armed
egress itself, so claiming it would be false.

Import review is an agent task, not a gate

Filename and collision checks are the shallow half. Real overlap is semantic:
a hand-written skill that does what a built-in already does, differently,
sharing no filename. No deterministic gate finds that, so an agent reads both
corpora and reports redundant / conflicting / safe-to-drop. The agent
proposes and the user disposes
— it never rewrites the user's config.

The host tier is what makes this land: the agent is already sitting where the
user's config lives, so there is no push-then-inspect round trip.

Structure

  • docs/specs/runtime/runner-tiers.md (new) — the living tier strategy,
    following the existing docs/specs/ convention ("Living source-of-truth …
    how it currently behaves"). The host tier is marked unbuilt throughout.
  • docs/designs/infra/runtime/compass-host-runtime-tier/design.md (new) — the
    point-in-time record: the why, the 9-method mapping with degenerate cases
    named, the plan.
  • compass-runner-adoption-strategy/design.md — strategy prose moves to the
    spec; the ruling, alternatives, plan and every DL citation stay.

ContainerRuntime stays frozen — the host tier implements it, it does not
amend it. Ledger rows are proposed in each record's Ledger delta for the
coordinator to mint at freeze; this PR does not touch DECISIONS.md.

Verification

markdownlint-cli2 0 issues across 209 files; moon run design-ledger-gate:ci OK (308 ledger rows, 130 record headers valid); rollup
green.

Open question for review

Onboarding-convenience and permanent host-capability (an agent that needs the
user's real session bus — window management, host tooling) are two motivations
in one feature. They may have different lifecycle and trust requirements, and
folding them could distort the onboarding tier. Recorded as an Open Question
with a recommendation to accept the onboarding framing here and revisit
host-capability in its own record.

Refs RIG-3512

@linear-code

linear-code Bot commented Sep 8, 2026

Copy link
Copy Markdown

RIG-3070

RIG-3512

@trunk-io

trunk-io Bot commented Sep 8, 2026

Copy link
Copy Markdown

Merging to main in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown

Compass engineering docs preview: https://compass-rig-3070-host-runner.compass-eng-docs.pages.dev

Deployed from compass/rig-3070-host-runner-tier at d390cee.

Changed pages:

@rigel-mintaka
rigel-mintaka force-pushed the compass/rig-3070-host-runner-tier branch from 74353c7 to f1c2e19 Compare September 8, 2026 19:41
…sport (RIG-3512)

Fold the three high review findings on the host-runtime-tier record.

- The AsUser rejection rule would have errored on every provision exec: the
  fleet agent uid is the baked constant 1000, so the host tier now derives
  Workspace.UID from the Runner os.Geteuid(). The strict rejection stays.
- The egress fail-loud triggered on a non-empty allowlist, which rejected a
  looser policy and silently discarded the strictest one (an empty host set is
  pure default-deny). It now triggers on the policy presence.
- The plan omitted the agent transport: the socket and config paths are frozen
  in the compass-agent package and delivered by bind-mount, which a host
  process does not have. Adds the host provision leg and the path override.
…shape (RIG-3512)

Matt ruled the host tier is needed wherever a user runs an agent on their own
machine, not only in a single-tenant deployment. The record had scoped it as a
"self-host single-tenant onboarding tier", which contradicts DL-325 itself:
that row says the boundary follows the trust model, NOT the deployment shape.
A user running one agent on their own box puts exactly one trust domain on
that host whatever topology their Server sits in.

Re-scopes the tier as single-trust-domain, forbidden for untrusted work and
for isolating mutually-distrusting principals — properties of the trust
domain, not the product.

Also rules the second open question: host capability (real session bus,
display, device access) is a PERMANENT case, not onboarding scaffolding a user
graduates off. That work has to run where the hardware and the session are.
Withdraws the weaker "supported consequence, not a designed-for surface"
framing; narrows the remaining question to the device/session-bus surface.

Touches the placement prose, the constraint, the tier table and when-to-use in
the living spec, and the proposed ledger row.

markdownlint 0 issues/209 files; design-ledger-gate:ci and orion-ref-gate:check
clean.

Refs RIG-3512
…orkload* (RIG-3553)

The host tier makes ContainerRuntime span a third non-container backend,
after MicroVMRuntime already made it span a second. SelectBackend's own
comment says the container path goes away entirely, leaving the interface
with no container implementation.

Rule the successor name (Workload*), reject Session* (already the
user-facing conversational stream, and one environment outlives many
sessions) and Sandbox (asserts isolation the host tier does not provide).
AgentRuntime is not renamed; it is the per-agent lifecycle facade and that
name is accurate. The ~365-reference migration is tracked in RIG-3553,
sequenced after the microVM default flip.
The runtime interface was named for one of its backends, and it now has
four: podman containers, microVM guests, Apple container on macOS, and the
direct host processes the host tier adds. SelectBackend already says the
podman path eventually goes away, which would leave an interface named
ContainerRuntime with no container implementation.

ContainerRuntime -> WorkloadRuntime (85), ContainerID -> WorkloadID (198),
ContainerSpec -> WorkloadSpec (58), InContainerError -> InWorkloadError (8),
plus the comment and design-record prose describing the seam.

Genuine containers keep the old vocabulary: ContainerController (podman-only
stack supervisor), ContainerRef (a message container), the testcontainer
specs, and the container_name wire field, which is a compatibility boundary.
AgentRuntime keeps its name; it is the per-agent lifecycle facade.

The S1 freeze reserves the method set, not the identifier. No signature,
method set, or behaviour changed.
@rigel-mintaka
rigel-mintaka force-pushed the compass/rig-3070-host-runner-tier branch from 30e6f3b to 5caad8b Compare September 8, 2026 20:38
…ry (RIG-3553)

The type rename left five comments describing backend-agnostic constructs
as container-specific: the seam file header, requireSuccess, AgentRuntime,
BackendConfig, and SelectBackend. Comments on the podman implementation
itself are unchanged - those genuinely describe podman containers - as are
the two references to "the container path" in SelectBackend, which name the
podman backend specifically.

Comment-only; no code change.

Co-authored-by: Matt Wilkinson <matt@rigel.build>
@rigelbuild-bot

Copy link
Copy Markdown
Contributor

Re-requesting review — your approval predates most of this PR

Your approval is from 2026-09-08T02:39:30Z, on a 4-commit docs-only record. The head is now d390cee34 at 6 commits, and the two new ones are Go code you have not seen. GitHub still reports reviewDecision: APPROVED because an approval is not re-checked on push, so this PR currently looks ready and is not. Please do not enqueue on the old approval.

What landed after you approved:

Commit What
5caad8bf8 refactor(runtime)!: the Container*Workload* seam rename, 56 files
d390cee34 comment prose on five backend-agnostic constructs, comment-only

The rename is the one you ruled on ("we're going to be supporting 3 runtimes, we can't keep calling the interface ContainerRuntime… let's do workload"). Mapping: ContainerRuntimeWorkloadRuntime (85 refs), ContainerIDWorkloadID (198), ContainerSpecWorkloadSpec (58), InContainerErrorInWorkloadError (8). No signature, method set, or behaviour change — the S1 freeze reserves the method set, not the identifier.

Worth your attention while reading:

  • It's four backends, not three. DL-330 makes Apple container the macOS runtime via a new AppleContainerCLI behind SelectBackend, alongside podman, microVM, and the host processes this record adds.
  • Kept as-is, deliberately: ContainerController (podman-only stack supervisor), ContainerRef (a message container), the testcontainer specs, the container_name wire field (a compatibility boundary), and AgentRuntime. Word-boundary anchoring protected them; their reference counts are verified unchanged.
  • The proto is touched. Two comment lines in guest_control.proto — the rename hit generated .pb.go comments and tripped compass-proto:drift, so I fixed it at the source rather than the generated file.

Verification on 5caad8bf8: rollup: success (the sole required check) with moon (go|bun|nix), pgtest, microvm, e2e, darwin, deploy, setup all green and zero non-green. d390cee34 is comment-only and gofmt/build/vet clean; its CI is still running.

I should not have pushed onto an approved PR — rule://trunk-merge-queue prohibits exactly this, and I read the rename as a continuation of the same work rather than as a new head needing its own review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants