Pixieby Sociofabric

Mechanism: Taking Over a Live Huddle

The driver's seat decides which of your devices does autonomous work, and it decides it privately: get it wrong and you get duplicate notes nobody else ever sees. A huddle is the one case where the decision is visible to other people. Your agent is mid-conversation with your friends' agents when the phone holding the seat goes under a tunnel. Now the failure mode is not a duplicate note, it is a group that stalls waiting for a participant who is never coming back.

Prerequisites: The Driver's Seat, which establishes the seat and the two-phase handoff this extends, and Who Acts for what a huddle is.


1. A commitment other people can see

The seat problem has two failure modes, and The Driver's Seat solves both for the private case: never orphaned (no device driving, work silently not happening) and never sustained overlap (two devices driving, duplicate work). Both are recoverable. A duplicated nudge is noise; a missed one arrives late.

A huddle changes the stakes because the commitment is external. Your device is a member of a group whose other members are other people's devices, taking turns in a structured round. Those peers are waiting on you. If your driver drops:

So the private seat protocol is necessary and not sufficient. Taking the seat makes a sibling (another device signed into your own account) willing to drive. It does not make it a member of the group it now has to drive.


2. A beat your siblings can hear and the cohort cannot

The first requirement is that a sibling knows a huddle is happening at all. It cannot learn this from the cohort (the set of devices actually in the huddle's group) because it is not in the cohort, and putting it there would leak your device fleet to your friends.

So a live driver advertises to its own account. The marker carries what a successor would need:

struct ActiveHuddleMarker: Codable, Equatable {
    var cohortID: Data
    var memberPubkeys: [Data]   // each member's in-huddle device pubkey
    var memberHashes: [Data]    // each member's user_id_hash (parallel array)
    var convener: Data
    var lastBeat: UInt64        // unix seconds — freshness for drop detection
    var stateRoot: Data?        // the driver's shared progress at this beat
}

It rides the sealed sibling channel (the end-to-end encrypted channel only your own devices can read), so the cohort roster never leaves the account. That is the asymmetry that makes the whole mechanism work: your siblings learn the roster of a group they are not in, and the group learns nothing about your siblings.

The beat is deliberately unconditional: a live driver emits it every few seconds whether or not the round is progressing. That decoupling is what makes silence meaningful. If the marker stream were tied to turn progress, a driver that was merely thinking hard would look identical to one that had dropped, and the cutoff would have to be set to the worst-case turn length. Instead:

static let driverDropCutoff: TimeInterval = 8
static let markerFreshness: TimeInterval = 20

Eight seconds of silence from a stream that beats every few seconds is a real drop, not a slow turn. That is what lands failover (the switch to a replacement device) in roughly ten seconds, inside the delay a huddle round can absorb, rather than waiting the much longer time the seat protocol of The Driver's Seat needs before it declares a silent device gone.

YOUR ACCOUNT THE COHORT DRIVING DEVICE SIBLING · WATCHING ACTIVE HUDDLE MARKER ROSTER · CONVENER · STATE ROOT SEALED PEER AGENT PEER AGENT SEES ONE PARTICIPANT NEVER A FLEET SIBLINGS LEARN THE ROSTER · THE GROUP LEARNS NOTHING

3. Silence is not evidence, so ask the room

Eight seconds of silence is evidence of a drop, but it is evidence gathered from one vantage point: yours. The sibling watching the marker stream is on a different network from the driver. The thing it actually observed is "I stopped hearing beats," which is consistent with the driver being gone and equally consistent with the sibling's own connectivity having degraded.

Seizing on that alone would produce exactly the failure the seat protocol exists to prevent, only now in public: two of your devices driving one huddle, both taking turns, both visible to your friends.

So before it seizes, the watching sibling asks every other member of the cohort a single question: have you heard from this driver recently? The peers are in a better position to answer than the sibling is: they are the ones the driver has been taking turns with.

A single sighting vetoes the seizure. That asymmetry is deliberate: "somebody still sees the driver" is a positive fact from a witness with direct evidence, while "nobody answered" is consistent with the poll itself having failed. One yes is conclusive; the absence of yes is not.

The probe is answered only by a live in-huddle member, and only for a requester that owns a member account (the same authorisation check the takeover itself uses). So the poll cannot be turned into an oracle: a question-answering service an outsider could use to probe whether a given cohort is live.

SILENCE IS NOT EVIDENCE SIBLING · LOST THE BEAT MEMBER MEMBER MEMBER ONE MEMBER STILL SEES IT · STAND DOWN NOBODY SAW IT · SEIZE ONE YES IS CONCLUSIVE · THE ABSENCE OF YES IS NOT THE WITNESSES ARE THE PEERS WHO TOOK TURNS WITH THE DRIVER

4. Exactly one peer serves the takeover, and it is usually a joiner

Once the sibling decides to take over, it publishes a KeyPackage (the bundle of public keys a group needs in order to add a new member) and asks the cohort to readmit it. The group runs on MLS, the Messaging Layer Security protocol, a standard for end-to-end encrypted groups. The sibling asks every member, because it does not know which of them are reachable. But only one of them may act, or two concurrent MLS adds would collide on the group.

The serving peer is therefore deterministic: the member with the lowest user_id_hash (a hashed stand-in for an account id) among members other than the requester's own account. Every member computes the same answer from the roster it already has, with no negotiation and no round trip.

Now the observation that made this fail in practice for a long time. The deterministic serving peer is not the convener (the member who created the group). It is whichever member happens to sort lowest, which, most of the time, is someone who joined the group via a Welcome (the MLS message that hands a newcomer everything it needs to enter) rather than creating it.

That matters because a member's ability to admit another member is not automatic. It depends on the group configuration that member persisted when it joined. MLS arranges the group's member keys in a structure called the ratchet tree. A joiner whose stored configuration omitted the ratchet tree can hold a perfectly valid membership, take turns normally, and be unable to add anyone: the Welcome it emits is missing the very structure the next joiner needs to build the group.

Every ordinary test adds from the group's creator, so the defect is invisible until exactly this path runs: the one where the admitting member is, by construction, not the convener.

The fix is that a joined group carries the ratchet-tree extension, and that a group already persisted without it is repaired when it is next loaded, because a configuration written at join time outlives the code that wrote it.


5. The add happens inside the turn loop

The serving peer does not admit the new driver on the spot. It hands the pending add to the running session, which services it between turns.

This is a concurrency decision, not a stylistic one. An MLS commit (the message that changes the group's membership and rolls its keys) mutates the group's key schedule. A session mid-turn is encrypting, decrypting, advancing its own epoch (the numbered generation of the group's keys). Applying a commit from a network callback at that moment is a data race on the group state: two strands of work touching the same state at once, with an outcome no test would reproduce reliably. Servicing the add at a known point in the loop makes it ordered with respect to everything else the session does.

The receiving member still validates before it acts: the requester must own an account that is a current member of the cohort. That is proven by the sealed sender certificate (a signed identity statement carried inside the encryption) rather than by anything the requester asserts. The relay, the server ferrying the messages, never sees this check; it happens after decryption, against the account-bound identity in the envelope.


6. What is handed over, and what is deliberately rebuilt

The snapshot the serving peer returns carries only what every device in the huddle holds identically: the deterministic round machine, the broadcast transcript (the shared record of what has been said), the floor lines, the per-slot discussion, and the session-isolation nonce (a one-time value that keeps this session's state from mixing with any other's).

It deliberately omits three classes of thing:

The distinction is what makes a peer able to serve a takeover at all. A snapshot containing your private state could only be served by another of your devices, and your devices are precisely the ones that might be offline. Restricting it to shared state means the person who hands your phone the huddle back can be someone else's phone entirely, holding nothing of yours.


7. The check that warns rather than refuses

The marker carries a stateRoot: a deterministic hash, a short fingerprint, of the driver's shared progress at its last beat. The taker compares the peer-served snapshot against it.

A mismatch does not stop the takeover. It logs, and the session resumes anyway.

That is worth arguing rather than asserting, because "verify then proceed regardless" reads like a check that does nothing. What a mismatch actually means is that the serving peer is behind the dropped driver's last published position, or that the driver advanced past its own last marker before dropping. Neither is evidence of an attack; both are ordinary consequences of an asynchronous group where the marker and the snapshot are produced by different devices at different moments.

And the alternative is worse. Refusing to resume on a mismatch strands the huddle: the peers keep waiting, and the one device willing to continue has declined to. The snapshot is the best state any peer holds, so the choice is between resuming from it and abandoning the group. The check earns its place as a signal that reconciliation is needed, not as a gate.

What it is not is a repair mechanism. The taker does not pull the missing progress before it drives. It proceeds on what it was given.


8. Coming back is not the same as taking over

When the original device returns, it does not stay away. It reclaims, and a reclaim is a different protocol from a takeover, because the returning device is still a member.

Its membership was never revoked; it simply stopped participating. So there is no MLS add. The returning device syncs its existing membership forward through the commits it missed, and the sibling currently driving hands over the live state inline and steps out.

The request looks identical on the wire to a takeover request. It is distinguished by a single comparison: the requester's user_id_hash equals that of the device serving the request. Same account, therefore a reclaim rather than a peer-served admission. A reclaim also bypasses the deterministic serving-peer gate, which would otherwise exclude the very device being asked: that gate selects among members other than the requester's account, and in a reclaim the requester's account is ours.


9. Removing a leaf you cannot name

The step-down leaves the group holding two leaves (two member slots in the MLS ratchet tree) for one person: the sibling that was driving, and the one that just reclaimed. The leave model here is that a member never commits its own removal; a sponsor, another member acting on its behalf, posts the Remove. So the reclaiming driver has to prune the leaf of the device that stepped down.

The obvious way to do that is to name the leaf by the device's public key. The obvious way does not work, and it failed silently for a long time.

MLS leaf credentials in this system are minted per plan (a "plan" being the code's term for the shared project a group forms around; in practice, created fresh for each group) inside the FFI, the foreign-function interface where the app calls into the cryptography library. A leaf identity is an opaque per-group credential, unrelated to the device's envelope key and not derivable from it. A removal keyed on a device public key therefore matches no leaf at all and fails unconditionally, while looking, from the outside, exactly like a removal that had nothing to remove.

Only the leaving device can read its own credential. So it stamps it into the handover snapshot as it steps down, and the reclaiming driver removes precisely that leaf. The field is device-relative rather than shared, which is why it sits outside the shared-state root. Folding it in would break the root comparison on every honest reclaim, because the marker root is published from a snapshot that never carries it.

One more scoping rule, easy to miss and load-bearing on an account with several devices. The handed identity is honoured only when it arrives from the exact sibling being reclaimed from: the verified sender key must match. Account scope alone is not enough: a second sibling racing a snapshot into the same window would otherwise have its leaf pruned, while the device that actually stepped down kept its own.

A LEAF ONLY ITS OWNER CAN NAME SIBLING THAT WAS DRIVING STEPPING OUT RETURNING DRIVER STILL A MEMBER · SYNCS FORWARD HANDOVER SNAPSHOT · SHARED STATE PLUS ONE PER-PLAN LEAF CREDENTIAL SPONSOR-REMOVE EXACTLY THAT LEAF A DEVICE PUBKEY MATCHES NO LEAF · THE REMOVAL WOULD SILENTLY DO NOTHING ONLY THE DEVICE STEPPING DOWN KNOWS WHICH LEAF IS ITS OWN

10. The residual

A failed prune has two shapes the code cannot tell apart. Either the leaf was already gone (a stale handed identity, harmless, and the resume must continue) or the Remove merged locally and the commit never reached the group, in which case it is this device that is now out of step with the cohort. The error surfaces the same either way. So the resume proceeds and says so, on the argument that aborting on the benign case would kill an otherwise healthy reclaim. The alternative is a session that keeps running while every peer frame lands as "cannot decrypt", which is the worse silence.

Takeover is bounded by the poll, not by the seat. If the cohort is unreachable (everyone offline, or a network partition that isolates all of them), the sibling cannot gather the witness evidence it needs and will not seize. That is the correct trade for a group commitment, but it means a huddle can stall in a partition rather than migrating.

The resumed state is the best any peer had, not necessarily the driver's last. §7 is a warning, not a repair. In the mismatch case the huddle continues from a position slightly behind where the dropped driver actually got to, and the lost progress is whatever that driver had advanced past its final marker.

None of this makes the group aware of your fleet. Which is the constraint that shaped every other decision here: the peers see one participant whose device key changed mid-conversation, which is exactly what a re-admission looks like, and they never learn that a second device existed.


References

Back to The Driver's Seat, whose two-phase handoff times the step-down this article hands state across, or across to Who Gets to Put You in a Group, which governs who may admit a device to a cohort in the first place.

← Mechanism: The Driver's Seat