Skip to content

NATS Messaging and Topic Convention

NATS as Primary Transport

All inter-kernel communication in the Concept Kernel Protocol occurs over NATS messaging. NATS is the sole required transport for action dispatch, result delivery, event broadcasting, and streaming. A conformant CKP implementation MUST use NATS as the primary transport.

Why NATS

NATS was selected because it provides:

  • Subject-based routing -- topic names map directly to kernel identity, eliminating service discovery.
  • Dual transport -- native TCP for cluster-internal traffic, WebSocket Secure (WSS) for browser clients, over a single logical bus.
  • JetStream -- durable delivery for task lifecycle messages that MUST NOT be lost.
  • Zero broker configuration per kernel -- a kernel subscribes to its own topics at startup; no per-kernel broker configuration is required.

Why This Matters

NATS is not just a message queue. In CKP, it is the nervous system. Every action dispatch, every result delivery, every edge subscription, and every session interaction flows through NATS. The subject-based routing means that a kernel's NATS topics are derivable from its name and GUID -- no service discovery, no configuration databases, no registry servers.

Transport-Agnostic Principle

Although NATS is the primary transport, the messaging interface MUST remain transport-agnostic at the kernel boundary. A kernel processor receives a message envelope (headers + JSON body) and returns a result envelope. The transport layer -- NATS TCP, NATS WSS, or a future alternative -- is invisible to the handler.

This principle is enforced by the NatsKernelLoop abstraction in CK.Lib.Py (see Message Processing Cycle). Handlers are decorated functions (@on("action.name")) that receive parsed JSON and return dicts. The loop manages connection, subscription, dispatch, instance writing, and result publication.

Filesystem symlinks as transport. In local development, the filesystem itself serves as a degenerate transport. A kernel's storage/ directory is a shared volume; another kernel can read its instances directly via filesystem symlinks. This coexists with NATS -- the symlink provides read access to sealed instances, while NATS provides the event notification that a new instance exists.

ConcernHandled ByNOT Handled By
Message routingNATS subject-based routingKernel processor code
Connection authNATS credentials (JWT-SVID or Keycloak JWT)Kernel processor code
Message framingNATS protocolKernel processor code
Payload parsingNatsKernelLoop (JSON decode)NATS server
Instance writingNatsKernelLoop (DATA loop)Handler function
Result publicationNatsKernelLoop (NATS publish)Handler function

WSS for Browsers, TCP for Cluster

Browser clients MUST connect via WebSocket Secure (WSS). Server-side kernels SHOULD connect via native NATS TCP for performance.

Client TypeTransportPortAuth Mechanism
Browser (CK.Lib.Js)NATS WSS443 (via ingress)Keycloak JWT in headers
Server-side kernel (CK.Lib.Py)NATS TCP4222SPIFFE JWT-SVID
CLI tool (ckp)NATS TCP or WSS4222 or 443Keycloak JWT or SPIFFE
Local developmentNATS TCP4222None (anonymous)

Why WSS for browsers. Browsers cannot open raw TCP sockets. NATS provides a first-class WebSocket listener. The WSS endpoint is exposed through the cluster ingress controller, secured by TLS termination. No custom proxy is required.

Why TCP for cluster. Native NATS TCP avoids the WebSocket framing overhead. Server-side kernels running inside the cluster connect directly to nats://nats.nats.svc:4222. Latency is lower and throughput is higher.

Topic Convention

Two Formats, One Rule

CKP defines two topic formats: name-based (primary) and GUID-based (extended). Name-based topics are the default for all kernels. GUID-based topics are REQUIRED only when deploying multiple simultaneous instances of the same kernel class with SPIFFE isolation.

Processors MUST read topic names from conceptkernel.yaml spec.nats -- topic names MUST NOT be hardcoded. Migration between formats requires only a configuration change in conceptkernel.yaml.

Primary Format -- Name-Based

The namespace prefix in the kernel class name provides domain isolation. No topic collisions occur between CK.*, TG.*, CS.*, or other namespaces.

# Primary format: {direction}.{KernelName}
input.{KernelName}     # messages TO kernel
result.{KernelName}    # responses FROM kernel
event.{KernelName}     # broadcast events FROM kernel
stream.{KernelName}    # streaming events FROM kernel (LLM tokens, progress)

Examples:

input.CK.Task              # platform task kernel
result.CK.Task
event.CK.Task
stream.CK.Task
input.TG.Cymatics          # domain kernel
input.CK.ComplianceCheck   # compliance kernel
DirectionPublisherSubscriberPurpose
inputAny authorised clientTarget kernel processorAction dispatch
resultTarget kernel processorRequesting client(s)Action response
eventTarget kernel processorAny interested subscriberLifecycle broadcast
streamTarget kernel processorSubscribing client(s)Real-time streaming (LLM tokens, progress)

Extended Format -- GUID-Based

Used only when multiple instances of the same kernel class run simultaneously and require per-instance NATS isolation under SPIFFE. The format is declared in conceptkernel.yaml spec.nats:

# Extended format: ck.{guid}.{direction}
ck.{guid}.input          # messages TO specific instance
ck.{guid}.result         # responses FROM specific instance
ck.{guid}.event          # broadcast events FROM specific instance
ck.{guid}.stream         # streaming events FROM specific instance

