Peer-to-peer distributed systems

Across any network. No server holding the truth.

Most of what is called peer to peer keeps a server behind it — to introduce the devices to each other, to hold what they agree on, and to pass messages along when they cannot reach each other directly. Take the server away and the application stops.

Here every device keeps its own journal of what happened, and devices find each other by a cryptographic identity rather than by an address. The network helps them find a path to each other. When a direct path isn't available, a relay forwards packets it cannot read.

No server holds the truth, because the truth is already on the devices.

Get the code

What it does

What Puppeteer gives the application

The substrate we build on: every device keeps its own journal of what happened, and each decision has one writer.

  1. A transport that can change under the rules

    The rules know nothing about devices or networks, so what carries the messages can be replaced without changing a line of them.

    See what changed, and what did not
  2. The same logic in an app and in a browser

    The same rules and the same subject run in a native Android app and, compiled for the browser, in a page that installs like one.

    See the phone and the browser
  3. Nothing stops when one device goes down

    Every device holds its own part of the history, so the others carry on: players keep their game and sellers keep selling.

    See a player with the organizer down
  4. One authority where it matters

    Each decision has one writer, so no two devices sell the same item or award the same prize; a sale made while it was away is checked when it is back.

    See a sale go to the organizer
  5. Devices that were away catch up, in order

    A device that joins late, or comes back after a drop, receives what it missed in the order it happened; nothing is reconciled by hand.

    See a phone come back

What iroh gives the connection

The transport, reached through an adapter we wrote. The rules and the journals above do not depend on it.

  1. Devices that find each other by identity

    A device is its public key, not its address: nothing is opened on a router, and the link is encrypted end to end, even through a relay that forwards packets it cannot read.

    See the transport
  2. A room with no internet still works

    Phones on the hall's network reach each other directly; nothing outside the room has to be reachable.

    See the hall with no internet
  3. Connections that survive a change of network

    Wi-Fi to mobile data changes the path, not the device: the session carries on, or picks up where it left off.

    See a change of network

Where it fits

Three examples. What they have in common is a shape, not an industry: a group of devices that has to agree on something while the network cannot be relied on.

  1. Live events in a venue

    Run

    Draws, raffles, auctions, quizzes, votes. The room is the network, and the evening does not depend on anything outside it.

    See it run: The Village Hall
  2. Pop-up sales and markets

    Several sellers and one inventory: each sale has one writer, so no two devices sell the same item — and a seller keeps selling while that writer is out of reach.

  3. Field work with patchy coverage

    Teams keep working through dead zones and catch up, in order, when a path comes back.

These are examples and not a list. The capabilities above are general; what changes from one case to the next is how much of the network you can count on.

Built on Puppeteer and iroh

Three layers

  1. The rules

    The game, the sale, the prize, in plain .NET: it knows nothing about Puppeteer, devices or networks.

    No devices · no networks · no Puppeteer

  2. The subjects

    Puppeteer gives each subject an identity and a journal. Its Stage Manager keeps one writer per decision and replicas that replay its history; a tell carries a fact from one subject to another.

    Identity · journal · replay

  3. The transport

    An adapter carries the messages. Here it is iroh over QUIC; elsewhere the same model runs over sockets on a local network or a broker in a data center.

    Carries messages

    • Hereiroh over QUIC
    • ElsewhereSockets on a local network
    • ElsewhereA broker in a data center
How one ball reaches players in the hall and at homeThe organizer of one bingo tells each player the same ball, once per link: to a player in the hall over the local network, to a player at home directly, through NAT traversal, when the network allows it, and to another player at home through a relay that forwards packets it cannot read. Every device has its own key, which is its identity, and its own journal. What travels is sealed: the number is readable only at the two ends. Every tell travels over iroh, on QUIC, encrypted end to end. When the direct path to a home is cut, the same link goes through the relay, by the same key, and no ball is lost. Many faint phones stand for the rest of the players in the hall.Ball 42 · told once per link: three sealed copies, not a broadcastTHE HALL · ONE WI-FIINTERNETdirect, if the network allowsno direct pathRELAYforwards, cannot readthrough the relay, ifthere is no direct path42Organizerthe one writer42Playerin the hall42Playerat home42Playerat homea tell over iroh, on QUIC: one per playersealed end to end: only the two ends can read itidentity: a public key, not an addressits own journal, on every device

Ball 42 · told once per link: three sealed copies, not a broadcast

How one ball reaches players in the hall and at homeThe organizer of one bingo tells each player the same ball, once per link: to a player in the hall over the local network, to a player at home directly, through NAT traversal, when the network allows it, and to another player at home through a relay that forwards packets it cannot read. Every device has its own key, which is its identity, and its own journal. What travels is sealed: the number is readable only at the two ends. Every tell travels over iroh, on QUIC, encrypted end to end. When the direct path to a home is cut, the same link goes through the relay, by the same key, and no ball is lost. Many faint phones stand for the rest of the players in the hall.THE HALL · ONE WI-FIINTERNETdirectno direct pathRELAYforwards, cannot read42Organizerthe one writer42Playerin the hall42Playerat home42Playerat homea tell over iroh, on QUIC: one per playersealed end to end: only the ends read itidentity: a public key, not an addressits own journal, on every device

