Every other pipeline in this system — Meal Lens, Session Mode, Form Check, the Morning Brief — is only as good as its answer to one question: what do we know about this person? The Wiki Brain is that answer. It is a faithful per-user adaptation of the Karpathy LLM-wiki method (brief §5): raw/ sources the LLM reads but never edits, wiki/ pages it maintains, a schema doc that governs everything, index.md as the catalog, log.md as the journal, and three workflows — ingest, query, lint.
1. Why a wiki, not RAG-over-logs
The default 2026 answer to "give the LLM memory" is embeddings over chat history. We rejected it deliberately.
| RAG over raw logs | Wiki Brain | |
|---|---|---|
| What a query retrieves | K nearest raw chunks — possibly stale, contradictory, or mid-conversation fragments | 2–4 curated pages that already resolved contradictions and dated their claims |
| Cost of knowledge | Re-derived on every call (the model must re-infer "Marta hates evening check-ins" from scattered evidence, every time) | Compiled once at ingest, reused for pennies forever |
| Contradictions | Retrieved side by side, silently; the model picks one at random | Detected at write time, flagged with ⚠, alerted to Nata |
| Inspectability | Nobody can read an embedding index | Nata opens coaching-playbook.md and reads exactly what the bot believes — and the /export zip (see 03 §7) is honest for the user too |
| Editability | No place for Nata to inject judgment | Nata's edits are first-class ground truth (§5) |
| Cost as history grows | Retrieval gets noisier and context gets fatter | Flat: distillation rewrites pages instead of appending (§6) |
The deeper reason is the Karpathy governing principle: the human curates sources and asks questions; the LLM's job is everything else. A coach's expertise is not a transcript archive — it is a maintained model of the client: what works for them, what breaks them, what they'll actually do. Logs record; the wiki understands. And crucially, the division of labor from the brief §4.6 holds throughout: numbers live in Postgres (events, daily_metrics, sets, meals — queried with deterministic SQL), while judgments, patterns, and context live in the wiki. The wiki may quote a number to illustrate a pattern, but it is never the system of record for one.
2. The per-user repo
Each user owns one repo-shaped prefix in the versioned S3 bucket (wiki/{userId}/, git-style history via bucket versioning; pointers and version metadata in the wiki_pages table — see 03 §3). Marta's, in week 32:
users/marta-7f3a/
├── SCHEMA.md # the governing doc (contents below)
├── index.md # catalog: every page + one-line summary
├── log.md # append-only journal of every workflow run
├── raw/ # immutable — read, never edited
│ ├── 2026-08-08-events.md # daily event digest (from Postgres)
│ ├── 2026-08-09-events.md
│ ├── transcripts/
│ │ └── 2026-08-09-chat.md # notable free-chat excerpts
│ └── nata-notes/
│ └── 2026-08-05-block2.md # Nata's program-change rationale
└── wiki/
├── profile.md # who she is: life context, schedule, equipment
├── goals.md # goals + why they matter to her, milestones
├── constraints-injuries.md # safety-critical; removals by Nata only
├── training-history.md # blocks, responses to stimulus, per-lift notes
├── nutrition-patterns.md # habits, food priors, what feedback lands
├── recovery-patterns.md # sleep/recovery rhythms, what predicts bad days
├── preferences.md # channel, timing, format, interaction quirks
├── coaching-playbook.md # what works for THIS person (see §7)
├── exercises/
│ └── pull-up-progression.md # topic page, split out when it outgrew a section
└── weekly/
├── 2026-W31.md # frozen weekly reviews
└── 2026-W32.md
SCHEMA.md — the actual governing document
This file ships identically to every user's repo and is read by the distiller on every run. It is the constitution; when in doubt, the model re-reads it.
# SCHEMA — how this wiki works (v3, 2026-07-14)
## What this wiki is
A model of one client, maintained by the distiller and read by every coach
pipeline. Numbers live in Postgres; this wiki holds judgments, patterns, and
context — what a good coach KNOWS, not what a database STORES. A page may
quote a number as illustration, always with a date; it is never the record.
## Layers
- raw/ immutable inputs: daily event digests, chat excerpts, Nata notes.
Read freely. Never edit, never delete.
- wiki/ maintained pages. Rewrite freely within the rules below.
- index.md catalog of every wiki/ page with a one-line summary and last-updated
date. Update on every ingest that touches a page.
- log.md append-only journal. One dated entry per workflow run. Never rewrite.
## Page types and naming
- Core pages (always exist): profile, goals, constraints-injuries,
training-history, nutrition-patterns, recovery-patterns, preferences,
coaching-playbook.
- weekly/YYYY-Www.md — one per ISO week, written at Weekly Review, then frozen.
- Topic pages — create when a section outgrows ~15 lines (e.g.
exercises/pull-up-progression.md); leave a link behind at the old location.
- Filenames kebab-case. Every page: H1 title, one-line italic summary, sections.
## Update rules
1. Rewrite sections in place; do not accrete. Hard cap 90 lines per page.
2. Any claim that could change carries a date: "(as of 2026-08-09)".
3. Cross-reference with relative links; repair links whenever a page splits.
4. Blocks between <!-- coach --> and <!-- /coach --> are Nata's ground truth:
never edit, move, or delete them; write around them; on conflict, defer to
them and flag the tension instead of resolving it.
5. On contradiction between new evidence and an existing claim: keep both,
mark the pair with "⚠ CONTRADICTION", and raise an alert. Never silently
pick a winner.
6. constraints-injuries.md is safety-critical: the distiller may ADD entries,
only Nata may remove or downgrade one.
## Log entry format
## [YYYY-MM-DD] <workflow> | <subject> — what changed, pages touched.
3. The three workflows, mapped to coaching
3.1 Ingest — the nightly distillation (plus inline mini-ingests)
The main ingest is a per-user BullMQ job at 02:30 local time, running on the Claude Message Batches API (half price, zero latency pressure — see the cost table in 03 §5, row 8). The sequence diagram lives in 03 §4.3; the logic:
- Fetch all events past the user's watermark (a typical Marta day: 37 events — sleep, recovery, 1 session with 14 sets, 3 meals, brief confirmation, a few chat turns).
- Write the digest to
raw/2026-08-09-events.md— a compact, human-readable rendering of the day. Immutable from birth. - Read
SCHEMA.md,index.md, and candidate pages selected by the event→page map (§8) — typically 3–6 pages, not the whole wiki. - One Opus-tier call rewrites the affected pages: fold the day's evidence into existing patterns, date the claims, repair cross-references, and emit a contradiction list.
- Validate deterministically (no LLM):
raw/untouched, every<!-- coach -->block byte-identical, page size caps respected, all relative links resolve. Fail → retry ×3 → dead-letter + alert, wiki left at last good version. - Commit: new page versions to S3,
index.mdsummaries refreshed,log.mdappended, watermark advanced,wiki.distilledevent emitted.
# tail of Marta's log.md
## [2026-08-07] lint | weekly — 1 stale claim retired (bedtime), 1 gap queued (wrist)
## [2026-08-08] ingest | rest day — 22 events; recovery-patterns.md, nutrition-patterns.md
## [2026-08-09] ingest | session: lower body A — 37 events; training-history.md,
recovery-patterns.md, weekly/2026-W32.md; no contradictions
Inline mini-ingests exist because some facts cannot wait for 02:30. A short trigger list runs a targeted ingest (1–2 pages, standard API, seconds) the moment the event lands:
| Trigger | Why it can't wait | Pages touched inline |
|---|---|---|
pain.reported / injury keywords (Haiku triage) |
The very next Session Mode call must already know | constraints-injuries.md (+ Nata alert, always) |
| Goal change stated in chat ("I want to run a 10k") | Tonight's brief and Nata's view must reflect it | goals.md |
program.updated by Nata |
Tomorrow's session runs the new block | training-history.md, goals.md |
| Travel announced (Denys: "flying Tue–Sat") | Meal Lens and Session Mode switch to travel mode | profile.md, preferences.md |
| Integration connected / revoked | Recovery logic changes source or falls back | profile.md |
Contradiction flagging is the ingest's most coach-like behavior. Example from Denys's distillation of 2026-08-04: recovery-patterns.md claimed "runs reliably even on hotel weeks (as of 2026-06-30)", but the week's events show two skipped zone-2 runs during the Berlin trip. The distiller does not decide who is right — it marks both lines ⚠ CONTRADICTION, writes an alerts row (wiki_contradiction, low severity), and Nata sees it in the Coach Console the next morning next to a one-tap resolution: keep old claim / accept new pattern / edit herself.
3.2 Query — how every pipeline reads the brain
No pipeline ever gets "the whole wiki." The Orchestrator's context builder (the single prompt-assembly code path — see the isolation rules in 03 §7) always assembles: persona layer + active program + index.md + 2–4 wiki pages + recent events from Postgres. Page selection is a deterministic hint list per pipeline, with Haiku choosing from index.md summaries only for free chat:
| Pipeline | Always opens | Opens if index summary suggests |
|---|---|---|
| Morning Brief | recovery-patterns.md, coaching-playbook.md |
goals.md, current weekly/ |
| Session Mode (coach-voice moments) | training-history.md, constraints-injuries.md, coaching-playbook.md |
relevant exercises/*.md |
| Meal Lens feedback | nutrition-patterns.md, coaching-playbook.md |
preferences.md |
| Form Check | constraints-injuries.md, training-history.md |
relevant exercises/*.md |
| Free chat | coaching-playbook.md |
Haiku picks ≤ 3 from index.md |
| Weekly Review | goals.md, current weekly/, coaching-playbook.md |
any page updated this week |
Coach-voice drafts cite internally — the draft Nata sees in the approval queue carries footnotes like [training-history.md: bench stalled at 3×5×42.5 since W29], so she can audit why the bot said something in one glance.
Good answers get filed back (the Karpathy query rule). When a synthesis is expensive and reusable, the pipeline enqueues a "file this" note for the next ingest. Real case: Denys asks "what should I actually do in a hotel gym with two dumbbells and a treadmill?" — the answer (a 35-minute DB full-body + 25-minute incline-walk fallback, mapped to his program's slots) becomes wiki/travel-protocol.md, and every future travel week starts from that page instead of re-deriving it.
3.3 Lint — the weekly health check
Sunday 03:00 local, after Saturday's distillation and before the Weekly Review generates (so the review benefits from a clean wiki). One Opus batch call reads the entire wiki/ tree plus index.md and checks:
| Check | Example finding | Disposition |
|---|---|---|
| Contradictions missed at ingest | preferences.md says "hates voice replies"; coaching-playbook.md says voice notes land well |
⚠ mark + Nata alert |
| Stale claims (volatile pages, > 28 days undated or unconfirmed) | "bedtime ~23:30 (as of 2026-06-12)" but June's sleep events say 00:40 | Rewrite with date, note revision in log.md |
| Orphan pages / broken links | exercises/goblet-squat.md no longer linked after a split |
Re-link from training-history.md, fix index.md |
| Size caps | nutrition-patterns.md at 112 lines |
Split topic page, leave link |
| Gaps | Marta mentioned knee discomfort twice in chat (Aug 3, Aug 7) but constraints-injuries.md has no knee entry |
Becomes a queued question |
Gaps are the clever part: a lint gap does not trigger an interrogation. It becomes one question in a per-user queue that the bot asks at the next natural moment — right after a session completes or a meal confirm, when the user is already engaged — capped at one lint question per day (consistent with the question budget in 04 §1):
[19:24] NataCoachBot: Lower Body A done — 4,120 kg moved, nice. 💪
[19:24] NataCoachBot: One thing while you're here: you've mentioned your right
knee twice this week. Is it a real thing we should train around, or
just a passing niggle?
[ It's a thing ] [ Just a niggle ] [ Tell you more ]
[19:25] Marta: [ Just a niggle ]
[19:25] NataCoachBot: Noted — I'll keep an eye on it and won't nag. 👍
→ event checkin.soreness → constraints-injuries.md gets a dated
"watch item"; if it recurs, escalates to Nata per safety rails
4. Nata as editor
The Coach Console has a Client wiki tab that renders any client's pages as editable markdown. When Nata edits:
- The save writes directly to the S3 page (new version,
updated_by = 'nata'inwiki_pages), wraps her prose in<!-- coach 2026-08-03 --> … <!-- /coach -->markers, and appends alog.mdline:## [2026-08-03] coach-edit | coaching-playbook.md — silence protocol added. - Those blocks are ground truth: SCHEMA rule 4 forbids the distiller from editing, moving, or deleting them. The LLM appends around them and defers to them on any conflict.
- If accumulating evidence genuinely contradicts a coach block (say Nata wrote "responds well to blunt numbers" but three blunt weigh-in messages each preceded a two-day silence), the distiller may not touch the block — it raises a
wiki_contradictionalert phrased as a question to Nata. The machine never overrules the coach; it shows her the evidence and lets her edit her own claim.
This is the mechanism that makes ten Wiki Brains hers rather than ten chatbot memories: five minutes of her judgment, typed once, steers every future draft for that client.
5. Token economics — why this stays cheap forever
Per-call context assembly budget (Opus-tier coach calls), with plausible measured sizes:
| Context component | Tokens (typical) |
|---|---|
| Persona layer (Nata tone guide — stable, prompt-cached) | ~1,200 |
index.md |
~500 |
| 2–4 wiki pages (≤ 90 lines each, per SCHEMA cap) | 1,600–3,600 |
| Active program excerpt + recent events (Postgres) | ~1,200 |
| Wiki-derived share | ≈ 3–6k |
Contrast with naive full-history stuffing: an engaged user produces ~1,000 tokens/day of chat plus event renderings — after 6 months that is ≈ 300–400k tokens of history. Stuffed into every Opus call at $5/MTok that is $1.50–2.00 of input per message (the entire daily budget, per call), plus lost-in-the-middle degradation and every contradiction the user ever walked back sitting live in context. The Wiki Brain's answer is compile, don't re-read: distillation pays once per day (~$0.15 batched, row 8 in 03 §5) to fold history into pages, and every subsequent call reads ~3–6k tokens for ~$0.02–0.03.
The budget stays flat as history grows because nothing loaded at query time grows unboundedly: pages are rewritten under a hard size cap, not appended; weekly reviews freeze into weekly/ but only the current week is ever auto-loaded; raw/ grows forever but is read only by ingest (one day at a time) and never at query time. Six-month Marta and first-week Marta cost the same per message — six-month Marta just gets much better answers. The stable prefix (persona + index.md + coaching-playbook.md) also makes prompt caching effective, the ~40% chat-input saving already counted as buffer in 03 §5.
6. Nightly distillation — flowchart
flowchart TD
CRON["02:30 local per-user job (BullMQ repeatable)"] --> FETCH["Fetch events past watermark (Postgres)"]
FETCH --> ANY{"New events?"}
ANY -->|"no"| SKIP["log.md: 'ingest — quiet day', done"]
ANY -->|"yes"| RAW["Write raw/YYYY-MM-DD-events.md (immutable digest)"]
RAW --> READ["Read SCHEMA.md + index.md + candidate pages (event-to-page map)"]
READ --> LLM["Opus Batches call: rewrite pages, date claims, fix links, list contradictions"]
LLM --> VAL{"Deterministic validation: raw/ untouched, coach blocks intact, size caps, links resolve"}
VAL -->|"fail"| RETRY["Retry x3 then dead-letter + pipeline_failure alert — wiki stays at last good version"]
VAL -->|"pass"| WRITE["Write new page versions (S3 versioned bucket)"]
WRITE --> IDX["Refresh index.md summaries + append log.md entry"]
IDX --> WM["Advance watermark, emit wiki.distilled"]
WM --> CONTRA{"Contradictions flagged?"}
CONTRA -->|"yes"| ALERT["alerts row (wiki_contradiction) — Coach Console next morning"]
CONTRA -->|"no"| DONE["Done"]7. A complete example: Marta
wiki/coaching-playbook.md — full page
# Coaching playbook — Marta
*How to coach this specific person. Read before drafting anything in her voice channel.*
## Tone that works
- Warm, brisk, a little playful. Two short paragraphs max; she reads on the move.
- Celebrate process ("3rd week of 3/3 sessions") over outcomes; she distrusts hype.
- Ukrainian gym slang lands well occasionally; full formality reads cold to her.
## Motivators
- The first pull-up. Frame hard sets as "deposits" toward it — this works every
time (band-row PR reaction, 2026-07-22).
- Streaks and visible progress bars in the Weekly Review; she screenshots them.
- Being told the plan was ADJUSTED FOR HER ("we planned around your night"), never
that she fell short of it.
## Red buttons — do not press
- No comments on postpartum body or weight beyond the goals SHE stated in goals.md.
- Never suggest "try to sleep more." The baby decides. Adjust load silently instead.
- No guilt framing on a missed session, ever. She self-blames enough (as of 2026-07).
## What works operationally
- Morning Brief before 07:00 or it drowns in the family morning (preferences.md).
- One-tap everything; each typed reply costs real goodwill.
- A real Nata voice note within a day of a PR — outsized effect, twice observed.
## What doesn't
- Educational paragraphs about protein timing: read receipts say unread twice.
- Evening check-ins after 20:30 — answered next morning or never.
## Escalation notes
- Wrist discomfort in front-rack positions → substitute per constraints-injuries.md,
don't debate it in chat.
- Right knee: watch item since 2026-08-09, "just a niggle" per her; recurs → Nata.
<!-- coach 2026-08-03 -->
If she goes quiet for 3+ days it's almost always a rough baby patch, not lost
motivation. Send ONE light no-ask message ("thinking of you — zero pressure this
week"). Do not send adherence stats that week. — Nata
<!-- /coach -->
index.md — her catalog
# Index — Marta (7f3a) · last ingest 2026-08-09 · 11 pages
| Page | One-line summary | Updated |
|---|---|---|
| profile.md | 34, Kyiv, marketing lead, 8 mo postpartum; trains 3x/wk at home-adjacent gym; Whoop | 2026-08-02 |
| goals.md | First strict pull-up (est. Nov 2026) + daytime energy; explicitly NOT weight-focused | 2026-07-28 |
| constraints-injuries.md | Left wrist: no barbell front rack; right knee watch item (niggle, 2026-08-09); postpartum core progression rules from Nata | 2026-08-09 |
| training-history.md | Block 2 wk 3 of 6; band rows 3x8 → 3x11 since W29; squat responds fast, presses slow | 2026-08-09 |
| nutrition-patterns.md | Home cook, protein gap at lunch (~48 g days recur); Sunday batch-cooking works | 2026-08-08 |
| recovery-patterns.md | Recovery baseline 58%; <5h30 nights → RPE inflation next day; ⚠ CONTRADICTION on bedtime under review | 2026-08-09 |
| preferences.md | Brief before 07:00; buttons over typing; no messages after 20:30; likes emoji, hates walls of text | 2026-07-30 |
| coaching-playbook.md | Tone, motivators, red buttons; Nata's silence protocol (coach block, 2026-08-03) | 2026-08-09 |
| exercises/pull-up-progression.md | Band ladder: green → purple (current) → red; test week planned W36 | 2026-08-06 |
| weekly/2026-W31.md | 3/3 sessions, protein avg 96 g/d, one rough-sleep adjustment — frozen | 2026-08-02 |
| weekly/2026-W32.md | In progress: 2/3 sessions so far, knee watch item opened | 2026-08-09 |
Denys's full tree, sample pages, and week-in-the-life are in 10-personas.md.
8. Event types → wiki pages they can touch
The distiller's candidate-page selection starts from this deterministic map (the model may open more if index.md suggests it, never fewer). Event names match the inventory in 04 §8.
| Event type | Emitted by | Pages it can touch |
|---|---|---|
sleep.recorded, recovery/HRV/RHR metrics |
Health Sync | recovery-patterns.md, current weekly/ |
activity.imported (auto-detected workouts) |
Health Sync | training-history.md, current weekly/ |
session.started / session.completed |
Session Mode | training-history.md, current weekly/ |
set.logged, rpe.recorded |
Session Mode | training-history.md, exercises/*.md |
pain.reported |
safety triage | inline → constraints-injuries.md, coaching-playbook.md |
checkin.soreness |
decision-gated question | constraints-injuries.md, training-history.md |
checkin.mood |
decision-gated question | profile.md, coaching-playbook.md |
meal.analyzed / meal.scored |
Meal Lens | nutrition-patterns.md, current weekly/ |
meal.corrected |
user adjustments | nutrition-patterns.md, or its food-priors.md topic page once split out (see 05 §3) |
weight.confirmed |
weekly weigh-in | goals.md, current weekly/ |
brief.confirmed / brief adjusted |
Morning Brief | recovery-patterns.md, preferences.md |
video.reviewed |
Form Check | exercises/*.md, constraints-injuries.md |
program.updated |
Nata via Console | inline → training-history.md, goals.md |
| Chat highlights (from transcript digest) | free chat | any; most often preferences.md, coaching-playbook.md, profile.md, goals.md |
integration.connected / revoked |
Health Sync | profile.md |
wiki.distilled, system.heartbeat |
the system itself | log.md only — never a wiki page |
One rule closes the loop: nothing reaches a wiki page except through an event or a Nata edit. That keeps the Wiki Brain a pure function of raw/ + coach ground truth — rebuildable from scratch if we ever change the schema, auditable line by line if we ever wonder why the bot believed something.