Skip to content

2026-05-16 (Stone C revision) — Q2 settled-revised: ONE `ThreadPeer<I, O>` type with typ…

Forward-correcting a prior design decision per feedback_inscription_immutable — past INTERSTITIAL entries called for two distinct types (Thread/Client<I, O> + Thread/Server<I, O>) generated from one logical Thread<I, O> declaration. That was the answer that “fell out” of the design conversation; user surfaced 2026-05-16 (post-arc-198 closure) that the simpler answer was sitting right there.

“why isn’t it just a (:wat::kernel::Thread/println peer data) and (:wat::kernel::Thread/readln peer)… the server-ness and client-ness isn’t relevant?… we need a new type who holds the appropriate ends of the pipe pair?.. client = (rx, tx), server = (tx, rx)… a ThreadPeer?… we provision the pipes and then assign the appropriate pipes positions to the peer instance?.. making a thread needs two peer instances who cross communicate?…”

The answer: yes. The Client/Server role is CONCEPTUAL — the structure is identical on both sides (a pipe pair with a write end + a read end). The “side” is encoded by which type-parameter binding each peer instance gets.

:wat::kernel::ThreadPeer<I, O>
;; I = "what I read (input to this peer)"
;; O = "what I write (output from this peer)"
;; ONE verb family, peer-relative:
(:wat::kernel::Thread/readln peer) -> :I
(:wat::kernel::Thread/println peer data) -> :wat::core::nil ;; data : O

For a Request/Reply protocol:

  • Server peer: ThreadPeer<Request, Reply> — reads Request, writes Reply
  • Client peer: ThreadPeer<Reply, Request> — reads Reply, writes Request

Both peers are instances of the SAME struct with mirror type-parameter bindings. The substrate bracket wires two pipes, constructs both peer instances with the appropriate type params, hands each to its respective fn.

Two distinct types (Client/Server)Single ThreadPeer with swap
ObviousMarginal — explicit roles, but ceremonyYES — peer is peer; side is param swap
SimpleNO — two generators per declaration; two verb familiesYES — one struct; one verb family
HonestMarginal — naming difference; structure identicalYES — names the structure (pipe pair); roles are conceptual
Good UXMarginal — verboseYES — fewer concepts to learn; same verbs on both sides

Single type wins YES YES YES YES. Previous Client/Server framing failed on simple — substrate would have minted two type-generators per logical declaration, two verb families per type, more surface area for users to learn. The single-type-with-swap is the correct shape.

Process server has ambient stdin/stdout (one stdio per OS process). So:

  • :wat::kernel::ProcessPeer<I, O> — client-side wrapper around (Process/stdin, Process/stdout)
  • Process server uses ambient (readln) / (println) — no peer struct needed
  • ONE ProcessPeer type, only instantiated on the parent (client) side

Thread is symmetric (two peers cross-wired); Process is asymmetric (client gets a peer; server uses ambient). The asymmetry is honest — it reflects the substrate primitive difference (Thread channels vs OS process stdio).

Per the prior STONES.md draft: “120-180 min sonnet; type-system work is fiddly.”

Post-revision:

  • ONE ThreadPeer<I, O> substrate type + 2 verbs
  • ONE ProcessPeer<I, O> substrate type + 2 verbs (mirror)
  • Bracket setup mechanics (substrate-internal pipe wiring + peer construction)
  • Tests for both type minting + verb dispatch

~60-90 min total. Decomposable into C1 (Thread family) + C2 (Process family) per the arc 198 slice 2 lesson: small bounded stones beat one-shot multi-piece changes.

Design correction committed. STONES.md updated with the revised Stone C scope. Future-me reads this section and knows: one peer type per unit, mirror type-param bindings encode side, ambient-stdio asymmetry on Process is real.