Kibitz

← The Kibitz Engine · deep dive

Kibitz Architecture

Kibitz is an account-free, serverless, peer-to-peer call + collaboration engine — an embeddable widget and a headless controller. This is the system overview the other docs slot into.

See also: verification.md (who gets in), agent-platform.md (agents in a room), threat-model.md (what's protected).


1. The shape

2. The room model

A room id (normalized from the link) maps to a deterministic signaling id. The first peer to claim it becomes the coordinator; later peers join as participants. The coordinator is positional, migratory plumbing — it keeps the roster, runs presence ping/reap, relays signaling (including the lobby/lock/host-command channel), and applies the verification gate. It has no discretionary moderation power of its own and is just a participant's browser; if it leaves, the role migrates to another peer (heartbeat + reclaim — room.ts becomeAuthority), and the new coordinator rebuilds the roster and inherited settings from what it took over.

Admin is a separate role. The waiting room (admit/deny knocks), lock/unlock, kick, and reset belong to a verified host, decoupled from the coordinator. This fixes the "coup" (a stranger who became coordinator could otherwise seize moderation) and "bans vanish on migration". The link can commit a host identity at one of four tiers:

The host role does not migrate with the coordinator: on migration (and on the host's own disconnect) the host slot resets to empty until someone re-proves it (room.ts:360-364, verifiedHostId = '' :647). A committed key disables the weaker name/email tiers. See verification.md.

Presence runs as a star to the coordinator (each participant ↔ coordinator); content does not (next section).

3. Three transport planes

Plane Carries Topology Encryption Helper
Signaling / presence room join, roster, lobby/lock/kick, gate announces star → coordinator (PeerJS) WSS/TLS to the broker self-hosted broker signal.kibitz.chat, with automatic fallback to the public PeerJS broker
Media audio / video / screen full WebRTC mesh DTLS-SRTP, E2E Cloudflare TURN when direct fails
Data chat, co-browse, directed messages, agent envelopes full DTLS data mesh DTLS, E2E own DTLS DataConnections, reconciled over the same roster

Each participant actually runs on two separate PeerJS peers: a presence connection to the coordinator, and a dedicated media peer whose id is the participant's voiceId carried on the roster (callMedia.ts peerJsMedia; room.ts:34-38). The data mesh is its own set of DataConnections, dialled in parallel to the media MediaConnections and surviving media re-dials — it is not the same connection set (mesh.ts createVoiceMesh, dataDialled :209-255). Which signaling broker the call uses is chosen dynamically but consistently across participants: /api/signal reports the self-hosted worker when healthy, else the public PeerJS broker, so two peers always meet on the same broker (signalConfig.ts).

The crucial property: media and data are a peer-to-peer mesh, end-to-end encrypted — no participant (not even the coordinator) relays content, and there is no media server that could decode or record it. The broker sees only presence metadata; TURN forwards encrypted packets it can't read. (See threat-model.md.)

A TURN relay (Cloudflare Realtime) is used only when two networks can't connect directly; it forwards the still-encrypted call. A per-browser "hide my IP" toggle routes your media and data through TURN only (relayOnlyiceTransportPolicy:'relay', fail-closed — no reachable TURN means no connect, never a silent direct fallback), so peers see the relay's IP, not yours (relayPref.ts, callMedia.ts:28-32).

Note that relayOnly uses only relay candidates, so it relays even when the two peers are on the same LAN — a direct local connection would hand the other peer your LAN IP, which is exactly what the toggle exists to prevent. ('relay' is all-or-nothing; WebRTC has no "direct-on-LAN-only" policy.) Without the toggle, same-LAN peers connect directly over host candidates — having TURN configured never forces its use, it's only a fallback when no direct path forms. For a call that should stay local and direct with no relay at all, use the offline / same-Wi-Fi hub (iceServers: [], below).

Resilience

4. The composable engine

mount(opts) boots the engine and returns a controller (MountedWidget):

The Widget UI is just one consumer of this controller. The same controller powers the Whist reference game (headless, draws its own table) and the Agent SDK. An in-memory transport (createLocalBus) runs the real presence engine with no network, for deterministic tests.

5. Identity & verification

Two independent, composable layers, both peer-to-peer:

Who may enter a room is the verification gate: the link carries a verifier, the coordinator checks credentials before rostering.

6. Participant capabilities

Once in, what each participant may do is itself scoped — a general per-participant permission model (humans and agents), not an agent-only bolt-on. Each participant carries a Grant of what it may perceive (content that flows to it) and act (what it may emit):

Defaults are by kind (meta.role): a human is full; an agent is read-onlyread-chat/read-roster/receive-directed, no act, no media. The host can widen or revoke any grant live, with a per-agent consent panel + a local-only audit feed (blocked acts + grant changes). The model is pure and serializable (core/capabilities.ts).

The engine enforces it per-peer (not the app — there's no server to police it):

Disclosure: an agent may declare its model backend and whether what it perceives **egress**es the E2EE room — shown to the host, never a privilege it grants itself. See agent-platform.md.

7. Edge infrastructure

All stateless and content-blind, on Cloudflare:

Optionally, a room can require a per-minute agent network-access credit — a signed, short-lived credential an agent re-presents ~every minute; the coordinator verifies it and reaps an agent whose credit lapses (room.ts:122-134, requireAgentCredits :203). See agent-platform.md.

Offline / LAN hub (separate, user-run, optional): a tiny Go relay published as the kibitz-offline project routes WebRTC handshakes on one Wi-Fi so a call needs no internet at all. Content-blind like the cloud helpers — see offline-mode.md.

The project is operated pseudonymously; nothing requires an account or holds call content.

8. Surfaces an integrator uses

9. Non-goals