The Coach Console is the other half of Coach-in-the-loop. Everything the clients experience — Meal Lens verdicts, Session Mode adjustments, re-engagement nudges — either passed through this surface or earned the right to skip it. The design constraint is brutal and simple: Nata's total console time must fit in ~20 minutes a morning plus glances during the day, at 10 clients, or the whole "scale Nata" thesis fails. Per locked decision 6 in the brief, every number on every screen here is deterministic SQL over the events table (03-system-architecture.md) — the LLM writes drafts, never metrics.
Interactive mockups: mockups/coach-console.html · mockups/coach-console.png.
1. Two surfaces, one Console
Nata lives in Telegram like her clients do. The Mini App is the daily driver — thumb-reachable, opened from a pinned chat with @NataCoachBot (her user ID is allowlisted; the Mini App validates Telegram initData, per 03). The web app (same Next.js codebase, Telegram Login Widget auth) is for seated, keyboard work.
| Job | Surface | Why there |
|---|---|---|
| Approval queue | Mini App | Arrives all day; median decision ~20 s; must work one-handed on a phone |
| Alert acknowledgement | Mini App | Red alerts push to her phone; ack and act in the same place |
| Roster glance | Mini App | "Is anyone on fire?" between her own training clients |
| Chat takeover | Mini App (web mirror) | Stepping in is a messaging act — phone-native |
| Daily / Monday digest | Delivered as a bot message | Zero-open cost; deep links into the Mini App |
| Program builder | Web | Training programs (sets, rep ranges, progression rules — 06 §1) and food programs (targets, bands, context budgets, standing rules — 05 §8) — spreadsheet-shaped work |
| Wiki Brain editing | Web | Markdown pages with diffs and version history; her edits are ground truth (08) |
| Deep analytics & cost telemetry | Web | 30/90-day charts, cross-client comparisons, LLM spend vs the $2/user/day budget |
| Autonomy dial overview | Web | Deliberate policy changes, not reactive taps (promotions still arrive as queue items, §3.3) |
| Invite links & onboarding pre-fill | Web | Generates the ?start=inv_… deep links from 02 §1 |
Build order follows 11-roadmap.md: Console v0 is the queue plus a per-client timeline and nothing else; the roster, alerts and dial land in v1; the program builder and full analytics in v2.
2. The roster: ten clients on one screen
One row per client, sorted needs-attention-first — never alphabetically. Nata should read the top row, not scan the table.
2.1 Column spec
| Column | Content | Source (all SQL, §5) |
|---|---|---|
| Client | Name + status dot (● red / ◐ amber / ○ calm, from attention band) | attention_score |
| Program week | e.g. W4/8 of the current mesocycle |
program schedule tables (06) |
| Adherence 7d | Sessions completed / scheduled, trailing 7 days | session.completed events vs schedule |
| Recovery trend | ↑ / → / ↓ / – (– when < 4 data points) | slope of recovery_score in metrics |
| Nutrition | 7-day nutrition score vs the client's target, e.g. 68 / 80 |
nutrition.day_closed events (05) |
| Last contact | Time since last inbound user action (message, tap, photo — wearable webhooks don't count) | events where source = 'user' |
| Open flags | Count + icon of unacked alerts, worst severity first | alerts table |
2.2 Sort order: the attention score
Computed nightly and re-computed on every alert change; at 10 clients this is a trivial query. Higher score sorts first.
| Component | Points | Cap |
|---|---|---|
| Each open red alert | +60 | — |
| Each open yellow alert | +25 | +50 |
Low adherence: (70 − adherence_7d) × 0.5 when adherence < 70% |
up to +35 | +35 |
| Recovery trend arrow is ↓ | +15 | +15 |
| Silence: +10 per silent day beyond the first | +10/day | +30 |
| Wearable stale ≥ 2 days | +5 | +5 |
Tie-break: hours since last contact, descending; then name. Bands: ≥ 60 red ●, 25–59 amber ◐, < 25 calm ○. The score is a sort key and a paint color — it is never shown to clients and never fed to the LLM as a "judgement" of the person.
2.3 Roster screen (Mini App, condensed two-line rows)
┌────────────────────────────────────────────────┐
│ Coach Console Mon 10 Aug · 07:41 │
│ Queue 14 · Alerts 2●/3◐ · Spend $0.98/u │
├────────────────────────────────────────────────┤
│ ● Client 07 W6/12 adh 33% ↓ │
│ pain flag (knee) · nutr 61/80 · seen 10 h │
├────────────────────────────────────────────────┤
│ ◐ Denys W11/16 adh 50% → │
│ protein 5d low · Lisbon wk · seen 2 h │
├────────────────────────────────────────────────┤
│ ◐ Client 04 W3/8 adh 67% ↓ │
│ recovery <33% ×3d · nutr 74/80 · seen 1 h │
├────────────────────────────────────────────────┤
│ ○ Marta W4/8 adh 100% → │
│ nutr 84/80 · PR Tue · seen 40 min │
├────────────────────────────────────────────────┤
│ ○ Client 03 … ○ Client 10 (6 calm rows) │
├────────────────────────────────────────────────┤
│ [ ▶ Queue (14) ] [ ⚠ Alerts (5) ] [ ⚙ Web ] │
└────────────────────────────────────────────────┘
(Only Marta and Denys are specified personas per the brief; Client NN rows are placeholders for the other eight pilot clients.) The web app renders the same data as a full seven-column table with sparklines.
2.4 Drill-down screen (tap a row)
┌────────────────────────────────────────────────┐
│ ← Denys · fat loss 96 → 88 kg · W11/16 │
│ Garmin ✓ (synced 06:10) · home since Fri │
├────────────────────────────────────────────────┤
│ Recovery 7d ▄▅▅▃▄▅▅ → (slope +0.4/d) │
│ Weight 30d 93.1 → 91.4 kg (−0.42 kg/wk ✓) │
│ Adherence 7d ██░░ 2/4 · volume WoW −18% (trvl)│
│ Nutrition 7d 68/80 · protein 5d avg 68% 🎯 │
├────────────────────────────────────────────────┤
│ OPEN FLAGS │
│ ◐ protein < 70% target, 5 logged days │
├────────────────────────────────────────────────┤
│ LAST EVENTS │
│ 19:42 meal.scored burger+fries 1,525 kcal │
│ 07:02 brief.confirmed zone-2 moved to 18:00 │
│ Sun weight.confirmed 91.4 kg │
├────────────────────────────────────────────────┤
│ WIKI BRAIN — coach summary (wiki/index.md) │
│ "Travel weeks read +0.5 kg water; responds to │
│ blunt numbers + a concrete next step." │
├────────────────────────────────────────────────┤
│ [ 💬 Open chat ] [ 🎙 Voice note ] │
│ [ 🛠 Adjust program ] [ ⏸ Step in ] │
└────────────────────────────────────────────────┘
Everything above the buttons is read-only SQL; the Wiki Brain summary is the one LLM-authored element, clearly attributed to wiki/index.md.
3. The approval queue
The queue is where Coach-in-the-loop physically happens. Every LLM draft that the autonomy dial says needs review becomes a row in approvals (03); the Mini App renders it as one card: context → draft → three buttons. Design budget: < 30 s median per item (approvals.latency_s is tracked and charted; pilot target median ≤ 25 s, p90 ≤ 60 s).
3.1 Item types
| Type | Volume/day (10 clients, draft phase) | Context shown | Typical decision |
|---|---|---|---|
| Meal feedback drafts (05) | ~25 (3–5 meals × active loggers) | Photo thumbnail, macro card, day totals vs targets, Wiki Brain tone/pattern notes | Approve 85–90% |
| Session adjustments (06) | ~4 | Recovery score, proposed load change, last session's RPEs | Approve or nudge a number |
| Re-engagement messages (02 §6.4) | ~1 | Silence duration, last known context, ladder step | Often Edit — these carry relationship weight |
| Form Check reviews (07) | ~2/week | Video, extracted key-frames, drafted cues, safety classifier output | Watch video, then Approve/Edit — the slow item type (~2–3 min), budgeted outside the 30 s target |
The client is never left hanging while a draft waits: deterministic layers (Meal Lens macro card, Session Mode set cards) always send instantly; only the coach-voice layer queues (the two-stage reply in 02 §2.3). Nothing auto-sends in draft mode — a draft older than 4 h re-pushes to Nata's phone instead.
3.2 Card anatomy (fenced example)
#3 of 14 · Meal feedback · Denys · dial: draft
────────────────────────────────────────────────
CONTEXT Dinner 19:42 — burger, fries, lager (est. 1,525 kcal,
confidence medium). Day: 2,775 / 2,200 kcal · protein
130 / 140 g (travel budget). Wiki Brain: travel week;
blunt-but-kind; always end with a plan.
DRAFT "That's the travel-day budget plus ~575 on top. It ends
here tonight: water or tea, 20-min walk before calls.
Tomorrow: eggs + yogurt at the buffet; your 45-min
zone-2 is already planned. Do that and the week still
lands near a 2,500 kcal deficit."
[ ✓ Approve ] [ ✏️ Edit ] [ ↻ Rewrite ] streak 12/20
────────────────────────────────────────────────
Approve sends as-is. Edit opens the text inline; the diff is stored (edit distance feeds meal.feedback_sent events and prompt tuning). Rewrite discards the draft, Nata types her own; the pair (context, her text) is logged as a gold example and triggers a prompt-review task. The streak counter is the graduation mechanic, next.
3.3 The autonomy dial
The dial is per message type, per client: Marta's meal feedback can be autonomous while Denys's is still audited. Three positions: draft → auto-send with audit → autonomous.
| Message type | Pilot start | → auto-send with audit | → autonomous | Ceiling |
|---|---|---|---|---|
| Meal feedback | draft | 20 consecutive unedited approvals (per client) | 30 audited sends, 0 retractions, ≥ 14 days in audit | autonomous (1-in-10 spot-check sampled into the queue) |
| Session adjustments — minor (±1 load step, volume cuts ≤ 20%) | draft | 20 consecutive unedited approvals | 40 audited sends, 0 retractions | autonomous |
| Session adjustments — structural (swaps, week changes) | draft | 20 consecutive unedited approvals | never | audit |
| Re-engagement (24 h / 48 h ladder steps) | draft | 10 consecutive unedited approvals (low volume accrues slowly) | never — the 72 h step is always Nata in person (02 §6.4) | audit |
| Form Check reviews | draft | not during pilot | never | draft |
| Safety (pain / injury / medical) | escalate to Nata | never | never | escalate — hard-coded, per locked decision 7 |
Mechanics — every transition is itself propose-confirm, applied to Nata:
flowchart LR D["draft: every message approved"] -->|"20 consecutive unedited approvals"| P1["promotion card appears in queue"] P1 -->|"Nata confirms"| A["auto-send with audit feed"] A -->|"criteria met again"| P2["second promotion card"] P2 -->|"Nata confirms"| F["autonomous + 1-in-10 sampling"] A -->|"1 retraction or 2 post-hoc edits in 7 d"| D F -->|"any retraction"| A
- Counters: Approve = +1; Edit = reset to 0; Rewrite = reset + prompt-review task. Counters live per
(client, message_type)and are visible on the card (streak 12/20). - Audit mode: messages send immediately and appear in a chronological audit feed; each has
[ Retract & take over ], which deletes nothing (the client saw it) but sends Nata's follow-up and demotes the type back to draft. safety.flaggedbypasses the dial entirely, in every mode, forever: programming pauses and Nata is pushed immediately.
Expected pilot trajectory (assumption, tracked against reality in the web analytics): meal feedback reaches audit for most clients by week 3–4, autonomous by week 8; everything else stays human-gated longer, exactly as 11-roadmap.md phases it.
4. Alerts and triggers
Alerts are rows in the alerts table, produced by a deterministic alert engine: a streaming check on safety keywords plus a nightly job after the metrics rollup. No LLM decides whether to alert.
| Alert | Exact trigger (SQL over events/metrics) | Severity | Latency | Delivery | Prewritten next step | Auto-resolves when |
|---|---|---|---|---|---|---|
| Missed 2 sessions | 2 consecutive scheduled sessions with no session.completed and no confirmed reschedule |
◐ yellow | nightly | Digest + roster flag | Re-engagement draft queued at the matching ladder step | next session.completed |
| Pain / injury keyword | pain.reported or safety.flagged event |
● red | immediate | Push to Nata's phone; programming already paused by the bot | None — Nata acts personally (usually Step in, §7) | manual only |
| Recovery < 33% × 3 days | recovery_score < 33 for 3 consecutive days in metrics |
● red | on 3rd day's rollup | Push + digest | Sick-mode offer draft (02 §6.3) | first day ≥ 50% |
| Protein < 70% target × 5 days | protein_g / protein_target < 0.7 on 5 consecutive logged days (nutrition.day_closed) |
◐ yellow | nightly | Digest | Food-program tweak draft (a proposed diff to the food program, 05 §8 — e.g. add a fixed protein anchor meal) | 2 consecutive days ≥ 85% |
| Weight trend diverging × 2 weeks | 14-day slope of weight_kg opposite in sign to goal direction (or flat when a deficit is programmed), confirmed by 2 consecutive weekly weight.confirmed |
◐ yellow | weekly (after weigh-in) | Digest | Calibration proposal per 05 §ground-truth ("−150 kcal or +1 zone-2") | slope re-aligns for 1 week |
| Silent 3 days | no inbound user event for 72 h | ● red | at 72 h | Push — this is the 72 h ladder step | None auto-drafted: Console shows the Wiki Brain summary; Nata writes or voices it herself | any inbound user event |
| Wearable disconnected 4 days | connection active but no provider webhook for 96 h (04) | ◐ yellow | nightly | Digest | Reconnect nudge with OAuth deep link — plumbing, not coaching, so it may auto-send even in draft phase | next webhook received |
Red alerts push through Telegram immediately (they are exempt from nothing — but note clients' quiet hours never apply to Nata's own notifications, only to client-facing messages, per 02 §5). Yellow alerts wait for the digest. Every alert card carries [ Ack ], [ Open chat ], and its prewritten next step where one exists.
5. Metric definitions (SQL, never LLM)
All roster and digest numbers derive from events and the metrics projection (03 §3). Formulas are versioned in the repo; if a formula changes, historical charts are recomputed from events — that is the point of event sourcing.
| Metric | Formula from events | Window | Displayed |
|---|---|---|---|
| Adherence 7d | 100 × count(session.completed) / count(scheduled sessions); a session moved via propose-confirm counts as scheduled on its new day, not missed |
trailing 7 days | 86% |
| Meal-log coverage | logged meals / expected meals; expected from Nata's food program (05 §8: Marta 3/day = 21/wk, Denys 4/day = 28/wk); meal.scored events — explicit ✓ and implicit confirmations both count (05 §3) |
trailing 7 days | 87% |
| Nutrition score | mean of per-day scores over days with ≥ 1 log; day score = 0.4·P + 0.4·E + 0.2·C where P = min(protein_g/target, 1)×100; E = 100 inside ±10% of kcal target, linear to 0 at ±35% (symmetric on purpose — chronic under-eating scores down too, which matters for Marta); C = min(meals_logged/meals_expected, 1)×100. Unlogged days are excluded here and punished via coverage instead — no double penalty |
trailing 7 days | 68 / 80 (score vs per-client target) |
| Volume load WoW | Σ(weight_kg × reps) from set.logged, strength sessions only, current ISO week vs prior; flag > +15% (ramping fast) or < −20% (unless the program says deload week) |
ISO week | +6% |
| Recovery trend | OLS slope of recovery_score per day; ↑ if slope ≥ +1.5/day, ↓ if ≤ −1.5, → otherwise; – if < 4 data points |
trailing 7 days | ↓ |
| Last contact | max(ts) over events with source = 'user' |
— | 2 h |
Two exemplary queries (the rest follow the same shape):
-- adherence_7d
with sched as (
select count(*) as n from session_schedule
where user_id = $1 and planned_date between current_date - 6 and current_date
),
done as (
select count(distinct payload->>'session_instance_id') as n from events
where user_id = $1 and event_type = 'session.completed'
and ts >= current_date - 6
)
select round(100.0 * done.n / nullif(sched.n, 0)) as adherence_7d
from sched, done;
-- recovery trend arrow
select case
when count(*) < 4 then '–'
when regr_slope(value, extract(epoch from day) / 86400.0) >= 1.5 then '↑'
when regr_slope(value, extract(epoch from day) / 86400.0) <= -1.5 then '↓'
else '→' end as arrow
from metrics
where user_id = $1 and kind = 'recovery_score' and day >= current_date - 6;
The LLM reads these numbers (they are injected into draft prompts as context) but never produces them; any number in a draft that doesn't match SQL is a lint failure in review.
6. Nata's day
6.1 The 20-minute morning (07:40–08:00)
journey title Nata's morning routine, Mon 10 Aug section Digest Read daily digest message: 5: Nata section Queue Clear 14 approval cards: 4: Nata section Alerts Ack 2 red + 3 yellow: 3: Nata section Deep-dive Drill into Client 07 knee flag: 4: Nata
| Time | Step | What actually happens |
|---|---|---|
| 07:40 | Digest (2 min) | Bot message, no app open needed: roster deltas, queue size, open alerts, spend. Deep links jump straight into the Mini App |
| 07:42 | Queue (8 min) | 14 cards × ~27 s median; overnight meal drafts and today's session adjustments. Two edits, one rewrite |
| 07:50 | Alerts (3 min) | Ack yellows into their prewritten next steps; reds get real attention |
| 07:53 | One deep-dive (5 min) | Exactly one client, chosen by top attention score — today Client 07's knee: reads the takeover transcript, adjusts the program to upper-body-only for the week |
| 07:58 | One voice note (2 min) | One client per day, rotating or triggered (a PR, a rough week). Her real voice is the moat, per locked decision 5 |
During the day the queue trickles (meal drafts at meal times, ~20 s each from the lock screen — this is what keeps the coach comment under 60 s in 02 §2.3). Separately, once a week she batch-records voice notes for all 10 clients from the Console digest in ~15 minutes (02 §4).
6.2 The Monday digest (fenced example)
Daily digests are the short form (roster deltas + queue + alerts). Monday's adds the week numbers, program-week rollovers, and the autonomy report:
NataCoach · Coach digest — Monday 10 Aug 2026, 07:30
ROSTER (10) — attention first
1. ● Client 07 — pain flag: right knee, Sun 21:14. Programming
paused, takeover transcript attached. → queue #1
2. ◐ Denys — travel week (Lisbon, 4 nights). Adherence 50% (2/4),
protein 5-day avg 68% of target. Food tweak drafted.
3. ◐ Client 04 — recovery < 33% three days; sick-mode offer drafted.
4. ○ Marta — best week yet: adherence 100%, nutrition 84/80,
volume +6% WoW, goblet squat PR 12 kg × 8. Voice note?
5–10. ○ calm.
WEEK NUMBERS (vs prior week)
sessions 31/36 completed (86%, ▲3 pts) · meals logged 224 (87%, ▼2)
avg nutrition score 76 · weigh-ins 9/10 (Denys travel-skipped)
approval latency median 22 s · you edited 9% of drafts (▼3)
AUTONOMY
Meal feedback → audit proposed for Marta (streak 20/20) [ Confirm ]
4 clients already auto-send with audit · 0 retractions this week
SPEND $9.80/day avg = $0.98/user (budget $2.00) ✓
[ Open queue (14) ] [ Open alerts (2●/3◐) ] [ Open roster ]
Program-week rollovers (who enters a deload, whose mesocycle ends) appear here too, feeding her program-builder work for the week.
7. Chat takeover
Nata can open any client's full chat history from the Console, read silently, and step in at any moment. This is not surveillance-by-surprise: onboarding states the model plainly — "You're coached by Nata through this bot. AI drafts, Nata supervises everything, and sometimes it's literally her typing." Clients know the deal from message one; there is no deception and no impersonation ambiguity — when Nata is live she says so.
stateDiagram-v2 [*] --> BotActive BotActive --> CoachLive: Nata taps Step in CoachLive --> BotActive: Nata taps Hand back BotActive --> SafetyPaused: safety.flagged event SafetyPaused --> CoachLive: Nata steps in CoachLive --> CoachLive: Nata messages as herself
While CoachLive:
- The bot pauses its persona: no proactive messages, no drafts, no auto-sends for that chat. Deterministic utilities keep working (a meal photo still gets its macro card) but coach-voice output holds.
- Nata's messages go out through @NataCoachBot but are always prefixed "Nata here —" or sent as her voice notes. Inbound and outbound are logged as events like everything else.
takeover.started/takeover.endedevents bracket the exchange. On handback, the nightly distillation ingests her messages as ground truth for the Wiki Brain (Karpathy rule: the human's edits outrank the model, 08) — so stepping in doesn't just fix the moment, it teaches the system.
Sample, continuing Client 07's Sunday-night pain flag:
21:14 Client 07: right knee felt sharp on the last lunge set,
stopped there
21:14 NataCoachBot: Stopping was exactly right. I've flagged this
to Nata and I'm pausing knee loading until she's seen it.
Nothing for you to do tonight — ice if it's puffy.
⟵ [pain.reported event · red alert · push to Nata]
21:18 NataCoachBot: Nata here — I read the whole session. Sharp on
the way down or the way up? Any swelling right now?
21:20 Client 07: on the way down, deep part. no swelling
21:23 NataCoachBot: Nata here — ok, that pattern is usually load
management, not damage, but we treat it seriously: no lunges
or squats this week, upper body Wed as planned. If it's not
clean by Friday, physio — I'll send you my person. Deal?
21:24 Client 07: deal, thanks for jumping in 🙏
⟵ [Nata taps Hand back · programming stays paused ·
takeover.ended → distilled to wiki/constraints-injuries.md]
The bot resumes on handback but keeps the pause Nata's decision created — the state machine and the program are separate: handing back the chat never silently un-pauses training.
8. Related docs
- 02-user-experience.md — the client-facing side of approvals, the two-stage meal reply, the re-engagement ladder
- 03-system-architecture.md —
events,metrics,approvals,alertsschemas this doc computes from - 05-food-analysis.md / 06-training-experience.md / 07-video-feedback.md — the pipelines that feed the queue
- 08-llm-wiki-brain.md — the Wiki Brain summaries shown in drill-downs; Nata's edits as ground truth
- 10-personas.md — Marta and Denys in full
- 11-roadmap.md — Console v0 → v2 build order and dial-movement expectations
- Mockups: mockups/coach-console.html · mockups/coach-console.png