Pixieby Sociofabric

Mechanism: Forming a Cohort Without a Server

A huddle is a conversation between the agents of people who all mutually trust each other. Before it can happen, somebody has to work out who those groups are. The obvious answer is a server, because the maximal cliques of a graph (the largest groups in which everyone is connected to everyone) seem to need the whole graph. This article is the argument that they do not, and the four pieces that let every phone find its own cohorts from facts it is already entitled to know.

This is a mechanism deep-dive under The Agent in Your Day, which introduces the cohort and the huddle it holds. Here we go underneath one sentence of it: how the set of people is decided, by whom, and what anyone else learns along the way.


1. The problem: a clique seems to need the graph

Two words, because both recur. A cohort is the group: the set of people. A huddle is a session that cohort holds. Cohorts are durable; huddles happen and end.

The membership rule is strict on purpose. You mark someone inner-circle; if they mark you back, an edge exists: a mutual link between the two of you. Picture a dot for every person and a line for every mutual pair: that picture is the mutual-consent graph. A cohort is a set of people who are all pairwise mutual (every two members share an edge) and as large as it can get without breaking that rule. In graph terms, that is a maximal clique. The rule is decisive because a huddle shares sensitive inferred context: things the agents have worked out about their users. It guarantees nothing about you is ever spoken in front of an agent whose user you do not mutually trust.

Now the difficulty. Alice can see her own edges: she knows who she has marked and who has marked her back. She cannot see whether Bob and Cindy have marked each other. Yet that one fact decides whether {Alice, Bob, Cindy} is a cohort or merely two separate pairs. So it seems a device cannot compute its own cliques without being told about edges between other people, and telling it those edges means somebody, somewhere, holds the social graph.

That reasoning is correct about edges in general. It is wrong about the edges that matter, and the gap between those two statements is the whole mechanism.

