The most useful sentence I found while evaluating peer-to-peer messaging was not in a manifesto. It was in the documentation, describing how Keet delivers messages to people who are offline: blind peering "restores the server role" in a serverless design.
That sentence settled more than any benchmark could. It meant the question in front of us was never "server or no server". Both designs need something that is always on. The question was who owns it, who operates it, and what it is allowed to know.
For Circo, at the turn of 2025, the answer was that we should own it. Messaging moved to WebSockets on servers we run.
The problem both designs have to solve
Strip messaging down and one hard case remains: the recipient is not there.
If both people are online, almost any design works. A direct peer-to-peer stream, a socket through a server, even polling. The message goes from one screen to the other and everybody is happy.
The hard case is the ordinary one. A fan sends a creator a message at midnight. The creator's phone is asleep. Tomorrow morning they open the app on a different device. The message has to have waited somewhere, intact and private, and it has to arrive in order with everything else that happened overnight.
In a peer-to-peer system, a message lives in its author's log, and that log is only available while some peer holding it is online. If the sender goes offline before the recipient connects, the message waits on a device nobody can reach. The fix is a peer that never goes offline: a blind peer that stores encrypted blocks it cannot read and serves them when the recipient returns.
In a server system, the server is that peer. It accepts the message, stores it, tells the recipient's phone through the platform's push service that something is waiting, and delivers it when the app reconnects.
Run both, side by side, and the symmetry is obvious.
The Message Relay
A sender and a recipient, with three ways to carry a message between them: directly peer to peer, peer to peer with an always-on blind peer, or through a server over WebSockets. Take the recipient offline and see which designs still deliver.
The Message Relay
A sender and a recipient, with three ways to carry a message between them: directly peer to peer, peer to peer with an always-on blind peer, or through a server over WebSockets. Take the recipient offline and see which designs still deliver.
A sender and a recipient, with three ways to carry a message between them: directly peer to peer, peer to peer with an always-on blind peer, or through a server over WebSockets. Take the recipient offline and see which designs still deliver.
Interactive figure: The Message Relay. A sender and a recipient, with three ways to carry a message between them: directly peer to peer, peer to peer with an always-on blind peer, or through a server over WebSockets. Take the recipient offline and see which designs still deliver.
Take the recipient offline: the direct path has nowhere to hold the message, and the two designs that deliver both rely on something that never sleeps.
Fig. 1The Message Relay. Take the recipient offline: the direct path has nowhere to hold the message, and the two designs that deliver both rely on something that never sleeps. Open the full exhibit
What WebSockets actually give you
WebSocket is an old, plain standard. A client opens an ordinary web request, asks to upgrade it, and from then on holds one long-lived, two-way connection to a server. Either side can send whenever it likes. The server can push a message the moment it exists, without the client asking.
That is all the protocol does. It does not store anything, wake anyone up or know who is allowed to read what. Everything that makes messaging feel reliable is application work on top: the queue that holds messages for people who are away, the push notification that brings a sleeping phone back, the sync that brings a second device up to date, the ordering rules that make a conversation read the same everywhere.
That work is also exactly what a creator platform needed us to own.
What Circo needed from messaging
When we wrote down what messaging had to do on Circo, the list was not about networking at all.
Accounts, not keys. People sign in to Circo, change phones and recover their accounts. Their identity is an account the platform can restore, not a seed phrase they must never lose. Messaging had to follow the account across every device.
Delivery to sleeping phones. Mobile operating systems suspend background work to save battery and data. A message has to reach a creator whose phone has been in their pocket all day, and that means push notifications and a place for the message to wait.
Safety for creators. Creators need to report, block and moderate. A platform that cannot act on abuse in its own messages cannot promise its creators a safe place to build a community. Acting on abuse requires a system that can enforce the decision.
One product, one place to debug it. When a message does not arrive, someone has to be able to find out why. A design where the delivery path runs through servers we operate is a design we can observe, support and fix.
Every item on that list needs a party that is always on, accountable and able to act. In the peer-to-peer design, that party is a blind peer we would run anyway. In the server design, it is a server we run. Once we were going to run the always-on node regardless, the server design gave us more for the same responsibility: accounts, push, moderation and support in one place, built on technology every engineer on the team already knew how to operate.
What we kept from peer-to-peer
Moving to WebSockets was not a retreat from what the experiment taught us. Several of its ideas went straight into how we think about the server design.
A message is a fact. It is created once, with its own identity, and never edited in place. That makes delivery safe to retry: if a message arrives twice, the second copy is recognised and dropped.
The offline case comes first. We design every messaging flow starting from "the recipient is not there", because in peer-to-peer you cannot avoid that case, and in a server design it is far too easy to forget.
And we hold only what we must. Peer-to-peer starts from the position that the platform holds nothing. A server design starts from the opposite. The discipline the experiment left us with is to treat every stored message as a responsibility, and to know exactly why we are keeping it.
The honest symmetry
It is tempting to tell this story as "we tried the new thing and it did not work". That is not what happened, and it is not fair to the technology.
The peer-to-peer stack did what it says it does. Its designers are clear about availability, and their answer to offline delivery is good engineering. What we learned is that "serverless" describes who owns the infrastructure, not whether infrastructure exists. Something always has to be awake.
For a messaging product where ownership by the users is the point, running that always-on node blind and letting the users hold everything else is exactly the right design. For a creator platform where safety, recovery and support are the point, the always-on node needs to be accountable, and accountability is easier when you own it.
We chose to own it. The next question was harder: could the same thinking apply to video itself?
Experiment 03 · Messaging
Readout
Route
Direct peer to peer
Always-on node
None
Held while away by
Waiting
Message
In flight
A sender, a recipient and a message. Choose how it travels, and whether the recipient is awake.
What happens, step by step
1
The sender signs the message and appends it to their own log, on their own device.
2
The recipient's phone is asleep. There is no other peer, so the message stays on the sender's device.
3
The sender closes the app. The only copy of the message is now on a device nobody can reach.
4
The recipient opens the app later. The sender is gone, and no peer online holds the message.
5
The message waits on the sender's device until both are online at the same time.