Session Topics

Multi-user sessions use a dedicated topic namespace scoped to the project:

session.{project}.{session_id}

Multiple authenticated users subscribe to the same session topic. Each message carries JWT identity in its headers. Kernel results fan out to all session subscribers.

PhaseMessageActor
Create{action: "session.create"} on input.{Kernel}Authenticated user
JoinSubscribe to session.{project}.{id} via NATS WSSOther users
InteractMessages with X-User-ID + Authorization headersAll participants
PresenceHeartbeat every 30s on session topicEach participant
Close{action: "session.close"}Session creator

Results from kernel actions MUST be published to both result.{kernel} (for the requesting client) and the originating session topic (for all participants).

Session Topics vs Kernel Topics

Session topics are conversation-scoped. Kernel topics are kernel-scoped. A single kernel action may produce a result on result.{kernel} (for the caller) AND on session.{project}.{id} (for all session participants). These are different audiences with different subscription patterns.

Topic PatternPurposeWho Subscribes
input.{kernel}Action dispatchKernel processor
result.{kernel}Action resultsRequesting client
stream.{kernel}Claude streamingSubscribing client
event.{kernel}Lifecycle eventsAny interested party
session.{project}.{id}Shared conversationAll session participants

Per-Loop NATS Topics

Every loop emits structured events over NATS. These topics form the complete event surface for a kernel instance. Per-loop topics use the GUID-based format because they refer to specific kernel instances.

CK Loop Topics

TopicDescription
ck.{guid}.ck.commitCK loop repo received new commit
ck.{guid}.ck.ref-updateBranch pointer moved
ck.{guid}.ck.promoteVersion promoted to stable
ck.{guid}.ck.rollbackVersion rolled back
ck.{guid}.ck.canaryCanary weight updated
ck.{guid}.ck.schema-changeontology.yaml or rules.shacl changed
ck.{guid}.ck.depends-onDependency on another CK declared or updated

TOOL Loop Topics

TopicDescription
ck.{guid}.tool.commitTOOL repo received new commit
ck.{guid}.tool.ref-updateTool branch pointer moved
ck.{guid}.tool.promoteTool version promoted to stable
ck.{guid}.tool.invokedTool execution started
ck.{guid}.tool.completedTool execution finished successfully
ck.{guid}.tool.failedTool execution failed

DATA Loop Topics

TopicDescription
ck.{guid}.data.writtenNew instance written to storage/
ck.{guid}.data.indexedIndex files updated
ck.{guid}.data.proof-generatedproof/ entry created
ck.{guid}.data.ledger-entryaudit.jsonl appended
ck.{guid}.data.accessedstorage/ read by another kernel (audit)
ck.{guid}.data.exportedDataset derived from storage/ for consumers
ck.{guid}.data.amendedInstance amendment committed and proof rebuilt
ck.{guid}.data.shacl-rejectedSHACL validation rejected a write
ck.{guid}.data.nats-degradedNATS reconnection after local queue overflow

Task Lifecycle Topics

Task lifecycle is driven entirely through NATS. All task state transitions are published to input.{KernelName}:

input.CK.Task  { action: "task.start",    task_id: "..." }
input.CK.Task  { action: "task.update",   delta: {...}   }
input.CK.Task  { action: "task.complete", output: {...}  }  # seals data.json

Task lifecycle NATS messages MUST use JetStream with at_least_once delivery guarantee. If NATS is unavailable:

  1. Task state transitions queue locally in storage/ledger/pending_events.jsonl.
  2. On NATS reconnection, pending events replay in order.
  3. If the local queue exceeds 1000 events, the kernel enters degraded state and publishes ck.{guid}.data.nats-degraded on reconnection.
  4. data.json MUST NOT be written without NATS confirmation of the task.complete event.

DANGER

Losing a task.complete message would leave an instance in an intermediate state with no proof record. JetStream provides the at_least_once guarantee needed to prevent data loss. The local queue provides resilience during transient NATS outages, but data.json MUST NOT be written without NATS confirmation.

Conformance Requirements

CriterionLevel
NATS MUST be the primary transport for all inter-kernel communicationREQUIRED
Kernel handlers MUST be transport-agnosticREQUIRED
Browser clients MUST connect via WSSREQUIRED
Server-side kernels SHOULD connect via native TCPRECOMMENDED
Filesystem symlinks MAY supplement NATS for local read accessOPTIONAL
Name-based topics MUST be the default formatREQUIRED
Topic names MUST be read from conceptkernel.yaml, not hardcodedREQUIRED
Task lifecycle messages MUST use JetStream with at_least_once deliveryREQUIRED
Session results MUST be published to both result.{kernel} and the session topicREQUIRED
Session messages MUST carry JWT identity in headersREQUIRED
Presence heartbeats SHOULD be published every 30sRECOMMENDED
Session topics MUST be scoped to project (no cross-project leakage)REQUIRED
Implementations SHOULD support GUID-based topics for multi-instance deploymentsRECOMMENDED

See also: Message Envelope for the NATS message header and body schema, Authentication for JWT verification over NATS, Sessions for multi-user session management.

Released under the MIT License.