The transport can change without changing the domain or the subject model. In September it did: the transport moved from HTTPS between phones to iroh, and the game did not change a line. Its domain tests never mention a network.

iroh is the adapter we chose for phones across networks. The adapter is ours; the model does not depend on it.

Trust here is not a promise anybody makes. A device is its own key, a relay forwards packets it cannot read, and a journal can be replayed by anyone holding it. What has to be trusted is cryptography and a record — not a party, and not us.

What Puppeteer is
One stack, where only the bottom layer changesOne device with three layers: the game's rules, a Puppeteer subject with an identity and a journal, and the transport. The transport takes three forms: HTTPS between phones until September, iroh since September, and in a browser a channel the page opens, where the page asks for each ball. The rules and the subject are the same code in all three and never move. Each ball comes in through the transport, is written in the subject's journal and marks the card.Since September: iroh underneath · the rules and the subject did not change a lineThe rulesthe game, the sale, the prize42The subjectPuppeteer: an identity and a journalHTTPSbetween phonesirohdialed by its keyA channel the page opensin a browser, the page asksthe same codein all threeHTTPSiroh · QUICasksOrganizerthe one writeruntil Septembersince Septemberin a browser

Since September: iroh underneath · the rules and the subject did not change a line

One stack, where only the bottom layer changesOne device with three layers: the game's rules, a Puppeteer subject with an identity and a journal, and the transport. The transport takes three forms: HTTPS between phones until September, iroh since September, and in a browser a channel the page opens, where the page asks for each ball. The rules and the subject are the same code in all three and never move. Each ball comes in through the transport, is written in the subject's journal and marks the card.The rulesthe game, the sale, the prize42The subjectPuppeteer: an identityand a journalHTTPSbetween phonesirohdialed by its keyA channel the page opensin a browser, the page asksthe samecode inall threeHTTPSiroh · QUICasksOrganizerthe one writeruntil Septembersince Septemberin a browser
Fault tolerance

What keeps working when a piece goes down.

Every phone holds its own part of the evening, so one piece going down does not stop the others.

Organizer down · the card stays on the screenOrganizerdownPLAYERkeeps showing

Organizer down · the card stays on the screen

OrganizerdownPLAYERkeeps showing
The organizer's phone goes downPlayers keep their gameCards and the balls called so far live in the player's own journal, so the screen keeps showing them, and a browser can even reopen them. It catches up when the organizer is back.Emulators
Organizer down · sales are kept on the phoneOrganizerdownhanded overwhen it is backSELLERkept on its phone

Organizer down · sales are kept on the phone

OrganizerdownSELLERkept on its phone
The organizer's phone goes downSellers keep sellingEach sale is written on the seller's own phone and handed over when the organizer is back, where it is checked like any other.Emulators
No internet · balls go over the hall's networkTHE HALLOrganizerPlayerPlayerINTERNETnot needed

No internet · balls go over the hall's network

THE HALLOrganizerPlayerPlayerINTERNETnot needed
The hall has no internetThe hall keeps playingPhones reach each other over the hall's own network, even after the organizer restarted without internet, and the balls reached them within two seconds.Emulators
What has been run

An evening across four homes.

The round · 23 Sep 2026

  • Four homes
  • Four different networks
  • Organizer in an emulator on a PC
  • No port forwarding
  • No tunnel
  • Wi-Fi → mobile data, mid-round
  • Back as the same device
  • No private network
Five devices through the live round of 23 September, as the organizer saw themOne row per device: four phones in four homes and a control emulator that reached the organizer over mobile data only. Each bar is a link to the organizer. The phone in the first home held a single link for 22 minutes 16 seconds, through all 69 balls. The others dropped and came back, and every return came back with the identity it had left with.12:0012:0512:1012:1512:2069 balls · 12:02–12:18Home Aone link · 22 min 16 sHome BHome Csame identityHome DControlemulator, data onlyEach row is one device. Each bar is one link to the organizer.Five devices through the live round of 23 September, as the organizer saw themOne row per device: four phones in four homes and a control emulator that reached the organizer over mobile data only. Each bar is a link to the organizer. The phone in the first home held a single link for 22 minutes 16 seconds, through all 69 balls. The others dropped and came back, and every return came back with the identity it had left with.12:0012:0512:1012:1512:2069 balls12:02–12:18Home Aone link · 22 min 16 sHome BHome CHome DControlemulator,data onlyEach column is one device.Each bar is one link to the organizer.same identity
What changes when a phone moves from Wi-Fi to mobile dataA remote player's phone carries an identity that does not change. Its address changes from home Wi-Fi to a carrier address, and the path it takes to the organizer changes with it, passing through a relay that acts as a meeting point. The connection itself is one unbroken line from the player to the organizer.Remote playeridentityhome Wi-Ficarrier addressthe address changesrelaymeeting pointsame connection, new pathsame connection,new pathOrganizer

Measured on Android emulators. In the round itself, phones that dropped came back as the same device, as the timeline above shows.

