Compass design-decision ledger
Read this first. It is the canonical index of current Compass design truth: every ratified, citable decision is one row — a stable ID, a one-line statement, a live/superseded status with provenance, and a link to the frozen record holding the full rationale. Per-decision truth for ledgered decisions lives here; rationale lives in the linked records (two hops by design).
Absence of a row is not evidence of no ruling. The ledger covers named, citable decisions (a record’s numbered Decision, a Global Constraint, or a §-level ruling another record cites) plus every known overturn — not inline micro-choices. For anything not here, the linked frozen records remain authoritative.
This is the one living document under docs/designs/ — continuously appended
and flipped, never frozen. The rules that keep it honest (append-only rows,
immutable Decision cells, the same-PR flip that a design PR owes its ledger
delta) live in the design record and AGENTS.md; the design-ledger-gate CI
check enforces the mechanical half. Full rationale:
the design-ledger record.
Conventions
Section titled “Conventions”- ID —
DL-<zero-padded int>, globally unique, append-only, never reused. - Status — exactly
Active (<who>, YYYY-MM-DD)orSuperseded by DL-<n> (<who>, YYYY-MM-DD). Every row carries provenance so overlapping one-liners have a recency signal. - Decision — a one-line paraphrase, immutable after append. A new ruling is
a new row plus a
Supersededflip on the old, never an in-place reword. - Record — a relative link into the frozen record; an
#anchoris required for links into large records so rationale is one hop away.
Strategy & positioning
Section titled “Strategy & positioning”| ID | Decision | Status | Record |
|---|---|---|---|
| DL-001 | Compass is an agent development environment: named workstream agents supervised by a Dispatcher, gated by a Warden | Active (Matt, 2026-06-30) | v0.3 §Solution |
| DL-002 | OMP is the default/reference agent, carried as an external OSS fork; Compass builds on it, does not rebuild it | Active (Matt, 2026-07-01) | v0.4 §OMP is the default |
| DL-003 | seal is scoped to hosting the Warden, not the whole runtime | Active (Matt, 2026-07-01) | v0.4 §seal scoped to Warden |
| DL-004 | Adopt Cotal as the coordination substrate behind a thin Compass-owned seam | Superseded by DL-005 (Matt, 2026-07-04) | v0.4 §Cotal is the substrate |
| DL-005 | The NATS/JetStream substrate is kept, but Cotal moves from adopted dependency to reference implementation; comms is first-party behind a Compass seam | Superseded by DL-021 (Matt, 2026-07-04) | v0.5 §D1 |
| DL-006 | Upstream contribution to OMP is a distribution channel, not a governance dependency | Active (Matt, 2026-07-01) | v0.4 §Upstream contribution |
| DL-095 | The agent tree is Compass’s organizing primitive — Compass is a tool to build and manage agent trees: AgentAccount carries parent_agent_id (field 4; empty = root, set at creation — the spawning agent, or user choice — and editable thereafter via a ReparentAgent mutation, so users iterate hierarchy without teardown), the workspaces sidebar and board views derive from and filter by the tree, REPLACING the user-defined folder organization (replace, not coexist); channels (SEA-1622) and roles (SEA-1623) compose onto the tree later | Active (Matt, 2026-08-01) | agent trees §Approach |
Topology & tiers
Section titled “Topology & tiers”| ID | Decision | Status | Record |
|---|---|---|---|
| DL-007 | Architecture is three tiers: Client → Server → Runner, with the communication layer as the spine | Active (Matt, 2026-07-04) | v0.5 §The three tiers |
| DL-008 | Evolve compass.v1 from a local daemon contract into the Client↔Server contract (multi-user) | Active (Matt, 2026-07-04) | v0.5 §D2 |
| DL-009 | The Server tier realizes the reserved hosted-mode transport seam | Active (Matt, 2026-07-04) | v0.5 §D3 |
| DL-010 | Multi-agent orchestration (Supervisor + Bridge) is in the MVP; single-agent is the first build increment | Active (Matt, 2026-07-04) | v0.5 §D4 |
| DL-011 | The Server tier is built by promoting compass-daemon in place, not a new server crate | Active (Matt, 2026-07-08) | v0.5-server §F1 |
| DL-012 | The authenticated network door is rustls-via-tonic TLS + bearer-token accounts (in-memory token store for T2) | Active (Matt, 2026-07-08) | v0.5-server §F2 |
Transport
Section titled “Transport”| ID | Decision | Status | Record |
|---|---|---|---|
| DL-013 | Transport is gRPC everywhere (Client↔Server and Runner↔Server), authenticated by per-Runner provisioned tokens | Active (Matt, 2026-07-06) | v0.6 §Transport: gRPC everywhere |
| DL-014 | NATS/JetStream is not a Client/Runner-facing transport; it is comms-internal only | Active (Matt, 2026-07-06) | v0.6 §NATS as a transport |
| DL-015 | The in-container agent talks to the Runner alone (Runner-sole topology), off stdio, over a RunnerCallTransport seam | Active (Matt, 2026-07-21) | agent↔Runner transport §Decisions |
| DL-016 | The concrete agent↔Runner transport is a per-container Unix socket, 1:1 with a session; network transport is a future additive | Superseded by DL-017 (Matt, 2026-07-21) | agent↔Runner transport §The concrete impl |
| DL-017 | Consolidate agent↔Runner onto socket streams: telemetry via Publish client-stream, control via Control server-stream on AgentGateway; retire the protocol stdio pipe | Active (Matt, 2026-07-22) | transport consolidation §What is superseded |
| DL-018 | The frozen RunnerService transport recommendation #2 is socket-only, superseding the earlier off-stdio carrier clause | Active (Matt, 2026-07-22) | agent comms tools §The frozen transport |
Storage
Section titled “Storage”| ID | Decision | Status | Record |
|---|---|---|---|
| DL-019 | Postgres is the store of record; transcript bodies live in object storage behind a blob seam; JetStream is comms-only | Active (Matt, 2026-07-04) | v0.5 §D12 |
| DL-020 | Postgres is the substrate and is not swappable; the in-memory event bus is a cache/fan-out ring, not a second store | Active (Matt, 2026-07-06) | v0.6 §State + storage |
| DL-021 | The comms substrate is Postgres write-through fan-out, not a swappable NATS-backed seam | Active (Matt, 2026-07-06) | v0.6 §The communication layer |
| DL-022 | Config distribution is a Runner-mediated pull into a read-only mount; agent binary + base image ride versioned OCI pulls | Active (Matt, 2026-07-04) | v0.5 §D11 |
| DL-052 | Only the Server holds forge write credentials, as a server_only declared secret filtered out of container injection — a NEW scoping mechanism this record introduces, since none exists in the tree. The agent keeps a push-scoped git credential. The boundary that creates is half enforced (the filter, which Compass controls and tests) and half operator hygiene (the agent token’s scope, which Compass neither mints nor inspects); closing the second half would require Compass to hold secret values, which today it deliberately never does | Active (Matt, 2026-07-27) | ownership layer §Decision 4 |
| DL-053 | Forge subscriptions are Server-side Postgres rows with a per-artifact FETCH cursor (advanced on any 200) split from a per-subscriber DELIVERY cursor (advanced only on that subscriber’s own successful notify), change-detected by conditional polling in v1 (webhooks are an additive accelerator), delivered by account on the existing Sessions → AgentGateway.Control push path | Active (Matt, 2026-07-27) | ownership layer §Decision 5 |
| DL-055 | Compass stores a row per artifact it authored (coordinate + agent + owner + session, written with the stamp), so agent ownership queries and the Bridge boards are local index scans over Server-recorded truth rather than forge-wide searches of untrusted header text; it is an ownership index, never a mirror of forge content | Active (Matt, 2026-07-27) | ownership layer §OQ-8 |
| DL-063 | Agent session transcripts persist to S3 for v1 via an ENDPOINT-AGNOSTIC backend (COMPASS_S3_ENDPOINT, generic Bun.S3Client, no S3-server-specific calls), reached under default-deny egress via an allowlisted endpoint — the S3 client path is exercised from v1, never a swap-later local-fs abstraction. Solo/self-host backend is a local-filesystem Garage (≥ v2.3.0) per Runner host, granting a full-bucket credential (cross-agent read/tamper accepted only within the single-trust-domain MVP); real/multi-user deployments MUST NOT use the Garage full-bucket credential — they use Cloudflare R2 with prefix-scoped tokens, which is a CONFIG change not a new build (endpoint-agnostic). Docs advisory + hardening follow-up (“R2 + prefix-scoped tokens for real deployments”) stated; no bucket-per-agent, no Runner signing proxy | Superseded by DL-084 (Matt, 2026-07-31) | session persistence §Appendix |
| DL-064 | The agent persists its transcript DIRECTLY from inside the container as the session runs: a Compass-owned S3 SessionStorageBackend wrapped in the SDK’s IndexedSessionStorage, injected at the cli.ts composition root via createAgentSession({ sessionManager }); no runner-ships-the-log-after-teardown step exists. The persist model is a SEGMENTED PER-EPOCH LOG — a stable session_id keys the prefix sessions/<session_id>/ and each container lifetime appends only to its own <epoch>.jsonl segment (continuous per-append full-object PUT of that one segment + terminal drain); reconstruction is an S3 LIST of the prefix reconstructed in ascending epoch order with title-slot CHECKPOINT SUPERSESSION — a segment whose first line parses as a title slot is a full-body-rewrite snapshot that supersedes prior epochs (readFull resets its accumulator to it), so SDK-internal compaction rewrites do not double-count (readFull=LIST+reconstruct vs append=single-current-segment asymmetry). Fresh opens <0>.jsonl via SessionManager.open; resume opens the PRIOR latest existing epoch key (context is seeded by the backend readFull=LIST+reconstruct on that existing, indexed key) then repoints the write target to a fresh <max+1>.jsonl via the public captureState/restoreState seams (session-manager.ts:919/939, onDisk:true, needsRewrite:false), so appends land only in the new segment and prior segments stay immutable — NO SDK patch | Superseded by DL-085 (Matt, 2026-07-31) | session persistence §Appendix |
| DL-065 | A Postgres pointer row (agent_session_transcripts: stable logical session_id PK → bucket + key PREFIX + a nullable endpoint provenance column, FK-rooted in the post-0004 agent_sessions) indexes each stored log; resume reuses StartAgentSession with an additive authz-gated resume_session_id on the PUBLIC request, and the resolved pointer rides a NEW top-level ResumeContext resume = 12 on the INTERNAL SessionsResponse (outside the command oneof, tag 2 untouched — additive, non-forgeable) so a client-set pointer can never reach the Runner (the public request is relayed verbatim). The new container reconstructs one logical log from the prefix and the built replay barrier gates live input (ReplayComplete alone lifts it — no TranscriptReplay frames on the resume path) | Superseded by DL-084 (Matt, 2026-07-31) | session persistence §Appendix |
| DL-066 | The TranscriptReplay payload shape (agent_message_json + index/total) is a PROPOSAL handed to SEA-1310, which compass owns; this record does not freeze it and does not depend on it. The handoff MUST carry a replay-path admission/size bound, because defining the payload turns the replay path into an admission path the retention cap exempts today (control.go:546-560) | Superseded by DL-086 (Matt, 2026-07-31) | session persistence §Appendix |
| DL-082 | Writer fencing for session-log persistence is force-teardown of the old container (PRIMARY fence) plus a per-container-lifetime epoch token in the object key (sessions/<session_id>/<epoch>.jsonl, BELT): force-teardown removes the zombie writer in the normal case, and the epoch guarantees a zombie from a crashed lifetime a Runner could not confirm torn down can only clobber its OWN dead epoch segment, never erase another lifetime’s segment | Superseded by DL-085 (Matt, 2026-07-31) | session persistence §Appendix |
| DL-083 | Session-transcript credential/storage posture: a full-bucket S3 credential is acceptable for solo/self-host v1 (Garage, single trust domain) despite the stated cross-agent read/tamper threat; real/multi-user deployments use Cloudflare R2 with prefix-scoped tokens so each agent is confined to its own sessions/<session_id>/ prefix. The S3 backend is endpoint-agnostic via COMPASS_S3_ENDPOINT (generic Bun.S3Client), so the hardening is config not a new build; the threat is stated and a hardening follow-up is filed | Superseded by DL-084 (Matt, 2026-07-31) | session persistence §Appendix |
| DL-084 | The Server owns durable session-transcript storage: a Postgres transcript-entry store (agent_session_transcript_entries: (session_id, entry_seq) PK, UNIQUE idempotency_key, checkpoint flag, FK agent_sessions) persisted on receipt of durable TranscriptEntry frames at the CommitConversationFrame at-most-once commit seam, with CHECKPOINT SUPERSESSION server-side (the read view is latest checkpoint + later deltas — the old read-side reconstruction rule relocated to the one owner that reads). Storage backend choice and credential posture are Server-internal; the agent and Runner hold zero storage detail, zero S3 credentials | Superseded by DL-093 (Matt, 2026-07-31) | session persistence §Whole bodies in Postgres — rejected |
| DL-085 | The agent persists nothing: an injected emit-backend on the SDK createAgentSession({ sessionManager }) seam turns every committed session entry into a durable TranscriptEntry AgentFrame variant (entry_json verbatim, checkpoint discriminator for SDK full-body rewrites, agent-stamped entry_seq) on the delivered-or-erred, idempotency-keyed PostConversationFrame lane — awaited per-path for per-session send order; the local SessionManager is ephemeral and dies with the container. Writer fencing is force-teardown (primary) + the durable lane’s idempotency keys and entry_seq (belt); the S3 epoch-token fence is retired with the object model | Superseded by DL-089 (Matt, 2026-07-31) | session persistence §Appendix (collapse) |
| DL-086 | Resume is replay-driven: StartAgentSession{resume_session_id} (authz-gated, no storage locator on any message) makes the Server push its persisted transcript down the existing SessionsResponse.deliver_control/DispatchControl relay as TranscriptReplay ops consumed by appendMessage, gated by the Runner replay barrier until ReplayComplete/ReplayCompleteAck; the TranscriptReplay + TranscriptEntry payload shapes are co-ratified with SEA-1310 and BLOCK resume (inverting DL-066’s independence), and the replay-path admission bound is in-scope, enforced at the retention-cap site on the SEA-1569 RESOURCE_EXHAUSTED refuse-and-redeliver model | Superseded by DL-087 (Matt, 2026-07-31) | session persistence §Appendix (collapse) |
| DL-087 | Resume is file-materialized + SDK-native: on StartAgentSession{resume_session_id} (authz-gated, no storage locator on any message) the Server reconstructs the post-supersession session-JSONL body from its transcript store (latest checkpoint body verbatim + later delta lines) and hands it to the Runner on a TOP-LEVEL internal SessionsResponse sibling field (outside the command oneof, NOT inside the verbatim public start request); the Runner writes it into the new container’s session dir at PROVISION time via the provision-exec family, before the agent is exec’d; the agent loads it through the SDK’s own setSessionFile/loadEntriesFromFile seam. No control-lane replay, no TranscriptReplay payload, no replay barrier or admission bound, and no SEA-1310 dependency — the co-ratification is retracted (peer-contract relief). Inline-image blobs are out of MVP scope (SEA-1582). | Active (Matt, 2026-07-31) | session persistence §Appendix (collapse) |
| DL-088 | The durable session log and the live client trace are two projections of ONE canonical artifact — the SDK session-entry log: the agent emits committed entries, the Server persists them AND projects the block-level trace from the same store. MVP runs two agent→server streams (durable settled-entry log + ephemeral, non-persisted live per-token trace — token streaming is mandatory); collapsing them into one token stream carrying log metadata is the post-MVP follow-up SEA-1580 | Active (Matt, 2026-07-31) | session persistence §One canonical artifact |
| DL-089 | The agent persists nothing DURABLE: the injected backend is a TEE — it reads and writes the container-local EPHEMERAL session file normally (so the SDK’s own loader, compaction, and rewrites work) and tees each committed write upstream as a durable TranscriptEntry frame (append→delta, writeFull→checkpoint, awaited per-path for per-session send order; agent-stamped per-lifetime entry_seq, server-rebased to session scope). Zero S3 credentials, zero storage detail; the local file dies with the container. A definitively-erred emit buffers under a bounded cap with escalating warn→error retry and FAILS THE SESSION at cap exhaustion (resumable from the last committed prefix; cap value is tuning, not freeze-scope) | Active (Matt, 2026-07-31) | session persistence §The SDK seam |
| DL-093 | The Server’s durable session-transcript store is TWO-TIER: a Postgres hot-tail (agent_session_transcript_entries) holding [latest checkpoint..now] = the resume set, per-frame durable at the CommitConversationFrame seam and pruned at flush; superseded entries flush to an S3-compatible object-store cold archive (verbatim JSONL segments, PUT-before-prune, indexed by an agent_session_archive_segments manifest) at compaction (primary), a high size-cap safety valve, and session-end. Resume reads PG-only (S3 fallback only when the safety valve fired); the archive feeds the post-MVP opt-in analytics layer. Agent+Runner hold zero storage detail; the object-store client/credential is Server-internal (endpoint-agnostic: Garage/R2/MinIO). Realigns with DL-019 | Active (Matt, 2026-07-31) | session persistence §T4 |
| DL-161 | The board-ingestion forge poll is a ctx-cancellable driver in internal/ingest (immediate first pass then time.Ticker, sequential per-repo, log-and-continue on per-repo error, nil on ctx cancel) that instantiates the DL-053 FETCH-cursor model at repo-LIST granularity: a new forge_list_cursors table (per (provider, host, repo, page) ETag + hasNext) whose page cursor advances ONLY after every issue on that page durably sinks, so the board holds no per-subscriber DELIVERY cursor (its notify is the synchronous in-pass projection sink). One hand-rolled net/http GitHub read client (OQ-6 frozen: no new dep), a fail-fast x-ratelimit-* budget gate (never in-client sleep), and a DL-052 server_only token resolved behind a short TTL. Builds the FETCH half of DL-053; PR-C (DL-129 tracker-status) and the agent-notification slice both ride it | Active (Matt, 2026-08-08) | forge-poll driver §Approach |
| DL-162 | The board’s poll targets are durable TABLE rows, not config: a new forge_repo_subscriptions table keyed (forge_provider, forge_host, repo) with an enabled soft-disable flag, enumerated WHERE enabled at the top of every pass so add/remove is a live row op needing no restart or deploy. The --forge-repos CLI flag becomes a declarative boot SEED reconciled into the table ON CONFLICT DO NOTHING (bootstrap-only insert: the table is authoritative after first insert, a soft-disabled row stays disabled across restarts, the seed never deletes/disables/re-enables an existing row); a --forge-poll bool runs the driver with an empty seed. The dynamic add/remove mutation RPC/admin surface is a named non-goal of this slice | Active (Matt, 2026-08-08) | forge-poll driver §OQ-C |
| DL-163 | Migration 0015_forge_subscriptions.sql lands FOUR tables unconditionally — forge_repo_subscriptions (DL-162), forge_list_cursors (DL-161), and the two DL-053-spec’d tables agent_forge_subscriptions (DL-053’s forge_subscriptions, RENAMED to disambiguate the per-artifact agent-owned subscription from the board’s per-repo target) and forge_artifact_cursors (both writer-less this slice, shape frozen + pgtest-covered before their writers) — every key coordinate-aligned to the 0013 issue convention (SMALLINT provider + forge_host) and every provider CHECK admitting the full declared proto enum IN (1, 2, 3, 4) (the CHECK’s job is “never UNSPECIFIED(0)”, not gating rollout; rollout is gated by which forge.Provider has a real client — GitHub only this slice). The issues CHECK IN (1, 2, 3) widening for Linear ingestion is a separate future-slice prerequisite (OQ-E) | Active (Matt, 2026-08-08) | forge-poll driver §OQ-D |
Note (2026-07-31, SEA-1570 R5): DL-065’s retired internal
ResumeContext resume = 12envelope field is NOT silently reintroduced — the collapsed model’s resume body rides a NEW internalResumeBodycarrier on a FRESH internal tag (never the freed= 12slot); see the session-persistence record’s T1/T6 and its F8 appendix note.
Agent runtime & container
Section titled “Agent runtime & container”| ID | Decision | Status | Record |
|---|---|---|---|
| DL-023 | The in-container agent is a first-party program on the OMP SDK, emitting compass.v1 natively | Active (Matt, 2026-07-06) | v0.6 §Agent runtime |
| DL-024 | Each agent runs in a per-agent container on the Runner for blast-radius isolation, not credential avoidance | Active (Matt, 2026-07-06) | v0.6 §Per-agent container isolation |
| DL-025 | Container base image is self-contained; the agent manages its own devenv | Active (Matt, 2026-07-15) | agent container runtime §Decision 1 |
| DL-026 | Secrets are a Server-side SecretSpec + keyring store with inject-all, no repo manifest | Active (Matt, 2026-07-15) | agent container runtime §Decision 2 |
| DL-027 | No in-container supervisor for the MVP | Active (Matt, 2026-07-15) | agent container runtime §Decision 4 |
| DL-047 | The agent is integrated via ACP/BYOA over stdio, with OMP as the external default over ACP and other agents as BYOA citizens | Superseded by DL-023 (Matt, 2026-07-06) | v0.4 §OMP is the default |
| DL-078 | Agent config (skills/extensions/MCP configs) is declared into a Server-side FLEET-WIDE bundle store (one bundle all agents get) via new operator-scoped CompassService RPCs, never pushed as provision-request fields; the recommended MVP authoring workflow is a version-controlled config repo published via a CI compass config put step; persona/role-keyed bundles and native GitOps pull (Server reconciles a config repo) are the named post-MVP seams | Active (Matt, 2026-07-30) | config delivery §Decision CD-1 |
| DL-079 | Config carriage is Runner-fetch — a server-STREAMING FetchAgentConfig RPC + a ConfigVersion signal on the Sessions response stream (signal-then-pull over the frozen dial-out inversion; no inbound Server→Runner route) | Active (Matt, 2026-07-30) | config delivery §Decision CD-2 |
| DL-080 | Config injection is a Runner-local read-only bind mount of the parent config dir at /run/compass/agent-config (agent reads through current/); env-vars ride SEA-1327’s secret surface exclusively; MVP forbids credentials in MCP configs (SEA-1576 tracks the post-MVP credentialed path) | Active (Matt, 2026-07-30) | config delivery §Decision CD-3 |
| DL-081 | The MVP config-update path is re-materialize + in-place agent Reload (the exec-driven model), reserving throwaway-container restart for image changes; live structured injection (ConfigControl/SEA-1310) is the named future seam | Active (Matt, 2026-07-30) | config delivery §Decision CD-4 |
| DL-123 | Fleet OMP settings/config.yml are delivered whole-file (yml-only, no key-merge) as a bundle member, injected at createAgentSession as a Settings.loadIsolated settingsManager overlay with overlay-over-project precedence — extending the CD-1 bundle store with a settings category | Active (Matt, 2026-08-03) | config passthrough §CP-1 |
| DL-124 | The fleet AGENTS.md is a top-level bundle member injected at createAgentSession as a composed contextFiles global (fleet-first, the entrypoint re-runs project discovery), never workspace-placement — extending the CD family with an agent-context category | Active (Matt, 2026-08-03) | config passthrough §CP-2 |
| DL-125 | The complete OMP config-dir surface is enumerated with a per-category deliver/defer table; the MVP delivers every category the wave populates (the rest are named defer seams), fixing the config-passthrough scope boundary | Active (Matt, 2026-08-03) | config passthrough §CP-3 |
| DL-126 | Agent-dir-anchored categories (rules/, agents/, models.yml) are delivered per SDK seam — rules/ as a composed rules: Rule[] object injection, agents/ and models.yml via user-level symlinks; the CD-3 server-door credential denylist extends to models.yml BOTH credential surfaces — providers.<name>.apiKey AND providers.<name>.headers.* literal secrets (env-indirection references pass; non-env-indirection literals are rejected with a client warning), because header values materialize outbound value-first | Active (Matt, 2026-08-03) | config passthrough §CP-4 |
Comms & tools
Section titled “Comms & tools”| ID | Decision | Status | Record |
|---|---|---|---|
| DL-028 | The MVP agent comms toolset is two native tools (comms_post_message, comms_list_messages); no ask-answering capability | Active (Matt, 2026-07-22) | agent comms tools §Tool set |
| DL-029 | Agent comms identity/authz is session-resolved server-side, defaulting to the home channel | Active (Matt, 2026-07-22) | agent comms tools §Identity / authz |
| DL-048 | The Server is the ownership layer: agents reach Issues/Chat/PRs only through the Server, which stamps a machine-parseable author/owner header — replacing per-agent forge accounts (Forgejo/Plane deprioritised) | Active (Matt, 2026-07-27) | ownership layer §Approach |
| DL-049 | Forge tools ride the existing AgentGateway socket as a sibling ForgeCall* family relayed by RelayForgeCall, not a CommsCallRequest extension and not a second channel | Active (Matt, 2026-07-27) | ownership layer §Decision 1 |
| DL-050 | The compass:owner header is a top-placed HTML comment + rendered line, stamped at one chokepoint (forge.StampOwner), idempotent, unforgeable ON WRITE; a header PARSED from forge text is untrusted display metadata that may not reach any authz/routing/ownership decision | Active (Matt, 2026-07-27) | ownership layer §Decision 2 |
| DL-051 | The forge adapter is go/internal/forge behind a swappable Provider interface (GitHub first, Linear issues-only), wired as a runnerhub sink | Active (Matt, 2026-07-27) | ownership layer §Decision 3 |
| DL-054 | Notifications v1 is chat pings plus asks on the existing comms surfaces; no notifications page, centre, badge, or read state | Active (Matt, 2026-07-27) | ownership layer §Decision 6 |
| DL-071 | Channel→agent delivery is a Server-side comms-bus consumer dispatching deliver/steer ops through the RunnerHub over a generic SessionsResponse.DispatchControl relay variant onto the built control lane; agent-authored (streaming) messages are held and delivered at the author’s turn-settle edge (WORKING → READY/IDLE), human-authored at post (OQ-7, Matt 2026-07-29) | Active (Matt, 2026-07-29) | notification delivery §D1 |
| DL-072 | The durable delivery cursor is Server-owned: a contiguous low-water cursor plus a bounded above-cursor set per (agent_account_id, channel_id) on messages.seq (mirroring ControlAck’s acked_seq + applied_above), reconstructed from per-message acks, swept gap-aware on session start/reconnect; the per-(agent, channel) keying is ratified (Matt, 2026-07-29), amending RT-3’s “per-session” wording | Active (Matt, 2026-07-29) | notification delivery §D2 |
| DL-073 | DeliverControl (and channel-borne SteerControl) carry the full first-party compass.v1.Message and nothing else (no channel_seq); the agent acks per message via AgentFrame.delivery_ack { message_id } — the frozen ack shape | Active (Matt, 2026-07-29) | notification delivery §D3 |
| DL-074 | MVP presence is 4-state, Cotal-aligned (WORKING/IDLE/WAITING/OFFLINE): WORKING/IDLE/OFFLINE derived from the agent-session lifecycle, WAITING a server-side unanswered-ask overlay (an authored Ask with Ask.answered=false in a visible channel; WAITING > IDLE), no heartbeat, published as an additive SubscribeCommsResponse variant visible to actors sharing at least one visible channel with the agent, in-memory only with re-enroll reconciliation | Active (Matt, 2026-07-29) | notification delivery §D4 |
| DL-075 | Agent-facing spawn/despawn is owned by the spawning agent’s owner (the human): all wave agents share AgentAccount.OwnerUserID, and despawn authority is same-owner-scoped for the MVP (finer caller-scoped authz deferred to SEA-1573) | Active (Matt, 2026-07-29) | spawn/despawn §Identity and authz |
| DL-076 | Spawn/despawn rides a sibling LifecycleCall* family on the existing AgentGateway socket relayed by RelayLifecycleCall (mirroring DL-049), orchestrated server-side via a LifecycleCaller seam while the runnerhub resolves caller identity only; there is no public agent-callable spawn RPC (the egress-sealed agent holds no server token) | Active (Matt, 2026-07-29) | spawn/despawn §Seam (a) |
| DL-077 | Despawn stops the live session and calls a NEW public RemoveAgentWorkspace RPC (compass.proto, deliberately outside the SEA-1267 gen-fence); the agent account persists (teardown is compute-only). The MVP accepts the Runner’s serial-dispatch head-of-line blocking, guarded by a bounded relay deadline as the sole guard with no spawn rate cap (limits → SEA-1574, concurrent dispatch → SEA-1575) | Active (Matt, 2026-07-29) | spawn/despawn §Seam (b) |
| DL-090 | Repo/auto-clone is removed from every agent-start contract: neither SpawnPeerRequest nor ProvisionAgentWorkspaceRequest carries repo/ref; agents self-clone after launch with the container’s scoped git credential (post-MVP: a clone tool so agents hold no creds). Resolves OQ-4 (Workspace.Source optional->removed). | Active (Matt, 2026-07-29) | spawn/despawn §Spawn carries no repo |
| DL-092 | #995’s forge-shaped proto domain family (Issue/IssueComment/PullRequest/ForgeAuthor/ForgeChecksSummary/ForgeCheck in agent_gateway.proto) is not built: per DL-069 the forge Provider layer’s value types stay internal Go data at the ingestion boundary, the ForgeCall* carrier family survives with its result arms retyped to the canonical compass.v1 types, and AgentAttribution supersedes ForgeAuthor (closing #995 OQ-1’s field-set gate) | Active (Matt, 2026-07-31) | ownership amendment §What this amendment asserts |
| DL-135 | Agent roster is a pull: a public CommsService.GetRoster (account-visibility-scoped via accountVisibleFromWhere, tree-derived scopes NEIGHBORHOOD/SUBTREE/OWNER — OWNER ratified fleet-wide under the shared-owner model, activity strings fleet-visible) plus a native compass_roster tool on the CommsCallRequest relay family, reading the DL-074 in-memory presence enum joined with the agent tree; the activity string is DURABLE (its own agent_activity store table), recovered from Postgres on Server restart — agent-set via compass_set_status (write-through to the table + additive AgentPresenceChanged.activity), re-published by the agent-side harness on session (re)attach for freshness — a deliberate divergence from DL-074’s in-memory posture for the STRING; the presence enum stays in-memory. Presence EVENTS keep the shared-channel visibility rule while the roster uses account visibility (divergence stated: an owner sharing no channel with its agent gets snapshot-only presence). SEA-1721 | Active (Matt, 2026-07-31) | manager comms substrate |
| DL-136 | Channel post authority and forced subscription are channel-level policy fields (post_policy OPEN/OWNER_ONLY + owner_account_id + mandatory_subscription), never a ChannelKind or a visibility overload; mandatory_subscription is enforced read-side as a third disjunct in D1’s subscriber resolution; an owner/operator SetChannelPolicy (create-or-update) is the only post-creation mutation path, its txn seeding delivery cursors for every member a mandatory flip newly targets. A Manager’s coordination channel is auto-provisioned on first report gained via a reconciler hooked at the two store-level writers of agent_accounts.parent_agent_id (store.CreateAgent, store.ReparentAgent) — covering spawn, public CreateAgent, reparent, and any future writer by construction; resume on name-collision is ownership-checked (never adopts a user-created channel); membership reconciles with tree edges; dormant channels are never deleted (accepted MVP accretion). The coordination channel is OWNER_ONLY + mandatory-subscribe — a one-way manager→reports directive surface; report→manager and lateral coordination flows through DMs/group DMs, and agents at every level heavily prefer direct DMs/small targeted group DMs to keep coordination-token-cost low (standing directive, Matt). Precursor primitive to SEA-1622, not part of it. SEA-1722 | Active (Matt, 2026-07-31) | manager comms substrate |
| DL-137 | The pinned board is a server-side per-channel capped ordered POINTER set over existing topic-scoped messages: pinning references an existing message_id (validated to a topic of the channel, join through topics) and creates no Message, so DL-099’s single-write-path stands; edit = a normal topic-mandatory post + compare-and-swap repoint (redelivery is ordinary DL-071/072/073 delivery of a new message id, dedup-safe); board txns serialize on channels … FOR UPDATE (cap + repoint races); startup delivery is a session-start pin sweep beside the D2 cursor sweep dispatching current pins regardless of cursor position; edits land at turn-settle (never steer); explicitly distinct from DL-096’s client-local sidebar pins, and compatible with DL-054 (no centre/badge/read state). SEA-1723 | Active (Matt, 2026-07-31) | manager comms substrate |
| DL-182 | The forge notification/carrier types that AgentControl.forge_notification reaches (ForgeNotification/ForgeNotificationKind, ForgeArtifactKind, CommentRef) live in a new internal-only leaf proto/compass/v1/forge.proto (imports only compass.proto), NOT in agent_gateway.proto as #995 T1 / this record’s original §A1 add-list directed: that placement is a circular import (agent_gateway.proto already imports agent.proto, so agent.proto referencing an agent_gateway.proto type is a cycle buf rejects). The ForgeCall* carrier + op requests stay in agent_gateway.proto; the leaf is imported by agent.proto/agent_gateway.proto/runner.proto acyclically. Wire contract, field numbers, and generated names unchanged — placement only | Active (Matt, 2026-08-05) | ownership amendment §A1 |
Agent roles & prompts
Section titled “Agent roles & prompts”| ID | Decision | Status | Record |
|---|---|---|---|
| DL-138 | A Compass role’s system prompt REPLACES OMP’s block 0 via the fork’s customSystemPrompt path (custom-system-prompt.md), which keeps auto-injected skills/rules/always-apply lists and the project/env footer by construction — append is rejected (it pays for both prose sets every turn; the skills-loss fear was the fixed #3014 pre-fix behavior) | Active (Matt, 2026-08-04) | manager prompt §MP-1 |
| DL-130 | Compass OWNS both role prompts: the Manager block-0 is authored fresh (~350 words, coordinator-shaped, OMP as style reference only) and the implementer block-0 is a copy-and-adapt of OMP’s full block 0 — an owned file diverging where Compass differs, never an inherited prompt | Active (Matt, 2026-08-04) | manager prompt §MP-2 |
| DL-131 | “Manager” is an agent ROLE label in prompts/docs/UI/marketing; proto/Go/UI data types stay Agent/AgentAccount (no rename); the role mechanism itself (role field, per-role bundles, per-role model/thinking) is SEA-1724 (Beta), forward-referenced only | Active (Matt, 2026-08-04) | manager prompt §MP-3 |
| DL-132 | Role prompts version v0→target: the frozen text is the target contract, v0 ships naming only primitives that exist at the shipped commit, and every unshipped affordance is an explicit [TODO <issue>] line flipped on in the same PR that lands its primitive — updates ride the existing bundle re-materialize + Reload path (DL-081), never new delivery | Active (Matt, 2026-08-04) | manager prompt §MP-4 |
| DL-133 | The Manager content layer split is frozen: irreducible operating model (identity, tree position, async comms, work loop, human merge gate, compaction) in block 0; invariants as always-apply rules (never-block/own-your-issue/red-green-testing adapted, never-merge/design-first/compact-often new); procedure in on-demand skills (comms-playbook, management-trees, compass-setup, supervisor-channel, manager-coordination-channel, jj-stacking/review/design adapted) — with the name-Managers-by-FUNCTION-never-tool tenet | Active (Matt, 2026-08-04) | manager prompt §The layer split |
| DL-134 | Dogfood implementers are IN-PROCESS SUBAGENTS the Manager briefs via the live task mechanism (the Manager authors each subagent’s brief — task + context — at spawn time and names a standing subagent def whose body supplies block-0; there is no per-spawn systemPrompt param); the implementer block-0 is therefore an ACTIVE Dogfood deliverable, shipping as a fleet-delivered subagent def under config/agents/ (body = AgentDefinition.systemPrompt), consumed by task + discoverAgents at cf048ca with no cli.ts wiring; agents_spawn_peer/agents_despawn_peer are reserved for standing peer/child Managers; per-implementer containers are rejected for Dogfood (container-per-subagent too heavy) and gate on the SEA-1717 brain/hands split (Beta, distinct from SEA-1724’s role mechanism), which alone defers the containerized non-subagent implementer path (not the prompt) | Active (Matt, 2026-08-04) | manager prompt §MP-5 |
| DL-139 | The task-subagent prompt seam is ADDITIVE, not a replace: a subagent def body is spliced INTO the full default OMP block-0 (executor.ts:2808-2810, [...defaultPrompt.slice(0,-1), subagentPrompt, defaultPrompt.at(-1)]), which co-renders around it (identity, Tool Policy, Delivery Contract, Internal URLs, Delegation), and the def body renders VERBATIM with no injector (task/types.ts:362, systemPrompt: string). So the Dogfood implementer config/agents/implementer.md is a THIN ROLE DELTA — identity + only the Compass divergences the default gets wrong (async comms/no ask, one-slice-then-yield, jj-stacking push, operator-not-user) — dropping everything the default supplies and carrying no [runtime-injected] placeholders and no lsp/todo mandates (task.enableLsp defaults false, settings-schema.ts:4528; the wrapper COMPLETION already sets no-todo); the full copy-and-adapt of OMP’s block-0 is kept as the FUTURE SEA-1717 standalone-container artifact (a REPLACE path absent at cf048ca), not the Dogfood deliverable. Corrects DL-134’s “block-0” framing (the def is the delta, not the full block) | Active (Matt, 2026-08-05) | manager prompt §Implementer block-0 |
UI shell
Section titled “UI shell”| ID | Decision | Status | Record |
|---|---|---|---|
| DL-030 | UI: the communication layer is the primary surface (comms-primary shell) | Superseded by DL-031 (Matt, 2026-07-06) | v0.6 §T7 |
| DL-031 | UI shell is board-primary: channel chat lives inside the board-primary workspace, not as the top-level surface | Active (Matt, 2026-07-12) | v0.7 §Approach |
| DL-032 | Compass state is canonical; the tracker is a projection of it | Superseded by DL-129 (Matt, 2026-08-04) | ADE shell §D2 |
| DL-033 | The workstream lifecycle is Backlog→Todo→Queued→Blocked⇄In Progress→In Review→Done | Active (Matt, 2026-07-10) | ADE shell §D1 |
| DL-034 | The right sidebar mirrors Orca; fleet + workstream conversations live there | Active (Matt, 2026-07-10) | ADE shell §D5 |
| DL-035 | The bottom dock is a tabbed Dispatcher + Warden surface | Superseded by DL-036 (Matt, 2026-07-11) | ADE shell §D7 |
| DL-036 | The bottom dock is folded into the right sidebar: one activity bar, one signal, the dock removed | Active (Matt, 2026-07-11) | dock-in-sidebar §D1 |
| DL-037 | Standalone channel asks are answerable wherever they are asked (first-responder-wins), not read-only-deferred | Active (Matt, 2026-07-20) | ask-in-channel §The designed change |
| DL-067 | The workstream concept is removed from the Compass UI entirely; the board unit becomes the canonical Compass Issue (and PullRequest) — the forge artifact’s fields plus the Compass machinery (lifecycle state, priority, assignee, tracker projection), server-computed | Active (Matt, 2026-07-30) | issue model §Approach |
| DL-068 | Agent attribution on issues and PRs is consumed from the canonical type’s server-parsed agent/owner fields (parsed at ingestion per #995’s stamping) and rendered as an untrusted claim, hedged unless the server’s forge-login cross-check verifies it (DL-050, #995 OQ-1); the UI never stamps a header and never derives assignee (Compass truth) from agent (a parsed claim) | Superseded by DL-094 (Matt, 2026-08-01) | issue model §Attribution |
| DL-069 | Compass owns a single canonical compass.v1 Issue/PullRequest type pair (forge fields + agent author/owner attribution + Compass machinery); the server translates raw forge data into these types at ingestion, the raw forge shape is never a proto/wire type, and the UI consumes only the generated canonical type from @compass/client | Active (Matt, 2026-07-31) | issue model §The Compass Issue and PullRequest types |
| DL-070 | The DL-033 issue lifecycle (its seven working states unchanged, extended by a terminal ARCHIVED state per DL-091) is server-authoritative: a server-side board projection — composing the existing go/internal/board recorded-state pattern and DL-055’s board-as-local-index-scan — computes and streams the canonical type, moving DL-032’s canonical Compass state server-side | Active (Matt, 2026-07-30) | issue model §The server projection |
| DL-091 | Archiving a Compass issue is a lifecycle transition to a terminal ARCHIVED state via UpdateIssueState, not a separate archived_at marker field or a separate ArchiveIssue RPC; an archived issue drops off the active board and is listed in the Done view’s Archived section, and every board item carries a forge identity (ForgeRef provider+host) so multi-forge artifacts never collide on repo alone | Superseded by DL-129 (Matt, 2026-08-04) | issue model §The write path |
| DL-094 | Compass agent attribution is a plain display fact, not a trust claim: AgentAttribution slims to {agent_handle} (field numbers 2–3 and names owner_handle/verified reserved), populated directly from the parsed owner header at ingestion with no forge-login cross-check and no population gating; the board and the forge-tool path both render the bare @handle with no hedge; owner is an account property (AgentAccount.owner_user_id), resolved server-side, never restated per artifact. Reverses DL-068’s hedge-unless-verified board render and the verified/cross-check of DL-050 and #995 OQ-1 as applied to attribution display; routes around #1018/#1037 (unedited; no wire build shipped) | Superseded by DL-186 (Matt, 2026-08-07) | attribution simplification §Approach |
| DL-096 | Sidebar pinning is a user-configurable presentation layer over the agent set (the layer DL-095 names and defers): any agent can be pinned as a right-sidebar fleet tab, the hardcoded always-on Supervisor/Warden tabs and their FleetTab union literals are removed (no built-in privileged agent), the pin set is a per-user client-local UI preference (localStorage-backed; never DL-078’s fleet-wide bundle; server per-user prefs a post-MVP seam), the default pin set is EMPTY, and the shell boots onto the first pin that resolves to a visible agent, else Status, instead of a special-cased Supervisor conversation | Active (Matt, 2026-08-01) | sidebar pins §Approach |
| DL-097 | The Bridge board splits into peer Issues/PRs tabs inside the board view (a Bridge-local tab signal, not a new View or a third BoardMode): the Issues tab is today’s board ordered by Record C’s treeOrder; the PRs tab is a flat one-row-per-OPEN-PR list (the primaryPr compression deliberately does not apply) grouped by assignee in treeOrder with the tab badge = the open-PR row count; issues ↔ PRs cross-link through the existing issue-keyed selection (selectIssue, no new PR-selection state); and the card’s per-check pip strip collapses to a CI badge (the ChecksSummary.state roll-up) + a review badge (latest-per-author verdict, changes_requested > approved > commented) as pure board-render.ts helpers — a VIEW remodel consuming the frozen #1018 model, no proto/server/model change | Active (Matt, 2026-08-01) | board view §Approach |
| DL-129 | The issue-state write model is agent-primary: the Compass UI is read-only for lifecycle state; agents set state via a sibling BoardCall* family on the AgentGateway socket relayed by RelayBoardCall (DL-049/DL-076 convention), and users set state in the external tracker, whose native status is ingested into the DL-070 server projection through the reverse TrackerStatusMapping (DL-053 poll; echo-suppressed in tracker-status space, tracker-sourced transitions never mirror back, stale polls dropped by a recency guard). All issues flow from the tracker (no Compass-native/unlinked issues in the MVP); ARCHIVED is not agent-only (a Done issue auto-transitions to ARCHIVED after 24h, a user reopen un-archives via ingestion); MVP write-authz is single-trust-domain (hierarchical scope is a filed follow-up over DL-095’s tree). UpdateIssueState on CompassService is never built (no wire build shipped). Supersedes DL-091 (the UpdateIssueState-on-CompassService mechanism; archive-as-transition survives here) and DL-032 (the tracker is now also a state write source); DL-070/DL-033/DL-067 unchanged | Active (Matt, 2026-08-04) | agent-primary lifecycle §Approach |
| DL-113 | An unresolvable pinned agent does not vanish: the pin keeps its activity-bar item, its pane renders an “agent unreachable” state, and removal is a manual unpin only — a visibility fluctuation to unresolvable never auto-switches the active tab to status (the unpin-active→status fallback is retained, and the pane arm closes the Switch’s no-default-arm gap) — amending DL-096’s unresolvable-pin handling (the frozen record’s §T2 filter and §T3 fluctuation-coercion clauses are superseded; DL-096’s pinning-layer core stands) | Active (Matt, 2026-08-02) | unreachable-pin amendment §Approach |
| DL-127 | The Compass UI shell adopts @solidjs/router in HashRouter mode (Matt’s library ruling) emitting the frozen #/channel/<channelId>/topic/<topicId> route shape for a client-only SPA in the Wails v3 webview (DL-110); and the URL becomes the source of truth for view + routed selection — the store derives View and routed memos from the matched route (route drives store, not store drives route), with MemoryRouter for deterministic tests | Active (Matt, 2026-08-03) | shell routing §A2 |
| DL-128 | The Compass UI adopts @tanstack/solid-query + @connectrpc/connect-query-core for server-state: one app-lifetime QueryClient created beside the store singleton in index.tsx and provided via the SolidJS QueryClientProvider (Solid adapter, never React); server-state reads move off the store onto query hooks over the generated Connect clients, the store keeping only client/UI state | Active (Matt, 2026-08-03) | query layer §Approach |
| DL-186 | Pre-dogfood proto wire-compat is stripped: all reserved markers removed across compass/v1, live fields densely renumbered (no holes), and the buf breaking CI gate removed (re-armed at GA / first pinned client). Reclaims AgentAttribution field numbers 2,3 (DL-094’s forever-reservation) — DL-094’s attribution-as-display-fact semantics are UNCHANGED (owner resolved on AgentAccount.owner_user_id, not per-artifact); only its wire-number reservation is dropped, safe because zero clients are deployed and nothing on disk is proto-encoded. | Active (Matt, 2026-08-07) | attribution simplification §Approach |
| DL-193 | The Compass UI’s live agent roster is a reactive JOIN, not a GetRoster-as-roster replace: durable identity + tree come from the already-live SubscribeComms accounts (the single identity source — RosterEntry lacks homeChannelId/kind the UI requires), while ephemeral presence + activity come from a new CommsState.presence map seeded by one GetRoster(scope: OWNER) per snapshot boundary and tailed by the AgentPresenceChanged event the stream driver currently drops; STUB_AGENTS is demoted from live render source to the offline-store seed behind the store’s reactive agents accessor (the STUB_ISSUES seam pattern), never rendered on a live connection | Active (Matt, 2026-08-16) | live roster §Resolved decisions |
| DL-194 | The presence→dot projection is the total 4-state mapping WORKING→working, IDLE→idle, WAITING→waiting, OFFLINE→stopped plus a defensive UNSPECIFIED→undefined (unreachable on the GetRoster path, which defaults every agent absent from its in-memory presence source to OFFLINE; the client joinAgents mirrors this at its own seam, mapping a presence-map miss — a snapshot-boundary race or a post-snapshot accountChanged arrival — to stopped rather than the components’ ?? "idle" fallback, so the absent→stopped invariant holds end-to-end); the 4-state enum cannot distinguish a deliberately-stopped agent from a never-started one, so a freshly-seeded-but-unstarted agent renders the “stopped” dot — accepted as a known day-one cost, with the terminated/never-started split owed to the deferred AgentSessionStatus lane, which is also the only source for the remaining four AgentState values (done/paused/error/disconnected) | Active (Matt, 2026-08-16) | live roster §Resolved decisions |
| DL-195 | Agent.role/model/cwd are optional view-model fields with no server source: a live agent renders without them (the role pip is render-gated on a present, non-worker role — never derived from tree position, since “has children” is not “supervisor”), and terminals is [] for live agents until a terminal stream exists | Active (Matt, 2026-08-16) | live roster §Resolved decisions |
Threading & rendering
Section titled “Threading & rendering”| ID | Decision | Status | Record |
|---|---|---|---|
| DL-038 | The session trace is a dedicated OMP-native opaque session stream, rendered by OMP’s own renderer | Superseded by DL-039 (Matt, 2026-07-20) | v0.6 §Approach |
| DL-039 | Compass builds a first-party typed session renderer; session events cross a typed gRPC stream, not opaque bytes and explicitly not ACP | Active (Matt, 2026-07-20) | v0.8 §Change 2 |
| DL-040 | Threaded replies use a Slack-style side-thread panel keyed by a deterministic root id | Superseded by DL-098 (Matt, 2026-08-02) | v0.8 §Change 1 |
| DL-041 | The message surface is a virtualized thread list with streaming markdown (solid-markdown + Shiki), mention-first composition | Superseded by DL-098 (Matt, 2026-08-02) | message surface rendering §Approach |
| DL-098 | Compass adopts Zulip’s threading model: a channel is a collection of named topics and every message belongs to exactly one topic (messages.topic_id NOT NULL) and stores only that topic id — never a channel id; the channel is reached through topics.channel_id (so moving a topic between channels is a single-row update). parent_message_id and the message’s channel container are removed outright (pre-dogfood: no reserved tombstone, field numbers reused). Topics are first-class (proto Topic + topics table), named, id-stable, in exactly one channel; posting is topic-mandatory via topic_name get-or-create (one call); there is no default, general, or catch-all topic anywhere — channels carry zero messages directly. Rename-to-existing merges topics; the delivery cursor stays per-(agent,channel) with topic as deliver-op metadata, its channel resolved through the topic join (clarifies DL-072). The pre-dogfood SQL migrations collapse to a single baseline (no data conversion). UI is Zulip two-level drill-in: a channel shows its topic index (no composer), a topic shows its messages + composer (no nested threading), the left sidebar lists a channel’s recent topics. Reverses DL-040 (Slack side-thread panel) and DL-041 (virtualized thread list, re-pointed at the topic list) | Active (Matt, 2026-08-02) | Zulip threading model §Decisions |
| DL-099 | An agent’s streamed turn does not write to comms at all: the SEA-1364 T3 conversation write-through (streamed-turn → home-channel comms auto-post) is removed. A comms Message is created only by an explicit comms_post_message(topic) call (agents) or the human client’s PostMessage — the comms tool is the only agent comms-write path, and it carries a mandatory topic. The streamed turn survives in full on the session/trace surface (SessionFrame.typed_event). Structural enforcement of on-topic posting: an agent cannot flood a channel by talking, only by deliberately posting | Active (Matt, 2026-08-02) | Zulip threading model §D7 |
Ask contract
Section titled “Ask contract”| ID | Decision | Status | Record |
|---|---|---|---|
| DL-042 | The UI pivots around the communication layer; the frozen single-question compass.v1.Ask is D5’s contract child | Superseded by DL-043 (Matt, 2026-07-04) | v0.5 §D5 |
| DL-043 | Reshape compass.v1.Ask to repeated AskQuestion (Option A, buf-breaking), carrying all native axes + free-text | Active (Matt, 2026-07-15) | ask typed derivation §Approach |
Desktop shell
Section titled “Desktop shell”| ID | Decision | Status | Record |
|---|---|---|---|
| DL-044 | The Tauri desktop shell is a thin webview→daemon shell over a swappable transport seam; the daemon spawns detached and outlives the shell | Superseded by DL-106 (Matt, 2026-08-02) | Tauri shell §Approach |
| DL-106 | The Compass native app is one binary in two modes (embedded / native-client); the mode difference is confined to the connection provider and the stack supervisor, and nothing above the transport boundary assumes local | Active (Matt, 2026-08-02) | native app §A1 |
| DL-107 | One framework-neutral shell IPC contract (compass_rpc + head|body|end|error frames) carries gRPC-Web over a custom fetch for both modes — embedded pumps to the UDS, native-client to the TLS network door with shell-side private-CA trust and a shell-injected bearer | Active (Matt, 2026-08-02) | native app §A2 |
| DL-108 | Embedded lifecycle is a Go stack supervisor (go/internal/stack + compass-stack up|down|status) of spawned children (never in-process), linger-by-default, productizing the dogfood chain with a supervised private Postgres, expiry-aware cert rotation, and lockfiled attach | Active (Matt, 2026-08-02) | native app §A3 |
| DL-109 | Native-app mode selection is $XDG_CONFIG_HOME/compass/app.toml (absent → embedded default); the native-client bearer is entered in a connect screen and stored keychain-first (0600-file fallback), never in config-file or argv | Active (Matt, 2026-08-02) | native app §A4 |
| DL-110 | The Compass native shell is Wails v3 (Go), its module under go/ (go/cmd/compass-app) importing go/internal/stack and the bridge pump directly; re-decides DL-044’s framework on the Go stack (Wails, not Tauri) | Active (Matt, 2026-08-02) | native app §OQ1 |
| DL-111 | A new compass.v1 WhoAmI RPC returns the caller’s account id from its own credential (embedded: socket ambient-admin; native-client: bearer subject), retiring the parked caller_account_id seam and the connect-screen caller-id field | Active (Matt, 2026-08-02) | native app §OQ7 |
| DL-112 | The compass-agent image is published to GHCR and podman pulled by compass-stack at first run (not bundled in the app), making a GHCR publish lane for compass-agent a native-app packaging dependency | Active (Matt, 2026-08-02) | native app §OQ6 |
| DL-183 | Cross-process embedded-stack teardown: up persists each supervised child’s process-group ID plus a start-time identity token to an atomic stack.pgids record beside the lockfile; a fresh down refuses while an up holds the lock, then reads the record, verifies each group’s identity before signaling, SIGTERMs each exact persisted pgid in reverse start order with bounded SIGKILL escalation, and confirms teardown per component (server/postgres socket quiescence, runner group-ESRCH) — scoped to the three supervised stack children (postgres, compass-server, compass-runner); live agent containers escape the process group via conmon double-fork and are OUT of scope (SEA-1884). Refines DL-108 (which stays Active) and realizes the frozen record’s down-SIGTERMs-the-tree claim for the supervised children | Active (Matt, 2026-08-05) | cross-process teardown §Approach |
Agent batteries
Section titled “Agent batteries”| ID | Decision | Status | Record |
|---|---|---|---|
| DL-140 | The default Compass batteries bundle is LANGUAGE-NEUTRAL: no go-* rules or golang-* skills ship by default — for Dogfood the user adds language skills via the normal user-skill path; language packs (per-repo language → skill-pack selection) are the SEA-1739 Beta feature, an additive seam on the same bundle delivery surface | Active (Matt, 2026-08-05) | batteries §BI-3 |
| DL-141 | delegated-implementation ships as NO separate Compass skill: its when-to-delegate litmus, review-every-diff discipline, and brief contract fold into the management-trees skill’s delegation-mechanics section (SEA-1732 T5), with the OMP tier naming (implement/implement-hard, Opus thinking levels) stripped in favor of the task mechanism + standing subagent defs (DL-134) | Active (Matt, 2026-08-05) | batteries §BI-2 |
| DL-142 | The wave version-control rule’s three footguns (auto-amend; submit-is-the-only-push-path; review-fixes-are-additive-never-amend+force-push) fold into ONE Compass always-apply rule that keeps the jj model + additive-fixes + never-git push and keeps jj-vine submit as the push path (Compass KEEPS jj-vine as its stacked-PR tool — submit, PR-tree-in-description, gh stack forge integration — retargeted to the Compass repo; the jj-hp push-guard is Compass’s own push-authorization concern, resolved in SEA-1732 T8); the adapted skill is named jj (Compass names skills on their own merits, not by mirroring the wave) — renaming SEA-1732 T8’s jj-stacking (that frozen record’s jj-stacking text is superseded by this citation per the immutable-cell convention, §conventions); and there is no separate stacking skill (stacking lives in the one adapted jj skill, SEA-1732 T8) | Active (Matt, 2026-08-05) | batteries §BI-2 |
| DL-143 | Eight wave artifacts are EXCLUDED from the Compass batteries as wave-specific infrastructure that would confuse a Compass agent — multi-agent-wave, spawn-agent, wave-status-sync, session-recovery, nix-hosts, github-pr-review (folds into the adapted review), woodpecker-ci (Compass CI = GitHub Actions), zellij-session-safety (Compass agents are session-isolated); exclusion is curation, not deferral — a future need routes through a new decision row | Active (Matt, 2026-08-05) | batteries §BI-4 |
| DL-144 | Every forwarded always-apply rule in the Compass batteries self-scopes by ROLE in its authored body: the task executor forwards parent session rules into every subagent (executor.ts:2793) but NOT the session customTools (executor.ts:2829, MCP-proxy tools only), so a Manager always-apply rule rides every implementer-subagent turn without the Manager affordances it assumes — hold-your-lane, decision-authority, and own-your-issue are actively wrong there. Each such rule’s body branches explicitly (hands-subagent behavior AND Manager behavior); role-invariant rules need no branch. This is body-level self-scoping, NOT per-role delivery (the SEA-1724 seam). Resolves batteries OQ-1 | Active (Matt, 2026-08-05) | batteries §BC-7 |
| DL-145 | The B1 version-control always-apply-rule fold (DL-142) is hard-ordered AFTER SEA-1732 T8: it ships with or after T8, never before, and carries no [TODO T8] placeholder — an always-apply rule injected full-text every turn (BC-6) cannot ship the never git push invariant with only a placeholder where the one correct submit verb goes; T8 names the Compass submit path first, B1 writes it in (same BC-3 posture B5 takes with SEA-1734) | Active (Matt, 2026-08-05) | batteries §Tasks B1 |
| DL-146 | The Compass batteries ship process-safety at RULEBOOK tier (not always-apply as the wave file is): per-container session isolation (one container per standing agent, DL-143) removes the cross-agent/host clobber blast radius that justified alwaysApply: true on the shared wave box; the intra-container sibling-process residual (a Manager and its in-process task subagents share one container) is covered by rulebook forwarding (BC-7) + on-demand pull before a destructive kill, not always-on injection. Resolves batteries OQ-3 | Active (Matt, 2026-08-05) | batteries §Implementer domain rules |
| DL-147 | The default Compass batteries are STACK-neutral, not merely language-neutral: no default battery names a language- or CI-engine-specific command as normative (illustrative tables permitted, marked). devenv/direnv is a deliberate product baseline (kept, B3); GitHub-Actions-specific ci-failure-triage hooks come OUT of the default skill and become SEA-1739 pack territory (same seam shape as a language pack), the default skill shipping the CI-engine-neutral 4-step triage discipline. Refines DL-140, and reverses DL-143’s woodpecker-ci-row parenthetical rationale (Compass CI = GitHub Actions) — that parenthetical stays verbatim in DL-143 per the immutable-cell convention (§conventions), its normative posture superseded here; sharpens BC-4 to a checkable gate | Active (Matt, 2026-08-05) | batteries §BI-3 |
UX foundation (design system)
Section titled “UX foundation (design system)”| ID | Decision | Status | Record |
|---|---|---|---|
| DL-148 | Compass visual language is the frozen Rigel chase-light/dot-matrix spine worn at ADE density: surface-color elevation, blue-rationed interaction contrast (purple mark-only), 4px-grid density, mono-as-identity per the three-face type system, keyboard-answerable everything (D1) | Active (Matt, 2026-08-05) | ux foundation §D1 |
| DL-149 | Three-tier token model: --rigel-* primitives mirrored verbatim from the frozen brand token set (no invented names, no violet) → --cx-* semantic tier → scale tokens; one downward consumption rule (component CSS names only --cx-*, so the brand motion primitives are aliased into --cx-motion-slow/--cx-ease-*/--cx-pulse-*/--cx-stream-char-ms/--cx-cursor-blink); [data-theme] indirection keeping the light pair open; agent-state colors per brand (working #addb67 green, done cyan, waiting/disconnected amber, idle/paused/stopped mute); issue lanes remapped off the dead violet (D2) | Active (Matt, 2026-08-05) | ux foundation §D2 |
| DL-150 | First-party component system of ~20 primitives specified as class + data-* visual contracts with six mandatory states; the state dot consumes the frozen brand eight-glyph vocabulary (working », idle 3×3 block, waiting ?, done tick, paused bars, stopped hollow square, error !, disconnected broken square) as 9×9 crispEdges SVG; Kobalte scoped to a11y-hard behavior only (D3) | Active (Matt, 2026-08-05) | ux foundation §D3 |
| DL-151 | Focus model: the single upstream-frozen --cx-focus-ring (2px solid --rigel-blue) on :focus-visible, four interactive shell focus zones (left/main/right/topbar; the usage bar is a display-only landmark until it gains a control) with roving tabindex, pane-focus rule, escape ladder (D4) | Active (Matt, 2026-08-05) | ux foundation §D4 |
| DL-152 | Command palette (Cmd/Ctrl-K, registry-backed, actions + destinations) and global keymap (direct chords) as first-class surfaces; every primary action registers a command; carried intact across the brand freeze (D5) | Active (Matt, 2026-08-05) | ux foundation §D5 |
| DL-153 | The frozen IA renders through the component system surface-by-surface (agent tree, pinned right sidebar, Issues/PRs board, channel→topic comms, agent workspace = home channel + session trace) in BOTH render hosts (Wails desktop app + browser); four primary surfaces (Bridge board, Manager tree, channels+threads, session trace) carry an explicit excellence bar with concrete starting points (the Rigel-site mockups, Zulip’s threading UX, the brand streaming treatment) (D6) | Active (Matt, 2026-08-05) | ux foundation §D6 |
| DL-154 | Delivery layout: in-tree apps/ui/src/design/ (tokens.css/base.css/components/) + keyboard/ contracts; stylelint guard bans raw hex, --rigel-*, and literal durations outside tokens.css, with one narrow allowlist — the mark component’s CSS may name --rigel-purple directly (purple is never aliased into --cx-*); no token package, no W3C token source (D7) | Active (Matt, 2026-08-05) | ux foundation §D7 |
| DL-155 | Brand seam: primitives mirrored with provenance from the frozen spec; the mark per the brand surface table is the one purple per surface with the 16px floor honored; Compass-owned --cx-ed-* editor-theme mapping from the Night Owl syntax ramp; mutual co-review (D8) | Active (Matt, 2026-08-05) | ux foundation §D8 |
| DL-156 | Motion: the product UI consumes the frozen brand motion system in pure CSS/SVG (no client animation runtime) — brand duration/easing/pulse/streaming tokens, the green working pulse with the one-pulse-per-region budget, chase-light spinner/bar loaders, the boot-sequence, reduced-motion as substitution not removal; literal durations are review failures (D9) | Active (Matt, 2026-08-05) | ux foundation §D9 |
| DL-157 | Adoption path: design-first, five-step incremental migration (tokens → shell → keyboard spine → surface flips → legacy retirement); in-flight lanes SEA-1645/SEA-1633 re-skin post-merge (D10) | Superseded by DL-184 (Matt, 2026-08-07) | ux foundation §D10 |
| DL-158 | The agent workspace simplifies to the agent’s home channel + its session trace (two fixed panes, no arbitrary split tree); no terminal pane and no file-viewer pane in dogfood (isolated containers; an operator dev-server-view affordance is deferred to backlog, PR review lives on the user’s forge); the terminal PaneKind arm + newTerminalPane retire at the workspace flip (D6/D10) | Active (Matt, 2026-08-05) | ux foundation §D6 |
| DL-159 | One UI codebase renders in two hosts — the Wails v3 desktop app (primary) and the browser (the managed/hosted product at compass.rigel.build) — over the same transport-agnostic UI above the connection.ts provider seam; the layout is fluid within its window/viewport (a dense supervision surface that reflows, not a fixed-pixel canvas and not a mobile redesign) (D6/§Global Constraints 2) | Active (Matt, 2026-08-05) | ux foundation §D6 |
| DL-160 | The desktop app is first-class multi-window: every top-level surface (Bridge, a channel, an agent workspace, Backlog/Done, Settings) is an independently mountable window-scoped view (own DL-127 route, own focus zones + command scope, no sibling region required); compass-native spawns/manages OS windows, this record owns the decomposition; in-window tabs (Linear-style) and split views are deferred to the Beta milestone (SEA-1808), admitted by the same decomposition without rework (D6.1) | Active (Matt, 2026-08-05) | ux foundation §D6 |
| DL-184 | Token adoption is a FULL CUTOVER, not the DL-157 incremental strangler: a pre-dogfood codebase has no live users to protect, so the legacy :root tier is deleted and every consumer flips to --cx-* in one atomically-merged migration (canvas → Night Owl, base.css un-shadowed, state dots recolored, D7 stylelint guard wired directly at error), proven by a Playwright visual-smoke harness Matt reviews | Active (Matt, 2026-08-07) | DS token cutover |
| DL-174 | Compass test coverage is a differential-oracle pyramid: every seam carries a hermetic in-memory reference in the default gate plus a pgtest suite proving the real Postgres backend obeys the same contract; Postgres is the one live dependency the strategy gates on (D1) | Active (Matt, 2026-08-07) | test strategy §Decisions |
| DL-175 | The real-Postgres pgtest suites run INLINE as a step in the one existing CI job — no separate CI job, lane, or moon task; one required check, hermetic moon test stays dependency-free via the build tag (D-A1, overruling the orion ancestor’s separate test-pg lane; Matt: “trimmed but no extra CI job, it goes in the same existing job”) | Active (Matt, 2026-08-07) | test strategy §A1 |
| DL-176 | CI Postgres is the shipped GitHub Actions SERVICE CONTAINER — digest-pinned postgres:16-alpine, health-gated, URL-form TCP DSN, digest kept equal to pgtest.go’s image pin — not the orion ancestor’s per-step embedded postgresql_18 postmaster, which solved a Woodpecker constraint compass does not have (D-A2) | Active (Matt, 2026-08-07) | test strategy §A2 |
| DL-177 | Require-live teeth live in the harness: COMPASS_REQUIRE_LIVE=1 (set by the CI pgtest step) turns RequireDSN’s no-DSN skip path into t.Fatal at the one policy seam every suite passes through — layered with, not replacing, the workflow-level skipped-suite assert step (D2/S1) | Active (Matt, 2026-08-07) | test strategy §Decisions |
| DL-178 | Whole-flow scope is one thin composing test: runner.Dial over TLS with a real minted Runner-kind token through the production RunnerService door built by buildNetworkServer — the one seam no suite on main composes; a full smoke rebuild is rejected as re-describing shipped work (D3/S2) | Active (Matt, 2026-08-07) | test strategy §Decisions |
| DL-179 | The podman-tagged surface (runtime lifecycle suites and the go/e2e/ dogfood legs) stays skip-if-absent and out of this record’s gate; its CI promotion is owned by the dogfood-e2e/infra lane (D4) | Active (Matt, 2026-08-07) | test strategy §Decisions |
| DL-180 | No browser e2e in the test pyramid: the whole-flow level is a Go/pgtest composing test; the UI transport is contract-tested at the frame level and the generated client factory over the bun contract lane once it lands (D5) | Active (Matt, 2026-08-07) | test strategy §Decisions |
| DL-181 | The bun TS-client↔Go-server contract lane is filed as a follow-up feature against the compass-dogfood-e2e harness scope (it consumes that harness’s bring-up and served door rather than duplicating them); it is not built from this record (D6/S3) | Active (Matt, 2026-08-07) | test strategy §Decisions |
Bridge spawn control
Section titled “Bridge spawn control”| ID | Decision | Status | Record |
|---|---|---|---|
| DL-164 | Start-half (Active): starting an agent is the lifecycle operation (startAgent → composite SpawnAgent), which rejects when the target agent already holds a live session; an agent owns multiple cards, so the SessionBinding map is keyed by card (workstreamId = Issue.id), not agent, inside one container / one live session. Add-half superseded by DL-185 (Matt, 2026-08-07): the manual add-a-workstream board mutation (addWorkstream) and UI-side agent creation (CommsService.CreateAgent) are dropped. | Active (start-half; Matt, 2026-07-24) / add-half Superseded by DL-185 (Matt, 2026-08-07) | spawn control §Control flow |
| DL-165 | The Bridge spawn-control lane builds live-wired against the shipped AppStoreOptions client seam (real RPC awaits when clients are present, surfaced offline refusals when not — the shipped stopAgent shape), superseding the orion walking-skeleton posture whose premises (no injection seam, no live streams, stub stop) no longer hold in compass; parked for Matt’s ratification (OQ-P1) | Active (compass port amendment, 2026-08-07) | spawn control §Posture |
| DL-166 | Spawn is one server-side composite CompassService.SpawnAgent RPC orchestrating Provision→Start under a single client_request_id with server-owned end-to-end dedup (retry returns the same session_id, no second container) and a pre-Provision reject-on-live short-circuit ordered after the dedup-join lookup; client-orchestrated two-call flow rejected | Active (Matt, 2026-07-24) | spawn control §DL-166 |
| DL-167 | AgentSessionStatus gains agent_account_id, populated at every construction site (board projection, SubscribeEvents push synthesis, Runner-side Status) — a join onto the live hub binding Server-side and new Runner-local account carriage (AgentSpec→handle→liveSession), with the terminal STOPPED status resolving its account before unbindSession; makes the UI reconcile full-fleet within the binding’s live scope and is the prerequisite of the reject-on-live scan | Active (Matt, 2026-07-24) | spawn control §DL-167 |
| DL-168 | A stopped card stays restartable: the card’s start affordance shows on no-binding OR a stopped binding (agent not live), the exact predicate the startAgent guard reads, and restart mints a fresh binding — a no-binding-only affordance would strand every stopped card since applyStopped keeps the binding | Active (Matt, 2026-07-26) | spawn control §DL-168 |
| DL-169 | Retry renders on spawn-failed only; a stop-failed card recovers by re-issuing the idempotent stop (beginStop accepts running | stop-failed, and stop enablement covers stop-failed), because the session is still held and re-sending SpawnAgent would be bounced by reject-on-live | Active (Matt, 2026-07-26) | spawn control §DL-169 |
| DL-170 | The spec gains a SHALL binding one container per agent account, so the agent-scoped reject-on-live rule is contract-backed rather than resting on the Runner’s incidental container-name derivation (NamePrefix + accountID); lands with the T0 wire changes | Active (Matt, 2026-07-26) | spawn control §DL-170 |
| DL-171 | SpawnAgent classifies adminOnly on the network door, alongside the sibling lifecycle RPCs (it provisions AND starts — strictly more powerful); the Bridge control surface is bootstrap-admin-only for MVP, and any future non-admin opening is authenticatedOpen + per-account authorization in the handler, not a bare reclassification | Active (Matt, 2026-07-26) | spawn control §DL-171 |
| DL-185 | Drop the manual add-a-workstream / create-agent surface from the Compass UI (supersedes DL-164’s add-half): minting a board card and creating agents from the UI contradict Manager-builds-the-tree (DL-095/DL-134) and issues-come-from-the-tracker (DL-069/DL-161). Removed: NewWorkstreamDialog, store.addWorkstream, the WorkstreamSpec type, and the planned ”+ New workstream” toolbar button. Kept: StartAgentDialog + the spawn/stop phase machine + stop control — the human’s only lifecycle affordance is start/stop on an existing (forge-ingested) card. The positive replacement is server-side: SEA-1820 seeds a root Manager on first launch that builds the rest of the tree. | Active (Matt, 2026-08-07) | spawn control §Control flow |
First-turn delivery
Section titled “First-turn delivery”Ledger note: the four rows below (DL-187..190) were drafted as DL-186..189 in
compass-first-turn-delivery/design.md; renumbered on landing — DL-186 was consumed by the proto-strip decision (DL-186 under UI shell). The merged first-turn-delivery record’s frozen prose still names DL-186..189; resolve the stale numbers through this note (the freeze rule adds, never rewrites frozen prose).
| ID | Decision | Status | Record |
|---|---|---|---|
| DL-187 | initial_prompt is REMOVED from the whole contract (StartAgentSessionRequest field 2, SpawnAgentRequest field 2, SpawnPeerRequest field 3 — numbers AND names reserved; server/runner/SDK/UI/e2e consumers cut over atomically, no fallback): an agent session ALWAYS starts idle and its first turn arrives as a channel message over the SEA-1569 deliver path (DeliverControl → the idle-deliver arm starts a turn); a provisioned peer’s brief is a post from its provisioning manager into their per-pair DM channel (home channel for human owners) | Active (Matt, 2026-08-10) | first-turn delivery §Approach |
| DL-188 | The @compass reserved alias is FROZEN as the system-sender mechanism for ANY system-level message sender (not just the root-manager Setup thread), requiring reserved-handle validation at account creation; case-1 root-manager boot (a Compass-authored initial Setup thread in the manager’s home channel) uses it and is scoped OUT to follow-up SEA-1820, which owes only the sender representation + Setup flow; ratified in shape here so the interim can never revive a prompt field | Active (Matt, 2026-08-10) | first-turn delivery §Case 1 |
| DL-189 | Fresh-start barrier-lift: on a FRESH (non-resume) start the Runner sends AgentControl{replay_complete} as the first control op after Bind (seq 1, FIFO-first, drains before any deliver) — one mechanism symmetric with the resume path, no agent change; lifts the agent-side replay barrier so the first case-2 deliver is not refused-and-stranded (T-R3) | Active (Matt, 2026-08-10) | first-turn delivery §the seam |
| DL-190 | The case-2 brief carrier is a PER-PAIR manager↔peer DM channel (ChannelKindDM, 2 members: spawning manager + new peer, both owners carried by expandOwnerMembership so the operator retains visibility), auto-provisioned on the spawn edge (T-R0) — NOT the manager’s coordination channel, which is a broadcast to all reports. Token-minimization: siblings do not receive briefs they don’t need. UI channel-proliferation UX is a known deferred problem | Active (Matt, 2026-08-10) | first-turn delivery §Approach |
| DL-191 | The @compass system-sender is a dedicated system account type — a third first-class account shape alongside user and agent (the system = 12 proto case, not an unset-oneof discriminator), startup-seeded and reserved-handle-guarded at account creation, visible only to shared-channel co-members, and structurally never a deliver recipient nor present in directory/roster (no agent_accounts row) | Active (Matt, 2026-08-14) | system-sender first turn §A |
| DL-192 | The root-manager Setup-thread first-turn flow: a postSetupThread step in serve_seed.go fired from seedRootSupervisor on BOTH up-arms after the supervisor seed, posting the embedded versioned setup_thread.md content as @compass into the supervisor’s home channel, made idempotent by a supervisor-scoped client_request_id (content-invariant — the same supervisor id yields the same key, so a content revision never re-posts) | Active (Matt, 2026-08-14) | system-sender first turn §B |