Skip to content

docs(design): fold channels into the agent tree (RIG-1622) - #984

Open
rigel-mintaka wants to merge 1 commit into
mainfrom
compass-ui/rig-1622-channels-in-agent-tree
Open

docs(design): fold channels into the agent tree (RIG-1622)#984
rigel-mintaka wants to merge 1 commit into
mainfrom
compass-ui/rig-1622-channels-in-agent-tree

Conversation

@rigel-mintaka

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

Copy link
Copy Markdown
Contributor

Design record for RIG-1622 — folds comms channels into the agent tree, so a
channel can hang off an agent as a child node instead of living in the
separate folder hierarchy that PR #91 removed.

Docs-only: +1807 / -0 across two files. No code changes, no behaviour
change. The record is the contract that T1–T8 execute against, frozen on
merge per skill://design.

  • docs/designs/ui/compass-channels-in-agent-tree/design.md (new, 1805 lines)
  • docs/designs/DECISIONS.md (+2 — ledger rows DL-344/DL-345)

Two forks Matt ruled, now settled in the record

Both were asked mid-pass and are recorded as DECIDED in the record and the
ledger, so a later reader does not reopen them:

  1. Membership is two modes, not one derived rule. ChannelMembershipMode
    (proto field 12, column channels.membership_mode): EXPLICIT = 0 keeps
    hand-managed members, TREE = 1 derives the member set from the subtree.
    Deliberately not a ChannelKind value — kind is the DM-vs-channel axis —
    and not implied by parent_agent_id IS NOT NULL, which would foreclose a
    hand-membered channel that lives in the tree.
  2. The read grant is the owner set. By default every agent under an owner
    can read that owner's channels. Recorded as a product decision rather than
    as reuse of an existing precedent, because no such precedent exists for
    channels — the owner-set viewer CTE appears only in the two group
    queries (channels.sql:93, :118), never in the three channel predicates.
    A later ACL system is named as the successor, with its insertion point
    stated.

Review history