A clique containing Alice lives entirely inside {Alice} ∪ N(Alice): Alice plus her own neighbours. (N(Alice) is standard notation for Alice's neighbourhood, the people she has mutually marked; the ∪ just merges the two sets.) The clique cannot contain anyone she has not already mutually marked, because then it would not be a clique containing Alice. So the only third-party fact she needs is which of the people she already trusts trust each other. Not the graph. Just an intersection (an overlap) inside a set she is already a member of.

THE WHOLE GRAPH — WHAT A SERVER WOULD HOLD you strangers to you, and edges among them WHAT YOU ACTUALLY NEED you only: which of yours are mutual the unknown is an intersection inside a set you already belong to A CLIQUE CONTAINING YOU CANNOT REACH OUTSIDE YOUR NEIGHBOURHOOD

2. Peer tags: the intersection and nothing more

Alice needs N(Alice) ∩ N(Bob): the intersection, meaning the people who appear in both her circle and Bob's. She must not learn the rest of Bob's circle, and Bob must not learn hers.

Cryptographers call this problem private set intersection: two parties compute the overlap of their lists while revealing nothing else about them. There is a shortcut available here that a general private-set-intersection protocol does not get to assume: every mutual pair already shares a channel key. That key exists because they message each other: it is the key that encrypts their conversation. It gives the two of them a secret nobody else has, and a shared secret is all a keyed pseudorandom function needs. (That is a function which scrambles an input into output that looks like random noise to anyone without the key, while anyone holding the key gets the same output for the same input every time.)

So Bob sends Alice his mutual set, blinded (disguised entry by entry) under the key only he and Alice hold. Each person in his set becomes a short tag, computed with HMAC, a standard keyed hash built for exactly this job:

tag = HMAC(k_AB, domain ‖ BE64(epoch) ‖ member_hash)[..16]

Reading the formula: k_AB is the pair key only Alice and Bob hold, domain is a fixed label that keeps these tags from colliding with any other use of the same key, BE64(epoch) is the current time period written as a fixed-width number, member_hash identifies one person, ‖ joins the pieces, and [..16] keeps the first sixteen bytes of the result. Alice computes the same function over her own mutuals. Anything that matches is in both sets. Anything that does not match is sixteen opaque bytes: a person she cannot name, cannot count toward anything, and cannot recognise if she meets the same tag under a different pair, because a different pair means a different key and therefore an unrelated tag.

Three properties fall out of that construction, each doing a job:

The exchange is one-directional by construction. Alice learns her side; Bob runs the same thing in reverse to learn his. Neither ever holds the other's raw set.

BOB PUBLISHES — BLINDED UNDER THE PAIR KEY a41f… 7c02… 19bd… e770… 4a13… c8e1… 2b96… f05a… 66d4… bb31… 08cf… d92e… padded, sorted — size and order carry nothing same HMAC, same key ALICE RECOMPUTES HER OWN Dana → 9f11… Cindy → 7c02… match Erin → 30ac… Frank → c8e1… match non-matches stay sixteen opaque bytes ALICE RECOVERS EXACTLY THE INTERSECTION — PEOPLE SHE ALREADY TRUSTS Bob learns nothing here; he runs the exchange in reverse to learn his own side a different pair uses a different key, so tags are unlinkable SHE RECOVERS THE OVERLAP AND NOT ONE NAME BEYOND IT

3. Bron–Kerbosch, on a phone

With the neighbourhood assembled, finding the maximal cliques is a job for the textbook algorithm: Bron–Kerbosch, the classic recursive procedure for listing every maximal clique in a graph, run with pivoting (a standard trick that prunes redundant branches) and restricted to {v} ∪ N(v), you plus your neighbours. It is the same algorithm and the same output as the server-side version it replaces. What changes when it moves onto a handset is that the worst case suddenly belongs to someone who is holding the device.

Bron–Kerbosch is exponential in the worst case: a classic result of Moon and Moser says a neighbourhood of degree d (one with d members) can hold up to 3^(d/3) maximal cliques, a count that roughly triples every time d grows by three. Two caps bound the work:

The result is every maximal clique containing you. Each clique is sorted, and the list is ordered largest-first with ties broken lexicographically, so two devices that computed the same graph also pick the same cohort out of it.

4. A cohort id nobody assigns

The identifier is derived, not allocated. Every member hashes the same ingredients (H is a cryptographic hash, a one-way fingerprint function) and keeps the first sixteen bytes:

cohort_id = H(domain ‖ sorted member set)[..16]

Every member computes it independently and gets the same sixteen bytes. That is what lets it double as the group id for MLS (Messaging Layer Security, the standard protocol for end-to-end-encrypted group messaging) with nobody adjudicating. The convener (the member who establishes the MLS group and seeds the session nonce, a fresh random value used exactly once) is simply the member whose hash sorts first. No election, because no election is needed for a value everyone can compute.

The derivation is byte-identical to the one the server used, deliberately: same domain separator (the fixed label mixed into the hash so it can never collide with a hash computed for some other purpose), same sort, same truncation. A cohort that already had a persisted MLS group keeps its id across the migration instead of silently forking into a second group.

5. When two devices disagree

Devices compute cliques from their own edge views, and views diverge. Cindy revokes Bob; for a while only Cindy knows. Her clique set and Alice's no longer match, and both are sincerely reporting what they see.

The settling rule is one sentence: an edge is real if and only if both of its endpoints affirm it. Only X and Y are authoritative about the X–Y edge. A member reporting on someone else's edge is ignored outright, so no group of members can drag a pair into a cohort that pair does not consent to. Each member reports only the edges touching itself that its own view currently supports; anything else it believes is hearsay.

Running that rule over the collected reports is deterministic (the same reports in always produce the same answer out), so every participant reaches the identical result. That is what lets cohort_id agree with nobody adjudicating.

An earlier design worked from objections instead: it pooled every member's objections and subtracted the lot. It is worth recording why that was wrong, because it was consistent and still unfair. When Cindy had revoked Bob and only Cindy knew, subtracting objections dropped Bob (the member with the stale view) and kept Cindy, even though {Alice, Bob} and {Alice, Cindy} were equally valid outcomes. It silently cost Bob a huddle, and which of the two survived depended on who happened to object first. Affirmation is symmetric where objection is not.

EACH MEMBER REPORTS ONLY THE EDGES TOUCHING ITSELF ALICE AFFIRMS Alice–Bob Alice–Cindy nothing about Bob–Cindy BOB AFFIRMS Bob–Alice Bob–Cindy CINDY AFFIRMS Cindy–Alice (has revoked Bob) SETTLED Alice–Bob · both affirm · KEPT Alice–Cindy · both affirm · KEPT Bob–Cindy · one side only · DROPPED only the two endpoints of an edge get a vote on it so the outcome does not depend on whose view is freshest AN EDGE IS REAL ONLY IF BOTH ENDPOINTS AFFIRM IT

6. What is left on the server

Nothing about who knows whom.

The service that held the mutual-consent edges is gone. Consent now travels as a sealed message between the two people it concerns. Blinded mutual sets ride the channel those two already share. Cliques are computed on each handset, and the cohort id and convener are derived rather than assigned. The relay (the server that forwards traffic between devices) carries sealed envelopes it cannot read, addressed by values that mean nothing to it: the same position it holds for everything else that rides it.

It is worth being precise about the direction of that change, because the intuition runs the other way. Moving a computation onto the device did not merely relocate the disclosure. Alice learns which of her own mutuals are mutual with each other; the server previously saw every edge among everyone. The decentralised version discloses strictly less, to strictly fewer parties, than the arrangement it replaced.


The code is ios/Pixie/Pixie/Cognitive/Huddle/HuddleGraph.swift: HuddlePeerTags for the blinded exchange, HuddleCliqueGraph for Bron–Kerbosch and its caps, HuddleCohort for the derived id and convener, and HuddleCohortAgreement for settling divergent views. What the cohort then does (the rounds, the floor, the turns) is The Agent in Your Day; how a device is admitted to the resulting MLS group is Who Gets to Put You in a Group.

← Mechanism: Memory & the Notebook