OverviewFilesCommitsIssuesDocsReleasesPeers

hyperswarm-peer commits / 8c1e7e2

docs: write the swarmd design down before it is lost
The generic C/C++ daemon has no code, and until now the design existed only in a conversation — sketched, then revised substantially, then corrected again. In a long session that is effectively lost. Records what it is (a SWARM daemon: connectivity, Protomux channels, content-addressed transfer) and what it is not (not a hypercore daemon — there is no Hypercore replication in C anywhere; libhc's only `replicate` symbol is a namespace-hash derivation). The substantive revision since the first sketch: do not hand-roll the protocol. libcompact is the codec we hand-wrote, hyperschema-c generates the structs, hrpc-c generates typed sans-io RPC over the bare-rpc wire, librpc is the runtime — all C, no JS at runtime. And the correction on top of that: they generate protocols WE define; they cannot generate dht_messages.h, because hyperdht hand-writes its own wire. Also carries the defect session-daemon has that a generic daemon must not: the UDX thread writes to non-blocking client sockets unchecked, so a partial write desynchronises the client's stream rather than dropping a frame. No code. §9 is the order; §8 the gates; §10 the parity work that makes the transport worth building on. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
8c1e7e2cb436c0ba7935ed4f984d8c5e336563b5
by plankatron <fleeky@gmail.com> · 2026-09-10T17:00:30.000Z

Changed files (1) +161 -0

docs/SWARMD-DESIGN.md +161 -0
……@@ -0,0 +1,161 @@
1# swarmd — a generic C/C++ swarm daemon
2
3**Status: DESIGN. No code exists.** Written 2026-09-10 so the design survives the
4session it was worked out in. Every upstream claim here was read on disk or via
5the GitHub API that day; where something is unverified it says so.
6
7## 1. What it is, and what it is NOT
8
9⛔ **NOT a hypercore daemon.** It cannot offer feeds, Hyperbee, Corestore or
10Autobase — those exist only in JS. Verified: `libhc` has
11`core/crypto/encodings/hashes/keys/merkle_tree/store` and its only `replicate`
12symbol is `hc_hashes_replicate`, a namespace-hash derivation for a handshake.
13**There is no Hypercore replication in C anywhere.** A C program needing a real
14hypercore must talk to a Bare daemon.
15
16**It IS a swarm daemon**, exposing the three things hyperswarm-peer owns:
17
18- peer connectivity (HyperDHT, NAT traversal, Noise)
19- multiplexed protocol channels (Protomux)
20- content-addressed bulk transfer (liboverlay, hyper-overlay/v2)
21
22Pure C/C++. **No JS at runtime.** (Code generators run under Node at BUILD time;
23their output links C libraries only.)
24
25## 2. Why it exists
26
27`session-daemon` is the embryo and its own header already says the right thing:
28*"NOT BLENDER-SPECIFIC ON PURPOSE — exposing a Protomux channel over a swarm
29topic to a local socket has nothing to do with Blender."* But it is hardcoded to
30one topic, one protocol name, one slot, and it has no control plane.
31
32The daemon is the **generic seam**: Blender must have one (a shipped Blender
33extension is pure Python — `multi-user` 0.8.2 deleted all nine of its pyzmq
34wheels for exactly this reason). Godot editor and runtime don't need one but can
35use one. Any authoring tool with a Python-only plugin API uses it unchanged.
36
37## 3. ⛔ Do not hand-roll the protocol — generate it
38
39The first sketch of this design hand-rolled length-prefixed frames, a
40hand-written type+id header, and hand-written compact-encoding. All three
41already exist:
42
43| layer | use | not |
44|---|---|---|
45| codec | **`libcompact`** — ABI-identical to the JS codec | our hand-written `src/compact_encoding.h` |
46| structs | **`hyperschema-c`** — emits `preencode/encode/decode(compact_state_t*)` | hand-typed message structs |
47| RPC | **`hrpc-c`** — typed sans-io C over the bare-rpc wire, five handler kinds | a hand-rolled type/id header |
48| runtime | **`librpc`** | — |
49
50`hrpc-c` is *sans-io*: "it never owns a socket, an event loop, or stream state",
51so it composes with a UDS, a Protomux channel, a pipe.
52
53⛔ **These generate protocols WE define. They cannot generate `dht_messages.h`** —
54hyperdht has no `spec/`, no hyperschema dependency, and `lib/messages.js` opens
55`const c = require('compact-encoding')`. It hand-writes its wire exactly as we
56do. Matching hyperdht stays a hand-written port held to vectors.
57
58⛔ **Two memory rules from hrpc-c:** every `*_encode_*` heap-allocates and the
59CALLER frees; decoded views point INTO the inbound frame buffer and must be
60copied out before it is reused.
61
62## 4. Architecture
63
64 main thread UDS accept, per-client I/O, event fan-out, status
65 | queues (no lock held across a write)
66 UDX thread uv loop, udx, protomux, DHT, SwarmManager, overlay
67
68⛔ **The UDX thread must never write to a client socket.** `session-daemon` does
69today, unchecked, on non-blocking fds:
70
71 if (write(fd, hdr, 4) != 4) continue;
72 if (write(fd, data, len) != (ssize_t)len) continue;
73
74A partial write there does not drop a frame — it desynchronises the client's
75stream, and the client then reads a garbage length and closes. A slow local
76client silently corrupts its own session. The UDX thread enqueues; the main
77thread owns every fd, buffers per client, and drops a client that exceeds its
78high-water mark rather than writing partially.
79
80## 5. Surface
81
82Two planes over one socket. Control is request/response (hrpc-c unary); data is
83broadcast (hrpc-c events) — a grab is not a call, nobody replies.
84
85**Session:** `HELLO {name, app_identity(32), version} -> {client_id, daemon_pubkey}`,
86`STATUS_GET -> {dht_state, bootstrapped, peers_known, peers_connected, connecting, queued, local_clients}`
87
88**Swarm:** `SWARM_JOIN/LEAVE {topic(32), server, client}`, `PEER_ADD/REMOVE {pubkey(32)}`
89
90**Channels:** `CHANNEL_OPEN {protocol, id, slots} -> {channel}`, `CHANNEL_CLOSE`,
91`SEND {channel, slot, target, bytes} -> {accepted, writable}`
92
93**Content:** `CONTENT_HASH {path} -> {hash}`, `CONTENT_SERVE {hash, path}`,
94`CONTENT_FETCH {hash, dest_path}`, `CONTENT_CANCEL {hash, discard}`
95
96**DHT storage:** `DHT_PUT/GET`, `DHT_MUTABLE_PUT/GET`
97
98**Events:** `PEER_UP {peer, pubkey, topics[]}`, `PEER_DOWN`,
99`MESSAGE {channel, slot, origin_kind, origin_id, bytes}`, `DRAIN {channel}`,
100`STATUS`, `CONTENT_PROGRESS/DONE/FAILED`, `DHT_RESULT`
101
102`origin_kind` separates a remote peer from another local client — that is what
103lets a client suppress its own echo, which is impossible today.
104
105## 6. Bulk bytes never cross the socket
106
107A `.res` is 108 MB. **Content moves as files**: the overlay writes to a path, the
108client opens the path, the socket carries a 32-byte hash. So the daemon owns a
109content directory, which is also what makes a fetch resumable across restarts.
110
111## 7. What session-daemon lacks
112
1131. per-client output buffering + a high-water policy (§4)
1142. backpressure that survives the trip — Protomux already returns writability and
115 its header says discarding that answer "left every protocol above with no way
116 to avoid burying the connection". `SEND_RESULT.writable` + `DRAIN` carry it
1173. liveness — heartbeat `STATUS` plus socket close. #72: the panel said Connected
118 while the daemon was gone
1194. client ids and app identity, for echo suppression and membership later
1205. a stable transport identity (a stored seed, not a fresh keypair per start)
1216. discoverability — register the socket by name, as pgh-sidecar's `app-lookup` does
1227. a C++ client library. "Any program in C++" is the goal; today the only client
123 is Python
1248. Zig-built, pgh-published multi-arch. A plain g++ build will not load on ashatron
125
126## 8. Gates — all headless, no Blender, no Godot
127
128| # | asserts |
129|---|---|
130| D1 | two daemons on the PUBLIC DHT, two C++ clients, a frame both ways |
131| D2 | a client that stops reading is dropped; survivors' streams stay intact |
132| D3 | `SEND` past the high-water mark returns `writable=false`, `DRAIN` follows |
133| D4 | two channels on one connection, neither starves the other |
134| D5 | `PEER_DOWN` on channel close, not on timeout |
135| D6 | a 100 MB file crosses by hash and no frame exceeds the cap |
136| D7 | `STATUS` reports `dht=down` when the DHT is down |
137
138D1 and D7 are the gates #68 proved the suite lacked: every existing
139cross-machine test passes `--bootstrap` at a local testnet, the one configuration
140the products never use.
141
142## 9. Order
143
1441. `libcompact` replaces `src/compact_encoding.h` — useful alone, no schema needed
1452. define the schema; generate structs (`hyperschema-c`) and RPC (`hrpc-c`)
1463. the daemon skeleton: threads, UDS, per-client buffering (§4)
1474. control plane, then channels, then content
1485. the C++ client library
1496. port the Blender addon off `session-daemon`, and retire it
150
151⚠ Open decision: whether `swarmd` lives in hyperswarm-peer (where its includes
152are, making it that repo's first product rather than a library) or its own repo.
153
154## 10. Ground it stands on
155
156The transport was not trustworthy when this was first sketched. As of
1572026-09-10 (see `PARITY-hyperswarm.md`): the #72 wall is closed and verified on
158the public DHT by mutation drill; the announce/unannounce signable, a
159libudx back-pointer corruption, a 30s connect hold and the self-announce are
160fixed and gated; holder recording and liveness are verified against a real
161testnet; and our connect-failure rate is measured equal to hyperswarm's own.