OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-social commits / aed0740

Add bottom-up architecture: rooms are the only data structure
Eliminates hand-crafted Autobases for registries and meta-registries. Rooms carry metadata via config, registries are rooms with listing messages, profiles are rooms you follow, groups emerge from local MI computation. Supersedes AUTO-DISCOVERY-SPEC.md. Maps to AtomSpace concepts. ~870 lines removed, ~480 added. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
aed074017f710b0c778414755591343ded6b3033
by plankatron <fleeky@gmail.com> · 2026-02-21T13:28:12.000Z

Changed files (2) +573 -0

docs/README.md +1 -0
……@@ -16,6 +16,7 @@ P2P social layer built on Hypercore Protocol and Keet. A family of interoperable
1616| [ROADMAP.md](ROADMAP.md) | Evolutionary path from godot-keet to hyper-social — 8 phases, what exists, what to build |
1717| [AUTO-DISCOVERY.md](AUTO-DISCOVERY.md) | Layered auto-discovery design — 5 layers replacing hardcoded registries, privacy analysis |
1818| [AUTO-DISCOVERY-SPEC.md](AUTO-DISCOVERY-SPEC.md) | Implementation spec — code changes for keet-daemon, godot-keet, bridges (~1,400 LOC) |
19| [ROOMS-ARE-THE-COMPUTER.md](ROOMS-ARE-THE-COMPUTER.md) | **Latest**: Bottom-up architecture — rooms as only data structure, profile rooms, local MI computation, no hand-crafted Autobases. Supersedes AUTO-DISCOVERY-SPEC.md |
1920
2021## Quick Summary
2122
docs/ROOMS-ARE-THE-COMPUTER.md +572 -0
……@@ -0,0 +1,572 @@
1# Rooms Are the Computer
2
3A bottom-up architecture where rooms are the only data structure. Everything else — groups, registries, profiles, graphs, governance — is either room config, room messages, or local computation on room data.
4
5This document supersedes the Autobase-per-level approach in AUTO-DISCOVERY-SPEC.md and simplifies the ROADMAP.md architecture. The conceptual frameworks in SOCIAL-GRAPH-NAVIGATION.md and COMMUNITY-EMERGENCE.md remain valid — this document changes HOW they're implemented, not WHAT they describe.
6
7## The Insight
8
9keet-core already gives you a battle-tested, multi-writer, replicated, conflict-resolving Autobase for every room. Admins can write config. Members can post messages. Roles manage permissions. Invites manage access. This is the hardest distributed systems problem — and it's already solved.
10
11Every time we create a hand-crafted Autobase above the room level (discovery registry, meta-registry, topic index), we're reimplementing worse versions of what keet-core already provides. Each additional Autobase multiplies:
12
13- Writer management complexity
14- Replication overhead
15- View corruption risk
16- Recovery procedures
17- Code surface area
18
19**The fix: stop creating Autobases. Use rooms.**
20
21## The Architecture
22
23```
24OLD (3+ Autobase types, hand-crafted):
25 Room ← Autobase (keet-core) ✓ works great
26 Registry ← Autobase (hand-crafted) ✗ complex, error-prone
27 Meta-registry ← Autobase (hand-crafted) ✗ even worse
28
29NEW (1 Autobase type, all keet-core):
30 Room ← Autobase (keet-core) ✓ works great
31 Registry ← Room (it's just a room) ✓ reuses keet-core
32 Profile ← Room (it's just a room) ✓ reuses keet-core
33 Everything ← Room config + messages ✓ already exists
34 Graph ← Local computation ✓ no storage needed
35```
36
37### What a Room Already Provides
38
39| Capability | How | Multi-writer? |
40|-----------|-----|--------------|
41| Arbitrary key-value metadata | `setConfig(roomId, key, value)` | Yes (any admin) |
42| Structured data exchange | Messages with JSON payloads | Yes (any member) |
43| Access control | Roles: admin, moderator, peer, read-only | Yes (admin manages) |
44| Identity | Member profiles: name, avatar, username | Yes (each member) |
45| Replication | Hyperswarm + Autobase | Automatic |
46| Persistence | RocksDB | Automatic |
47| Invites | `createInvitation(roomId)` | Yes (admin) |
48| Real-time updates | Push events via subscription | Automatic |
49
50Every feature that a "discovery registry Autobase" provides is already available through room primitives. The registry was a room with extra steps.
51
52## Room Types (Convention, Not Code)
53
54A room's purpose is declared by a config key. No new Autobase schemas. No new data structures. Just conventions about what `room.config.type` means.
55
56### Chat Room (default)
57
58```javascript
59room.config = {
60 type: "chat", // or absent — default behavior
61 title: "Rust P2P Dev",
62 description: "Building distributed systems in Rust",
63 topics: ["rust", "p2p", "systems"],
64 discoverable: true, // opt-in to DHT topic announcement
65 bridgedTo: [ // set by bridge bots when they connect
66 { platform: "irc", network: "libera", channel: "#rust-p2p" }
67 ],
68 relatedRooms: ["roomId1", "roomId2"] // admin-curated links
69}
70```
71
72### Registry Room
73
74A room whose purpose is listing other rooms. Members post structured messages as room listings. The room's membership and roles control who can list rooms.
75
76```javascript
77room.config = {
78 type: "registry",
79 title: "Berlin P2P Meetup",
80 description: "Rooms for the Berlin Hypercore community",
81 topics: ["berlin", "p2p", "meetup"],
82 avatar_small: "base64...", // group icon
83 avatar_large: "base64..."
84}
85
86// Listing a room = posting a structured message:
87{
88 type: "room-listing",
89 roomId: "abc123...",
90 title: "Hypercore Study Group",
91 description: "Weekly deep-dives into Hypercore internals",
92 topics: ["hypercore", "study-group"],
93 inviteToken: "xyz...",
94 listedBy: "member:alice0"
95}
96
97// Unlisting = posting a removal:
98{
99 type: "room-unlisting",
100 roomId: "abc123...",
101 unlistedBy: "member:alice0"
102}
103```
104
105**Why this works:**
106- Room roles = registry roles (admin can curate, members can browse)
107- Room bans = registry bans (ban a spammer from listing)
108- Room invites = registry invites (share access to the registry)
109- Chat history = listing history (searchable, versioned, auditable)
110- No new Autobase. No writer key management. No view corruption.
111
112### Profile Room
113
114Each user has a personal room that IS their public profile. They control it. Others follow by joining.
115
116```javascript
117room.config = {
118 type: "profile",
119 title: "alice0's profile",
120 description: "Rust developer, Berlin",
121 topics: ["rust", "berlin", "p2p"],
122 publicRooms: true // signals that this profile shares room listings
123}
124
125// Sharing a room = posting a structured message:
126{
127 type: "room-share",
128 roomId: "def456...",
129 title: "Hypercore Dev Chat",
130 topics: ["hypercore"],
131 inviteToken: "abc...", // optional — only if room is open
132 sharedAt: 1708402400000
133}
134```
135
136**UX flow:**
1371. Alice clicks Bob's name in any shared room
1382. She sees his member info (already works)
1393. New: "Follow" button appears
1404. Follow = join Bob's profile room (invite auto-created, or discoverable)
1415. In Bob's profile room, she sees his shared rooms as messages
1426. She joins any that interest her via posted invite tokens
1437. Her local MI computation now has data about Bob's rooms
1448. Unfollow = leave the profile room
145
146**Why this solves user discovery:**
147- keet-core cannot store arbitrary fields on identity profiles (fixed schema)
148- Profile rooms work around this using existing room primitives
149- "Follow" is just room membership — no new concept
150- The profile room IS the user's public presence
151- Auto-created during identity setup (one extra `createRoom` call)
152
153## Room Metadata Vocabulary
154
155These are conventions for `setConfig` keys. No code changes to keet-core needed. Any room can use any of these.
156
157### Content Metadata
158
159```
160topics : string[] — ["rust", "p2p", "gaming"]
161language : string — "en", "de", "jp"
162category : string — "development", "social", "gaming", "music"
163```
164
165Enables: content-based clustering, DHT topic discovery, search.
166
167### Structural Metadata
168
169```
170relatedRooms : string[] — ["roomId1", "roomId2"] (admin-curated links)
171parentRoom : string — "roomIdX" (thread/sub-room relationship)
172bridgedTo : object[] — [{platform, network, channel}] (set by bridge bots)
173```
174
175Enables: explicit graph edges, hierarchy, cross-platform topology.
176
177### Discovery Metadata
178
179```
180discoverable : "true" — opt-in to DHT topic announcement
181invitePolicy : string — "open" | "invite-only" | "approval"
182publicDescription : string — what non-members see (may differ from description)
183```
184
185Enables: DHT discovery, invite flow, privacy control.
186
187### Type Metadata
188
189```
190type : string — "chat" | "registry" | "profile" | "wiki" | "board"
191```
192
193Enables: room type conventions, UI behavior switching.
194
195## The Graph: Local Computation, Not Shared State
196
197### Input (from rooms you're in)
198
199```
200For each room you're a member of:
201 - room.config (topics, type, related rooms, bridges)
202 - room.members (member IDs)
203
204For each profile room you follow:
205 - room-share messages (rooms the person has shared)
206```
207
208All of this data is already on your machine. keet-core replicated it when you joined the rooms.
209
210### Computation (local, in-memory)
211
212**Step 1: Build co-occurrence matrix**
213
214For every pair of rooms (A, B), count how many members appear in both:
215
216```
217members_A = {alice, bob, carol}
218members_B = {bob, carol, dave}
219shared(A, B) = {bob, carol} → count = 2
220```
221
222**Step 2: Compute MI**
223
224```
225MI(A, B) = log2( P(A,B) / (P(A) × P(B)) )
226
227P(A,B) = shared(A,B) / total_member_room_pairs
228P(A) = |members_A| / total_members
229P(B) = |members_B| / total_members
230```
231
232High MI = rooms share more members than chance predicts = structurally connected.
233
234**Step 3: Cluster**
235
236Build maximum spanning tree from MI scores. Cut edges below threshold. Connected components = groups.
237
238**Step 4: Extract room archetypes (optional)**
239
240Rooms with similar connection patterns (similar MI profiles) are the same "type" even if they have different topics. A "social hub" room connects broadly. A "focused dev" room connects narrowly. A "bridge portal" connects to platforms. The connector pattern reveals the role.
241
242### Output (used by UI)
243
244```javascript
245{
246 clusters: [
247 { id: 0, rooms: ["abc", "def", "ghi"], label: "Rust P2P", topics: ["rust", "p2p"] },
248 { id: 1, rooms: ["jkl", "mno"], label: "Berlin Social", topics: ["berlin"] }
249 ],
250 edges: [
251 { from: "abc", to: "def", mi: 3.2 },
252 { from: "abc", to: "ghi", mi: 2.8 }
253 ],
254 suggestions: [
255 { room: "pqr", reason: "3 shared members with your Rust cluster", via: "member:bob0" }
256 ]
257}
258```
259
260This powers: group bar ordering, room list sorting, graph view, "suggested rooms."
261
262### In AtomSpace Terms
263
264The computation maps exactly to the OpenCog language learning pipeline:
265
266```scheme
267;; INPUT ATOMS (from keet-core room data):
268
269;; Membership — the raw observation (like a word in a sentence)
270(MemberLink
271 (ConceptNode "member:alice0")
272 (ConceptNode "room:abc123"))
273
274;; Room metadata (from room config)
275(EvaluationLink
276 (PredicateNode "has-topic")
277 (ListLink
278 (ConceptNode "room:abc123")
279 (ConceptNode "topic:rust")))
280
281;; Bridge connection (from room config, set by bridge bot)
282(EvaluationLink
283 (PredicateNode "bridged-to")
284 (ListLink
285 (ConceptNode "room:abc123")
286 (ConceptNode "platform:irc")))
287
288;; COMPUTED ATOMS (local, ephemeral):
289
290;; Co-occurrence with MI value
291(EvaluationLink
292 (PredicateNode "room-pair-mi")
293 (ListLink
294 (ConceptNode "room:abc123")
295 (ConceptNode "room:def456")))
296;; FloatValue 3.2 attached as MI score
297;; FloatValue 7 attached as shared member count
298
299;; Cluster membership (from MST + threshold cut)
300(MemberLink
301 (ConceptNode "room:abc123")
302 (ConceptNode "cluster:0"))
303
304;; Room archetype / Section (connection grammar)
305(Section
306 (ConceptNode "room:abc123")
307 (ConnectorSeq
308 (Connector (ConceptNode "topic:rust") (ConnectorDir "+"))
309 (Connector (ConceptNode "topic:p2p") (ConnectorDir "+"))
310 (Connector (ConceptNode "platform:irc") (ConnectorDir "+"))))
311```
312
313**The pipeline is recursive.** The same computation that clusters rooms into groups can cluster groups into meta-groups. Same algorithm, same atoms, different scale. One piece of code, N levels of hierarchy.
314
315```scheme
316;; Level 0: rooms → groups (MI on shared members)
317;; Level 1: groups → meta-groups (MI on shared rooms between groups)
318;; Level 2: meta-groups → communities (MI on shared groups across platforms)
319```
320
321The hierarchy exists in the computation, not in stored data structures. Each user sees their own computed hierarchy from their own local data. Different users may compute different clusters — that's privacy, not a bug.
322
323## Discovery Without Global Registries
324
325### How It Works Now (manual, top-down)
326
327```
3281. Someone creates a registry Autobase
3292. They share the 64-char hex key out-of-band
3303. You paste the key into godot-keet
3314. Your node initializes the Autobase, replicates it
3325. You browse rooms listed in the registry
333```
334
335### How It Works With Rooms (organic, bottom-up)
336
337**Path 1: Through people (social traversal)**
338```
3391. You're in room X with Bob
3402. You click Bob's name → "Follow"
3413. You join Bob's profile room
3424. You see rooms Bob has shared
3435. You join one → meet Carol → follow Carol → discover more rooms
3446. Your local MI computation clusters everything automatically
345```
346
347**Path 2: Through topics (DHT discovery)**
348```
3491. Your room has topics: ["rust", "p2p"]
3502. Room config has discoverable: "true"
3513. keet-daemon announces on hash("hyper-social:topic:rust")
3524. Someone else searches for "rust" on the DHT
3535. They find your room's announcement (title, description, invite info)
3546. They join → the graph grows
355```
356
357**Path 3: Through registries (curated directories — still rooms)**
358```
3591. Someone creates a registry room for "Berlin P2P Meetup"
3602. They share the room invite (not a 64-char hex key — a normal invite)
3613. You join the registry room
3624. You browse room listings posted as messages
3635. You join rooms that interest you
3646. The registry room shows up as a group in your group bar
365```
366
367**Path 4: Through computation (emergent groups)**
368```
3691. You're in 5 rooms that share 80% of their members
3702. Your local MI computation detects this cluster
3713. Group bar shows a "?" icon: "5 rooms with high overlap — name this group?"
3724. You name it → it becomes a local group label
3735. Optionally: create a registry room for it → others can browse it too
374```
375
376All four paths feed the same local graph. The group bar shows the result: a mix of named groups (from registry rooms you've joined), computed clusters (from MI), and followed profiles (from profile rooms). The user doesn't need to know which mechanism discovered which group.
377
378## What Changes in Code
379
380### keet-daemon
381
382**Remove (~870 lines):**
383```
384lib/room-discovery.js — Autobase registry class (~500 lines)
385index.js discovery plumbing — config, eager init, heartbeat (~170 lines)
386router.js discovery routes — register, unregister, list, etc. (~200 lines)
387```
388
389**Add (~480 lines):**
390```
391lib/registry-room.js (~80) — Post/read room-listing messages in registry rooms
392lib/profile-room.js (~60) — Create profile room on identity setup, post room-shares
393lib/local-graph.js (~200) — MI computation on local membership data
394lib/topic-announce.js (~60) — DHT announce/search for discoverable rooms
395router.js new routes (~80) — registry.*, profile.*, graph.*, topic.* routes
396```
397
398**Net: ~870 removed, ~480 added. Fewer lines AND simpler code.**
399
400The 480 new lines are trivial compared to the 870 they replace:
401- `registry-room.js` calls existing room/chat APIs
402- `profile-room.js` calls existing room/identity APIs
403- `local-graph.js` is pure math on data already in memory
404- `topic-announce.js` is a thin DHT wrapper
405- No Autobase lifecycle management, no writer keys, no view corruption handling
406
407### godot-keet
408
409**Simplify:**
410```
411keet_discovery.gd (419→~200 lines)
412 — Remove: Autobase registry management, writer keys, admin roles
413 — Keep: list rooms, search, register/unregister (now calls room APIs)
414
415group_bar.gd (446 lines, same size, different logic)
416 — Before: fetch registries from discovery config
417 — After: show computed clusters + joined registry rooms + followed profiles
418```
419
420**Add:**
421```
422keet_profile.gd (~100 lines)
423 — Follow/unfollow users, read shared rooms from profile rooms
424 — "Follow" button on member popup
425
426keet_graph.gd (~150 lines)
427 — Request MI computation results from daemon
428 — Feed data to group_bar and (later) graph view
429```
430
431**Modify:**
432```
433keet_service.gd — add profile_mgr, graph_mgr (2 lines)
434member_panel.gd — add "Follow" button to member context (20 lines)
435chat_panel.gd — detect bridge message prefix, show badge (30 lines)
436```
437
438### index.js (launcher)
439
440**One line changes:**
441```javascript
442// Before:
443import { startDaemon } from 'keet-daemon'
444// After:
445import { startHub } from 'hyper-social-hub'
446// (startHub wraps startDaemon + adds hub routes)
447```
448
449Or: keet-daemon absorbs the hub routes directly (no separate package needed for Phase 1). The routes are thin enough to add to the existing router.
450
451## UI Philosophy
452
453From the research and this conversation, the UI principles:
454
455### Sparse by Default
456
457A UI that would be better if it wasn't there. Messages, names, avatars ARE the interface. Typography and spacing do the work. No decoration that isn't information.
458
459### Contextual, Not Comprehensive
460
461The interface is a different tool depending on what you're doing:
462- **Chatting**: message stream + input, everything else fades
463- **Navigating**: group bar + room list, chat dims
464- **Discovering**: profile rooms, registry rooms, topic search
465- **Managing**: room settings, bridge config, moderation
466
467Show only what's relevant to the current mode. Fade, don't remove — maintaining spatial memory.
468
469### Ambient State, Not Badges
470
471Room activity as subtle glow or pulse on group bar icons, not red circles with numbers. Unread state as color shift, not notification count. The emotional weight of a number ("47 unread") creates anxiety. A warm glow says "there's life here" without pressure.
472
473### Three Levels Max
474
475Group rail (60px) + room list + active content. That's it. Deeper navigation through drilldown (click into a room, click into a thread), not through more panels. The group bar shows computed clusters alongside named groups — no distinction in the UI between "groups someone created" and "groups the math found."
476
477### The Board Panel Is Dead
478
479Kanban boards don't serve this context. If task tracking ever matters, it's a room-level addon someone builds later, not a core feature. Removing it from the roadmap.
480
481### Wiki Is Inline
482
483A wiki page is a pinned, editable message — not a separate panel. You're discussing something, someone says "let's capture this," and the message becomes a wiki entry. Other people edit it in-place. The page list is a filtered view of chat history showing only wiki-type messages. No tab switching. The wiki lives in the conversation that created it.
484
485```javascript
486// A wiki entry is just a message with a type field:
487{
488 type: "wiki",
489 title: "How to set up Hyperswarm",
490 content: "## Step 1\n\nInstall the package...",
491 editedBy: "member:alice0",
492 editedAt: 1708402400000
493}
494```
495
496Renders inline as an expandable card. Click to read. Edit button for admins/mods. Version history = previous messages with the same title.
497
498## Migration Path
499
500Each step is independently deployable. The existing app works at every stage.
501
502### Step 1: Add room metadata conventions
503
504Set `topics`, `discoverable`, `type` on rooms via `setConfig`. No Autobase changes. No UI changes. Just establishing conventions.
505
506### Step 2: Add profile rooms
507
508Create a profile room during identity setup. Add "Follow" button to member popups. Post room-share messages when user opts to share a room publicly. This is ~160 lines of new code total.
509
510### Step 3: Add local graph computation
511
512Compute MI from membership data on a timer (every 60 seconds, or on room join/leave). Feed results to group bar for reordering. ~200 lines of JS, ~150 lines of GDScript. No Autobase changes.
513
514### Step 4: Add registry rooms
515
516Create a new room type "registry." Post room-listing messages instead of Autobase entries. The UI for browsing registries stays identical — it's just reading messages instead of Autobase records. ~80 lines of JS, ~100 lines of GDScript changes.
517
518### Step 5: Add DHT topic discovery
519
520Rooms with `discoverable: "true"` announce on topic-hashed DHT keys. Search by topic. ~60 lines of JS, ~80 lines of GDScript. No Autobase.
521
522### Step 6: Remove RoomDiscovery Autobase
523
524Once registry rooms are stable and profile rooms handle user discovery, remove the hand-crafted Autobase code. ~870 lines deleted. The existing discovery registries migrate to registry rooms (one-time conversion).
525
526### Step 7: Add bridge awareness
527
528Bridges set `bridgedTo` in room config when they connect. Chat panel detects bridge message prefixes and shows platform badges. ~30 lines in chat_panel.gd.
529
530### Step 8: Add graph view (optional)
531
5322D force-directed visualization of the computed graph. Rooms as circles, MI as edges, clusters as colors. Click to enter room. This is the visual expression of what the group bar already uses internally. ~400 lines of GDScript. Pure UI — no new data infrastructure.
533
534## What the Existing Docs Got Right
535
536- **SOCIAL-GRAPH-NAVIGATION.md** — The math is correct. MI, MST, clustering, sheaf theory. All stays.
537- **COMMUNITY-EMERGENCE.md** — The sociology is correct. Phase transitions, nucleation, three sovereignties. All stays.
538- **AUTO-DISCOVERY.md** — The layered approach is correct (Layers 0-4). The implementation changes from "new Autobases" to "room conventions + local computation."
539- **ROADMAP.md** — The phased evolution is correct. The Hub wrapping keet-daemon is correct. The specific phases change (drop board panel, inline wiki, add profile rooms).
540- **ARCHITECTURE.md** — The fracture principle is correct. Independent projects composing into a system.
541
542## What the Existing Docs Got Wrong
543
544- **AUTO-DISCOVERY-SPEC.md** — Proposed new Autobases (topic-discovery, mi-engine as stored state). Wrong tool. Replace with rooms + local computation. This document supersedes it.
545- **ROADMAP.md Phase 5** — Board panel doesn't serve this context. Drop it.
546- **ROADMAP.md Phase 5** — Wiki panel as separate tab. Wrong UX. Wiki is inline in chat.
547- **ROADMAP.md Phase 1** — Hub-daemon as separate package may be unnecessary. The routes are thin enough to add to keet-daemon's router directly. Decide during implementation.
548- **AUTO-DISCOVERY-SPEC.md** — Proposed ~1,400 LOC of changes. Actual changes are ~480 LOC of additions (after removing ~870 LOC of Autobase code). Net simpler.
549
550## Gaps Still Open
551
5521. **Profile room discoverability** — How does Alice find Bob's profile room if they aren't in a shared room yet? Answer: she can't, and that's correct. Discovery happens through shared rooms, not through a global directory. This IS the privacy model.
553
5542. **Registry room migration** — Existing discovery registries (Global, testy) need to be migrated to registry rooms. One-time conversion: read all entries from the Autobase, create a room, post them as messages. The Autobase discovery key becomes the room's invite.
555
5563. **Bridge bot identity** — When a bridge connects to a room and sets `bridgedTo` in config, it needs admin access. Bridge bots should be added as room members with moderator role (not admin — they shouldn't be able to delete the room).
557
5584. **Profile room spam** — If "Follow" is just joining a room, and the profile room is discoverable, bots could spam it. Mitigation: profile rooms default to invite-only. Following requires a mutual connection (you're both in at least one room).
559
5605. **MI computation cost** — For users in 100+ rooms with 1000+ total members, the pairwise MI computation is O(n² rooms × m members). Benchmark needed. May need sparse representation or incremental updates.
561
5626. **Cross-platform identity linking** — COMMUNITY-EMERGENCE.md discusses it, ROADMAP.md Phase 6 plans it, but no spec exists. Likely: identity links are messages in profile rooms — "I am also alice on IRC Libera" — cryptographically signed by both identities. Implementation TBD.
563
5647. **Moderation service** — COMMUNITY-EMERGENCE.md describes moderation as a subscribable label service (Blacksky pattern). No implementation spec. Likely Phase 8. Could be: a room whose messages are moderation labels, subscribable by other rooms.
565
566## The Principle
567
568The community already exists. The rooms already exist. The people already exist.
569
570Rooms are the computer. Everything else is either room config, room messages, or local math on room data. One Autobase type (managed by keet-core). One computation (MI pipeline). One UI pattern (sparse, contextual, ambient).
571
572The structure was always there. The tools just need to see it.