Connections
Connections are how peers reach each other. In kuilt, each connection implementation is called a fabric.
Every fabric is wrapped in the same Loom/Seam contract, so from your app's point of view the workflow stays the same. Your app logic never needs to know whether peers are connected by WebSocket, LAN discovery, or direct radio.
Pick a connection path by deployment shape
Use WebSocket when you want the fastest path to cross-platform connectivity.
Use mDNS when peers need local-network discovery before connecting.
Use direct device links when you want peer-to-peer connectivity without a relay server.
WebSocket fabric (kuilt-websocket)
WebSocket is usually the quickest path to a cross-platform session. Setup is role-split (server accepts, client connects), but once connected both peers use the same Seam API. The split is:
KtorServerLoom— JVM/Android. Supports onlyhost();join()throws.KtorClientLoom— all targets. Supports onlyjoin();host()throws.
Server:
Client:
The advertisement includes the server's PeerId, so both ends get the same membership view without an extra handshake message.
mDNS discovery (kuilt-mdns, JVM/Android)
mDNS helps peers find each other on a local network. It is discovery, not transport: the session still runs over WebSocket. MDNSPeerLinkFactory registers an mDNS service on host() and resolves an MDNSAdvertisement to a WebSocket join on join(). Discover peers separately with MDNSServiceDiscoverer:
Limit your collection with a timeout or take(n), because discoveries() keeps emitting.
Direct device links
If you want direct device-to-device links — nearby phones talking to each other with no server in the middle — use one of the peer-to-peer fabrics. They implement Loom like any other fabric, so replacing InMemoryLoom with one of these leaves your app code unchanged. Each uses the same instance for host and join, because every peer both advertises and looks for others (one in-process mesh).
kuilt-nw(iOS/macOS) — Apple's Network.framework. This is the Apple fabric to reach for. Every peer advertises, browses, and dials, and the redundant double-dial is folded into one link. It needs aPattern.roomKey: the key becomes the shared secret that encrypts the link, and a session opened without one is refused.kuilt-nearby(Android) — Google Nearby Connections.kuilt-multipeer(iOS/macOS) — Apple Multipeer Connectivity. Superseded bykuilt-nw; prefer that for new code.
kuilt-webrtc (wasmJs) provides a WebRTC data-channel fabric. WebRTC sessions need signaling, but that stays inside the fabric implementation — callers only see Loom/Seam.
Writing your own fabric
When your transport is not packaged yet, implement Loom (and a private Seam) and prove it behaves like every other kuilt fabric by subclassing SeamConformanceSuite.
Why this matters: conformance tests keep your custom fabric from surprising the layers above it.
newLoomPair() returns (hostLoom, joinerLoom). In-process radio fabrics return the same instance twice (shared mesh). Role-split fabrics (WebSocket, mDNS, WebRTC) return distinct host and joiner instances wired together. The suite runs host() and join() concurrently, which matters for WebSocket-style fabrics where host() suspends until a client connects.
The suite tests:
weave(Rendezvous.New(...))returns aSeamwith a non-emptyselfId.broadcastandsendTodeliver frames and stampsender.peerstracks membership.incomingis single-collection and ordered.close()is idempotent.capability()(and theavailability()it derives) returns sensibly.
Keep real-network smoke tests in a separate test that is opt-in (e.g. -Pmy.fabric.integration.tests=true) so the conformance suite stays fast and deterministic.
Tag and custom discovery
Tag is an open interface. Each fabric defines its own (WebSocketAdvertisement, MDNSAdvertisement, …). A custom fabric provides a Tag with whatever its join() call needs.
The membership layer (kuilt-session)
Seam is pure transport — peers reflects whoever the wire says is connected. When your product needs room semantics (identified members, host role, reconnect behavior), add kuilt-session.
SeamRoomFactory wraps any Loom and produces Rooms with an admit/identify handshake, a roster of admitted members, reconnect tokens, and partition detection:
Because SeamRoomFactory accepts any Loom, the same code runs over InMemoryLoom in tests and over WebSocket or mDNS in production.