Different network. Same night.

A phone on a home Wi-Fi joins an organizer it has no network route to: the organizer runs inside an emulator, behind two layers of NAT. Through the transport adapter we wrote for Puppeteer, iroh finds the organizer by its identity; the player signs up over that link, gets its card and marks the next ball half a second later.

Galaxy A54 on home Wi-Fi and an Android emulator · 25 Sep 2026 · one pause of about two seconds cut

Measured, not projected

What was measured, and on what.

Eleven results, each with the device it was taken on and the day it was taken. Four were taken on emulators and two come from a prototype rather than the published web client, and each of those says so.

WhatResultMeasured on
Phones on different home networksFour phones on four networks over QUIC through iroh, with no port opened on any routerAndroid phones · 23 Sep 2026
Longest single connection in a live round22 minutes, through all 69 drawsAndroid phone on a home network · 23 Sep 2026
Every draw of the live round, over mobile data onlyEmulator69 of 69 reached the control device, which could reach the organizer only over mobile dataAndroid emulator · 23 Sep 2026
Wi-Fi to mobile dataEmulatorNext event in the same second; six seconds after two switches in a rowAndroid emulators · 23 Sep 2026
A room with no internetEmulatorEvents delivered in zero to two seconds over direct pathsAndroid emulators · 23 Sep 2026
Back after an outageEmulatorReconnected by itself 10 to 52 seconds after the network returnedAndroid emulator · 23 Sep 2026
The browser as a peerPrototypeEvents pushed over a channel the browser opens outward, 58 to 237 ms round tripGalaxy A54, Chrome 153 · 23 Sep 2026
The browser back from the backgroundPrototypeReconnected 4 and 12 seconds after returning to view — the handshake itself takes half a second — and received the 13 and the 8 draws it had missedGalaxy A54, Chrome 153 · 23 Sep 2026
The web clientInstalls as an Android app over HTTPS, and from there reads the organizer over the local networkGalaxy A54, Chrome 153 · 24 Sep 2026
The web client with its host unreachableOpened from its cached copy and kept talkingDesktop Chrome 153 · 24 Sep 2026
First visit of the web client11.6 MB gzipped — 9.6 MB of it the runtime and the engine, and 0.15 MB the pageBuild output · 24 Sep 2026

Every row above has a raw output behind it, with the hour it was taken and the command that produced it.

What to plan for

  • A browser cannot accept incoming connections; it has to open the channel itself.
  • Phones freeze apps in the background within seconds, so plan for reconnecting and catching up, not for a connection that stays.
  • Across networks a relay is needed when no direct path exists — a public one or your own; inside one room it is not.
  • Safari on iPhone will not let a page served over HTTPS reach devices on the local network.
  • The behaviour holds on the devices we tried. Scale is the next question.

Questions

How do I build a peer-to-peer app without a server?

Give every device its own journal and let the devices exchange facts directly. One subject writes each decision and its replicas share the history, so no server has to hold the truth.

Can peer-to-peer apps connect across different networks without port forwarding?

Yes. Devices identify each other by a public key rather than an address and connect over QUIC through iroh, with NAT traversal first and a relay that cannot read the traffic as the fallback.

What happens when a phone switches from Wi-Fi to mobile data?

The device keeps its identity: on emulators the connection carried on over the new path; on phones in a live round, the link dropped and came back as the same device, and caught up.

Can a peer-to-peer app work on a local network with no internet?

Yes. Devices in the same room reach each other over direct local paths, and nothing outside the room takes part.

How do devices catch up after being offline?

They receive what they missed, in the order it happened. A device that joins late receives everything that already happened.

Is peer-to-peer traffic encrypted when it goes through a relay?

Yes. The connection is authenticated and encrypted end to end between the devices, and the relay only forwards packets it cannot read.

Can a web browser be a peer?

Partly. The same logic runs in the browser and the page installs like an app, but a browser cannot accept incoming connections — it has to open a channel outward.

What is iroh?

An open-source library for direct, encrypted connections between devices over QUIC. A device is dialed by its public key; iroh prefers a direct path, found by NAT hole punching, and keeps the traffic on a relay only when there is none. Puppeteer reaches it through a transport adapter we wrote.

Does iroh work on Android?

Yes. The bingo runs it inside a .NET app on Android phones: on 23 September four phones on four home networks joined one round this way, with no port opened on any router.

How do devices stay consistent without a central server?

Each decision has one writer, which checks every write before recording it in its journal. Devices that work alongside it keep a replica that replays that history in the same order; the rest are told each fact as it happens. Reads come from each device's own copy, so writes and reads take separate paths, as in CQRS.

Is this event sourcing?

Yes, in the sense that matters here: each device's state is its journal replayed, and a mistake is corrected by a new entry, never by editing an old one. What the journal records is the operation itself rather than an event describing its outcome, so replaying it runs it again.

How is this different from CRDT sync?

CRDTs let every device write and merge the results afterwards. Here each decision has one writer, so there is nothing to merge: replicas receive the same history in the same order, and two devices cannot both sell the same card.