Skip to content

Compass dogfood loop

Status: Active

Design record. The full devenv up dogfood loop for Compass: build + load the compass-agent image, bring up postgres + a TLS-door compass-server, mint a runner token, enroll compass-runner, and drive one real agent session end to end. The design targets the RigelBuild/compass repo (its devenv.nix, agent-image/, and go/cmd/* binaries); every devenv.nix:*, agent-image/*, and go/cmd/* citation below is a path in that repo at HEAD 21241f720, not this one. It lives in the sealed design corpus because that is where the wave’s design records freeze.

devenv up today brings up only the human-facing half of Compass: postgres plus a compass-server serving the Unix socket and the loopback dev-http door (devenv.nix:164-167: --socket "$COMPASS_SOCKET" --dev-http 127.0.0.1:… --database "$COMPASS_DATABASE_DSN"). The Runner half — the TLS network door, a runner enrollment, the agent image, and a real per-agent container running an agent turn — is never exercised locally. Matt ruled a FULL dogfood loop, designed first: on devenv up, the compass-agent OCI image is built and loaded into rootless-podman containers-storage, postgres + compass-server come up with the TLS network door open, a runner token is minted, compass-runner enrolls (trusting the self-signed cert) and idles, and a real session is driven end to end (CommsService.CreateAgentCompassService.ProvisionAgentWorkspaceStartAgentSession), so a real container spawns and runs an agent turn.

Note on ledgers: this record lives in the sealed design corpus (docs/designs/platform/), which the design-ledger-gate governs only for the product corpus (docs/designs/product/DECISIONS.md). A platform record adds no DECISIONS row and declares no ledger delta, so nothing here is ledger-tracked; the compass repo it targets has no design-ledger tooling of its own.

  • Linux-only. Every dogfood process/service/task lives under the existing guards: services.postgres = lib.optionalAttrs pkgs.stdenv.isLinux (devenv.nix:124), processes = lib.optionalAttrs pkgs.stdenv.isLinux (devenv.nix:131), and the agent image itself is containers = lib.optionalAttrs pkgs.stdenv.isLinux (agent-image/devenv.nix:65).
  • Rootless podman + uid 1000. The runner refuses any other uid: verifyRunnerUID enforces defaultAgentUID uint32 = 1000 (go/cmd/compass-runner/main.go:148, :79-81 “Ahead of every operator-input check”). Containers launch with plain --userns=keep-id (agent-image/devenv.nix:73-74 citing internal/runtime/podman.go:357). Prereqs on the dev box: rootless podman with subuid/subgid configured (verified on this box: podman 5.8.4, rootless, uid 1000).
  • One shared self-signed cert. The gen-cert output “is both the Server’s --tls-cert and the Runner’s --ca trust anchor” (go/cmd/compass-gen-cert/main.go:50-51); no second keypair, no relaxed loopback TLS.
  • Idempotent tasks (F2, DECIDED). gen-cert skips when both files exist (main.go:85-89 shouldSkipGen); mint with --token-out is skip-if-present/heal-without-rotate (compass-mint-runner-token/main.go:53-59). Re-running devenv up mints/generates nothing new.
  • Ordering (F3, DECIDED). gen-cert → postgres → compass-server(ready) → mint-runner-token → compass-runner. Server readiness stays the existing GetServerInfo probe (devenv.nix:191-197), which flips only after the store is migrated.
  • Session drive is opt-in (D5). The image build and the live session drive land as opt-in tasks, not wired into devenv up; up itself stays light.
  • Lane split. compass-repo lane owns all devenv.nix wiring; the compass-server lane owns the net-new admin session driver (D3), with a scoped compass-runner mount flag (T5a). Tasks below are tagged so the wiring ships independently of the driver.
  • Commits authored as Matt with the seal co-author trailer (rule://commit-conventions); the spawning agent ships the PR.

Extend the existing devenv up bring-up (postgres + compass-server, devenv.nix:115-200) with the Runner half, composed from binaries that already exist for exactly this purpose — no new server code is needed for enrollment; the only net-new code is the admin session driver (compass-server lane, D3).

tasks: dogfood:gen-cert ──────────┐
processes: postgres ── compass-server(+TLS door, ready=GetServerInfo)
tasks: dogfood:mint-runner-token ◄─┘ (after server ready)
processes: compass-runner (enrolls, idles) ◄─ token file + cert
opt-in: dogfood:agent-image (build+load), dogfood:session (drive a turn)

All new units go under the existing Linux guards next to compass-server (devenv.nix:131). Ordering uses devenv’s task/process dependency names — after = [ "devenv:processes:<name>" ] with @ready the default for processes and @succeeded for tasks (forks/devenv/src/modules/processes.nix:186-191: “Use task names like "devenv:processes:postgres" or "myapp:setup"”).

A tasks."dogfood:gen-cert" runs compass-gen-cert --cert-out $STATE/compass/tls.crt --key-out $STATE/compass/tls.key. Defaults suffice: SANs default to 127.0.0.1,::1,localhost (go/cmd/compass-gen-cert/main.go:38 const defaultHosts = "127.0.0.1,::1,localhost"), and the run is skip-if-present — “when both files already exist, leave them be so a restart does not swap the cert out from under a live Server/Runner pair” (main.go:82-84). Cert is written 0644 (public trust anchor), key 0600 (main.go:49-53). The binary is built the same way the server process builds its binary today (devenv.nix:158-163 go build into $DEVENV_STATE/compass/).

2. compass-server grows the TLS network door

Section titled “2. compass-server grows the TLS network door”

Add --listen 127.0.0.1:<port> --tls-cert $STATE/compass/tls.crt --tls-key $STATE/compass/tls.key alongside the existing --socket/--dev-http argv (devenv.nix:164-167), with a ports.network.allocate port. The three flags are all-or-none (go/cmd/compass-server/main.go:146-154: “Either all three are set (the authenticated TCP door is enabled) or none are”). Consequences, all existing behavior:

  • The network door mounts CompassService + CommsService behind bearer + admin interceptors, “plus the internal RunnerService door a Runner enrolls over” (go/server/serve.go:269-273); the RunnerService door exists ONLY here — “Runners are remote, so they dial the authenticated TLS door, never the loopback socket” (serve.go:199-201).
  • The bootstrap-admin token is minted and written 0600 to <StateDir>/admin-token on network-door startup (go/server/network_door.go:35-38 adminTokenFile = "admin-token"; serve.go:269-271 “It mints and writes the bootstrap token 0600 under the state dir”). StateDir defaults to the socket’s parent dir (network_door.go:249-256), i.e. $DEVENV_STATE/compass/ given COMPASS_SOCKET = "${config.devenv.state}/compass/server.sock" (devenv.nix:174). The driver reads this file for its bearer credential. The write is SERIAL before any door serves: Serve runs bindListeners → store.Open (migrate) → BootstrapAdminbuildNetworkServer (which synchronously mints + writes the token) and only then starts the serve goroutines in one errgroup (serve.go:106-115,168-186,276-306; network_door.go:249-258) — so GetServerInfo cannot answer before the token exists, and there is no race for the driver. T7’s “admin-token exists 0600 at server-ready” smoke is the regression guard for this implicit ordering.
  • Migrations still run at store open (go/internal/store/store.go:55-57 “applies any pending embedded migrations under an advisory lock”), and the existing readiness probe (GetServerInfo over dev-http, devenv.nix:184-197) already gates on post-migration serving — mint can safely depend on it.

3. mint-runner-token (idempotent task, after server ready)

Section titled “3. mint-runner-token (idempotent task, after server ready)”

tasks."dogfood:mint-runner-token" runs compass-mint-runner-token --runner-id dogfood --token-out $STATE/compass/runner.token with COMPASS_DATABASE_DSN set to the same DSN the server uses (go/cmd/compass-mint-runner-token/main.go:100-101: “resolveDSN mirrors compass-server’s precedence exactly”). It must run after the server is ready because store.Open verifies the migrated schema (main.go:88, store.go:55-59 “refusing to serve on a failed migration or a version mismatch”) — ordering: after = [ "devenv:processes:compass-server" ] (default @ready). With --token-out the mint is idempotent: “if the file exists and its token is already registered in the store, it no-ops; if the file exists but the store no longer knows the token (e.g. the database was replaced), it re-registers that same token without rotating it” (main.go:53-58). File is 0600, written atomically, raw token no newline (main.go:192-196).

4. compass-runner (process; enrolls and idles)

Section titled “4. compass-runner (process; enrolls and idles)”

A processes.compass-runner execs:

compass-runner --runner-id dogfood \
--server https://127.0.0.1:<network-port> \
--ca $STATE/compass/tls.crt \
--image compass-agent:latest \
--runtime-dir $STATE/compass/runner
COMPASS_RUNNER_TOKEN=$(cat $STATE/compass/runner.token) # env only, never a flag

Grounding: --runner-id/--server/--image/--ca/--runtime-dir flags at go/cmd/compass-runner/main.go:40-57; the token is “env only, never a flag (a flag leaks into the process table)” (main.go:91-93); --ca “swaps the system root pool for a single trusted CA — the local dogfood path, where the Server’s self-signed 127.0.0.1 cert is the trust anchor” (main.go:106-108). The image ref includes the tag: the runner resolves it out of containers-storage with no pull (agent-image/devenv.nix:93-97), and the runtime documents the ref shape as “e.g. compass-agent:latest” (go/internal/runtime/podman.go:71-72). --runtime-dir overrides the /run/compass default (main.go:56-57) — root-only to create — to a state-dir path owned by uid 1000: the per-container agent socket is created at RuntimeDir/containers/<container>/agent.sock (main.go:56-57 flag doc), so the dir must be writable by the runner’s uid or socket creation EACCESes. On start the runner enrolls then idles: Dial performs the Enroll RPC with the bearer interceptor (go/internal/runner/runner.go:101-113), then link.RunSessions “blocks until the stream ends” awaiting Provision/Start commands (go/internal/runner/run.go:96-121). Ordering: after = [ "dogfood:mint-runner-token" ] (a task dep, default @succeeded). The uid-1000 guard runs before any flag validation (main.go:75-81), so a wrong-uid box fails fast with a named cause.

Egress: --egress-allow stays EMPTY (pure default-deny) for the base loop — the local_path file:// clone needs no network and no credentials (go/internal/runtime/workspace.go:109-110). Note the consequence for a real turn: “An empty host set is pure default-deny (only loopback, established flows, and DNS to the container’s own resolver)” (go/internal/runtime/egress.go:29-31), enforced by nftables armed at launch (go/internal/runtime/agent.go:148-150) — so the agent’s LLM provider call is dropped by its own firewall until the provider host is added to --egress-allow. That addition rides the real-turn leg (D1/D2 — the LiteLLM proxy host is added when the credential path lands); do NOT add a provider host to the base argv.

5. Agent image build + load (opt-in task, VERIFIED command)

Section titled “5. Agent image build + load (opt-in task, VERIFIED command)”

tasks."dogfood:agent-image" runs, from agent-image/:

Terminal window
nix run path:./forks/devenv#devenv -- container copy agent

This single command builds AND loads: container_copy first calls container_build (forks/devenv/devenv/src/devenv/container.rs:54 let spec = self.container_build(name).await?;), then runs the copy script. With --registry omitted the CLI passes "false" (container.rs:85 "registry": registry.unwrap_or("false")) and the script falls back to the container’s configured registry (forks/devenv/src/modules/containers.nix:288-292 if [[ "$1" == false ]]; then registry="${cfg.registry}"), which agent-image pins to registry = "containers-storage:" (agent-image/devenv.nix:98). The destination is ${registry}${cfg.name}:${cfg.version} (containers.nix:295) with version defaulting to latest (containers.nix:324-327) — i.e. containers-storage:compass-agent:latest, exactly what the runner’s --image compass-agent:latest resolves. (devenv container build agent alone builds the derivation but loads nothing — container.rs:16-45 only nix-builds.) The invocation is pinned to the vendored fork’s own CLI via nix run path:./forks/devenv#devenv rather than the PATH devenv: the agent-image devenv pins only its MODULE set to the fork (agent-image/devenv.yaml:44-45), so a PATH CLI could diverge from the verified fork source — the pin eliminates that risk.

This task is opt-in (not after-wired into up, D5): the image closure is large (“The agent’s store is large and grows”, agent-image/devenv.nix:100-105) and rebuilding on every up violates the never-heavy-on-up constraint. The runner starts fine without the image present — it only resolves the image at Provision time.

6. The session drive (net-new admin driver — compass-server lane)

Section titled “6. The session drive (net-new admin driver — compass-server lane)”

The session drive is a net-new admin client owned by the compass-server lane (confirmed with that owner): a compass-dogfood / compass drive CLI verb under go/cmd, sequenced AFTER ITEM 7 (secret key-delivery) and ITEM 1 (spawn/despawn record) so the driven agent can actually receive its LLM key. It:

  1. Reads the bootstrap-admin token from $STATE/compass/admin-token (network_door.go:35-38) and dials https://127.0.0.1:<network-port> trusting $STATE/compass/tls.crt, sending Authorization: Bearer <token>.
  2. CommsService.CreateAgent{handle, display_name} — the request carries only those two fields (proto/compass/v1/comms.proto:450-458); “The owner is the caller” (comms.proto:40-42). CreateAgent is authenticatedOpen on the network door (go/internal/auth/admin_gate.go:68-71), so the admin bearer passes. Idempotency: find-by-handle via ListAccounts first, create on miss (CreateAgent has no idempotency key).
  3. CompassService.ProvisionAgentWorkspace{agent_account_id, local_path, ref, client_request_id} — adminOnly (admin_gate.go:50-56), relayed Server → RunnerHub → Runner (go/server/service.go:107-137, go/internal/runnerhub/commands.go:48). client_request_id set to a stable key so “a timeout-retry with the same id returns the same container_name” (compass.proto:342-348). Repo source: local_path (D4) — “local_path clones a container-local bare mirror over file:// for a hermetic, network-free clone” (compass.proto:331-335; go/internal/runtime/workspace.go:20-23 notes it is “typically a bare mirror bind-mounted read-only from a host cache”). A file:// clone needs no forge credentials (workspace.go:109-110 “a file:// clone of a local mirror needs none”).
  4. CompassService.StartAgentSession{container_name, initial_prompt} (compass.proto:359-365) with a trivial smoke prompt; returns the session_id (service.go:147-157).

Mount-surface blocker (LOAD-BEARING). The local_path mirror path is unimplementable against the binaries at this HEAD: the RPC carries only an in-container path string — validateLocalPath constrains it to “a plain absolute in-container path” (go/internal/runner/spec.go:202-218) — and container mounts come EXCLUSIVELY from SpecDefaults.Mounts (spec.go:26-32,107 Mounts: d.Mounts), which cmd/compass-runner/main.go never populates: the SpecDefaults literal sets only Image/Egress/CheckoutDir/HomeDir/UID/NamePrefix (main.go:117-124) and the flag set has no --mount/--volume (main.go:40-64). runtime.Mount exists and is wired through podman (go/internal/runtime/agent.go:42-43 “Mounts is read-only host mounts (e.g. a bare-repo mirror cache)”, :213), but no operator surface reaches it — so no host bare mirror can become container-visible, and Provision’s file:// clone would target a nonexistent path. The missing piece is a small runner-lane flag (e.g. repeatable --mount host:container[:ro] into SpecDefaults.Mounts); this mount-surface flag is scoped as T5a (D3); the driver ownership itself is resolved (compass-server lane).

Ownership and shape are RESOLVED: the compass-server lane owns this driver as a committed compass-dogfood / compass drive Go CLI (reusing the generated Connect clients + the CA-trust client), which reads the admin token, then CreateAgent → ProvisionAgentWorkspace → StartAgentSession → tails the session. The runner mount surface below is the T5a scope (D3); the driver is a compass-server-lane compass-dogfood / compass drive CLI — the record specs the RPC contract, the owning lane implements it.

The runner forwards a model selector: --agent-model / $COMPASS_AGENT_MODEL becomes the agent’s COMPASS_MODEL (go/cmd/compass-runner/main.go:50-53; go/internal/runner/agent_exec.go:65-67). The provider credential is separate: the agent reads a 0600 $HOME/.compass/auth-seed.json “the Runner’s materializer writes (design §T5)” (packages/compass-agent/src/cli.ts:11-12, :47-49 authSeedPath), but the T5 materializer is not yet implemented in the runner (grep Materialize|auth-seed over go/internal/runner + go/internal/runtime at this HEAD: no matches; only the secrets-plumbing types exist, go/internal/secrets/secrets.go:48-57). An agent with no seed still boots and reports — “an agent with no provider credential must still start and report, not crash on first call” (packages/compass-agent/src/cli.test.ts:166-168) — so the loop can smoke container-spawn + session-start today. But spawn+idle does NOT satisfy the ruled acceptance (“runs an agent turn”), and a real turn needs BOTH a credential in the container AND the provider host in --egress-allow (Approach §4). The production cred path IS being built: the SEA-1327 secrets materializer (ITEM 7 — FetchSecrets → the runner writes the frozen auth-seed.json) is the writer the compass-server lane owns, so the real-turn leg sequences AFTER ITEM 7 rather than needing a throwaway seed. How and when the turn becomes real is D1 (sequencing) + D2 (LiteLLM proxy, key via user SetSecret).

One compass-gen-cert artifact is simultaneously the server’s --tls-cert and the runner’s (and driver’s) --ca — “the single cert is its own CA, so the same file is the Server’s –tls-cert and the Runner’s –ca trust anchor — one artifact exercising the real production TLS enroll path locally (no external CA, no relaxed loopback)” (go/cmd/compass-gen-cert/main.go:6-9).

  • Wire everything (image build + session drive) into every devenv up. Rejected as the default (D5): the image closure is large and an LLM turn on every shell entry is slow, costly, and needs credentials. Enrollment-on-up + opt-in drive keeps up light while the full loop stays one command away.
  • Skip TLS locally (serve the RunnerService over dev-http). Not possible and not desirable: RunnerService is mounted only on the network door (serve.go:199-201), and the --dev-http door fail-closes adminOnly RPCs (serve.go:245-249). The self-signed-cert path exercises the production enroll seam, which is the point of dogfooding.
  • Mint the runner token to stdout and capture in the process script. Rejected: --token-out is the designed idempotent path (compass-mint-runner-token/main.go:53-59); stdout “always mints” (main.go:60-62) and would rotate the credential on every up.
  • A second devenv for the loop. Rejected: agent-image is its own devenv for image-env isolation reasons (agent-image/devenv.nix:5-14), but the loop processes belong beside the existing processes.compass-server — a second process tree would duplicate the postgres/DSN wiring.

Tasks are lane-tagged: [repo] = compass-repo lane (devenv.nix wiring, independently shippable); [compass-server] = the net-new admin session driver (T5); [compass-runner] = the runner mount-surface flag (T5a). Each task carries a real red-green cycle plus a smoke assertion (rule://red-green-testing).

T1 [repo] — gen-cert task + TLS door on compass-server

Section titled “T1 [repo] — gen-cert task + TLS door on compass-server”

Add tasks."dogfood:gen-cert" and extend the processes.compass-server argv with --listen 127.0.0.1:<ports.network> --tls-cert --tls-key, keeping the existing --socket/--dev-http (devenv.nix:164-167). Build compass-gen-cert into $DEVENV_STATE/compass/ the same way the server binary is built (devenv.nix:158-163). Order: the task runs before the compass-server process. Allocate the network port beside ports.devhttp (devenv.nix:170).

Interfaces:

  • Consumes: compass-gen-cert --cert-out <path> --key-out <path> (SAN default 127.0.0.1,::1,localhost, skip-if-present, --force to rotate) (go/cmd/compass-gen-cert/main.go:48-64).
  • Consumes: compass-server --listen <addr> --tls-cert <pem> --tls-key <pem> all-or-none (go/cmd/compass-server/main.go:50-60,146-154).
  • Produces: $DEVENV_STATE/compass/tls.crt (0644), tls.key (0600), and a server whose network door writes $DEVENV_STATE/compass/admin-token (0600) on startup (go/server/network_door.go:35-38,249-256). Note the coupling: the token path is a FUNCTION of the socket path — StateDir defaults to the socket’s parent, and compass-server has no --state-dir flag (the ServeConfig literal sets only SocketPath/Version/DevHTTP/Listen/TLS/ DatabaseDSN, go/cmd/compass-server/main.go:136-143), so the default ALWAYS applies. Relocating COMPASS_SOCKET silently moves the driver’s credential; T7’s admin-token smoke covers it.

Test cycle: red — devenv up on main has no TLS door: curl --cacert tls.crt https://127.0.0.1:<port>/...GetServerInfo fails (connection refused). Green — after wiring, the same probe returns 200-class over TLS with the generated cert, and admin-token exists 0600. Idempotence smoke: second devenv up leaves tls.crt mtime unchanged.

Add tasks."dogfood:mint-runner-token", after compass-server readiness (GetServerInfo probe, devenv.nix:184-197; mint needs the migrated store — go/cmd/compass-mint-runner-token/main.go:88 store.Open, which “applies any pending embedded migrations” go/internal/store/store.go:55-57).

Interfaces:

  • Consumes: compass-mint-runner-token --runner-id dogfood --token-out $DEVENV_STATE/compass/runner.token with COMPASS_DATABASE_DSN from the server’s env (devenv.nix:179; DSN precedence parity per compass-mint-runner-token/main.go:100-101).
  • Produces: $DEVENV_STATE/compass/runner.token, 0600, raw token, no newline (main.go:192-196), idempotent/heal-without-rotate (main.go:53-58).

Test cycle: red — task absent; no token file after up. Green — token file exists 0600 after up; a second up does not rotate it (byte-identical); deleting the database but keeping the file re-registers the same token (covered by the binary’s own tests, main_test.go; the devenv smoke asserts the no-rotate case).

Add processes.compass-runner, after the mint task, exec’ing the runner with the flags in Approach §4 and COMPASS_RUNNER_TOKEN read from the token file inside the exec script (env-only contract, go/cmd/compass-runner/main.go:91-96). ready is left UNSET: the devenv readiness type supports only exec/http.get/notify (forks/devenv/src/modules/lib/ready.nix:8-56) — there is no log-line readiness, and the runner exposes no HTTP surface to probe (it idles in RunSessions, run.go:115-121). Unset is safe because nothing afters the runner (the session drive is opt-in/manual) and enrollment is asserted by T7’s log grep. Set restart.on = "on_failure" (matching compass-server’s convention, devenv.nix:198): Dial/Enroll is single-shot with no retry (go/internal/runner/runner.go:101-125; run.go:96-104 returns the error), so without a restart policy a transient enroll failure leaves a permanently dead runner.

Interfaces:

  • Consumes: compass-runner --runner-id dogfood --server https://127.0.0.1:<network-port> --ca $DEVENV_STATE/compass/tls.crt --image compass-agent:latest --runtime-dir $DEVENV_STATE/compass/runner + COMPASS_RUNNER_TOKEN env (main.go:40-57,91-96); --egress-allow empty for the base loop (Approach §4).
  • Produces: an enrolled, idle runner (Enroll RPC then RunSessions, go/internal/runner/runner.go:101-113, run.go:96-121).

Test cycle: red — no runner process on main. Green — devenv up log shows “runner enrolled” with runner_id=dogfood; kill/restart the runner process and it re-enrolls (reattach path, run.go:100 link.Reattached()). Failure smoke: a corrupted token file yields the runner’s Unauthenticated error (“an Unauthenticated here means a bad/expired/wrong-kind token”, runner.go:97-99), proving the door actually authenticates.

T4 [repo] — agent-image build+load task (opt-in per D5)

Section titled “T4 [repo] — agent-image build+load task (opt-in per D5)”

Add tasks."dogfood:agent-image" running nix run path:./forks/devenv#devenv -- container copy agent from agent-image/ (build+load semantics verified in Approach §5; the invocation is pinned to the vendored fork’s CLI). Not wired after into up (D5).

Interfaces:

  • Consumes: nix run path:./forks/devenv#devenv -- container copy agent in agent-image/ (forks/devenv/devenv/src/cli.rs:1100-1113, devenv/container.rs:47-108).
  • Produces: containers-storage:compass-agent:latest (forks/devenv/src/modules/containers.nix:295, agent-image/devenv.nix:67,98, version default latest containers.nix:324-327).

Test cycle: red — podman image exists compass-agent:latest fails on a clean box. Green — after the task, it succeeds. Linux-only guard: the task is a no-op (or absent) on non-Linux, matching agent-image/devenv.nix:65.

T5 [compass-server] — the admin session driver (owned by compass-server lane)

Section titled “T5 [compass-server] — the admin session driver (owned by compass-server lane)”

Implement the driver in Approach §6: read $DEVENV_STATE/compass/admin-token, dial the TLS door with the shared cert as trust anchor, then CreateAgent (find-or-create by handle) → ProvisionAgentWorkspace (local_path mirror, stable client_request_id) → StartAgentSession (smoke initial_prompt). BLOCKED at this HEAD on the missing runner mount surface (Approach §6 “Mount-surface blocker”): a container-visible bare mirror requires a new compass-runner --mount host:container[:ro] flag wiring into SpecDefaults.Mounts (go/internal/runner/spec.go:26-32,107) plus devenv wiring to pass it — a cross-lane dependency whose mount-surface flag is scoped as T5a (D3; the driver itself is compass-server-owned). Preparing the bare mirror itself is driver-side work (the runtime integration test seeds one the same way, go/internal/runtime/lifecycle_test.go:99-102 — though that test drives the runtime API directly, below the runner binary).

Interfaces:

  • Consumes: CommsService.CreateAgent{handle, display_name} → {account} (proto/compass/v1/comms.proto:450-462); CompassService.ProvisionAgentWorkspace{agent_account_id, local_path, ref, client_request_id} → {container_name} (compass.proto:326-355); CompassService.StartAgentSession{container_name, initial_prompt} → {session_id} (compass.proto:359-371); bearer auth per admin_gate.go:50-56,68-71.
  • Produces: a running compass-agent-* container (podman), a live session id, and a nonzero exit + named cause on any step’s failure.

Test cycle: red — driver absent/failing against a stood-up loop. Green — driver exits 0; podman ps shows the agent container; GetAgentStatus (adminOnly, admin_gate.go:50-56) lists the session.

T5a [compass-runner] — the runner mount-surface flag

Section titled “T5a [compass-runner] — the runner mount-surface flag”

Add a repeatable --mount host:container[:ro] flag on compass-runner that populates SpecDefaults.Mounts (go/internal/runner/spec.go:26-35, the field is designed and wired through to podman at spec.go:100-107 but no operator surface reaches it today, cmd/compass-runner/main.go:40-64,117-124). This unblocks the local_path bare mirror (D3): the devenv wiring passes the host mirror path so it becomes container-visible for Provision’s file:// clone.

Interfaces:

  • Consumes: compass-runner --mount <host>:<container>[:ro] (repeatable), wiring into SpecDefaults.Mounts (spec.go:26-35).
  • Produces: a runner that mounts the host bare mirror read-only into the agent container at the path ProvisionAgentWorkspace{local_path} names.

Test cycle: red — a runner built without the flag leaves SpecDefaults.Mounts nil, so Provision’s file:// clone targets a path absent from the container and fails. Green — --mount <host-mirror>:<container-path>:ro populates SpecDefaults.Mounts and the mirror is readable inside the container, so the clone succeeds.

T6 [repo] — teardown: dogfood:clean task

Section titled “T6 [repo] — teardown: dogfood:clean task”

Nothing tears agent containers down: on runner shutdown only the agent SOCKETS close — “every container lives until the Runner process ends” refers to socket lifetime; Close touches h.sockets only (go/internal/runner/host.go:162-179), and the podman container survives devenv down. Container names are deterministic (NamePrefix + agent account id, go/internal/runner/spec.go:33-35), and the client_request_id dedup is in-memory (gone after restart) — so the SECOND dogfood run’s Provision hits a podman create name collision: without cleanup the loop works exactly once per boot. Add an opt-in tasks."dogfood:clean" that podman rm -fs the compass-agent-* containers and sweeps the runner socket dir ($DEVENV_STATE/compass/runner/containers/). Alternative (implementer’s choice): a driver-side stale-container preflight that podman rm -fs its own target container name before Provision.

Interfaces:

  • Consumes: podman ps -a --filter name=compass-agent- + podman rm -f; the runner socket dir layout RuntimeDir/containers/<container>/agent.sock (go/cmd/compass-runner/main.go:56-57).
  • Produces: a clean slate such that a re-run of the session drive provisions successfully.

Test cycle: red — two consecutive session drives without clean: the second Provision fails on the name collision. Green — drive → clean → drive succeeds twice.

T7 [repo] — end-to-end devenv up smoke + docs

Section titled “T7 [repo] — end-to-end devenv up smoke + docs”

A scripted smoke (an opt-in dogfood:session task per D5 chaining T4+T5, or CI-style script) asserting, in order: postgres up; server ready with TLS door answering under the generated cert; admin-token + runner.token present 0600 (the admin-token assertion doubles as the regression guard for the serial token-write-before-serve ordering, Approach §2); runner log shows enrollment; and — gated on the session-drive decision — T5’s green assertions. Update the devenv up header comment (devenv.nix:115-123) to describe the full loop; document the box prereqs (Linux, rootless podman, uid 1000, subuid/subgid) and cert expiry: gen-cert is skip-if-present forever with a finite --validity (go/cmd/compass-gen-cert/main.go:57-58,82-89), so on expiry the loop fails with an opaque TLS error — rerun compass-gen-cert --force to rotate.

Interfaces:

  • Consumes: everything T1-T6 produce.
  • Produces: one command a developer runs to verify the loop; the record’s acceptance evidence.

Test cycle: the smoke IS the test; red is any assertion failing on a clean checkout before the wiring lands, green is all passing after.

  • T1 [repo] gen-cert task + TLS door on compass-server (red-green + idempotence smoke)
  • T2 [repo] mint-runner-token task after server-ready (red-green + no-rotate smoke)
  • T3 [repo] compass-runner process: enroll + idle, restart on_failure (red-green + bad-token smoke)
  • T4 [repo] agent-image build+load opt-in task via the fork-pinned CLI (red-green via podman image exists)
  • T5 [compass-server] admin session driver compass-dogfood/compass drive: CreateAgent → Provision(local_path) → Start → tail (red-green + podman ps smoke) — owned by compass-server lane, sequenced after ITEM 7 + ITEM 1
  • T5a [compass-runner] runner --mount host:container[:ro] flag into SpecDefaults.Mounts (unblocks local_path bare mirror) — scoped here per D3
  • T6 [repo] dogfood:clean teardown task (red-green via second-run provision)
  • T7 [repo] end-to-end devenv up smoke + prereq/cert-expiry docs (session leg is opt-in per D5)

F2 (idempotent devenv tasks) and F3 (gen-cert → postgres → server-ready → mint → runner ordering) are recorded in Global Constraints. The forks below were batched to Matt at the design-PR gate and ruled as noted.

  1. D1 — Real-turn sequencing (was OQ1): ship the enroll loop now; the real-turn leg follows the ITEM 7 + driver chain. The compass-repo enroll loop (postgres, server+TLS, mint, runner enrolled+idle) has no credential dependency and ships standalone as [repo] tasks T1-T4/T6/T7. The FULL “runs a turn” acceptance completes once the SEA-1327 secrets materializer (compass-server lane) and the driver (T5) land. A real turn needs three coupled pieces, all deferred to that chain: (i) the provider credential — the frozen 0600 $HOME/.compass/auth-seed.json the SEA-1327 materializer writes (packages/compass-agent/src/cli.ts:47-50,98-102; cli.test.ts:110-111); (ii) the provider host in --egress-allow (default is pure default-deny, go/internal/runtime/egress.go:29-31, agent.go:148-150); (iii) the model selector via --agent-modelCOMPASS_MODEL (go/cmd/compass-runner/main.go:50-53).
  2. D2 — Model + credential path (was OQ1b): LiteLLM proxy, key via the user SetSecret (KIND_PROVIDER) path. The dogfood agent targets the LiteLLM proxy as its provider; a developer seeds the key once via the user-facing SetSecret flow (the production cred path, ITEM 7), so the dogfood exercises the real secret path end to end. --egress-allow gets the LiteLLM proxy host for the real-turn leg. Applies when the real-turn chain (D1) lands.
  3. D3 — Mount-surface scope (was OQ2): a scoped T5a task in this record. Driver ownership is settled (compass-server lane, T5). The remaining gap — nothing populates SpecDefaults.Mounts at this HEAD, so the local_path bare mirror cannot reach the container (Approach §6 “Mount-surface blocker”) — is closed by a net-new compass-runner --mount host:container[:ro] flag into SpecDefaults.Mounts (T5a, go/internal/runner/spec.go:26-32,107), with the devenv wiring passing it ([repo]). Not a separate design record — a single scoped task here.
  4. D4 — local_path mirror (was OQ3). Repo source is local_path with a bare mirror of the compass repo itself — hermetic, credential-free (compass.proto:331-335; go/internal/runtime/workspace.go:109-110), contingent on the D3 mount surface. Follows from D3.
  5. D5 — Session drive is opt-in (was OQ4). The heavy legs (image build, live LLM turn) land as explicit opt-in tasks (devenv tasks run dogfood:session), not wired into devenv up. Note: devenv up is an explicit command that starts the process stack — it is NOT run automatically on shell entry (direnv loads env/tooling on entry; up is separate). So the split keeps the on-demand heavy legs off the always-on up process set: up brings up the light, idempotent enroll loop; the image build + real turn run on demand.

A related public-API fork in the ITEM 7 (SEA-1327 secrets) lane was ruled in the same batch: the SetSecret/ListSecrets/DeleteSecret RPCs go on a new SecretsService (not folded onto CompassService). That governs the ITEM 7 proto edit, not this record’s tasks; noted here only for provenance.