NataCoach / product & system design Wiki Brain Personas Coach Console ↗

08 — The Wiki Brain

The heart of NataCoach: a per-user, Karpathy-style LLM-maintained wiki that turns a stream of events and chats into a compounding, inspectable, Nata-editable model of one client.

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:

  1. 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).
  2. Write the digest to raw/2026-08-09-events.md — a compact, human-readable rendering of the day. Immutable from birth.
  3. Read SCHEMA.md, index.md, and candidate pages selected by the eventpage map (§8) — typically 3–6 pages, not the whole wiki.
  4. 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.
  5. 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.
  6. Commit: new page versions to S3, index.md summaries refreshed, log.md appended, watermark advanced, wiki.distilled event 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' in wiki_pages), wraps her prose in <!-- coach 2026-08-03 --> … <!-- /coach --> markers, and appends a log.md line: ## [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_contradiction alert 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.