eleven / ocho Public
🤖 Instance contexts — one engine, different social defaults
Status: design (not built). Builds on docs/threads-and-dms.md and docs/invite-redesign.md. Nothing here weakens the E2EE invariant: the server still only ever stores ciphertext and key-blobs it cannot open.
The idea
Different kinds of instances — a family, a friend group, a community — share ~80% of what they need and differ in ~20%. The shared 80% is the messaging core: threads, per-thread AES-GCM keys, the instance key, DMs, attachments, mini-apps, the whole crypto model. None of it changes across kinds.
The 20% that differs is entirely social structure: who may cross the trust boundary, who can see whom, and which channels exist by default. So "context" is not a mode the engine branches on — it's a named preset over a handful of orthogonal knobs, plus tailored onboarding copy and channel scaffolding.
- A context is a template chosen at instance creation. It expands into concrete values on the knobs below, seeds a channel or two, and sets the onboarding language.
- We store the resolved knobs, not the context. Real instances drift — a family later wants a General; a friend group wants restricted DMs for the one kid. The admin console exposes the dials directly; the context only seeds them.
- "Other / Custom" always exists — the dials with no preset. Future contexts (Team, Club, Business) are new template rows, not engine changes.
The knobs
| Knob | Options | Backing | |---|---|---| | 1. Admission — who adds a person to the instance | admin-only · members-can-invite · open join-link | usersCanInvite / user_invites_enabled (exists); open join-link is new | | 2. Default channels | none · one "General" · several topic channels | instance-key–encrypted threads, auto-created | | 3. Member-created open channels | off · on | deferred "group join links, instance-members-only" (threads-and-dms.md) | | 4. Discovery / roster visibility | full-open · restricted (e.g. kids ↔ parents only) | deferred "restricted discovery" | | 5. Who can DM whom | anyone ↔ anyone · restricted | same restricted-discovery lever |
Everything else (composing, reactions, attachments, mini-apps) is the shared core and is identical in every context.
Contexts as presets
| | Admission | Default channels | Member open channels | Discovery / DMs | |---|---|---|---|---| | Family | admin-only (parents) | none | off | restricted (kids ↔ parents), else open | | Friends | members-can-invite | one General | maybe | full-open | | Community | members-invite or open-link | several topic channels | on | full-open (role-gated later) | | Other | fully custom | — | — | — | | Team (later) | domain/SSO-gated | per-project | on | open within org | | Business (later) | admin/SSO | departmental | on | + retention/audit ⚠️ (see below) | | Club (later) | invite or open-link | few | on | open |
Each row is a starting point on those five dials, nothing more.
Two knobs that aren't cosmetic
Most knobs are defaults you can flip freely. Two change the trust model and must be designed as first-class features, not preset side effects.
Admission is the security posture
Family's "admin-only" is the safety model: a parent vets everyone who can read anything. Community's "open join-link" means unvetted people get keys wrapped to them. That is not a preference — it changes who you are trusting. The open join-link is its own carefully-gated feature (expiring links, optional admin-approval step), never something a preset silently turns on.
Open channels: subscription vs. access
An "open" channel (General, topic channels) is cleanest encrypted with the instance key: every member already holds it, and new members get it auto-granted via the existing nudge-and-wrap flow (instance-key/nudge → grantInstanceKeyTo), so the channel is instantly readable with zero per-member re-wrap. That gives a clean split:
- Open channels → instance-key encrypted. Any member can read (even browse before joining). Membership here is a subscription — it controls what's in your list and what notifies you, not access.
- Private groups / DMs → per-thread key. Membership is access — you cannot decrypt without being wrapped in.
Honest limitation: an open channel is cryptographically readable by every current and future instance member, forever. "Leaving" one is unsubscribing, not losing the ability to decrypt. For a community that is usually the intended meaning of "open." A channel that is open-to-join but revocable-on-leave needs a per-channel key + rekey-on-leave, which costs an online key-holder and O(members) re-wraps — a bill that grows with size.
Cross-cutting concerns (not knobs)
- Scale is the silent axis. Family (~5) and Friends (~20) sit in the model's sweet spot. Community (100s) strains two mechanics: the nudge-and-grant instance-key handshake assumes a holder is reachable, and remove-a-member rekey is O(members) and needs someone online. Decide how far up the size curve each context is meant to go before designing it.
- Business bends the model. Team and Club are basically Community + roles. Business drags in retention, compliance, and admin-visible audit — several of which directly conflict with E2EE (you cannot hand a compliance officer plaintext the server never sees). Business needs its own trust-model conversation; it is not a preset.
Status / next steps
Design only — nothing here is built. Natural build order once we commit:
- Land the knobs as instance config + admin-console dials (extends the existing
user_invites_enabledpattern). - Instance-key–encrypted "General" / open channels + the subscription-vs-access split.
- Member-created open channels (the deferred group-join-links design).
- Restricted discovery (kids ↔ parents).
- Context templates + onboarding copy on top, once the dials exist.