Five review rounds (skill://review), each one blocking on a real defect the
previous fix pass introduced or missed. The record grew 445 → 1805 lines
across them. The substantive finds, all driver-verified against code:

  • A representational change recurs at every layer that reads the old
    representation.
    Adding tree-derived membership was first fixed only at the
    post gate (messages.go:59); the identical defect then reappeared at six
    more sites, every one of them FROM channel_members — the delivery fan-out
    (SubscribedAgents, ChannelAgentMembers, SeedChannelDeliveryCursors,
    SweepChannels), three missed membership-probe callers (topics.go:23,
    channels.go:550, channels.go:765), and the wire, where an empty
    member_account_ids makes deriveMembership return "none". Channel-keyed
    sites need subtree descent; account-keyed sites need ascent.
  • RLS fails open for a new table. 0001_init.sql:939-950 enables row-level
    security by iterating a hardcoded tenant_tables array, so a table added
    outside that list ships with RLS off. The record calls the array edit out
    explicitly in T2.
  • One construction site, structurally. CreateChannel returned a
    hand-written Channel{} literal after commit (channels.go:206-213),
    bypassing channelFromRow — so both new columns were silently dropped on the
    create path. Enumerated properly: 49 return Channel{ minus 47
    return Channel{} = exactly two field-populating sites. Fixed by re-reading
    through getChannel after commit, collapsing it to one, so no future column
    can miss create. The two-line shortcut is explicitly forbidden in the record,
    because the extra site is the defect.

One accepted limitation, stated rather than papered over: the post-commit
re-read is fallible where the literal was not. A fallback literal would
reintroduce the site the fix eliminated, and both siblings
(UpdateChannelMembers, SetChannelPolicy) already propagate this way.

Plan

T1 proto (attach edge, membership mode, ReparentChannel) · T2–T6
compass-server (schema + RLS, participant probe, visibility predicates and
read paths, subscriptions and delivery, comms RPC edge and events) · T7–T8
compass-ui (sidebar tree, live adapter and fixtures). Cross-lane: only T7–T8
are mine.

Five open questions remain in the record, each marked load-bearing or not.
One is load-bearing (whether the owner-set read grant extends to
OWNER-grouped channels) and is flagged for a decision before T4 starts.

Gates

  • bun tools/design-ledger-gate/index.tsOK — 310 ledger row(s), 130 record header(s) valid
  • moon run root:markdownlint — 207 files, 0 errors
  • CI 7 pass / 6 skipping / 0 fail

Rebased onto main 13a9e8f5. The DECISIONS.md conflict was resolved as the
union with my rows renumbered DL-338/339 → DL-344/345 (main had taken 338), and
the one prose citation of the old id updated with them.

RIG-1622

@linear-code

linear-code Bot commented Sep 7, 2026

Copy link
Copy Markdown

RIG-1622

@trunk-io

trunk-io Bot commented Sep 7, 2026

Copy link
Copy Markdown

Merging to main in this repository is managed by Trunk.

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

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

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown

Compass engineering docs preview: https://compass-ui-rig-1622-channels.compass-eng-docs.pages.dev

Deployed from compass-ui/rig-1622-channels-in-agent-tree at e74ecab.

Changed pages:

@rigel-mintaka
rigel-mintaka force-pushed the compass-ui/rig-1622-channels-in-agent-tree branch 5 times, most recently from 926a383 to e1c7371 Compare September 7, 2026 23:10
@rigel-mintaka
rigel-mintaka force-pushed the compass-ui/rig-1622-channels-in-agent-tree branch from e1c7371 to e74ecab Compare September 7, 2026 23:15
rigel-mintaka added a commit that referenced this pull request Sep 8, 2026
…621)

Adds the design record for RIG-1621: a persistent center-of-top-bar search
input alongside the D5-frozen Cmd+K palette, covering agents, issues, PRs and
chat messages. Both surfaces feed ONE provider set, so neither can drift from
the other, and the corpus stays server-side per Matt's ruling that the client
cannot hold the full text. Scope is the whole board, not the caller's
assigned slice.

Chat search is nearly free: the server already ships
`rpc SearchMessages` with a tsvector index, and no UI code references it.
Issue search is the real new work — a generated `search_tsv` column, a GIN
index, and a `SearchIssues` RPC mirroring the messages pipeline.

Two schema findings, both measured against PostgreSQL 18.4 rather than
reasoned about, and both of which would have shipped a broken gate:

The obvious spelling of the column does not compile:

  ERROR:  generation expression is not immutable

A STORED generation expression must be IMMUTABLE, and `array_to_string` is
only STABLE (`labels::text` fails the same way — `array_out` is STABLE
too). The cause is polymorphism, not arrays: `anyarray` must stay
conservative for element types whose output reads a GUC. So labels route
through a `compass_labels_text` wrapper declared IMMUTABLE with a pinned
`search_path` — honest, because for TEXT[] the concrete element output
function `textout` is itself IMMUTABLE. The one alternative that both
compiles and is genuinely immutable, `array_to_tsvector`, bypasses the
parser: labels land as raw lexemes, so a search for `triage` against a
`Needs-Triage` label matches NOTHING. It fails silently, which is worse
than the error. T1 therefore requires a label-only test.

And the column must weight its fields with `setweight`. Unlike the messages
column it spans a 40-character title and a multi-kilobyte body, and
`ts_rank` applies no length normalization by default: flat concatenation
scores a title hit and a hit buried in 10 KB of body EXACTLY equal
(0.06079 vs 0.06079; weighted 0.60793 vs 0.24317). The task list already
asserted 'title hit above body hit', which was unsatisfiable as specified.
The column is STORED, so deferring this would cost a table rewrite.

PR results are scoped honestly as fixture-only at v1: `Issue.prs` has zero
Go writers, so PR search looks complete against fixtures and returns nothing
in production. Not worse than today's PRs tab, but no live-mode PR assertion
may gate the release.

Review found three executability gaps, all fixed here rather than left for
an executor to hit: providers cannot reach an RPC client at all today (the
`AppStore` exposes none, and hanging one on it is forbidden by DL-128), so
the seam is now OQ-7; the message destination cannot navigate through
`store.openTopic`, which no-ops on any topic outside the client-held set,
so it routes on wire data; and the debounce moved from T6 to T4, where the
effect it wraps is written, removing a T4-T6 ordering knot. Ledger
citations dropped their line numbers — DECISIONS.md is append-only, so a
line number into it rots, and four were already off by three rows.

Mints DL-346 (DL-344/345 were already claimed by open PR #984, six hours ahead of this one — allocated, not merged, so main's max would not have shown them). Leaves DL-233's assigned-issue scope for Matt as OQ-6, and
whether the palette itself re-scopes as OQ-8: this design reverses a frozen
decision, and frozen decision prose is never edited in place.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant