|
| 1 | +# ADR-0004: Engine Anchor Files as Instructions Spine |
| 2 | + |
| 3 | +**Status:** Proposed |
| 4 | +**Date:** 2026-05-27 |
| 5 | +**Author:** KAI HACKS AI (LLM-with-maintainer collaboration) |
| 6 | +**Supersedes:** -- |
| 7 | +**Superseded by:** -- |
| 8 | + |
| 9 | +## Context |
| 10 | + |
| 11 | +The engine domain (`engine/`) carries the universal scaffold every culture |
| 12 | +uses. It has eight subfolders today, each modelling one universal aspect |
| 13 | +of personas and scenes: |
| 14 | + |
| 15 | +``` |
| 16 | +engine/adoption/ how an idea moves across a system |
| 17 | +engine/gender/ how a body is read before it speaks |
| 18 | +engine/group/ how a configuration holds people together |
| 19 | +engine/language/ how language passes between persona and room |
| 20 | +engine/org/ how an organisation refracts work through a claim |
| 21 | +engine/stress/ how demand exceeding capacity discharges |
| 22 | +engine/wow/ how a work life enters a room |
| 23 | +engine/stack/ (integration layer -- not a domain axis) |
| 24 | +``` |
| 25 | + |
| 26 | +Each subfolder ships position files (HOLD-shaped: Has / Orders / Loses / |
| 27 | +Drives) and / or process files (IDLE-shaped: Initiated by / Direction / |
| 28 | +Lever / Echo) plus a README. As axes were added one at a time, no formal |
| 29 | +contract existed for "what is the single entry point into this engine". |
| 30 | +Three problems followed: |
| 31 | + |
| 32 | +1. **Inconsistent anchoring.** Some axes had a natural entry that named |
| 33 | + the axis itself (`engine/group/position_group.md`, |
| 34 | + `engine/org/process_operate_org.md`, `engine/adoption/process_adopt_idea.md`). |
| 35 | + Others didn't (gender had only `position_male.md` + `position_female.md` |
| 36 | + with no axis-level file; wow had 14 sibling positions with no anchor). |
| 37 | + |
| 38 | +2. **`instructions.md` drift.** The runtime spine |
| 39 | + (`engine/stack/instructions.md`) references engine entries in prose to |
| 40 | + tell agents what to invoke -- but only five of seven engine axes were |
| 41 | + referenced (language, stress, adoption, group, org). gender and wow |
| 42 | + were structurally invokable but operationally invisible: the runtime |
| 43 | + had no canonical pointer to them. |
| 44 | + |
| 45 | +3. **Discovery inside zips.** Engine READMEs are excluded from the |
| 46 | + shipped zips by `scripts/build_zips.py`. Readers (deployers, downstream |
| 47 | + LLM agents) inside an extracted culture pack cannot navigate via the |
| 48 | + README; they need a per-axis entry file they can find from its name |
| 49 | + alone. |
| 50 | + |
| 51 | +Without a contract, every new engine axis re-litigates "what's the |
| 52 | +entry", `instructions.md` drifts further from completeness, and zip |
| 53 | +deployment can't reliably enumerate the engine surface. |
| 54 | + |
| 55 | +PR #577 (gender anchor) and #581 (wow anchor) closed the immediate |
| 56 | +gaps, but the underlying pattern was implicit. This ADR makes it |
| 57 | +explicit so future engine axes follow it by default and |
| 58 | +`instructions.md` becomes a complete registry of invokable engines. |
| 59 | + |
| 60 | +## Decision |
| 61 | + |
| 62 | +1. **One anchor per engine axis.** Every domain subfolder under |
| 63 | + `engine/<axis>/` carries exactly one anchor file -- its single clear |
| 64 | + entry point. The integration layer (`engine/stack/`) is exempt: it |
| 65 | + has `stack.md` + `instructions.md` and is structurally distinct. |
| 66 | + |
| 67 | +2. **Two anchor flavours, picked by what the axis is.** |
| 68 | + |
| 69 | + | Flavour | File pattern | Shape | When | |
| 70 | + |---|---|---|---| |
| 71 | + | Position-anchor | `position_<axis>.md` | HOLD (Has / Orders / Loses / Drives) | The axis is something the room *reads* on the persona -- static, held continuously. | |
| 72 | + | Process-anchor | `process_<verb>_<axis>.md` or `process_<axis>.md` | IDLE (Initiated by / Direction / Lever / Echo) | The axis is something that *happens* -- dynamic, with an Initiated-by trigger and an Echo. | |
| 73 | + |
| 74 | + The choice is editorial, made when the axis is introduced, and |
| 75 | + documented in the axis README. |
| 76 | + |
| 77 | +3. **Anchor enumerates its expressions in prose.** The anchor file names |
| 78 | + every sibling expression in its body (Initiated by for processes, |
| 79 | + ## Expressions / Has for positions) with inline sibling links, so a |
| 80 | + reader inside a zip can navigate from the anchor to every expression |
| 81 | + without depending on the README. Following the |
| 82 | + `process_operate_org.md` precedent (which links all four CVF |
| 83 | + positions) and `position_gender.md` README convention (which lists |
| 84 | + the male/female expressions). |
| 85 | + |
| 86 | +4. **`engine/stack/instructions.md` is the registry.** Every engine |
| 87 | + anchor is referenced in `instructions.md` with an invocation mode: |
| 88 | + |
| 89 | + | Mode | Meaning | Examples | |
| 90 | + |---|---|---| |
| 91 | + | **global** | The engine is invoked for every persona / every scene. | language, stress, gender, wow | |
| 92 | + | **per-khai-type** | The engine is invoked when a specific khai entity (other than `persona`) appears in scope. | group (when a group exists), org (when an organisation exists), adoption (when an idea-adoption arc is active) | |
| 93 | + |
| 94 | + The two modes are mutually exclusive at the engine level. A persona |
| 95 | + always carries the global engines; the per-khai-type engines attach |
| 96 | + when their triggering entity is present. |
| 97 | + |
| 98 | +## Architecture |
| 99 | + |
| 100 | +### Anchor file shape |
| 101 | + |
| 102 | +Frontmatter: |
| 103 | + |
| 104 | +```yaml |
| 105 | +--- |
| 106 | +khai: position # or "process" |
| 107 | +license: CC-BY-NC-4.0 |
| 108 | +stamp: |
| 109 | + owner: KAI HACKS AI |
| 110 | + version: vX.Y.Z |
| 111 | + date: 'YYYY-MM-DD' |
| 112 | +--- |
| 113 | +``` |
| 114 | + |
| 115 | +Body: |
| 116 | + |
| 117 | +``` |
| 118 | +# Position: <Axis> (or "# Process: <Verb>") |
| 119 | +
|
| 120 | +## Title |
| 121 | +<Axis> (or "<Verb>" for processes) |
| 122 | +
|
| 123 | +## Owner |
| 124 | +- Project: Cultures |
| 125 | +- Scope: Universal |
| 126 | +
|
| 127 | +--- |
| 128 | +
|
| 129 | +## Has / Orders / Loses / Drives (HOLD, positions) |
| 130 | +## Initiated by / Direction / Lever / Echo (IDLE, processes) |
| 131 | +``` |
| 132 | + |
| 133 | +The `## Title` section is required by |
| 134 | +`khai_tests.components.test_has_title` for newly-added position files |
| 135 | +and recommended (consistency) for process files. |
| 136 | + |
| 137 | +### Anchor → expressions linking |
| 138 | + |
| 139 | +The anchor's prose body names every expression with a sibling link. |
| 140 | +The link surface lives in the section most natural to the flavour: |
| 141 | + |
| 142 | +- **Position-anchor** (e.g. `position_gender.md`): the README's |
| 143 | + `## Expressions` section lists siblings; the anchor file itself may |
| 144 | + reference expressions abstractly without enumerating them. |
| 145 | +- **Process-anchor** (e.g. `process_operate_org.md`, |
| 146 | + `process_wow.md`): the **Initiated by** section enumerates the |
| 147 | + sub-axes / quadrants / channels the process refracts through, each |
| 148 | + with a sibling link. |
| 149 | + |
| 150 | +Expressions are not required to backlink the anchor in their own prose |
| 151 | +(matches the existing convention: `position_clan_org.md` does not link |
| 152 | +`process_operate_org.md`). If the codebase later adopts comprehensive |
| 153 | +two-way navigation, that's a separate refactor across every engine |
| 154 | +axis. |
| 155 | + |
| 156 | +### `instructions.md` registry |
| 157 | + |
| 158 | +`engine/stack/instructions.md` carries a section that lists every |
| 159 | +engine anchor with its invocation mode. Shape (to be finalised in the |
| 160 | +implementation PR): |
| 161 | + |
| 162 | +```markdown |
| 163 | +# Engines |
| 164 | + |
| 165 | +Each engine declares one entry. The agent invokes it according to |
| 166 | +the engine's mode. |
| 167 | + |
| 168 | +| Engine | Entry | Mode | Triggered by | |
| 169 | +|---|---|---|---| |
| 170 | +| gender | [../gender/position_gender.md](../gender/position_gender.md) | global | every persona | |
| 171 | +| language | [../language/process_using_language.md](../language/process_using_language.md) | global | every persona | |
| 172 | +| stress | [../stress/process_stress.md](../stress/process_stress.md) | global | every persona | |
| 173 | +| wow | [../wow/process_wow.md](../wow/process_wow.md) | global | every persona | |
| 174 | +| adoption | [../adoption/process_adopt_idea.md](../adoption/process_adopt_idea.md) | per-khai-type | `khai: idea` in scope | |
| 175 | +| group | [../group/position_group.md](../group/position_group.md) | per-khai-type | `khai: group` in scope | |
| 176 | +| org | [../org/process_operate_org.md](../org/process_operate_org.md) | per-khai-type | `khai: organisation` in scope | |
| 177 | +``` |
| 178 | + |
| 179 | +The existing prose in `instructions.md` (Human / Agent / Collaboration |
| 180 | +/ Knowledge / System) is preserved; the engine registry is an |
| 181 | +additional section that makes the global/per-khai-type contract |
| 182 | +explicit. |
| 183 | + |
| 184 | +### Relationship to KAIHACKS |
| 185 | + |
| 186 | +This pattern lives at the **khai-spine** level, not the Cultures |
| 187 | +project level. The Cultures repo holds the concrete instance: |
| 188 | +`engine/stack/instructions.md` is the Cultures-flavoured spine. |
| 189 | +Other projects building on khai (e.g. a different culture stack) |
| 190 | +follow the same anchor + mode contract for their own engines. |
| 191 | + |
| 192 | +KAIHACKS' `ARCHITECTURE.md` documents the khai spine; this ADR is the |
| 193 | +Cultures-side concrete description of how engine anchors plug into |
| 194 | +that spine. |
| 195 | + |
| 196 | +## Alternatives considered and rejected |
| 197 | + |
| 198 | +- **One anchor flavour for all axes (always position, or always process).** |
| 199 | + Rejected: gender and group are read-shaped (static); adoption and |
| 200 | + stress are happening-shaped (dynamic). Forcing one flavour onto both |
| 201 | + loses the distinction the codebase already uses naturally. Picking |
| 202 | + per axis is an editorial cost paid once at introduction. |
| 203 | + |
| 204 | +- **README as the anchor.** Rejected: engine READMEs are excluded from |
| 205 | + shipped zips by `scripts/build_zips.py`. A reader inside an |
| 206 | + extracted pack cannot reach the README; the anchor must be a `.md` |
| 207 | + file that ships. |
| 208 | + |
| 209 | +- **Multiple anchors per axis.** Rejected: violates "one clear entry". |
| 210 | + Without a single canonical entry, `instructions.md` cannot reference |
| 211 | + the engine unambiguously. |
| 212 | + |
| 213 | +- **Machine-readable anchor declaration (frontmatter field |
| 214 | + `engine_anchor: true`).** Rejected for now: the file-name convention |
| 215 | + (`position_<axis>.md` or `process_<verb>_<axis>.md` matching the |
| 216 | + containing directory name) is sufficient for the validator to find |
| 217 | + the anchor by inspection. A frontmatter field would add validator |
| 218 | + surface without giving the runtime new capability. Reconsider if a |
| 219 | + future need demands programmatic detection that can't rely on |
| 220 | + filenames. |
| 221 | + |
| 222 | +- **Registry file separate from `instructions.md`.** Rejected: |
| 223 | + `instructions.md` is already the spine and the natural home for "what |
| 224 | + the agent invokes". Splitting the registry into a second file creates |
| 225 | + a synchronisation duty between two files for no clear gain. |
| 226 | + |
| 227 | +- **Comprehensive expression -> anchor backlinks.** Rejected for this |
| 228 | + ADR: not done in any existing engine axis (e.g. |
| 229 | + `position_clan_org.md` does not link `process_operate_org.md`). If |
| 230 | + adopted, it would be a separate refactor touching every position and |
| 231 | + process file in `engine/`. Out of scope here. |
| 232 | + |
| 233 | +## Consequences |
| 234 | + |
| 235 | +**Positive:** |
| 236 | + |
| 237 | +- Every engine axis has a discoverable entry. New axes follow a known |
| 238 | + pattern; reviewer cost on "what's the entry" drops to zero. |
| 239 | +- `instructions.md` becomes a complete, machine-readable-ish registry |
| 240 | + of invokable engines. The global vs per-khai-type mode is explicit; |
| 241 | + the agent can load global engines eagerly and gate per-khai-type |
| 242 | + engines on entity presence. |
| 243 | +- Zip deployment carries every anchor (anchors are not README-style; |
| 244 | + they ship). A downstream LLM agent reading the culture pack can find |
| 245 | + the entries by name. |
| 246 | +- The position/process distinction at anchor level mirrors the |
| 247 | + position/process distinction at expression level -- one shape, two |
| 248 | + scopes (anchor + expression). |
| 249 | + |
| 250 | +**Negative:** |
| 251 | + |
| 252 | +- The position/process choice is editorial: a new engine axis still |
| 253 | + needs a human (or LLM-plus-human) call on which flavour fits. The |
| 254 | + ADR offers heuristics but not a mechanical rule. |
| 255 | +- Every engine README needs an `## Anchor` section. Gender (#577) and |
| 256 | + wow (#581) added theirs; adoption / language / stress / org / group |
| 257 | + pre-date this ADR and have their entries but not the formal `## Anchor` |
| 258 | + framing. Bringing them to convention is follow-up work, not blocking. |
| 259 | +- The global / per-khai-type mode declaration only lives in |
| 260 | + `instructions.md` -- if the file is missing or stale, the runtime |
| 261 | + has no source of truth. The ADR accepts this trade-off in exchange |
| 262 | + for keeping the registry in one place. |
| 263 | + |
| 264 | +## Open follow-ups |
| 265 | + |
| 266 | +1. **Implementation PR for `instructions.md`.** Add the engines |
| 267 | + registry section listing all seven domain axes with their modes |
| 268 | + (governance branch -- the file is engine/stack/, which is engine/ |
| 269 | + territory under `culture/engine` scope). |
| 270 | + |
| 271 | +2. **Backfill `## Anchor` sections.** adoption, language, stress, org, |
| 272 | + group READMEs already have entries in practice (e.g. |
| 273 | + `process_operate_org.md` is the wrapper root) but no explicit |
| 274 | + `## Anchor` section. A follow-up culture/engine PR adds the |
| 275 | + convention uniformly. |
| 276 | + |
| 277 | +3. **Validator: one anchor per axis.** Optional governance follow-up: |
| 278 | + a test in `tests/` that walks `engine/*/` and asserts exactly one |
| 279 | + anchor file exists per domain subfolder, named after the directory. |
| 280 | + Catches the next "we forgot the anchor" regression at CI time. |
| 281 | + |
| 282 | +4. **KAIHACKS-side spec.** Mirror this contract in KAIHACKS' |
| 283 | + `ARCHITECTURE.md` at the khai-spine level, so non-Cultures khai |
| 284 | + projects inherit the same engine-entry pattern. The Cultures-side |
| 285 | + ADR is the concrete instance; the KAIHACKS-side spec is the |
| 286 | + abstract spec. |
| 287 | + |
| 288 | +5. **Per-khai-type trigger formalisation.** "Triggered by `khai: X` in |
| 289 | + scope" is descriptive prose. If the runtime grows a formal scope |
| 290 | + model (e.g. "what khai entities exist in the current scene"), the |
| 291 | + trigger column in `instructions.md` could become machine-checkable. |
| 292 | + Deferred until that runtime exists. |
| 293 | + |
| 294 | +--- |
| 295 | + |
| 296 | +*ADR-0004 -- v0.1.0 -- KAI Worlds* |
0 commit comments