# Decision Architecture Restructure — Librarian's Analysis **Author:** Qatux (Documenter) **Date:** 2026-02-11 **Status:** Proposal for team review --- ## Executive Summary DECISIONS.md has grown to 474 lines (47KB) with 31 confirmed decisions, 11 open questions, and 10 rejected alternatives. At current growth rates (~7 decisions per workshop round), we'll reach 1000+ lines by Round 25 — likely by end of Q1 2026. The current single-file approach creates three problems: 1. **Context window waste:** Agents read all 474 lines for session context but typically need only 5-8 decisions relevant to their domain 2. **Maintenance burden:** Every decision addition requires re-indexing 47KB in Qdrant, updating briefings across the board 3. **Discovery friction:** Finding decisions requires either linear scan of the full file or Qdrant semantic search with no domain filtering I recommend a **hybrid domain-split with DB index** approach: split decisions into domain files (`decisions/architecture.md`, `decisions/content.md`, etc.), maintain a SQLite decision index for cross-referencing and ticket linkage, and use Qdrant for semantic discovery. This reduces per-agent context load by ~70-80%, improves maintainability, and preserves all current workflows. **Migration effort:** ~4 hours. **Risk:** Low — git history preserved, all IDs stable, rollback straightforward. --- ## 1. Problems with Current Approach at Scale ### 1.1 Context Window Inefficiency From my maintenance perspective, this is the most immediate problem. Current workflow: - Agent session starts → reads briefing → briefing says "read DECISIONS.md" → agent reads all 474 lines - Tyre needs D-010, D-020, D-026, D-030 (architecture decisions) — 4 decisions, ~80 relevant lines - Tyre gets 474 lines, 394 of which are irrelevant to architecture work At 1000 lines (projected Round 25), this becomes 920 wasted lines per session. Multiply by 8 agents × multiple rounds per week, and we're burning thousands of tokens on irrelevant context. **Briefings partially solve this:** I maintain per-agent briefings that excerpt key decisions. But briefings are summaries, not sources of truth. When an agent needs detail, they read DECISIONS.md. And briefings don't eliminate the problem — they duplicate it. I now maintain: - DECISIONS.md (source of truth, 474 lines) - 8 agent briefings (each excerpting 5-10 decisions, ~40-80 lines per briefing) - Total maintenance surface: 474 + (8 × 60 average) = ~950 lines across 9 files When D-032 arrives, I update DECISIONS.md, re-index it in Qdrant, and update 2-4 relevant briefings. That's 3-5 file edits per decision. ### 1.2 Discovery Friction How do agents find decisions? Three current methods: 1. **Read DECISIONS.md linearly** — Works for small files. At 474 lines, agents skim. At 1000 lines, they give up. 2. **Qdrant semantic search** — `qdrant-search "chunk-based maps"` finds D-012. Works well! But no domain filtering. A search for "NPC generation" returns matches from D-024 (content), D-026 (simulation), D-028 (dialogue), and potentially D-008 (procedural generation). Agent must filter manually. 3. **Ask Qatux** — I retrieve by ID or topic. This works but creates a bottleneck — every agent query waits for me. The ideal discovery workflow: - Agent knows the domain (architecture, content, UI, etc.) - Agent searches within domain semantically or scans domain file (~80-120 lines) - Agent finds decision, reads detail, cross-references dependencies Current structure supports none of this except the last step. ### 1.3 Cross-Reference Complexity Decisions reference each other frequently: - D-002 superseded by D-005 - D-006 superseded by D-027 - D-010 referenced by D-011, D-017, D-020, D-030 - D-024 references D-017 (perception modes as pattern) - D-027 supersedes D-006, references D-009, D-010 In a single file, cross-references are implicit — scroll up/down. But they're not machine-readable. The ticket database has `decision_ref TEXT`, which links tickets to decisions. But decisions don't link to tickets, to related decisions, or to discussion rounds in a structured way. **What I maintain manually:** - "Supersedes: D-NNN" in decision rationale (freeform text) - "Resolves: Q-NNN" in decision metadata (freeform text) - Briefing cross-references (freeform text, duplicated per agent) None of this is queryable. I can't ask "which decisions depend on D-010?" without grepping the file. ### 1.4 Superseded Decision Cruft D-002 and D-006 are superseded. They appear in DECISIONS.md as: ```markdown ### D-002: SUPERSEDED by D-005 ``` No rationale, no original text. The history is in git, but not in the document. From a librarian perspective, this is information loss — I can't answer "why was D-002 rejected?" without `git log`. And I can't direct agents to compare D-002 vs D-005 because D-002 is a tombstone. For the record: I archived the original decisions in git history (commit `6a3c1f2`, 2026-02-08). But that's invisible to agents who don't read git logs. ### 1.5 Qdrant Re-indexing Overhead Every time DECISIONS.md changes: 1. Edit the file (add D-032, update Q-009 status, etc.) 2. Commit to git 3. Re-index: `qdrant-index docs/DECISIONS.md` 4. Qdrant chunks the 47KB file into ~15-20 semantic chunks 5. All chunks re-embedded via ollama (nomic-embed-text) 6. ~30-45 seconds per index operation At current rates (2-7 decisions per round, ~3 rounds per week), that's 6-21 re-index operations per week. Not painful yet. But at scale: - 1000-line DECISIONS.md → ~40-50 chunks → 60-90 seconds to re-index - More frequent updates → more re-indexing friction - Agents waiting for "has Qatux indexed the new decisions yet?" **Alternative:** If decisions are split into domain files, only the changed domain file re-indexes. D-032 (architecture) → re-index `decisions/architecture.md` (80 lines, ~3-5 chunks, 10 seconds). 6x faster, no cross-domain noise. ### 1.6 Ticket Linkage Underutilized The ticket schema has `decision_ref TEXT` (line 17 of `schema.sql`). Currently 59/273 tickets (21.6%) use it. Why so low? From maintenance experience: agents forget. When creating tickets, they don't always remember to link the decision. And I don't have a workflow to audit "which decisions lack linked tickets?" or "which tickets reference nonexistent decisions?" A decision index table would enable: - `SELECT * FROM decisions WHERE id NOT IN (SELECT DISTINCT decision_ref FROM tickets WHERE decision_ref LIKE 'D-%')` — decisions with no linked tickets - `SELECT * FROM tickets WHERE decision_ref NOT IN (SELECT id FROM decisions)` — tickets referencing nonexistent decisions - Automated cross-referencing in briefings: "D-020 is referenced by tickets #45, #67, #89" --- ## 2. Options Considered ### 2.1 Status Quo (Single File) **Keep DECISIONS.md as-is, accept the scaling costs.** **Pros:** - Zero migration effort - Familiar to all agents - Git history continuous - Simple mental model **Cons:** - Context window waste grows linearly with decision count - Discovery via linear scan or Qdrant only (no domain filtering) - Re-indexing overhead grows with file size - Cross-references remain manual and unstructured - Superseded decisions are tombstones - No automated ticket/decision linkage auditing **My assessment:** This is the path of least resistance, but it fails in 6 months. By Round 30 (~Q2 2026), DECISIONS.md will be 1200+ lines and agents will stop reading it. We'll rely entirely on briefings, and briefings will be stale. I'll spend more time updating briefings than documenting decisions. **Verdict:** Reject. The problem is real and worsening. --- ### 2.2 Split by Domain (Multiple Markdown Files) **Split DECISIONS.md into domain files:** `decisions/architecture.md`, `decisions/content.md`, `decisions/process.md`, etc. Each file contains decisions relevant to that domain. Agent briefings point to specific domain files. **Structure:** ``` docs/decisions/ architecture.md # D-010, D-012, D-020, D-030 (client-server, engine, testability) content.md # D-023, D-024, D-025, D-028, D-029 (NPCs, dialogue, templates) camera-perception.md # D-011, D-015, D-016, D-017, D-018, D-019 (fog, camera, sound) scope.md # D-001, D-003, D-005, D-006, D-007, D-014, D-027 (game concept, prototype) process.md # D-022 (workflow) meta.md # D-021 (title), D-004 (team) world.md # Future: world map, time system, wormholes (D-031, D-013) combat.md # Future: D-008, combat mechanics questions.md # All Q-NNN entries rejected.md # All R-NNN entries ``` **Discovery workflow:** 1. Agent knows domain (e.g., Tyre working on architecture) 2. Agent reads `decisions/architecture.md` (~80-120 lines) 3. Agent finds D-020, sees cross-reference to D-010 4. Agent opens same file (D-010 is architecture), reads detail **Briefing updates:** - Tyre's briefing: "Read `docs/decisions/architecture.md` for relevant decisions" - Paula's briefing: "Read `docs/decisions/content.md` for NPC/dialogue decisions" - Gestalt's briefing: "Read `docs/decisions/scope.md` and `docs/decisions/content.md`" **Pros:** - **Context efficiency:** Agents read 80-150 lines (relevant domain) instead of 474 (everything). ~70-80% reduction. - **Qdrant efficiency:** Re-index only changed domain file. 6x faster, cleaner search results (domain-filtered). - **Discovery:** Domain files are scannable. "I need architecture decisions" → read one file. - **Maintainability:** I update one domain file per decision, not a monolithic file. Git diffs are cleaner (architecture changes don't mix with content changes). - **Briefing simplicity:** Point to 1-2 domain files instead of "read all of DECISIONS.md and filter mentally." **Cons:** - **Cross-domain references harder:** D-024 (content) references D-017 (perception). Agent must open two files. (Mitigated by hyperlinks in markdown.) - **Chronological view lost:** No single "what did we decide in order?" file. (Mitigated by discussion round archive in `docs/discussions/README.md`, which already tracks chronology.) - **Migration effort:** ~2 hours to split, update briefings, re-index. Not trivial but not expensive. - **Domain boundaries unclear for some decisions:** Where does D-013 (insert/POI system) go? Camera-perception (UI), or world (navigation)? I'd need to define domain taxonomy. **My assessment:** This solves the context window problem immediately and the Qdrant re-indexing problem partially. It's a 70% solution — good, but not complete. Cross-referencing and ticket linkage remain manual. **Verdict:** Strong candidate. Improvements over status quo are significant and immediate. --- ### 2.3 Move to Database (SQLite) **Replace DECISIONS.md with a SQLite table.** Decisions become rows, cross-references are foreign keys, tickets link via `decision_ref`. **Schema sketch:** ```sql CREATE TABLE decisions ( id TEXT PRIMARY KEY, -- 'D-001', 'Q-001', 'R-001' type TEXT NOT NULL CHECK(type IN ('decision', 'question', 'rejected')), title TEXT NOT NULL, date TEXT NOT NULL, decision TEXT, -- The actual decision text rationale TEXT, raised_by TEXT, dissent TEXT, status TEXT DEFAULT 'active' CHECK(status IN ('active', 'superseded', 'resolved')), superseded_by TEXT REFERENCES decisions(id), resolves TEXT REFERENCES decisions(id), -- e.g., D-031 resolves Q-009 domain TEXT, -- 'architecture', 'content', 'process', etc. round INTEGER, -- Round number where decided created_at TEXT DEFAULT (datetime('now')), updated_at TEXT DEFAULT (datetime('now')) ); CREATE TABLE decision_refs ( source_id TEXT NOT NULL REFERENCES decisions(id), target_id TEXT NOT NULL REFERENCES decisions(id), ref_type TEXT NOT NULL CHECK(ref_type IN ('supersedes', 'references', 'resolves', 'depends_on')), PRIMARY KEY (source_id, target_id, ref_type) ); CREATE INDEX idx_decisions_domain ON decisions(domain); CREATE INDEX idx_decisions_type ON decisions(type); CREATE INDEX idx_decisions_status ON decisions(status); ``` **Workflow:** - I document decisions by inserting rows: `sqlite-exec "INSERT INTO decisions (id, type, title, date, decision, rationale, raised_by, domain, round) VALUES (...)"` - Agents query: `sqlite-query "SELECT * FROM decisions WHERE domain='architecture' AND status='active'"` - Tickets link via existing `decision_ref` field (already in schema) - Cross-references queryable: `SELECT target_id FROM decision_refs WHERE source_id='D-010'` **Export to markdown:** Generate `decisions/architecture.md` from DB on demand or via git hook: ```bash sqlite-query "SELECT * FROM decisions WHERE domain='architecture'" | format-as-markdown > decisions/architecture.md ``` **Pros:** - **Structured cross-references:** Foreign keys, queryable. "Which decisions reference D-010?" is a JOIN, not a grep. - **Ticket integration:** `decision_ref` in tickets already exists. Now decisions table exists too. Bidirectional: tickets→decisions and decisions→tickets. - **Status tracking:** Superseded decisions remain queryable with full text, just marked `status='superseded'`. No tombstones. - **Domain filtering:** `WHERE domain='architecture'` — instant. - **Audit queries:** "Decisions without linked tickets", "decisions modified in last 7 days", "decisions from Round 17", etc. - **Qdrant still works:** Export markdown from DB, index markdown. Qdrant becomes a semantic layer over structured data. **Cons:** - **Not human-readable in native form:** Agents can't `cat decisions.db`. They must query. (Mitigated by markdown export.) - **Git diffs unusable:** Binary SQLite file. Can't see "what changed in D-020?" in a commit. (Mitigated by exporting markdown to git, treating DB as source of truth and markdown as build artifact.) - **Write friction:** Adding a decision requires SQL INSERT, not markdown edit. Higher ceremony for me. (Mitigated by wrapper script: `decision-add --id D-032 --title "Foo" --domain architecture --decision "We decided X" --rationale "Because Y"`) - **Learning curve for agents:** Agents must learn to query the DB instead of reading markdown. (Mitigated by keeping markdown exports in git as read-only artifacts.) - **Migration complexity:** Parsing 31 decisions + 11 questions + 10 rejected from DECISIONS.md into structured rows. ~3-4 hours. **My assessment:** This is the most powerful solution long-term, but the steepest upfront cost. The cross-reference and audit capabilities are compelling — this is what a librarian wants. But it trades away the simplicity of "just edit markdown" for structured data discipline. And git history becomes opaque (binary DB vs diffable markdown). **Hybrid mitigation:** DB as source of truth, markdown exports to git. Best of both worlds? Possibly. But now I'm maintaining two formats — insert into DB, export to markdown, commit markdown. That's 3 steps per decision instead of 1. **Verdict:** Powerful but heavy. Overkill for current scale (31 decisions). Revisit at 100+ decisions. --- ### 2.4 Hybrid: Domain Files + DB Index **Combine 2.2 and 2.3:** Decisions live in domain markdown files (human-readable, git-diffable). A SQLite index table tracks metadata (ID, domain, status, supersedes, round, tags) for cross-referencing and querying. Markdown is source of truth for content; DB is source of truth for relationships. **Structure:** ``` docs/decisions/ architecture.md # D-010, D-012, D-020, D-030 (full text) content.md # D-023, D-024, D-025, D-028, D-029 (full text) ... index.db # SQLite index (see schema below) ``` **Index schema:** ```sql CREATE TABLE decision_index ( id TEXT PRIMARY KEY, -- 'D-001', 'Q-001', 'R-001' type TEXT NOT NULL CHECK(type IN ('decision', 'question', 'rejected')), title TEXT NOT NULL, domain TEXT NOT NULL, -- 'architecture', 'content', etc. file_path TEXT NOT NULL, -- 'docs/decisions/architecture.md' status TEXT DEFAULT 'active' CHECK(status IN ('active', 'superseded', 'resolved')), round INTEGER, -- Round number date TEXT NOT NULL, tags TEXT, -- JSON array: ["multiplayer", "client-server"] created_at TEXT DEFAULT (datetime('now')), updated_at TEXT DEFAULT (datetime('now')) ); CREATE TABLE decision_refs ( source_id TEXT NOT NULL REFERENCES decision_index(id), target_id TEXT NOT NULL REFERENCES decision_index(id), ref_type TEXT NOT NULL CHECK(ref_type IN ('supersedes', 'references', 'resolves', 'depends_on')), PRIMARY KEY (source_id, target_id, ref_type) ); CREATE INDEX idx_dindex_domain ON decision_index(domain); CREATE INDEX idx_dindex_type ON decision_index(type); CREATE INDEX idx_dindex_status ON decision_index(status); ``` **Workflow:** 1. **Adding a decision:** - I edit `docs/decisions/architecture.md`, add D-032 in full markdown - I insert metadata into DB: `sqlite-exec "INSERT INTO decision_index (id, type, title, domain, file_path, round, date) VALUES ('D-032', 'decision', 'Foo system', 'architecture', 'docs/decisions/architecture.md', 19, '2026-02-12')"` - I add cross-refs: `sqlite-exec "INSERT INTO decision_refs (source_id, target_id, ref_type) VALUES ('D-032', 'D-010', 'references')"` - I commit both markdown and DB: `git add docs/decisions/architecture.md docs/decisions/index.db && git commit` 2. **Agent discovery:** - Tyre queries: `sqlite-query "SELECT id, title, file_path FROM decision_index WHERE domain='architecture' AND status='active'"` → gets list of IDs + file paths - Tyre reads `docs/decisions/architecture.md` (80 lines, all relevant) - Or: Tyre knows domain, just reads the file directly (no query needed) 3. **Cross-reference queries:** - "Which decisions reference D-010?" → `sqlite-query "SELECT source_id FROM decision_refs WHERE target_id='D-010'"` - "Which tickets reference D-010?" → `sqlite-query "SELECT id, title FROM tickets WHERE decision_ref='D-010'"` 4. **Qdrant indexing:** - Re-index changed domain file: `qdrant-index docs/decisions/architecture.md` (fast, domain-scoped) - Qdrant semantic search finds D-032, returns ID, agent queries index for file path 5. **Superseded decisions:** - D-002 remains in `docs/decisions/scope.md` with full text (archived, not deleted) - Index marks: `status='superseded', superseded_by='D-005'` - Queries can filter: `WHERE status='active'` or include superseded for historical context **Pros:** - **Preserves markdown readability:** Agents read domain files, see full decision text, context, rationale - **Git diffs clean:** Changes to D-020 show up as markdown diff in `architecture.md` - **Structured cross-references:** Foreign keys in DB, queryable - **Ticket integration:** Bidirectional — tickets link to decisions (existing), decisions link to tickets (new, via index) - **Context efficiency:** Agents read 80-150 lines (domain file) instead of 474 (monolithic) - **Qdrant efficiency:** Re-index domain file only (fast, domain-scoped) - **Audit queries:** Leverage SQLite for "decisions without tickets", "decisions modified in Round 17", etc. - **Superseded decisions preserved:** Full text in markdown, status in DB - **Low migration cost:** Split markdown (~2 hours) + seed DB index (~1 hour) = ~3 hours total **Cons:** - **Dual maintenance:** Markdown (content) + DB (metadata). I must keep them in sync. - **Index can drift:** If I edit markdown without updating DB, index becomes stale. (Mitigated by linting: `decision-lint` script checks markdown IDs match DB IDs.) - **Slightly higher ceremony:** Each decision is 2 operations (edit markdown + insert into DB). But wrapper script can streamline: `decision-add D-032 --domain architecture --title "Foo" --markdown "$(cat)"` → inserts markdown template + DB row. **My assessment:** This is the sweet spot. It preserves the human-friendliness of markdown (git diffs, agent readability) while adding the structure I need for cross-references and auditing. The dual-maintenance concern is real but manageable — I'm already maintaining DECISIONS.md + 8 briefings (9 files), so maintaining 6 domain files + 1 DB index (7 entities) is actually simpler. **Verdict:** Recommended approach. --- ### 2.5 Tag-Based (Single File + Front Matter) **Keep DECISIONS.md as single file, add YAML front matter to each decision with domain tags.** Qdrant indexes with tags, agents query by tag. **Example:** ```markdown ### D-020: Engine and architecture selection - **Date:** 2026-02-09 - **Decision:** Godot client + Rust simulation via subprocess/IPC ... ``` **Discovery:** - Agent queries Qdrant: `qdrant-search "client-server architecture" --filter domain:architecture` - Or: Agent uses grep: `grep -A 30 "domain: architecture" DECISIONS.md` **Pros:** - Single file (simple mental model) - Tags add structure without splitting files - Qdrant can filter by tags (if we enhance the indexer to parse front matter) - Git history continuous **Cons:** - **Still 474+ lines per read:** Doesn't solve context window problem - **Front matter is noise:** Each decision grows by 5-8 lines of YAML - **Tag discipline required:** I must tag every decision correctly (more ceremony) - **Cross-references still manual:** Tags don't create queryable relationships - **Qdrant filter support unclear:** Current `qdrant-connector.py` doesn't parse front matter. Would need to enhance. **My assessment:** This adds complexity (front matter) without solving the core problem (context window bloat). Tags are useful metadata, but they don't reduce the read burden. And implementing tag-based filtering in Qdrant requires custom indexer logic. **Verdict:** Reject. Adds work, solves little. --- ## 3. Recommended Approach: Hybrid Domain Files + DB Index **For the record:** I recommend **Option 2.4 — Hybrid domain files with SQLite index.** ### 3.1 Why This Approach 1. **Context efficiency:** Agents read 80-150 lines (domain file) instead of 474 (monolithic). Projected 70-80% reduction in context waste. 2. **Maintainability:** I update one domain file per decision, not a 474-line monolith. Git diffs are cleaner. Merge conflicts less likely (agents working in different domains don't conflict). 3. **Discovery:** Three pathways, all improved: - **Domain-aware scan:** Agent knows domain, reads domain file (~2 minutes) - **Semantic search:** Qdrant indexes domain files, returns results with file path metadata → agent reads file - **Structured query:** Agent queries index by domain/status/round → gets list of IDs + file paths → reads file 4. **Cross-references:** SQLite index enables queries like "which decisions reference D-010?" and "which tickets implement D-020?" Previously impossible without manual grep. 5. **Superseded decisions preserved:** Full text remains in markdown (archival integrity), status tracked in DB (queryable). No more tombstones. 6. **Qdrant efficiency:** Re-index only changed domain file. 6x faster, cleaner search results. 7. **Ticket integration:** Existing `decision_ref` field in tickets table links to decisions. New index table enables reverse lookup: decisions→tickets. 8. **Audit capabilities:** "Decisions from Round 17", "decisions without linked tickets", "decisions modified in last 7 days" — all queryable. 9. **Low migration cost:** ~3-4 hours to split markdown, seed index DB, update briefings, re-index in Qdrant. One-time cost. 10. **Rollback path:** If this fails, the domain markdown files can be concatenated back into a single file. Git history preserved. Index DB can be discarded. ### 3.2 Domain Taxonomy I propose the following domain split (subject to team review): | Domain | File | Decisions (Current) | Projected Growth | |--------|------|---------------------|------------------| | **architecture** | `decisions/architecture.md` | D-010, D-012, D-020, D-030 | 10-15 total (engine, networking, testability, performance) | | **content** | `decisions/content.md` | D-023, D-024, D-025, D-028, D-029 | 20-30 total (NPCs, dialogue, templates, storyteller) | | **camera-perception** | `decisions/camera-perception.md` | D-011, D-015, D-016, D-017, D-018, D-019 | 8-12 total (fog, vision, sound, monologue) | | **scope** | `decisions/scope.md` | D-001, D-003, D-005, D-006, D-007, D-014, D-027 | 12-18 total (game concept, pillars, prototype scope) | | **world** | `decisions/world.md` | D-013, D-031 | 12-20 total (time, maps, wormholes, POIs, navigation) | | **combat** | `decisions/combat.md` | D-008 | 8-12 total (combat mechanics, z-levels, hubris wall) | | **process** | `decisions/process.md` | D-022 | 5-8 total (workflow, tooling, team process) | | **meta** | `decisions/meta.md` | D-004, D-021 | 3-5 total (team, title, licensing) | | **multiplayer** | `decisions/multiplayer.md` | D-009 | 6-10 total (multiplayer design, networking) | | **questions** | `decisions/questions.md` | Q-001 through Q-011 | Variable (questions resolve into decisions) | | **rejected** | `decisions/rejected.md` | R-001 through R-010 | 15-25 total (rejected alternatives, rationale) | **Rationale for groupings:** - **architecture** = technical foundation (engine, client-server, testability) - **content** = NPC/dialogue/template systems (the "what" of the game) - **camera-perception** = player viewport and information channels (fog, sound, monologue) - **scope** = game concept and prototype definition (the "what are we building" decisions) - **world** = spatial and temporal systems (maps, time, navigation) - **combat** = action pillar (separate from world/perception because it's a distinct system) - **process** = how the team works (workflow, tooling, documentation) - **meta** = project-level decisions (title, team, licensing) - **multiplayer** = separate from architecture because it's a design domain, not just technical **Edge cases:** - D-026 (simulation tiers) — could be architecture or content. I'd put it in **architecture** (performance budgets) with cross-reference note in **content**. - D-013 (insert/POI system) — could be camera-perception or world. I'd put it in **world** (navigation) with cross-reference in **camera-perception**. **Flexibility:** Domain boundaries can shift. If **content** grows to 200 lines, we split it: `decisions/content-npcs.md`, `decisions/content-dialogue.md`. The index DB makes this trivial — update `file_path` column, move text. ### 3.3 Index DB Schema (Final) ```sql -- Decision metadata index CREATE TABLE decision_index ( id TEXT PRIMARY KEY, -- 'D-001', 'Q-001', 'R-001' type TEXT NOT NULL CHECK(type IN ('decision', 'question', 'rejected')), title TEXT NOT NULL, domain TEXT NOT NULL, -- 'architecture', 'content', 'scope', etc. file_path TEXT NOT NULL, -- 'docs/decisions/architecture.md' status TEXT DEFAULT 'active' CHECK(status IN ('active', 'superseded', 'resolved', 'open')), round INTEGER, -- Round number where decided/raised date TEXT NOT NULL, -- YYYY-MM-DD tags TEXT, -- JSON array: ["multiplayer", "client-server"] (optional) created_at TEXT DEFAULT (datetime('now')), updated_at TEXT DEFAULT (datetime('now')) ); -- Cross-references between decisions CREATE TABLE decision_refs ( source_id TEXT NOT NULL REFERENCES decision_index(id), target_id TEXT NOT NULL REFERENCES decision_index(id), ref_type TEXT NOT NULL CHECK(ref_type IN ('supersedes', 'references', 'resolves', 'depends_on')), note TEXT, -- Optional context, e.g., "CombatCapability component pattern" PRIMARY KEY (source_id, target_id, ref_type) ); -- Indexes CREATE INDEX idx_dindex_domain ON decision_index(domain); CREATE INDEX idx_dindex_type ON decision_index(type); CREATE INDEX idx_dindex_status ON decision_index(status); CREATE INDEX idx_dindex_round ON decision_index(round); CREATE INDEX idx_drefs_source ON decision_refs(source_id); CREATE INDEX idx_drefs_target ON decision_refs(target_id); ``` **Why this schema:** - `decision_index` table = lightweight metadata (no full decision text, which stays in markdown) - `file_path` column = bridge to markdown source of truth - `tags` = optional JSON array for future filtering (not MVP, but nice-to-have) - `decision_refs` table = structured cross-references, queryable bidirectionally - `ref_type` enum = semantic meaning (supersedes vs references vs resolves) **Storage size:** ~500 bytes per decision row + ~100 bytes per cross-reference. 31 decisions + 20 cross-refs ≈ 18KB. At 100 decisions, ~60KB. Negligible. ### 3.4 Workflow Changes #### For me (Qatux): **Adding a new decision (manual process, MVP):** 1. Determine domain (architecture, content, etc.) 2. Edit `docs/decisions/{domain}.md`, add decision in full markdown format (title, date, decision, rationale, etc.) 3. Insert into index DB: ```bash db/connectors/sqlite-exec "INSERT INTO decision_index (id, type, title, domain, file_path, round, date, status) VALUES ('D-032', 'decision', 'Foo system', 'architecture', 'docs/decisions/architecture.md', 19, '2026-02-12', 'active')" ``` 4. If cross-references exist, insert into `decision_refs`: ```bash db/connectors/sqlite-exec "INSERT INTO decision_refs (source_id, target_id, ref_type) VALUES ('D-032', 'D-010', 'references')" ``` 5. Re-index domain file in Qdrant: ```bash db/connectors/qdrant-index docs/decisions/architecture.md ``` 6. Update relevant agent briefings (point to domain file if not already) 7. Commit: ```bash git add docs/decisions/architecture.md db/commonwealth.db docs/briefings/*.md git commit -m "docs(decisions): add D-032 Foo system (architecture)" ``` **Later: Streamline with wrapper script** (future enhancement, not MVP): ```bash decision-add D-032 \ --domain architecture \ --title "Foo system" \ --decision "We will use X because Y" \ --rationale "Rationale here" \ --raised-by "Tyre" \ --round 19 \ --references D-010 ``` Script generates markdown, inserts into DB, re-indexes Qdrant, prompts for briefing updates. #### For agents: **Discovery:** - **If domain known:** Read `docs/decisions/{domain}.md` directly (80-150 lines). Example: Tyre reads `docs/decisions/architecture.md` before architecture work. - **If searching semantically:** Use Qdrant: `qdrant-search "client-server architecture"` → returns D-020 with file path → read `docs/decisions/architecture.md` - **If querying by metadata:** Use SQLite: `sqlite-query "SELECT id, title, file_path FROM decision_index WHERE domain='content' AND status='active'"` → get list → read file **Cross-references:** - Markdown hyperlinks work: `See [D-010](architecture.md#d-010)` (relative link within domain file) or `See [D-024](content.md#d-024)` (cross-domain link) - Or query index: `sqlite-query "SELECT target_id, ref_type FROM decision_refs WHERE source_id='D-032'"` **Ticket linkage:** - Existing: Tickets reference decisions via `decision_ref` column - New: Query which tickets implement a decision: `sqlite-query "SELECT id, title, status FROM tickets WHERE decision_ref='D-020'"` - New: Query which decisions have no linked tickets: `sqlite-query "SELECT id, title FROM decision_index WHERE type='decision' AND id NOT IN (SELECT DISTINCT decision_ref FROM tickets WHERE decision_ref LIKE 'D-%')"` ### 3.5 Briefing Update Strategy Current briefings duplicate key decisions as freeform text. New approach: **Per-agent briefing structure:** - Section: "Decisions Relevant to Your Role" - Content: "See `docs/decisions/{domain}.md` for {domain} decisions. Key decisions: D-010 (client-server), D-020 (engine), D-030 (testability)." - Optionally: 1-2 sentence summary per key decision (but NOT full text — that's in the domain file) **Example (Tyre's briefing, updated):** ```markdown ## Decisions Relevant to Your Role See `docs/decisions/architecture.md` for technical architecture decisions. Key decisions: - **D-010:** Four architectural principles (client-server, information boundaries, deterministic sim) - **D-020:** Godot client + Rust/bevy_ecs server via subprocess/IPC, MessagePack serialization - **D-030:** Testability architecture (hybrid test org, gdUnit4, deterministic replay) See `docs/decisions/camera-perception.md` for perception/rendering decisions. Key decisions: - **D-011:** LOS shadowcasting for fog of war - **D-017:** Perception modes as observer queries (pattern for all future systems) See `docs/decisions/content.md` for NPC/simulation decisions. Key decisions: - **D-024:** CombatCapability as ECS component (pattern extends to future capabilities) - **D-026:** Simulation tier budgets (Active: 30-80 NPCs, Background: 500-2K) ``` **Benefit:** Briefing is now 15-25 lines of pointers, not 80-120 lines of duplicated decision text. When D-032 is added to `architecture.md`, I update Tyre's briefing with one line: "D-032: Foo system." That's it. ### 3.6 Superseded Decisions **Current problem:** D-002 and D-006 are tombstones — no detail visible in DECISIONS.md. **New approach:** 1. D-002 remains in `docs/decisions/scope.md` with full original text, marked: ```markdown ### D-002: Dynasty grand strategy concept [SUPERSEDED] - **Date:** 2026-02-08 - **Decision:** [Original text here] - **Superseded by:** D-005 (first-person single-character concept) - **Rationale:** [Why we moved away from this] ``` 2. Index DB marks: `status='superseded'` 3. Index DB links: `INSERT INTO decision_refs (source_id='D-005', target_id='D-002', ref_type='supersedes')` 4. Queries default to active: `WHERE status='active'` 5. Historical queries include superseded: `WHERE status IN ('active', 'superseded')` **Benefit:** Agents can read the original decision, understand why it was superseded, compare with the replacement. Git history no longer required. Archival integrity preserved. --- ## 4. Migration Path ### Phase 1: Split Markdown (2 hours) 1. Create `docs/decisions/` directory 2. Split current DECISIONS.md into domain files: - `architecture.md` ← D-010, D-012, D-020, D-030 - `content.md` ← D-023, D-024, D-025, D-028, D-029 - `camera-perception.md` ← D-011, D-015, D-016, D-017, D-018, D-019 - `scope.md` ← D-001, D-003, D-005, D-006, D-007, D-014, D-027 - `world.md` ← D-013, D-031 - `combat.md` ← D-008 - `process.md` ← D-022 - `meta.md` ← D-004, D-021 - `multiplayer.md` ← D-009 - `questions.md` ← Q-001 through Q-011 - `rejected.md` ← R-001 through R-010 3. Add markdown header to each file: ```markdown # {Domain} Decisions This file contains decisions related to {domain}. For the full decision archive, see [decisions README](README.md). Last updated: 2026-02-11 ``` 4. Add relative hyperlinks for cross-references: - D-024 references D-017 → `See [D-017](camera-perception.md#d-017-perception-modes-as-character-build-system)` 5. Create `docs/decisions/README.md`: - Index of all domain files - Link to discussion rounds (`docs/discussions/README.md`) - Explanation of structure 6. Archive current DECISIONS.md to `docs/decisions/archive/DECISIONS-monolithic.md` (preserve git history) 7. Replace root `DECISIONS.md` with redirect file: ```markdown # Decisions Archive Decisions have been reorganized by domain. See: - [docs/decisions/README.md](docs/decisions/README.md) for the domain index - Individual domain files in `docs/decisions/` This file archived 2026-02-11. See `docs/decisions/archive/DECISIONS-monolithic.md` for the original single-file format. ``` ### Phase 2: Seed Index DB (1.5 hours) 1. Create `db/schema-decisions.sql`: ```sql -- Decision index schema (see Section 3.3 for full schema) CREATE TABLE decision_index (...); CREATE TABLE decision_refs (...); CREATE INDEX ...; ``` 2. Apply schema to `db/commonwealth.db`: ```bash sqlite3 db/commonwealth.db < db/schema-decisions.sql ``` 3. Seed `decision_index` table with 31 decisions + 11 questions + 10 rejected: - Write script: `db/scripts/seed-decision-index.py` (reads domain files, extracts metadata, inserts into DB) - Or manual SQL inserts (tedious but doable for 52 entries) 4. Seed `decision_refs` table with known cross-references: - D-005 supersedes D-002 - D-027 supersedes D-006 - D-031 resolves Q-009 - D-024 references D-017 - D-030 references D-010 - (Scan decision text for "See D-NNN" and "References D-NNN" patterns) 5. Validate: `sqlite-query "SELECT COUNT(*) FROM decision_index"` → should return 52 ### Phase 3: Update Briefings (1 hour) 1. Update all 8 agent briefings: - Replace "Read DECISIONS.md" with "See docs/decisions/{domain}.md" - Condense decision references to 1-2 lines per decision (ID + title only, not full text) 2. Update my briefing (`docs/briefings/qatux.md`): - Document new workflow (add decision → edit domain file + insert into DB) - Update priorities (maintain domain files, not monolithic file) ### Phase 4: Re-index Qdrant (30 minutes) 1. Remove old DECISIONS.md from Qdrant: ```bash db/connectors/qdrant-connector.py delete-file docs/DECISIONS.md ``` 2. Index all domain files: ```bash for file in docs/decisions/*.md; do db/connectors/qdrant-index "$file" done ``` 3. Verify: `db/connectors/qdrant-count` → should see ~45-55 chunks (11 domain files × 4-5 chunks each) ### Phase 5: Commit and Announce (15 minutes) 1. Commit all changes: ```bash git add docs/decisions/ docs/briefings/ db/commonwealth.db db/schema-decisions.sql git commit -m "docs(decisions): restructure into domain files with SQLite index - Split DECISIONS.md into 11 domain files (architecture, content, scope, etc.) - Add decision_index and decision_refs tables to commonwealth.db - Update agent briefings to reference domain files - Re-index domain files in Qdrant - Archive original DECISIONS.md to docs/decisions/archive/ Rationale: Reduce per-agent context load from 474 lines to ~80-150 lines (domain-specific). Enables structured cross-references, ticket linkage queries, and faster Qdrant re-indexing. See docs/decisions/README.md for new structure." ``` 2. Announce to team (create discussion round or brief in next team session): - Explain new structure - Update CLAUDE.md with new instructions (if needed) - Demonstrate query examples ### Phase 6: Validation and Rollback Plan (if needed) **Validation checklist:** - [ ] All 31 decisions, 11 questions, 10 rejected alternatives present in domain files - [ ] All cross-references converted to hyperlinks - [ ] Index DB contains 52 rows in `decision_index` - [ ] Known cross-references in `decision_refs` (at least 5-8) - [ ] All agent briefings updated - [ ] Qdrant contains ~45-55 chunks from domain files - [ ] Qdrant search for "client-server" returns D-010 and D-020 - [ ] SQLite query `SELECT * FROM decision_index WHERE domain='architecture'` returns 4 rows **Rollback plan (if validation fails):** 1. Restore `docs/DECISIONS.md` from `docs/decisions/archive/DECISIONS-monolithic.md` 2. Delete `docs/decisions/` directory (except archive) 3. Drop tables: `sqlite-exec "DROP TABLE decision_refs; DROP TABLE decision_index;"` 4. Revert briefings: `git checkout HEAD~1 docs/briefings/` 5. Re-index old DECISIONS.md: `qdrant-index docs/DECISIONS.md` 6. Total rollback time: ~15 minutes **Low risk:** All data preserved in git history and archive. No information loss. --- ## 5. How This Works with My Workflow ### 5.1 Documentation Workflow **Current (monolithic DECISIONS.md):** 1. Discussion round produces decision (e.g., D-032) 2. I edit DECISIONS.md (scroll to end of CONFIRMED DECISIONS section, insert D-032) 3. I commit DECISIONS.md 4. I re-index DECISIONS.md in Qdrant (~30-45 seconds) 5. I update 2-4 relevant agent briefings (duplicate D-032 summary in each) 6. I commit briefings 7. **Total:** 3 file edits (DECISIONS.md + 2-4 briefings), 2 commits, 1 Qdrant re-index **New (domain files + index DB):** 1. Discussion round produces decision (e.g., D-032, domain = architecture) 2. I edit `docs/decisions/architecture.md` (add D-032 in markdown) 3. I insert into index DB: `sqlite-exec "INSERT INTO decision_index (...) VALUES (...)"` 4. I insert cross-refs (if any): `sqlite-exec "INSERT INTO decision_refs (...) VALUES (...)"` 5. I re-index domain file in Qdrant (~10 seconds): `qdrant-index docs/decisions/architecture.md` 6. I update Tyre's briefing (add one line: "D-032: Foo system") 7. I commit: `git add docs/decisions/architecture.md db/commonwealth.db docs/briefings/tyre.md && git commit` 8. **Total:** 3 file edits (domain file + DB + 1 briefing), 1 commit, 1 Qdrant re-index **Comparison:** - File edits: same (3) - Commits: reduced (1 vs 2) - Qdrant re-index time: 3x faster (10s vs 30-45s) - Briefing updates: reduced (1 vs 2-4) — because briefings point to domain files, not duplicate full text - Context lines read by agents next session: 80-150 (domain) vs 474+ (monolithic) **My assessment:** Slightly more ceremony (DB insert), but net time savings due to faster re-indexing and fewer briefing updates. And agents benefit massively (70-80% context reduction). ### 5.2 Qdrant Indexing **Current:** Re-index 47KB DECISIONS.md → ~15-20 chunks → 30-45 seconds. **New:** Re-index 5-8KB domain file → ~3-5 chunks → 10 seconds. **Benefit:** 3x faster per change. Over time, this compounds — 20 decision additions per month × 3x speedup = ~10 minutes saved per month. Not huge, but appreciated. **Search quality:** Domain-scoped chunks reduce noise. A search for "NPC generation" returns chunks from `content.md` only, not mixed with architecture/combat/etc. Agent gets cleaner, more relevant results. ### 5.3 Briefing Maintenance **Current:** Briefings duplicate 5-10 decision summaries each (40-80 lines per briefing). When D-032 is added, I update 2-4 briefings with full 8-10 line decision excerpt. **New:** Briefings point to domain files with 1-line references. When D-032 is added, I update 1 briefing (Tyre's) with one line: "D-032: Foo system." If another agent needs it later, I add one line to their briefing too. But most decisions don't require broad briefing updates — only domain-relevant agents care. **Time saved:** ~15-20 minutes per workshop round (fewer briefing edits, shorter diffs). ### 5.4 Cross-Reference Retrieval **Current:** Agent asks me "which decisions reference D-010?" → I grep DECISIONS.md manually → tell agent. **New:** Agent queries directly: `sqlite-query "SELECT source_id, ref_type FROM decision_refs WHERE target_id='D-010'"` → gets answer in 50ms. Or I query and respond. Either way, structured query beats manual grep. **Benefit:** I'm no longer a bottleneck for cross-reference questions. Agents can self-serve. --- ## 6. Future Enhancements (Not MVP) These are nice-to-haves, not required for initial migration: ### 6.1 Wrapper Script for Adding Decisions `decision-add` CLI tool: ```bash decision-add D-032 \ --domain architecture \ --title "Foo system" \ --decision "We will use X because Y" \ --rationale "Rationale here" \ --raised-by "Tyre" \ --round 19 \ --references D-010 \ --dissent "None" ``` Generates markdown template, inserts into DB, re-indexes Qdrant, optionally updates briefings. Reduces ceremony from 7 steps to 1 command. **Effort:** ~3-4 hours to build. **Priority:** Medium. Nice quality-of-life improvement, but not blocking. ### 6.2 Decision Linter `decision-lint` script: - Reads all markdown files in `docs/decisions/` - Extracts decision IDs (D-NNN, Q-NNN, R-NNN) - Compares with `decision_index` table - Reports mismatches: - IDs in markdown but not in DB - IDs in DB but not in markdown - Cross-references to nonexistent decisions - Tickets referencing nonexistent decisions Run as git pre-commit hook or CI check. Prevents index drift. **Effort:** ~2-3 hours. **Priority:** Medium-high. Prevents maintenance errors. ### 6.3 Auto-Generated Domain README Script generates `docs/decisions/README.md` from index DB: - Lists all domains with decision counts - Links to domain files - Summary stats (31 decisions, 11 open questions, 10 rejected, etc.) - Recent changes (last 5 decisions added) **Effort:** ~1-2 hours. **Priority:** Low. Nice-to-have, not critical. ### 6.4 Decision Timeline View Generate chronological view from index DB: ```bash decision-timeline --round 17 ``` Output: ``` Round 17 (2026-02-10): Content Architecture - D-023: Three-tier content model (content) - D-024: NPC generation model (content) - D-025: Social site / functional cluster (content) - D-026: Simulation tiers (architecture) - D-027: Vertical slice (scope) - D-028: Dialogue architecture (content) - D-029: Population entanglement (content) ``` Restores the "what did we decide in order?" view lost by splitting into domain files. **Effort:** ~1 hour. **Priority:** Low. Discussions archive already provides this (see `docs/discussions/README.md`). ### 6.5 Decision-Ticket Linkage Report Generate report: "Which decisions have no linked tickets?" ```bash decision-ticket-report ``` Output: ``` Decisions without linked tickets: - D-013: Insert/POI navigation system - D-016: Internal monologue - D-018: Three-range sound model - D-021: Official title - D-029: Population entanglement ratio Total: 5 / 31 decisions (16%) lack implementation tickets. ``` Helps identify decisions that haven't been actioned yet. **Effort:** ~30 minutes (simple SQL query + formatting). **Priority:** Medium. Useful for planning. --- ## 7. Open Questions for Team Review Before proceeding with migration, I recommend team review on: 1. **Domain taxonomy:** Does the proposed 11-domain split (architecture, content, camera-perception, scope, world, combat, process, meta, multiplayer, questions, rejected) make sense? Should any domains be merged or split differently? 2. **Cross-domain decisions:** Some decisions span domains (e.g., D-026 simulation tiers touches both architecture and content). How should we handle this? Options: - Put in primary domain (architecture) with cross-reference note in content file - Duplicate in both files (violates DRY, but improves discoverability) - Create "cross-domain" file for decisions that don't fit cleanly (13th file, not ideal) 3. **Wrapper script priority:** Should I build `decision-add` wrapper (6.1) before or after migration? Before = smoother migration. After = faster to production. 4. **Linter as pre-commit hook:** Should `decision-lint` (6.2) block commits if index/markdown drift detected? Or just warn? 5. **Ticket-decision linkage:** Should I audit and link all 31 decisions to existing tickets during migration? Or defer as ongoing maintenance? --- ## 8. Conclusion The hybrid domain-split + DB index approach solves the immediate problem (context window bloat) and the long-term problems (cross-referencing, ticket linkage, discoverability, maintenance burden) without sacrificing the human-friendliness of markdown or the auditability of git history. **My recommendation:** Proceed with migration. Estimated effort: ~4 hours (split markdown, seed DB, update briefings, re-index Qdrant). Risk: low (rollback path clear, no data loss). Benefit: 70-80% reduction in agent context load, 3x faster Qdrant re-indexing, structured cross-references, and a foundation for future scaling to 100+ decisions. **For the record:** This is the right long-term structure. We should implement it now, before DECISIONS.md reaches 1000 lines and the migration becomes painful. **Next steps (pending team approval):** 1. Review this analysis with Team Leader (Jeroen) 2. Finalize domain taxonomy (adjust if needed) 3. Execute migration (Phase 1-6, ~4 hours) 4. Validate and announce 5. Build wrapper script and linter (Phase 6.1-6.2, future enhancement) --- **Document ends.** For questions or clarifications, ask Qatux.