From 36865149fce258628e764d1b17214f07f601fcd4 Mon Sep 17 00:00:00 2001 From: Joel Teply Date: Thu, 13 Aug 2026 07:45:40 -0500 Subject: [PATCH 1/2] =?UTF-8?q?docs(activities):=20recipe=20formalization?= =?UTF-8?q?=20=E2=80=94=20identity,=20registry,=20taxonomy=20(#274)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Design, not built. Written after auditing every recipe artifact in the tree, and the audit is the headline. THE FINDING: the recipe system did not fail to get built — it was DEMOTED in the Rust rewrite. legacy/src/system/recipes/ 28 recipes, 20 fields core/…/experience/recipes/ 4 recipes, 4 fields The live four (benchmark/hard-rs, chat, profile, video-chat) carry only `purpose, regions, affordances, layout` — PRESENTATION. Everything describing how an activity BEHAVES was dropped: pipeline, ragTemplate, strategy, roles, tools, sentinelTemplates, access, isPublic, locked, tags, name/description, and — the one that bit us — uniqueId + version. `ai-debate-club.json` ships `roles: [{role, type, requires, prefers}]` and `strategy.responseRules`. Those are exactly the two things found missing this session when citizens had no roles and no participation rules and looped in academy. Not unbuilt. Designed, authored 28 times, lost in the port. Several open cards are re-deriving pieces of it: #371 (recipe-owned objective), #6 (ground persona in room purpose), #264's role gap. IDENTITY is the specific loss that caused the live defect. uniqueId + version were replaced by ONE free-form `purpose` string doing two jobs — identity AND taxonomy. That is why `benchmark` (family) and `benchmark/hard-rs` (instance) were indistinguishable, why spawn's docs advertised a purpose resolving to nothing, and why such rooms render as plain chat. THE DESIGN: - RecipeId (UUID) + version = identity. Stable across content edits, so a bugfix to chat.json does not orphan every chat room. NOT a content hash for that reason. - Reproducibility moves to the RUN RECEIPT: (recipe_id, version, content_hash), so a benchmark's exact bytes are pinned forever without making identity brittle. Same shape forge-alloy already uses for models. - purpose demoted to a free-form hierarchical LABEL. Grouping falls out of the path: `benchmark/*` is a prefix query, rooms live at academy/bench//, and the web UI rewrites URLs from the same taxonomy instead of a hand-kept nav. - ONE registry, TWO ways in: shipped (embedded, stable ids) and installed (`recipe/install` at runtime). Spawn resolves an id and does not care which. Install is the validation boundary — serde, fail loud, assign identity. - NOT an enum (a closed set cannot express a system where anyone writes a recipe, and it reintroduces the compiler-to-author trap experience/source.rs warns about). Named constants over an open registry: `shipped::BENCHMARK_HARD_RS`. - Binding carries (id, version, content) so a partitioned node still renders its rooms — addressing by identity while keeping bytes local, as expert paging does. Six-step build order; steps 1-2 remove the silent-chat-room fallback, step 4 fixes per-run benchmark rooms (#346 slice 3), step 5 restores roles (#264's open half), step 6 ports 28 activity types forward as pure data with zero Rust. Explicitly recorded as NOT to build: room field on work cards (retracted), an activity-type enum, hierarchical purpose fallback, automatic room expiry. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo --- .../recipes/RECIPE-FORMALIZATION.md | 213 ++++++++++++++++++ 1 file changed, 213 insertions(+) create mode 100644 docs/activities/recipes/RECIPE-FORMALIZATION.md diff --git a/docs/activities/recipes/RECIPE-FORMALIZATION.md b/docs/activities/recipes/RECIPE-FORMALIZATION.md new file mode 100644 index 0000000000..6536c3dfb4 --- /dev/null +++ b/docs/activities/recipes/RECIPE-FORMALIZATION.md @@ -0,0 +1,213 @@ +# Recipe Formalization — identity, registry, and the taxonomy that groups everything + +**Parent:** [Recipes](./RECIPES.md) · **Task:** #274 · **Status:** Design, not built +**Created:** 2026-08-13 + +> Spawning an activity is the most ordinary creative act in the system. Joel, the +> citizens, and the agents must be able to write a recipe and open a room **all the +> time**, without a compiler and without asking permission. Everything below exists +> to make that true and keep it safe. + +--- + +## 1. The audit that forced this + +Two populations of recipe exist in the tree today. + +| | count | fields | +|---|---|---| +| **Live** (`core/continuum-core/src/experience/recipes/`) | 4 | `purpose, regions, affordances, layout` | +| **Legacy** (`legacy/src/system/recipes/`) | 28 | `uniqueId, version, name, displayName, description, tags, pipeline, ragTemplate, strategy, roles, tools, sentinelTemplates, layout, view, access, isPublic, locked, inputs, room, entityType` | + +The live four are `benchmark/hard-rs`, `chat`, `profile`, `video-chat`. The legacy +28 include `coding`, `research`, `newsroom`, `ai-debate-club`, `academy-training`, +`creative-writing`, `factory`, `gan`, `terminal`, `universe`, `dm`, +`multi-persona-chat`, `training-dashboard`, `grid-overview`, and more. + +**The rewrite reduced a recipe to PRESENTATION.** `regions` + `affordances` + +`layout` describe what a room looks like and which verbs it offers. Everything +describing how the activity *behaves* was dropped: + +- `pipeline` — the command sequence the activity runs +- `ragTemplate` — its context strategy +- `strategy` — `conversationPattern`, `responseRules` (participation!) +- `roles` — `[{role, type, requires, prefers}]` per participant +- `tools`, `sentinelTemplates` — capability surface +- `access`, `isPublic`, `locked` — governance +- `name`, `displayName`, `description`, `tags` — discovery and grouping +- `uniqueId`, `version` — **identity** + +This is not a backlog of unbuilt features. It was designed, authored 28 times, and +lost in the port. Several open cards are re-deriving pieces of it from scratch: +#371 (recipe-owned objective), #6 (ground persona in room purpose — legacy had +`strategy.responseRules`), #264's "citizens have no roles" (legacy had `roles`). + +**And identity is the specific loss that bit us.** `uniqueId` + `version` were +replaced by a single free-form `purpose` string doing two jobs at once — identity +AND taxonomy. That is why `benchmark` (family) and `benchmark/hard-rs` (instance) +were indistinguishable, why the spawn docs advertised a purpose that resolves to +nothing, and why the resulting rooms silently render as plain chat. + +--- + +## 2. Identity: `RecipeId` (UUID) + `version`, with `purpose` demoted to a label + +``` +RecipeId — UUID. THE identity. Stable across content edits. +version — monotonic. Which revision of that recipe. +purpose — free-form hierarchical LABEL: "benchmark/hard-rs". Never resolution. +``` + +**Why UUID and not a content hash.** A shipped recipe is prod-critical and +long-lived; a bugfix to `chat.json` must not orphan every chat room in existence. +Identity must survive content edits, so identity cannot BE the content. + +**Where reproducibility lives instead.** A run's receipt records +`(recipe_id, version, content_hash)`. Identity is stable; the exact bytes an exam +was administered under are pinned in the receipt, forever, and independently +verifiable. This is the forge-alloy pattern — artifact identity plus verified hash +— and it is what makes a published benchmark number falsifiable. Putting the hash +in the *binding* instead would have made every recipe edit orphan its rooms. + +**`purpose` survives as taxonomy** (§5) and as the human-facing name. It stops +being load-bearing, so the family/instance ambiguity simply stops mattering. + +--- + +## 3. Registry: one resolution path, two ways in + +``` + ┌── shipped: embedded at build, stable ids, prod-critical +RecipeRegistry ──┤ + └── installed: registered at runtime (authored, generated, peer) + │ + recipe/install ← THE validation boundary +``` + +**One lookup.** `registry.get(RecipeId) -> Option<&Recipe>`. `activity/spawn` +resolves an id against the registry and does not care how it got there. No +name-vs-hash-vs-inline branching at the call site. + +**`recipe/install` is a first-class verb**, not a filesystem side-effect. It: +1. deserializes and validates (serde + schema), failing loud and naming the file +2. assigns or accepts a `RecipeId`, bumps `version` if the id already exists +3. stores it so it resolves — and, being an ordinary command, is callable by a + citizen inventing an activity, by the operator, or by a generator + +This formalizes what `builtins_with_overlay` already half-does (embedded floor + +disk overlay). Today authoring is "drop a file and hope"; install makes it a +receipted act. + +**Spawn accepts either an id or a full recipe.** Passing a recipe inline is just +install-then-spawn — same path, one extra step, so the ergonomic case (a citizen +inventing an activity mid-conversation) needs no special mechanism. + +**Partition tolerance.** A room's wall binding carries `(recipe_id, version)` AND +the recipe content. A node at the park with no network must still render its +rooms; addressing by identity while keeping the bytes local is the same shape as +expert paging. Binding by reference alone would make an offline room a blank. + +--- + +## 4. Naming shipped recipes in Rust: constants, not an enum + +Joel's question — "constants possibly in an enum, some rust thing". + +**Not an enum.** An enum is a closed set. Recipes are open by construction: +generated on the fly, installed at runtime, authored by citizens. An enum would +have to be edited (and the binary recompiled) for every new activity — the exact +"if authoring an activity requires a compiler, activities get hand-made instead" +failure `experience/source.rs` already warns about. + +**Named constants over an open registry:** + +```rust +pub mod shipped { + /// The Rust-coder gym. Prod-critical, ships in the binary. + pub const BENCHMARK_HARD_RS: RecipeId = RecipeId::from_u128(0x…); + pub const CHAT: RecipeId = RecipeId::from_u128(0x…); + pub const PROFILE: RecipeId = RecipeId::from_u128(0x…); + pub const VIDEO_CHAT: RecipeId = RecipeId::from_u128(0x…); +} +``` + +Typed, greppable, refactorable references to the prod-critical set — so core code +never spells a magic string — while the registry itself stays open. Shipped +recipes get their ids authored INTO the JSON (`"id": "…"`), so the constant and +the data agree and a test pins that every shipped id resolves. + +--- + +## 5. Taxonomy: `purpose` as a URI path, grouping for free + +`purpose` becomes an explicit hierarchical path, and grouping falls out of it: + +``` +chat academy/bench/hard-rs/ +benchmark/hard-rs academy/bench/swe-lite/ +benchmark/swe-lite academy/training/ +``` + +- **Rooms** live at `academy/bench//` — per-run, per your ruling that a + new benchmark is a new activity. Fresh board, no inherited cards, no inherited + claims. +- **Recipes** are grouped by prefix with zero extra structure: `benchmark/*` is a + query, not a registry table. +- **UI** groups by the same prefix and rewrites URLs from it (web only) — sections + come from the taxonomy rather than a hand-maintained nav. +- **Discovery**: `tags` (restored from legacy) for cross-cutting search that a + single path can't express. + +The tree is the grouping mechanism for rooms, recipes, and navigation at once. + +--- + +## 6. The restored `Recipe` shape + +Presentation (live today) plus the behavioural half recovered from legacy: + +``` +identity id: RecipeId, version: u32, purpose: String (path), tags: [String] +human name, display_name, description +presentation regions[], affordances[], layout ← already live +behaviour pipeline[], rag_template, strategy, roles[] ← restore from legacy +capability tools[], sentinel_templates[] ← restore +governance access, is_public, locked ← restore +children default_child_recipes: [RecipeId] ← "activities spawn activities" +objective ActivityObjective ← #371, recipe-owned +``` + +Every field optional except `id`/`version`/`purpose`, so the live four remain +valid unchanged and a minimal generated recipe stays a few lines. `default_child_recipes` +is what makes spawning cascade naturally instead of one call at one dispatcher. + +--- + +## 7. Build order + +1. **`RecipeId` + `version` + registry**, ids authored into the four shipped JSONs, + `shipped::` constants, test pinning each resolves. Purpose demoted to label. +2. **`recipe/install`** as a verb; `activity/spawn` resolves by id and **fails loud** + on an unknown one (closes the silent-chat-room fallback, which is live today). +3. **Binding carries `(id, version, content)`**; migrate existing bound rooms. +4. **Taxonomy + per-run rooms**: `academy/bench//`, dispatch spawns its + own activity. Closes #346 slice 3. +5. **Restore behaviour fields**, one at a time, each with a live recipe exercising + it: `roles` first (it is #264's open half), then `strategy`, then `pipeline`. +6. **Port legacy recipes forward** as data — 28 activity types return with zero Rust. + +Steps 1–2 remove the silent failure. Step 4 fixes benchmarks. Step 5 is where the +citizens get their roles back. + +--- + +## 8. What this deliberately does not do + +- **No enum of activity types.** Closed sets cannot express a system where anyone + writes a recipe. +- **No room field on work cards.** A card is published where its participants + stand; the ACTIVITY carries the room. (Retracted proposal, recorded on #274.) +- **No hierarchical purpose fallback** (`benchmark/swe` → `benchmark`). Resolution + is by id and exact; a missing suite recipe is an authoring act, not a lookup rule. +- **No automatic room lifecycle.** Unchanged from `modules/activity.rs`: a room is + the durable record of its activity, and nothing expires it on a timer. From 8a7434ab361889bb6829f0aa32beada7fbff7ce3 Mon Sep 17 00:00:00 2001 From: Joel Teply Date: Thu, 13 Aug 2026 07:46:56 -0500 Subject: [PATCH 2/2] =?UTF-8?q?docs(activities):=20appendix=20A=20?= =?UTF-8?q?=E2=80=94=20the=2029=20legacy=20recipes,=20triaged=20against=20?= =?UTF-8?q?open=20cards=20(#274)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Joel: 'Old shit was broken and a mess but many extra recipes we maybe miss.' Enumerates every legacy recipe so they cannot be lost twice, and maps them to work already underway. SEVEN describe activities we have OPEN CARDS re-deriving from scratch: training-dashboard (#141, in progress), grid-overview (#257), universe (#375), research (#93), academy-training, factory, inference-sample. Sharpest instance: general-chat's description is 'Natural conversation where AIs respond to humans thoughtfully, AVOIDING AI-TO-AI LOOPS.' That requirement was recipe-level, authored, and lost in the port — and this session was spent watching three citizens loop in academy. Port the concept and field content, not the Node implementation. Each port is a JSON file plus a test that its purpose resolves — zero Rust. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo --- .../recipes/RECIPE-FORMALIZATION.md | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/docs/activities/recipes/RECIPE-FORMALIZATION.md b/docs/activities/recipes/RECIPE-FORMALIZATION.md index 6536c3dfb4..a49ef9282f 100644 --- a/docs/activities/recipes/RECIPE-FORMALIZATION.md +++ b/docs/activities/recipes/RECIPE-FORMALIZATION.md @@ -211,3 +211,57 @@ citizens get their roles back. is by id and exact; a missing suite recipe is an authoring act, not a lookup rule. - **No automatic room lifecycle.** Unchanged from `modules/activity.rs`: a room is the durable record of its activity, and nothing expires it on a timer. + +--- + +## Appendix A — the legacy corpus (`legacy/src/system/recipes/`, 29 recipes) + +Joel: *"Old shit was broken and a mess but many extra recipes we maybe miss."* +Exactly right, and this appendix exists so they cannot be lost a second time. Port +the **concept and the field content**, not the implementation — the legacy runtime +was Node and is retired; the recipes are DATA and survive it. + +**The uncomfortable part: several of these describe activities we have OPEN CARDS +re-deriving from scratch.** The recipe already says what the thing is. + +| legacy recipe | what it is | status now | +|---|---|---| +| `training-dashboard` | loss curves, LR, GPU usage, historical runs | **#141 in progress** — recipe already describes it | +| `grid-overview` | topology, health, latency, GPU, routing decisions | **#257 pending** | +| `universe` | design complete experiences — Tron, Ghibli | **#375 pending** | +| `research` | parallel investigation, dedup, synthesis to docs | **#93 pending** | +| `academy-training` | Teacher sets exams → Student answers → Teacher scores | the training loop (#320, #135) | +| `factory` | forge floor — active forges, published models, leaderboard | forge work (#99, #102) | +| `inference-sample` | base vs adapter output side-by-side, during/after training | genome eval (#32/#98) | +| `coding` | read, write, review, ship code as a team | the coder gym | +| `live` | real-time voice, AIs hear humans AND each other | live mode (#286 beta gate) | +| `terminal` | tmux-like multiplexer over AI shell sessions | — | +| `multi-persona-chat` | organic multi-persona w/ resource management, BoW gating | relates #9, #168 | +| `general-chat` | humans answered thoughtfully, **AI-to-AI loops avoided** | directly relevant to #264 | +| `newsroom` | current events / world awareness for all personas | — | +| `outreach` | social engagement + external presence | — | +| `creative-writing` | one author holds the voice, advisors critique | — | +| `canvas` | shared drawing, realtime collab + AI | — | +| `browser` | web browsing with AI assistance | relates #93 | +| `gan` | GAN training visualization | — | +| `persona` | a persona's cognitive state, memory, activity | relates #184 | +| `diagnostics`, `diagnostics-log`, `logs` | health, metrics, per-persona logs, AI-assisted debug | relates #235 | +| `help` | help, documentation, onboarding | — | +| `settings` | API keys, preferences, AI-assisted config | — | +| `dm` | private 1:1 | — | +| `chat`, `profile` | — | **already ported** | + +`general-chat`'s one-line description is worth reading twice given this session: +*"Natural conversation where AIs respond to humans thoughtfully, avoiding AI-to-AI +loops."* The avoid-the-loop requirement was recipe-level, authored, and lost — and +we spent a night watching three citizens loop. + +**Also mine the old DESIGN docs for principles, not mechanisms.** `docs/activities/` +carries RECIPE-SYSTEM-REQUIREMENTS, SCOPE-BASED-RECIPES, RECIPE-DRIVEN-INVENTION, +RECIPE-LEARNING-DYNAMICS. The scope→training-data→LoRA→instant-expert loop in +SCOPE-BASED-RECIPES is the reason per-activity scopes matter at all; it is why +collapsing every run into one commons is destructive rather than merely untidy. + +**Port order:** the seven with open cards first — each retires re-derivation +already underway — then the rest as appetite allows. Each port is a JSON file and +a test that its purpose resolves. Zero Rust, by design.