decision
ADR-0015: Customer Brain — Four-Document Architecture (SOUL, BIBLE, HEARTBEAT, MEMORY)
ADR-0015 (Accepted (deployed 2026-05-15), 2026-05-15): Customer Brain — Four-Document Architecture (SOUL, BIBLE, HEARTBEAT, MEMORY).
Status: Accepted (deployed 2026-05-15)
Date: 2026-05-15
Deciders: Seth (Lead Architect), Blair (CEO)
Framing note (added post-implementation per #21)
The four documents are best understood as four context layers, each with distinct lifecycle and retrieval properties:
| Layer | What | Write semantics | Retrieval |
|---|---|---|---|
| SOUL | Communication preferences | LLM-edited via tool (rolling) | Always injected |
| BIBLE | Site facts and goals | LLM-edited via tool (rolling) | Always injected |
| HEARTBEAT | Current site state | Auto-generated on sync (render) | Always injected |
| MEMORY | Durable insights | Append-only via tool | Vector retrieval |
Future context types (session wrap-ups per ADR 0019, KPI trends per #18, temporal MEMORY supersession per #17, intra-session compaction per #20) are additional layers in this system, not candidates for becoming a fifth “document.” When evaluating a new context proposal, the right question is “what’s its lifecycle and retrieval shape?” — not “does it fit one of the four documents?”
The four-document names are kept because they’re shipped, in the UI tabs, referenced across the codebase, and customer-visible. The framing above captures the engineering reality without disturbing the customer-facing or code-level surface.
Context
The V1 spec (§1) describes the product bet as “a self-improving knowledge system (per-customer brain + global brain + cross-pollination) that becomes more valuable the longer customers use it.” Until 2026-05-15, the chat advisor had three structural gaps that blocked this bet:
- No memory across conversations. Each chat started from scratch. The AI did not remember that a particular operator cares about churn or that their site focuses on coaching courses.
- Site context was raw SQL on every request. Stats, memberships, and recent transactions were re-queried and re-formatted at every chat turn — unstructured, expensive, and incapable of capturing narrative insight.
- No self-improvement loop. Nothing wrote durable insight back into per-customer state, so the “becomes more valuable over time” promise was untestable.
The forces in play:
- Multi-site users. Operators have N sites; preferences are about the person, but facts and metrics belong to a specific site.
- Token budget. Always-on context must fit; reaching for irrelevant detail wastes tokens on every turn.
- Vector vs. always-on. Some context is so foundational it must be in every system prompt; some is episodic and only matters when the topic comes up.
- Authorship. Some context is best-written by the user (preferences), some is best-derived from data (current metrics), some is best-written by the LLM after a conversation (insights).
These forces pushed against a single freeform “context blob” or a generic key-value bag — neither fits the ownership / always-on / authorship split cleanly.
Decision
Per-customer brain is four persistent documents with distinct owners, scopes, and update mechanisms. Three are always-on context in every system prompt; one is retrieved by vector search when relevant.
| Document | Owner | Scope | Always-on? | Updated by |
|---|---|---|---|---|
| SOUL | User | ”Who this person is” — preferences, communication style, recurring concerns | Yes | LLM tool call (update_customer_brain) or user edit via PUT /api/v1/brain/soul (brain-management UI) |
| BIBLE | Site | ”What this site is” — narrative truth about the membership business | Yes (site-specific) | Initial seed auto-generated from site URL + memberships on first connect; subsequent updates via LLM tool call or user edit via PUT /api/v1/brain/bible |
| HEARTBEAT | Site | ”Current state” — auto-narrated metrics snapshot | Yes (site-specific) | Auto-regenerated on every sync_site() (no user or LLM write path — derived from synced data) |
| MEMORY | User+Site | ”What we’ve discussed” — episodic insights from prior conversations | No, retrieved | LLM tool call after a conversation yields something durable; user can DELETE /api/v1/brain/memories/{id} but not edit |
Schema: user_souls (one row per user) and site_contexts (one row per site, holds bible + heartbeat). MEMORY reuses the existing brain_entries table with collection='memory' and tenant_id=<user_id>. Files: src/memberintel/db/brain_models.py (UserSoul, SiteContext models — kept separate from db/models.py to scope brain state cleanly), src/memberintel/api/brain/context.py (generate_heartbeat(), generate_initial_bible()), Alembic migration alembic/versions/0004_customer_brain_tables.py (revision 0004_brain_tables).
Site-switch behavior. When the user switches sites in the SPA, the system prompt swaps in a different BIBLE+HEARTBEAT but keeps the same SOUL. MEMORY is searched scoped to user_id, so insights from one site can surface in conversations about another (which matches user expectation — preferences and learning belong to the person).
System prompt order: Base prompt → SOUL → BIBLE → HEARTBEAT → MEMORY search results → Global brain search results. Fallbacks: missing SOUL is omitted entirely; missing BIBLE shows the auto-generated initial; missing HEARTBEAT prompts the user to sync.
HEARTBEAT replaces build_site_context(). The prior raw-SQL site-context injection is removed. HEARTBEAT is written as narrative (“142 active members, up 3 this week. MRR $4,850. Churn 2.1% trending down.”) instead of a data dump, so the LLM gets a status report it can reason about.
Consequences
Positive:
- Ownership is explicit. The user controls SOUL, the system controls HEARTBEAT, the LLM proposes BIBLE/MEMORY changes for review. No ambiguity about who can write what.
- Token cost is bounded. SOUL+BIBLE+HEARTBEAT are short narrative documents; MEMORY is retrieval-gated. The always-on budget is predictable and the retrieved tail is query-relevant.
- The site-switch UX falls out for free — site-scoped documents live on
site_contexts, user-scoped onuser_souls, and the SPA’sactiveSiteIdselects which BIBLE+HEARTBEAT to load. - HEARTBEAT is narrated, not raw. The LLM no longer has to interpret tables and stats inline; it gets a coherent paragraph that matches how a human advisor would describe a business’s current state.
- The schema is small. Two new tables and one reuse — the brain isn’t a separate service.
Negative / costs:
- BIBLE is LLM-authored under read-modify-write. A confused or hallucinating LLM can rewrite the document badly. The brain-management UI (ADR-0018) is what makes this recoverable — users see and edit BIBLE directly.
- HEARTBEAT regeneration is coupled to
sync_site(). If sync fails, HEARTBEAT goes stale silently. There is no separate “regenerate HEARTBEAT” path; if a customer’s data is wrong, the fix is to re-sync. - Four documents is more conceptual surface than one. New engineers have to learn the model. Mitigated by the table above being the canonical explanation.
- MEMORY accumulates indefinitely without per-tier limits (those land in ADR-0018). Free-tier users will hit the cap fast; that’s intentional, but it does mean the “memory” feature has a cliff.
Mitigations:
- BIBLE drift: brain-management UI (
/brainroute in the SPA) shows the user every document and offers per-line edit/delete for Pro-tier users. SOUL has the same affordance. - Stale HEARTBEAT: the document includes its
last_synced_attimestamp explicitly so the LLM knows how fresh the data is and can prompt the user to sync if the gap is large. - Conceptual load: the spec at
docs/superpowers/specs/2026-05-15-customer-brain-design.mdis the canonical reference; the table at the top of this ADR is the on-ramp.
Alternatives considered
- Single freeform “context” document. Rejected. Mixes preferences, facts, and metrics in one blob the LLM has to constantly reshape. No ownership boundary — anything can write anything, and there is no separation between “stable truth” and “current state.” Token cost is unbounded.
- Key-value bag of attributes (
favorite_topic = "churn",member_count = 142, …). Rejected. Loses narrative — the LLM is best at reasoning over prose, worst at reasoning over schemas. Also pushes the schema-design burden onto every new piece of context. - Full conversation memory (store every transcript, retrieve relevant ones). Rejected. Token-explosive, privacy-heavy, and the LLM has to re-derive the same insight every time. MEMORY captures the insight rather than the transcript that produced it, and is far cheaper at retrieval.
- Two documents: per-user “profile” + per-site “context.” Rejected. Conflates “stable narrative truth” (BIBLE) with “current numerical state” (HEARTBEAT). Without the split, you have to choose between regenerating “context” on every sync (which destroys the narrative) or leaving stats stale in the narrative (which makes the LLM confidently wrong about MRR).
- Defer until V1.5. Rejected. The product bet (self-improving advisor) is the V1 differentiator. Without the brain in V1, the advisor is just a stateless chat over MP knowledge — that’s a worse copy of every general LLM. The four-document model is the minimum viable shape of the bet.