Skip to content

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)

Status: Active (Matt, 2026-08-02)

Record: ../../server/compass-zulip-threading-model/design.md#decisions-ratified