SessionNode
SessionNode: the Session.Ops network transport. A protocol's @[endpoints] roles on two nodes talk over one peer connection (distributed actors, step 4 of specs/progress/2026-09-15-remote-actor-dispatch.md).
The transport is protocol-independent: Session.Ops speaks in endpoint ids (Int) and encoded messages (Bytes, the generated <P>_Msg.encode), so nothing here names a protocol's constructors and nothing needs generating. What a node writes:
let link = SessionNode.open(conn, "node-a", false, fn ep -> ...) -- after connect_split / accept_split
let s = Session.attach(io, SessionNode.ops(link))
let st = Stream_Prod.register(s, 0)
... drive the role to its first suspension ...
SessionNode.serve(link) -- deliveries, until the peer's endpoint closes
SessionNode.finish(link)On the wire, per peer connection (the control/data split): data ACTOR_MSG "SessionNode.Deliver" [to, from, msg], through a NodeQueue (credit-based flow control); "SessionNode.Bye" when an endpoint closes. control "SessionNode.Hello" (the endpoint actor's GlobalPid; the ACCEPTING side announces first), CREDIT, and a final Bye.
Receiving: a delivery is sent to this node's endpoint actor, whose turn runs the continuation suspend installed. One actor, so one node's resumptions (and prints) run in mailbox order, which is the protocol's. "Consumed" for credit is "in the mailbox".
Types
Functions
Tear the link down: close data, say Bye on control, await the control reader, close control.
Open the transport on a split peer connection (ClusterConn.connect_split / accept_split). Spawns this node's endpoint actor, exchanges endpoint pids on the control connection (the accepting side, [accepted] = true, announces first), starts the data connection's NodeQueue and the control reader. [on_close] runs when a local endpoint closes, with its endpoint id.