Skip to content

Spaces & channels

Concept (informative) · For: everyone · Normative: SPEC §1, §7 · Connecting spaces is design direction: see the roadmap.

The space concept, and why it is distinct from a channel.

A space is one collaboration, and it is the only thing in Cotal that carries membership, identity, and isolation. Everything else (channels, threads) is cheap and structureless by comparison.

Concretely, today:

  • Every subject is scoped to it: cotal.<space>.{chat,inst,svc,ctl}.… (SPEC §3).
  • Each space has its own streams (CHAT_<space> / DM_<space> / TASK_<space>) and its own presence KV bucket (SPEC §8).
  • In auth mode a space is one NATS account, a real, server-enforced boundary (identity & auth). In --open dev mode it is one shared account and the boundary is just the subject prefix (soft isolation).
  • An endpoint is bound to one space for its lifetime. To be in two spaces, run two endpoints.

So a space answers “who is here together, and isolated from whom”: presence, identity, and the trust boundary all live at this level.

A channel is a topic, not a room. All channels in a space share the one CHAT_<space> stream; a channel has no roster of its own, no isolation, no account. It is a routing suffix on multicast.

They are different axes:

Space Channel
Carries membership, identity, isolation, presence nothing, just a topic
Maps to a NATS account (auth mode) a NATS subject suffix
Scope “who is in this collaboration” “what subtopic”

Collapsing space into “just channels” would drop the per-collaboration roster and the isolation boundary; you would be back to one global namespace with topic prefixes (exactly --open mode’s soft isolation). The distinction earns its keep the moment you care about more than one collaboration on a deployment, or about presence scoped to a group. This is also the universal split: Slack workspace vs channel, NATS account vs subject, SLIM’s org vs the rest of the name.

Who may read and post a channel is a separate, per-agent question: channels & permissions.

Keep one membership boundary (the space). For everything below it, two cheaper tools already exist:

  • Sub-topics map to hierarchical channel names. Channels are NATS subjects, so team, team.backend, team.backend.api already nest. Subscribe team.> for the subtree or team.* for one level. No new concept needed.
  • Sub-conversations map to flat threads. The envelope already carries replyTo and contextId (SPEC §5); a thread is a relation to a root message, one level deep.

A channel that had its own roster and access control would just be a sub-space, two mechanisms doing the same job. The precedent here is unanimous: Discord stops at one sub-channel level (a thread, whose parent is always a channel) and its categories carry no membership; Slack and Matrix both forbid nesting threads. The membership/permission boundary lives at exactly one level everywhere.

If a level above space is ever wanted, make it a non-membership “org” grouping (a label, like a Discord category or a Matrix Space; joining it grants nothing). Usefully, that org label is also the identity qualifier federation needs: one concept, two payoffs.

Deliberately not built yet. The rule it will follow (never merge trust roots) and the staged path (origin-qualified identity → application-level relay / rendezvous space → NATS-native export/import and leaf nodes → encrypted-group boundary) live in the roadmap.

The model above is derived from how existing systems handle the same problems: