Two devices, no server in between
Two devices you own should stay in step without a server in the middle. That takes a live send plus two recovery lanes โ a bounded re-offer and a catch-up when the two devices next meet. Shared-space content was riding the live send alone; now it rides all three.
How your two devices are supposed to stay in step
You own more than one device, and NAOMS treats that as ordinary rather than exceptional. Write something on the laptop; it should be on the phone. Both machines are yours, both hold your keys, and neither is a server that the other is a client of โ they are peers that recognise each other and share a private lane no one else can read. The word for the far end, throughout this piece, is your co-device: another machine belonging to the same owner, paired to this one.
Records travel between them over gossip: a family of peer-to-peer techniques where a node hands a new fact to the peers it can currently see, and those peers hand it on, until the fact has spread. There is no central relay deciding who gets what. Each send is a small, self-contained packet โ a datagram, fired once with no built-in guarantee it arrives โ addressed to a topic, which is just a named stream that interested peers subscribe to. Your paired devices share a private topic of their own, and only a device holding your keys can read it. That is the live path: you write something, and it goes out once on that private topic.
One send is a perfectly reasonable first attempt. It is not a delivery mechanism, because the moment you most need it is exactly the moment it is least likely to work: the seconds after you hit enter, while your two machines are still discovering each other on the local network. So underneath the live path sit two recovery lanes, and the design intent is that everything meant for your other device rides both of them:
- A periodic re-offer. A sweep runs on a timer and re-offers your recent records, on the theory that a send lost during mesh formation can be repeated a moment later when the mesh exists.
- A catch-up on meeting. The instant your two devices notice each other, the arriving side asks for whatever it missed while they were apart. This lane is driven by an actual event rather than by a clock.
The two are not two versions of the same idea, and the distinction is the most useful thing in this article. A timed re-offer is a bounded gamble; a catch-up on reconnection is a fact-driven repair. The gamble buys you coverage during the window where nothing is connected yet. The repair is what makes the system eventually correct, because it fires on evidence โ the peer is here โ and not on a guess about how long things take. A system that keeps its own history needs the second one to be the load-bearing part.
What this costs you by default
Because the default matters more than the mechanism, here it is precisely, for someone who changes no settings:
- It applies only between devices you own. The lane's delivery step targets a second device of the same owner on the private stream your paired devices share, and only a paired device holds the key to it. Other people in the same shared space were never on this lane and still aren't โ they receive the same content over the ordinary peer path, which is separate and untouched.
- The sweep does not run at all unless a second device of yours is actually present on that stream. With one device, there is nothing to re-offer and the sweep returns immediately; when a sibling shows up later it triggers the catch-up instead. So this costs a single-device user nothing, which is the overwhelmingly common case.
- When it does run, it runs every 12 seconds, looks back over the last 120 seconds of your records, and takes at most the 10 most recent โ and it drops the rest of that pass rather than carrying them forward. Write more than ten things in a burst and the eleventh is not queued for the next tick; it is simply not in this tick's set.
- Any single record is re-offered at most 3 times, spaced at least 8 seconds apart. Then it stops.
That last bound is the uncomfortable one, and it is worth being straight about why it looks the way it does. There is no acknowledgement in this subsystem โ no signal coming back from your other device saying received, you can stop. So there is nothing to stop on, and the bound is deliberately blind: three pushes, spaced at least eight seconds apart, then silence, whether or not anything ever arrived. Do the arithmetic and the trade is visible rather than hidden. Three pushes at eight-second spacing covers roughly the first 24 seconds of a 120-second window. It is sized to span the stretch in which two of your devices typically find each other โ and it is, precisely, a guess about how long that takes, dressed as a constant. It buys a hard ceiling on background noise and pays for it by giving up on the records whose delivery window runs long. What catches those is the catch-up lane, which does not depend on the guess.
What was wrong
Ordinary messages had both recovery lanes. Content you wrote into a shared space โ a group workspace whose members hold its records in common โ did not.
The live path had been widened to cover it: the code that decides this belongs on your other device too included shared-space content, correctly. The recovery path had not been widened with it. So that content had exactly one attempt. Send it once, hope it lands, and if it was lost while your machines were still finding each other, nothing ever tried again. Not late โ never arriving.
flowchart TD W["You write into a shared space"] --> S["One broadcast on your private device stream"] S -- "lands" --> OK["Your other device has it"] S -- "lost: the two devices
hadn't met yet" --> B{"Is this record
in a recovery lane?"} B -- "ordinary message: yes" --> R["Re-offered by the sweep,
or pulled on next meeting"] B -- "shared-space content: no
(the gap this week closed)" --> DEAD["Nothing ever tries again"] R --> OK
Everything about it was working as written. Nothing crashed. Nothing was dropped by mistake. The delivery simply had one attempt, and one attempt is not a delivery.
Two pieces of one mechanism had drifted apart, and the drift was invisible for exactly the reason drift usually is: the widened half was the half that fires when everything is working. The failure mode this produces is the worst-shaped one available โ intermittent, indistinguishable from a flake, and dependent on something nobody can see, namely whether the mesh between your two devices happened to be up at the instant you hit enter. Run the test once and it passes. Run it again and it doesn't.
The second defect was the mirror image, and it landed the same day. Before the per-record bound described above existed, the sweep had no memory of what it had already sent. Every record inside the 120-second window was re-offered on every tick, for the whole window, for as long as your other device was present. Measured: roughly 2,060 copies of a single record arriving at the other device. Nothing was broken, in the sense that everything worked. The record arrived. It arrived two thousand times.
How it was fixed, and what we are not claiming
The recovery lanes were widened to cover the same kinds of shared space the live path already covered, on the same two branches of the record โ the content and the description of it. Nothing else was added to the lane; the fix was to make one half of an existing mechanism match the other half. The storm was closed by the three-pushes-spaced-eight-seconds bound, which is why that bound is stated above as part of how the thing now works rather than as a footnote here.
The more durable half of the fix is the anti-drift work stapled to it. There had been no assertion anywhere saying these two sets must be the same set, because until the day they diverged, they trivially were. Now the condition used at send time and the query used by the sweep are written against one shared list of what counts, and the tests assert that the list and the query agree. It doesn't make the code faster or the delivery more reliable. It makes the next widening of the fast path impossible to do silently.
There is a general form of this worth carrying: whenever you have a happy path and a recovery path, the pair of them is a single mechanism with two spellings, and nothing will tell you when the spellings drift. Every check you run exercises the happy path โ that is what a passing test is. The recovery path is only exercised by the failure you are not currently having.
And what we are not claiming. The commit that landed this fix declines, in its own words, to call the underlying reliability gap closed. It states that the end-to-end proof across two live devices was still pending, and it refuses to claim the win until that evidence exists. Nothing on the main line since then records that closure. What the merge record does carry is repeated green runs of the covering test โ five out of five under a load gate, eleven out of eleven under normal load โ which is real evidence and is not the same thing as the end-to-end proof the author was holding out for.
So: the gap in the recovery path is closed and readable in the code. The retry is bounded and the bound is measured. The end-to-end demonstration that a lost first attempt is now always recovered on real hardware is the part we have not finished, and we would rather say that than let a green test stand in for it.
Related: Adding a Second Device Without Copying the Key ยท Why Gossip Must Never Move Your Big Files ยท Three Kinds of Sync, One Engine Underneath.
Written by AI agents from real project logs; owned and edited by Mujo.
Written by AI agents from real project logs; owned and edited by Mujo.