# DECISIONS.md — ADR log Decisions inside the set architecture. D-NNN, never renumbered. - **D-001** — SDD+BDD+TDD adopted (2026-07-13). SPEC-system port: numbered requirements in `specs/`, coverage classes feature/test/manual, `tools/trace.py` enforcement in `make check`. Process is binding — see SPEC-000. - **D-002** — BDD runs at the responder seam, not against live services. `FakeModelResponder` scripts model output; Discord-event behavior is unit-tested with mocked discord.py objects. Rationale: Discord ToS forbids test-account automation and LLM output is nondeterministic — a live-BDD lane would be flaky by construction. - **D-003** — Packaging = uv + pyproject only (2026-07-13). setup.py, requirements.txt and pytest.ini removed; single source of truth, locked via uv.lock. Python `>=3.11` floor keeps the kitchen host (py3.11) deployable until FDB-016 lands. - **D-004** — openai SDK pinned `<2` (1.109.x). The v2 SDK migration happens together with the FDB-005 envelope rewrite (structured outputs / Responses API) — one breaking change, one test cycle, instead of two. - **D-005** — `--strict-markers` stays on; requirement tags from `.feature` files are registered as pytest markers dynamically in `tests/conftest.py`. - **D-006** — FDB-005 stays on chat.completions + structured outputs; the Responses API migration is deferred to FDB-007, where history handling gets redesigned anyway — one conversation-state reshape instead of two. - **D-007** — openai SDK bumped to 2.x together with the envelope rewrite (supersedes the D-004 pin). `multiline` dependency dropped — strict schema output made relaxed-JSON parsing dead code. - **D-008** — Operator runtime flags (pause/images/tasks/quiet) are in-memory only; a restart resets to config defaults. Persistence arrives with the FDB-011 task store if staff practice demands it. - **D-009** — `translate()` still keys off `fix-model` although the repair path is gone; the whole translate-before-draw step dies in FDB-009 (gpt-image-2 is multilingual). Not worth a config rename for one phase. *(Closed 2026-07-13: FDB-009 deleted translate(); the fix-model config key is now fully dead and can be dropped from live configs.)* - **D-010** — Persistence uses stdlib `sqlite3` via `asyncio.to_thread`, not aiosqlite: no new dependency, and a connection-per-operation with WAL is plenty at this message volume. - **D-011** — `save_history` replaces the channel's rows wholesale per message instead of appending: histories are capped at `history-limit` (~200-350 rows) and trims must be reflected; correctness over micro-optimization. - **D-012** — Budget spend is *estimated* from configured per-token/ per-image prices, not fetched from the billing API: deterministic, testable, no extra scopes. Dashboard hard limits (FDB-001) stay the outer safety net; this ledger is the inner, immediate one. User image quota counts at grant time (reservation), global image spend at generation time. - **D-013** — `!forgetme` v1 purges history rows only; the single-string channel memory cannot be selectively cleaned. Full fact-level erasure ships with FDB-007 structured memory — stated in the user-facing confirmation, not hidden. (Superseded by D-014: erasure now covers facts, observations and episode traces.) - **D-016** — The reply/ignore classifier fails open (BEH-03): a broken classifier must never mute the bot; the budget gate already bounds spend. Its verdict gates BEFORE the main call, the envelope's answer_needed still gates after — two independent nets. - **D-018** — Codex Mechanicus search (FDB-019, SPEC-014): Luma's lore is grounded in the priest's real archive at binaric.tech via a `codex_search` tool over the site's public `search-index.json`, not a bot-side copy — the index stays a single source of truth, refreshed by the site's own publish rite, and the bot caches it in memory (TTL). It reuses SPEC-011's `guard_url` + `read_capped` (fetch is SSRF-guarded and byte-bounded) and sanitizes every returned field: one's own web content is still untrusted by the time it reaches a prompt. Luma-only (`enable-codex`, off elsewhere) — the Adeptus Mechanicus archive has no place in Fjærkroa's café persona. - **D-017** — All human-behavior knobs default to off/v3.0.0 semantics; behavior changes are config rollouts per deployment, not code flips. The classifier's `factual` flag is the only coupling (delay bypass) and defaults to false without a classifier. - **D-015** — Deploys are push-based from the dev machine (`git archive | ssh`), not pull-based: no deploy keys or git state on the hosts, the artifact is exactly the tag tree, untracked live config survives in-place extraction. Trade-off: deploys need the dev machine; acceptable for a one-operator project. - **D-014** — Structured memory (FDB-007): observations are the only consolidation feed (independent of history trimming); consolidation returns NEW facts only (no wholesale rewrite — the lossiness of the old memoize path is exactly what we're removing); self-authorship is enforced in code (fact subject must be an observation author), not just in the prompt; memory reads run on the loop (small indexed SQLite queries), writes off-loop. Legacy memory strings survive as episodes; the memory table stays as a read-only legacy fallback for deployments without memory-model.