|
| 1 | ++++ |
| 2 | +title = "Self-Managing Communities: How River Handles Inactive Members" |
| 3 | +date = 2026-02-15 |
| 4 | +author = "Ian Clarke" |
| 5 | +author_link = "https://x.com/sanity" |
| 6 | +tags = ["front-page"] |
| 7 | ++++ |
| 8 | + |
| 9 | +Membership management is a surprisingly interesting problem in decentralized systems. In a centralized |
| 10 | +chat app, an admin can remove inactive users from a group. But in a decentralized system without |
| 11 | +servers, who decides when someone is no longer active? And how do you enforce that decision across |
| 12 | +every peer? |
| 13 | + |
| 14 | +River answers this with a mechanism we call **message-based member lifecycle**: your |
| 15 | +presence in a room's member list is tied to whether you have recent messages. No messages, no |
| 16 | +membership entry. Send a message, and you're back automatically. |
| 17 | + |
| 18 | +#### The Problem |
| 19 | + |
| 20 | +Previously, River's room member list only grew. The only way to remove someone was to ban them, which |
| 21 | +is a hostile action that isn't appropriate for someone who simply stopped chatting. Over time, rooms |
| 22 | +would accumulate dozens of inactive members, making the member list meaningless and wasting bandwidth |
| 23 | +synchronizing their membership data across the network. |
| 24 | + |
| 25 | +This is a common problem in decentralized systems. Without a central authority to curate membership, |
| 26 | +most protocols either ignore the problem (letting lists grow unboundedly) or require manual |
| 27 | +intervention from room administrators. |
| 28 | + |
| 29 | +#### The Solution |
| 30 | + |
| 31 | +River takes a different approach. The room contract — the WebAssembly code that every peer runs to |
| 32 | +validate state changes — now enforces a simple rule: **members exist in the list only while they have |
| 33 | +at least one message in the room's recent message window** (100 messages by default, configurable by |
| 34 | +the room owner). |
| 35 | + |
| 36 | +When your last message ages out of the recent messages buffer, you're automatically pruned from the |
| 37 | +member list. When you send a new message, your original invitation is bundled with the message, and |
| 38 | +you reappear. From the user's perspective, nothing changes — they can always participate in rooms |
| 39 | +they've been invited to. But the member list now reflects who's actually active. |
| 40 | + |
| 41 | +This is conceptually similar to how a conference room works in real life: if you leave and come back |
| 42 | +later, you don't need a new invitation — but no one would list you as "present" while you're away. |
| 43 | + |
| 44 | +<img src="/img/member-lifecycle.svg" alt="Member lifecycle diagram showing how members are pruned when inactive and automatically re-added when they send a message" style="width: 100%; max-width: 800px; margin: 20px 0;"> |
| 45 | + |
| 46 | +#### Preserving Invite Chains |
| 47 | + |
| 48 | +One subtlety: River's permission model uses invite chains. The room owner invites Bob and Dave, Bob |
| 49 | +invites Carol. If Bob goes inactive and gets pruned, Carol's invitation would become unverifiable — |
| 50 | +her invite chain back to the room owner would be broken. |
| 51 | + |
| 52 | +The pruning algorithm handles this by keeping members who are in the invite chain of anyone with |
| 53 | +recent messages. Bob stays in the list as long as Carol (or anyone Bob invited) is active, even if |
| 54 | +Bob himself hasn't sent a message recently. Dave, having no active members in his invite chain, gets |
| 55 | +pruned. |
| 56 | + |
| 57 | +<img src="/img/invite-chain-pruning.svg" alt="Invite chain preservation diagram showing how inactive members in an active member's invite chain are kept" style="width: 100%; max-width: 800px; margin: 20px 0;"> |
| 58 | + |
| 59 | +#### Bans Survive Pruning |
| 60 | + |
| 61 | +Getting the interaction between pruning and bans right required careful thought. If Alice bans |
| 62 | +Charlie and then Alice goes inactive, what happens to the ban? |
| 63 | + |
| 64 | +The old logic removed bans when the banning member left the member list — which would mean that |
| 65 | +inactive members' bans would silently disappear, allowing banned users to rejoin. The new logic |
| 66 | +distinguishes between members who were *pruned* (just inactive) and members who were *banned* |
| 67 | +(explicitly removed). Bans issued by pruned members persist. Only bans from members who were |
| 68 | +themselves banned are treated as orphaned and removed. |
| 69 | + |
| 70 | +#### Enforced by the Contract |
| 71 | + |
| 72 | +All of this runs inside the room contract's WebAssembly, which means every peer enforces the same |
| 73 | +rules. Any delta that arrives — whether from a current or outdated peer — is validated and then cleaned up |
| 74 | +by the contract's `post_apply_cleanup` function, which runs after every state change and |
| 75 | +deterministically prunes members who shouldn't be there. |
| 76 | + |
| 77 | +This is the power of Freenet's contract model: the rules of the application are embedded in code that |
| 78 | +every participant executes, not policies that a server enforces on your behalf. |
| 79 | + |
| 80 | +#### What It Looks Like |
| 81 | + |
| 82 | +For users, the change is minimal. The member list sidebar now shows "Active Members" — the people |
| 83 | +actually participating. If you've been invited to a room but haven't chatted recently, you won't |
| 84 | +appear in the list, but you can send a message at any time and you'll reappear instantly. No |
| 85 | +re-invitation needed, no admin action required. |
| 86 | + |
| 87 | +#### Current Limitations |
| 88 | + |
| 89 | +Private rooms are temporarily disabled while we work through the implications of pruning for |
| 90 | +encrypted room key distribution, where the room owner needs to be online to distribute secrets to |
| 91 | +re-joining members. |
| 92 | + |
| 93 | +#### A Pattern for Decentralized Systems |
| 94 | + |
| 95 | +The broader lesson here is that decentralized applications need self-managing data structures. |
| 96 | +Without a server to run maintenance tasks, the data itself has to define its own lifecycle rules. By |
| 97 | +embedding pruning logic in the contract, River rooms stay clean without any human intervention — a |
| 98 | +small example of the kind of autonomous behavior that makes decentralized applications practical. |
0 commit comments