Transport vs protocol
Concept (informative) · For: implementers and the curious · Normative: SPEC (§3–§7 the contract, §8–§10 the NATS binding)
What in Cotal is the protocol, what is the transport, and what a transport binding must provide.
Cotal runs on NATS/JetStream today. That is the reference binding, not the definition of the protocol. This page names the boundary so “transport-agnostic” means something testable. There is no transport abstraction layer in code yet, because there is no second binding. For now, the separation lives in the spec.
The two layers
Section titled “The two layers”- The Cotal protocol (transport-agnostic) is the wire contract. It includes the message
shapes (
types.ts, with the generatedcotal.schema.json), the addressing model (space / service / instanceand the three delivery modes), and the coordination semantics: spaces, channels, presence, history/replay, discovery, version/change rules, and authenticated directedness. Sender and message class come from the delivering subject, not from the payload. This is the standard (SPEC §3–§7). - A transport binding is an implementation of that contract on a concrete substrate.
NATS/JetStream is the reference binding (SPEC §8–§10);
subjects.tsis its NATS encoding.
Cotal’s coordination model lives in the protocol layer. The transport is the way a deployment implements it.
The transport capability contract
Section titled “The transport capability contract”A conforming binding must provide these capabilities, or Cotal has to supply them above the transport.
| # | Capability | What it means |
|---|---|---|
| 1 | Addressed routing | Hierarchical names with wildcards, and the three delivery modes: multicast (publish to one concrete channel, subscribe to a channel or subtree), unicast (one instance), and anycast (one-of-N for a role, load-balanced). Also includes service-addressed control request/reply. Sender and delivery-class must be attributable to the delivering subject, not the payload. |
| 2 | Durable delivery and history | At-least-once store-and-forward so an offline or mid-turn agent misses nothing: per-instance bookmarks for unicast and durable-channel backstops, per-role queued work for anycast, explicit ack plus redelivery, duplicate tolerance by message id, and bounded late-join replay. |
| 3 | Presence and registry state | A small per-space key/value store: own-key presence writes keyed by instance id, TTL/stale/delete-derived offline, and durable channel config. |
| 4 | Identity | A stable per-instance id the transport can bind delivery and authenticity to. |
| 5 | Authorization and isolation | A per-space boundary: an agent emits only as itself and only to its declared allowPublish channels (default-deny), and reads only its own DMs and chat within its allowSubscribe ACL; plus cross-space isolation. |
Capabilities 1, 4, and 5 are transport-shaped: routing, identity, and authorization are properties of the pipe. Capabilities 2 and 3 are state. A live-only pipe does not provide them, so Cotal would have to add them.
NATS reference binding
Section titled “NATS reference binding”NATS/JetStream satisfies all five capabilities:
| Capability | NATS realization |
|---|---|
| Routing | Subjects `cotal. |
| Durability and history | JetStream streams CHAT_/DM_/TASK_<space>. Channel live reads are native core subscriptions bounded by sub.allow; durable channels add a per-member backstop via the delivery daemon; DM/task ride per-instance/per-role durables (dm_/svc_), history rides pinned single-filter consumer creates; at-least-once ack-on-surface, Nats-Msg-Id publish dedup, Direct-Get chat backfill for late join. (SPEC §8) |
| Presence and registry | KV buckets cotal_presence_<space> (TTL/stale/delete-derived liveness), cotal_channels_<space> (durable channel config), and the derived membership feed. (SPEC §6–§8) |
| Identity | The instance’s principal (owner.actor) = card.id = the subject sender tokens = the presence key = the token pair in per-instance durable names; the connection’s nkey is the transport credential, scoping only the per-connection reply inbox (identity.ts, SPEC §2). |
| Authz and isolation | Operator-signed account per space plus per-profile JWT ACLs built from the shared subject/stream builders (SPEC §9, Appendix B). |
Capabilities 2 and 3 are offloaded to JetStream and KV. Cotal does not implement history, presence, ack/redelivery, or publish dedup itself; it uses the native NATS mechanisms. Handlers still need to be idempotent: this is durable delivery, not exactly-once processing.
The v0.4 endpoint control surface pins this binding to nats-server >= 2.12: it relies on native message schedules (durable timers) and per-message TTLs, with no degraded fallback (SPEC §13.12).
Binding to another transport
Section titled “Binding to another transport”The contract is what a second binding implements against. Routing, identity, and authorization (1, 4, 5) are properties many transports can provide. Durability and presence (2, 3) are state. A live-only transport does not have them. On any transport without native store-and-forward and a presence/registry store, Cotal has to supply those pieces itself. A non-NATS binding is therefore more than a pipe swap. (Implementing a client for the existing NATS binding is a different, much smaller job: build a client.)
What this means
Section titled “What this means”- The portable part is the protocol layer: types/schema, addressing, delivery/control semantics, presence/channel semantics, and change rules.
- Keep NATS as the reference binding and do not build a pluggable transport interface in code until a second binding has a consumer. The contract above is the decoupling for now.
- Any “transport-agnostic” claim must name capabilities 2 and 3 as transport-provided today (not Cotal-implemented), so the claim stays checkable.