15 KiB
RFC-0910: FIRST-CLASS MESSAGING SYSTEM
The Submarine's Nervous System
Version: 0.2.0
Status: DRAFT
Layer: L0-L1 (Transport — Internal Process Communication)
Class: ARCHITECTURAL
Author: Markus Maiwald
Date: 2026-02-09
0. ABSTRACT
This document specifies the First-Class Messaging System for internal process communication within Libertaria nodes. After architectural review, we converge on Zenoh-only (via zenoh-pico) as the unified messaging layer.
Key Principles:
- No-broker sovereignty: No Kafka, no RabbitMQ, no central daemon
- Kenya compliance: ~50KB footprint (zenoh-pico)
- Unified semantics: Everything is a key-space query
- Pattern unification: PUB/SUB, REQ/REP, SURVEY all map to
z_get()with different key patterns - Single library: One mental model, one failure mode, one update cycle
Correction from v0.1.0: Dual-plane (Zenoh + NNG) was rejected. For solo development, two libraries = two failure modes = too much complexity. Zenoh's key-space query unifies all messaging patterns.
1. MOTIVATION
1.1 The Gap
Libertaria L0 (UTCP, LWF) handles inter-node communication. But intra-node communication—between Membrane Agent, Feed processor, Sensor Oracle, and cognitive streams—lacks a first-class solution.
WebSockets are inappropriate (HTTP upgrade semantics where HTTP has no business). Raw TCP is too low-level. We need:
- Brokerless operation (sovereignty requirement)
- Unified patterns (PUB/SUB, REQ/REP, SURVEY as one semantic)
- Content-based routing (subscribe to
sensor/berlin/pm25/**) - Kenya compliance (embedded-friendly footprint)
1.2 The Zenoh-Only Insight
After evaluating dual-plane (Zenoh + NNG), we reject it. Two libraries = two failure modes = too much complexity for solo development.
The key realization: Zenoh's key-space query unifies all messaging patterns.
| Pattern | Zenoh Equivalent |
|---|---|
| PUB/SUB | z_subscribe("sensor/berlin/pm25/*") |
| REQ/REP | z_get("query/qvl/trust/did:xxx") (specific key) |
| SURVEY | z_get("health/**") with timeout (wildcard + aggregation) |
Law: Everything is a key-space query. The pattern is in the key, not the library.
1.3 Why Not NNG?
NNG's PIPELINE pattern seems attractive for Membrane processing stages, but:
- The Pipeline is not a network problem. It's sequential function calls within a process.
- Stages run synchronously in the same address space. You need
fn process_frame(), not a socket. - Adding NNG adds complexity without solving a real problem.
Principle: Don't use message passing where function calls suffice.
2. ZENOH: THE UNIFIED MESSAGING LAYER
2.1 What is Zenoh?
Zero-overhead pub/sub with query semantics. Rust core, Eclipse Foundation lineage.
2.2 Why Zenoh-Only?
- Single mental model: Everything is a key-space query
- Pattern unification: PUB/SUB, REQ/REP, SURVEY all via
z_get()with different key patterns - Peer-to-peer AND routed: Brokerless by default, routers for scale
- Wire efficiency: 4-8 bytes overhead per message (binary protocol)
- Storage alignment: Built-in persistence backends (RocksDB, memory)
- zenoh-pico: C library, ~50KB footprint (Kenya-compliant)
2.3 Pattern Mapping: Zenoh Replaces All
PUB/SUB → z_subscribe()
// Subscribe to all PM2.5 sensors
var sub = try session.declare_subscriber("sensor/+/pm25", .{
.callback = onSensorReading,
});
// Publisher
var pub = try session.declare_publisher("sensor/berlin/pm25");
try pub.put("42.3");
REQ/REP → z_get() on specific key
// Requester: Query specific trust distance
var reply = try session.get("query/qvl/trust/did:libertaria:abc123");
const trust_score = reply.payload; // "0.87"
// Replier: Declare queryable
var queryable = try session.declare_queryable("query/qvl/trust/*", .{
.callback = onTrustQuery,
});
SURVEY → z_get() with wildcards + timeout
// Surveyor: Ask all health modules, aggregate responses
var replies = try session.get("health/**", .{.timeout_ms = 500});
while (replies.next()) |reply| {
std.log.info("{s}: {s}", .{reply.key, reply.payload});
}
// Automatically aggregates all matching responses within deadline
2.4 Namespace Design (LWF Service-Type Registry)
The Zenoh key-space IS the LWF Service-Type Registry, only readable.
$MEMBRANE/ ← L0/L1 Signals
defcon/current ← Current defense level
defcon/history ← Level history (queryable)
pattern/alert/* ← Pattern detection events
stats/throughput ← Real-time metrics
$AGENT/ ← L1 Agent Communication
{did}/caps ← Agent capabilities
{did}/negotiate ← Negotiation channel
{did}/status ← Online/Offline/Occupied
$SENSOR/ ← RFC-0295 Sensor Oracle
{geohash}/{metric} ← sensor/u33dc0/pm25
{geohash}/{metric}/history ← Queryable storage
$FEED/ ← RFC-0830 Feed Protocol
{chapter}/world/{post_id} ← World posts
{chapter}/channel/{chan_id} ← Channel posts
{chapter}/group/{group_id} ← Group messages
{chapter}/dm/{thread_id} ← E2E encrypted DMs
$QUERY/ ← REQ/REP Replacement
qvl/trust/{did} ← Trust graph query
economy/scrap/velocity ← Economic metrics
health/{module} ← Health check responses
2.5 Integration Points
| Component | Subscription | Publication/Query |
|---|---|---|
| Membrane Agent | sensor/+/pm25, $MEMBRANE/defcon |
Filtered items to $FEED/ |
| Sensor Oracle | sensor/+/+ (all sensors) |
Normalized readings |
| Feed Relay | $FEED/{chapter}/+ |
Relayed posts |
| Economic Engine | economy/scrap/** |
Velocity updates |
| Health Monitor | health/** (SURVEY mode) |
Aggregated status |
3. ARCHITECTURE
3.1 Node Interior Layout
┌─────────────────────────────────────────────────────────┐
│ LIBERTARIA NODE — Zenoh-Only Architecture │
│ │
│ ZENOH KEY-SPACE (Unified Messaging) │
│ ┌──────────────────────────────────────────────────┐ │
│ │ $MEMBRANE/ │ │
│ │ defcon/current │ │
│ │ pattern/alert/* │ │
│ ├──────────────────────────────────────────────────┤ │
│ │ $SENSOR/ │ │
│ │ {geohash}/{metric} │ │
│ │ {geohash}/{metric}/history (queryable) │ │
│ ├──────────────────────────────────────────────────┤ │
│ │ $FEED/ │ │
│ │ {chapter}/world/{post_id} │ │
│ │ {chapter}/channel/{chan_id} │ │
│ ├──────────────────────────────────────────────────┤ │
│ │ $QUERY/ │ │
│ │ qvl/trust/{did} (REQ/REP pattern) │ │
│ │ health/** (SURVEY pattern with timeout) │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ COMPONENTS │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Membrane Agent │ │ Sensor Oracle │ │
│ │ (sub+pub) │ │ (sub+pub) │ │
│ └─────────────────┘ └─────────────────┘ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Feed Processor │ │ Health Monitor │ │
│ │ (sub+pub) │ │ (SURVEY queries)│ │
│ └─────────────────┘ └─────────────────┘ │
│ │
│ NOTE: Membrane Pipeline = Function calls, not sockets │
│ ┌──────────────────────────────────────────────────┐ │
│ │ fn process_frame() │ │
│ │ Stage 0: Triage (validate) │ │
│ │ Stage 1: Context (build_context) │ │
│ │ Stage 2: Decide (policy_engine) │ │
│ │ Stage 3: Commit (state_manager) │ │
│ └──────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
3.2 Transport Selection Decision Tree
Is the communication:
├── Content-defined? (sensor/berlin/pm25)
│ └── Use ZENOH (key-expression routing)
├── High-frequency + small payload?
│ └── ZENOH (4-8 byte overhead)
├── Must survive intermittent connectivity?
│ └── ZENOH (designed for this)
├── Must guarantee delivery ordering?
│ └── ZENOH with reliability QoS
└── Is it internal pipeline stages?
└── Function calls, NOT message passing
4. SECURITY: LWF ENCRYPTION OVERLAY
Zenoh does not provide native encryption satisfying Libertaria's sovereignty requirements. Use LWF encryption overlay (RFC-0000).
4.1 Zenoh + LWF
Zenoh payload structure:
┌─────────────────────────────────────────────────────┐
│ LWF Header (72 bytes) │
│ ├── Version, Frame Type, Session ID │
│ ├── Sequence, Timestamp │
│ └── Payload Length │
├─────────────────────────────────────────────────────┤
│ Encrypted Payload (XChaCha20-Poly1305) │
│ └── Contains: Zenoh binary message │
├─────────────────────────────────────────────────────┤
│ MAC (16 bytes) │
└─────────────────────────────────────────────────────┘
Key Derivation: Per-session keys from X3DH handshake (RFC-0140), rotated per RFC-0010 epoch.
5. IMPLEMENTATION
5.1 Dependencies
// build.zig
const zenoh_pico = b.dependency("zenoh-pico", .{
.target = target,
.optimize = optimize,
});
exe.addModule("zenoh", zenoh_pico.module("zenoh"));
5.2 Zig API Example
const zenoh = @import("zenoh");
// Initialize session
var session = try zenoh.open(allocator, .{.mode = .peer});
// PUB/SUB: Subscribe to sensors
var sub = try session.declare_subscriber("sensor/+/pm25", .{
.callback = onSensorReading,
});
fn onSensorReading(sample: zenoh.Sample) void {
std.log.info("{s}: {s}", .{sample.key, sample.payload});
}
// PUB/SUB: Publish reading
var pub = try session.declare_publisher("sensor/berlin/pm25");
try pub.put("42.3");
// REQ/REP: Query trust distance
var reply = try session.get("query/qvl/trust/did:libertaria:abc123");
std.log.info("Trust: {s}", .{reply.payload});
// SURVEY: Health check all modules
var replies = try session.get("health/**", .{.timeout_ms = 500});
var healthy: usize = 0;
while (replies.next()) |r| {
if (std.mem.eql(u8, r.payload, "OK")) healthy += 1;
}
std.log.info("{d}/{d} modules healthy", .{healthy, replies.total});
6. KENYA COMPLIANCE
| Metric | Value | Status |
|---|---|---|
| Footprint | ~50KB (zenoh-pico) | ✅ Compliant |
| No broker | Peer-to-peer by default | ✅ Compliant |
| Offline tolerance | Designed for intermittent | ✅ Compliant |
| C ABI | Native @cImport |
✅ Compliant |
7. MIGRATION PATH
Phase 1 (v0.5.0; ~15h): Zenoh-Pico Binding + First Channel
- Build zenoh-pico Zig binding
- Implement
$MEMBRANE/defconas first live channel - Proof: Membrane Agent publishes defcon changes
Phase 2 (v0.6.0; ~15h): Sensor + Query Namespace
$SENSOR/namespace for Sensor Oracle$QUERY/qvl/trust/{did}for trust graph queries- Pattern Detection (RFC-0115) publishes to
$MEMBRANE/pattern/alert/*
Phase 3 (v0.7.0; ~10h): Feed Integration
$FEED/namespace for Feed Social Protocol (RFC-0830)- Gossip-Relay via Zenoh-Router
Phase 4 (v0.8.0; ~10h): LWF Encryption Overlay
- Add XChaCha20-Poly1305 encryption to Zenoh payloads
- Key rotation per RFC-0010 epochs
- Security audit
8. CONCLUSION
Zenoh-only is the right knife.
The dual-plane approach (Zenoh + NNG) was architecturally valid but practically wrong for solo development. Two libraries = two failure modes = too much complexity.
The insight: Zenoh's key-space IS the LWF Service-Type Registry, only readable.
| Pattern | Old Approach | Zenoh-Only |
|---|---|---|
| PUB/SUB | z_subscribe() |
z_subscribe() ✓ |
| REQ/REP | NNG REQ/REP | z_get(specific_key) ✓ |
| SURVEY | NNG SURVEY | z_get(wildcard, timeout) ✓ |
| PIPELINE | NNG PIPELINE | Function calls ✓ |
One library. One namespace. One mental model.
The submarine does not merely transport messages. It thinks through them.
APPENDIX: COMPARISON WITH v0.1.0
| Aspect | v0.1.0 (Dual-Plane) | v0.2.0 (Zenoh-Only) |
|---|---|---|
| Libraries | 2 (Zenoh + NNG) | 1 (Zenoh) |
| Mental Models | 2 | 1 |
| Failure Modes | 2 | 1 |
| Update Cycles | 2 | 1 |
| Pipeline | NNG PIPELINE | Function calls |
| Complexity | Higher | Lower |
| Solo-Dev Fit | Poor | Excellent |
APPENDIX: DEPENDENCIES
Zenoh-Pico
- URL: https://github.com/eclipse-zenoh/zenoh-pico
- License: EPL-2.0 OR Apache-2.0
- Zig binding: Direct C API via
@cImport - Footprint: ~50KB
End of RFC-0910 v0.2.0
The submarine's nervous system is unified. 🜏