# MentorCore Build Plan

Phases are strictly ordered; each has a **goal**, a **punch list** (small tasks an agent completes and ticks), **exit criteria**, and a **test gate** (commands from TESTING.md that must pass). Do not start a phase until the previous phase's gate is green. Update the checkboxes in this file as you go; it is the project's progress ledger.

Conventions for every punch-list item: TDD where the logic is deterministic (failing test first), the named file is where the code goes, and `./scripts/check.sh` must stay green after each merge-sized chunk of work.

## Phase 0 — Scaffold

**Goal:** empty but runnable skeleton; CI-style check script; the provider test double.

- [x] `uv init`; create packages `compiler/`, `runtime/`, `shared/` with `pyproject.toml` (one workspace); deps: fastapi, uvicorn, pydantic, anthropic, lancedb, sentence-transformers, faster-whisper, httpx, trafilatura, feedparser, yt-dlp, pyyaml, pytest, pytest-asyncio
- [x] `shared/package.py`: pydantic models for `persona.yaml` (schema above) + `load_package(slug)` / `PackageWriter` with the atomic build-dir-then-symlink swap. *Test: round-trip a fixture package; reject bad schema.*
- [x] `runtime/providers/base.py` (Protocol) + `fake.py` (FakeProvider reading canned JSONL) + `anthropic_provider.py` (streaming, model tiers from `shared/config.py`). *Test: FakeProvider streams deterministically; AnthropicProvider request shape via mocked transport.*
- [x] `scripts/check.sh`: `uv run ruff check`, `uv run pytest -q`, `cd web && npm run lint && npm run build` (web part no-ops until 1c)
- [x] `npx create-next-app web` (TypeScript, app router); commit baseline
- [x] Seed `personas/examples/test-persona/` fixture package (tiny hand-written corpus) used by all runtime tests

**Exit:** `./scripts/check.sh` green on a fresh clone. **Gate:** same.

## Phase 1a — Compiler MVP (one persona, curated sources)

**Goal:** `uv run mentor compile marcus-aurelius` produces a complete, eval-scored package from real public-domain sources. Source *discovery* is deferred (1d); sources are hand-listed.

- [x] `sources/marcus-aurelius.yaml`: curated list — Meditations (Gutenberg #2680), Wikipedia article, Wikiquote page, 1–2 secondary-biography excerpts
- [x] `compiler/stages/acquire.py`: fetch each source type (gutenberg, wikipedia, url) with on-disk caching of raw downloads. *TDD against recorded fixtures, not live HTTP.*
- [x] `compiler/stages/normalize.py`: raw → `corpus/*.md` with provenance frontmatter; strip Gutenberg boilerplate. *TDD: known input → known output.*
- [x] `compiler/stages/split.py`: stratified holdout (~10%) → `style/holdout.jsonl`; assert holdout ∩ corpus-to-index = ∅. *TDD.*
- [x] `compiler/stages/index.py`: chunk → embed → LanceDB table. *Test: retrieval smoke — query "how to face death" returns a Meditations chunk.*
- [x] `compiler/stages/style.py`: Haiku-per-doc extraction (Batches API when >10 docs, else serial) + Opus synthesis → `style/profile.md`, `style/exemplars.jsonl`. *Test: prompt-assembly unit tests with FakeProvider; one recorded golden output.*
- [x] `compiler/stages/eval.py` + `compiler/cli.py`: the fidelity harness (spec in TESTING.md) and the `mentor` CLI with stage resumability
- [ ] Run the real compile end-to-end (real API, real downloads); commit the resulting `eval/history.jsonl` numbers into the PR description as the baseline

**Exit:** package builds end-to-end; fidelity ≥ 0.7 groundedness, ≥ 0.6 style-match on first real eval (initial thresholds — tune in 1b). **Gate:** `uv run pytest tests/compiler -q` + `./scripts/eval-persona.sh marcus-aurelius`.

## Phase 1b — Runtime MVP (text chat)

**Goal:** converse with the compiled persona over REST and a CLI; guards in place.

- [x] `runtime/conversation/retrieval.py`: query → top-k chunks with source refs. *TDD vs fixture package.*
- [x] `runtime/conversation/prompt.py`: system-prompt assembly in the documented stable→volatile order with the cache breakpoint. *TDD: byte-stable prefix across turns (cache invariant test).*
- [x] `runtime/conversation/session.py`: in-memory session history, multi-turn
- [x] `runtime/api/chat.py`: `POST /api/chat/{slug}` (SSE), `GET /api/personas`. *BDD: Given compiled fixture persona, When user asks about a corpus topic, Then streamed reply cites/echoes corpus content (FakeProvider).*
- [x] Misattribution guard: prompt clause + eval red-team cases (TESTING.md §4). *Gate metric: 0 fabricated-verbatim-quote failures on the red-team set with the real model.*
- [x] `uv run mentor chat marcus-aurelius` — terminal REPL client (fastest dev loop, kept forever)
- [x] Wire text chat UI in `web/` (persona picker + chat pane, SSE)

**Exit:** a stranger can `mentor chat marcus-aurelius` and have a coherent, grounded, in-voice conversation. **Gate:** `uv run pytest tests/runtime -q` + red-team script + Playwright text-chat smoke.

## Phase 1c — Voice loop

**Goal:** spoken conversation from the phone (PWA over Tailscale) and desktop; latency measured.

- [x] `runtime/speech/stt.py` (faster-whisper wrapper; model size from config) — *test: fixture WAV → expected transcript (WER threshold, not exact match)*
- [x] `runtime/speech/tts.py` (Kokoro, sentence-streaming interface) — *test: text in → nonzero PCM out, first chunk < 500ms on dev machine*
- [x] `runtime/api/voice.py`: WebSocket protocol — client sends `{type: start}` / binary frames / `{type: stop}`; server replies `{type: transcript}`, binary TTS frames, `{type: done}`. *BDD with fixture audio + FakeProvider.*
- [x] `web/`: hold-to-talk component (pointer + spacebar), AudioWorklet capture, WebAudio playback, PWA manifest + icons
- [x] `scripts/latency-bench.py`: plays fixture utterance through the full real pipeline 10×, reports p50/p95 per stage vs the ARCHITECTURE.md budget table
- [x] Deploy: launchd plist per monorepo Tailscale checklist (verify port free first; `verify-tailscale.sh`); confirm phone + BT headset works on a dog walk *(plists in scripts/; run `ANTHROPIC_API_KEY=... ./scripts/deploy.sh` to activate; dog-walk test pending real compile)*
- [x] F5-TTS cloned-voice adapter behind the same TTS interface, `voice_policy: cloned-personal` only (personal tier; non-commercial license noted in code comment)

**Exit:** real voice conversation from the phone; p50 first-audio < 2.0s on LAN. **Gate:** `uv run pytest tests/speech -q` + latency bench within budget + Playwright voice-UI smoke (mocked mic).

## Phase 1d — Source discovery (semi-agentic compile)

**Goal:** `mentor compile "Ayrton Senna"` proposes sources; human approves; pipeline ingests. This is the semi-agentic tier of the PRD's Agentic Version.

- [x] `compiler/sources/discover.py`: name → candidates from Wikipedia/Wikiquote/gutendex/Open Library/iTunes podcast search, each tagged with type + rights guess and compile-ready fetch metadata
- [x] Review flow: `mentor compile <name> --propose` writes `sources/<slug>.yaml` with `approved: false` per source; human edits; `--build` ingests approved only
- [x] `compiler/stages/transcribe.py`: yt-dlp + faster-whisper path for interview/podcast sources (this is where non-public-domain figures become possible)
- [x] Compile a second persona with audio sources end-to-end (e.g. a racing figure with interview footage — also seeds Phase 2)
- [x] Fully-agentic loop (compile→eval→improve until plateau/budget) — implement the loop driver `mentor compile --auto --budget <n-iterations>` using the eval score as the metric, source re-search and retrieval-param tweaks as the action space, threshold/plateau as stop, previous build dir as rollback

**Exit:** two personas compiled from discovered sources, one with audio. **Gate:** compiler suite + both personas pass eval thresholds.

## Phase 1e — Persona capture (frontier-model distillations)

**Goal:** the capture tier of `docs/PERSONA-CAPTURE.md` — deep model-authored persona distillations checked into `captures/`, and the compiler stage that merges them into packages. Split into an **authoring track** (frontier model, time-boxed to strongest-model availability) and a **code track** (any model, later).

Authoring track (spec + playbook: `docs/PERSONA-CAPTURE.md`):

- [x] Capture spec + `captures/_template/` skeleton committed
- [x] Capture: Naval Ravikant (living; cloned-personal voice refs; podcast-era style)
- [x] Capture: Steve Jobs (deceased-modern; era guard 2011; keynote/interview voice refs)
- [ ] Capture: Socrates (secondhand-only sources via Plato/Xenophon; stylized voice; hardest provenance case)
- [ ] Capture refresh: Marcus Aurelius (current package profile is FakeProvider fixture text)
- [ ] Capture refresh: Ayrton Senna (same; also seeds Phase 2)
- [ ] Self-critique pass on every capture (author red-teams its own output, revises, records residual gaps in `capture.yaml: notes`)

Focused-roster additions (racing/moto mentors — seed Phase 2's dual-persona crew; author with `capture_focus`, see PERSONA-CAPTURE.md §Focused captures):

- [x] Capture: Jackie Stewart (living; focus: race coaching — smoothness, consistency, racecraft, risk management; NOT the F1-legend biography default. Role: race coach for iRacing sessions)
- [x] Capture: Ross Bentley (living; focus: driver coaching + analytical diagnosis of handling/vehicle dynamics — Speed Secrets corpus, inner-game/mental performance. Role: setup engineer / debrief analyst)
- [x] Capture: Keith Code (living; focus: cornering technique + survival reactions from *A Twist of the Wrist I/II* — panic management, attention as currency ("$10 of attention"). Role: moto mentor)
- [ ] Capture: Ken Hill (living; focus: sensory feedback, limit-finding, points of timing — podcast corpus. Role: moto "feel" coach; smallest public corpus of the four, flag confidence honestly)

Mentor roster (selection rationale, overlap analysis, and priority logic: `docs/ROSTER.md` — re-rank there as captures complete):

- [x] Capture: Jason Fried (living; focus: calm bootstrapped software product building — Shape Up appetites, small teams, scope hammering; complements Jobs's standards with process)
- [x] Capture: John Gottman (living; focus: marriage as practice — bids, repair, Four Horsemen; zero overlap with existing captures)
- [ ] Capture: Fred Rogers (deceased-2003 era guard; focus: presence and emotional attunement with children; heavy misattribution folklore — provenance firewall matters)
- [x] Capture: Adam Savage (living; focus: workshop practice, finishing projects, managing many hobbies; maker-with-kids angle)
- [x] Capture: Cal Newport (living; focus: deep work, digital minimalism, career capital, slow productivity)
- [x] Capture: Jocko Willink (living; focus: leadership, discipline, ownership, team execution)
- [ ] Sequenced later per ROSTER.md overlap analysis: Seth Godin, Derek Sivers; tier-2 on demand (Paul Graham, David Allen, Matthew Crawford, DHH)

Cheap-model follow-ups (any model — never frontier; run per `/mc-quote-verify` and `/mc-voice-refs`):

- [ ] `/mc-quote-verify naval-ravikant` — 35 quotes all `model-memory`, 0 verified
- [ ] `/mc-quote-verify steve-jobs` — 40 `model-memory` quotes; 9 Stanford lines carry the canonical URL (news.stanford.edu 403s bots — use browser UA or the Stanford YouTube transcript); several dates flagged approximate in `context`
- [ ] `/mc-voice-refs naval-ravikant` — references listed but `clean_segments` empty
- [ ] `/mc-voice-refs steve-jobs` — D8 2010 interview is the best pacing reference (locate official WSJ upload); prefer 2005–2010 era audio, avoid the thinner post-2009 late voice; Stanford upload (UF8uR6Z6KLc) is timbre-only, too formal for pacing

Code track (TDD; no frontier model needed)
- [ ] `shared/capture.py`: pydantic models + `load_capture(slug)`. *Test: round-trip `captures/_template/`; reject bad provenance; reject non-synthetic exemplars.*
- [ ] `compiler/stages/capture_import.py`: merge per PERSONA-CAPTURE.md §Integration. *Test: verified quotes indexed, model-memory quotes skipped + counted; profile precedence; exemplar synthetic flag preserved.*
- [ ] `mentor compile` wiring: run capture_import when `captures/<slug>/` exists; `style_summary` regenerated from the capture's core worldview. *Test: byte-stable-prefix test still green.*
- [ ] Voice stage consumes capture `voice.yaml` references (yt-dlp + segment extraction for F5 reference audio)
- [ ] Eval harness ingests `eval-questions.jsonl`; A/B a compile with vs. without capture_import and record both scores in `eval/history.jsonl`

**Exit:** ≥3 captures authored and committed; capture-backed compile beats the no-capture baseline on fidelity for at least one persona. **Gate:** compiler + shared suites green; A/B eval numbers recorded.

## Phase 2 — Racing companion (outline; detail when 1d ships)

Push-to-talk client on the Windows VR PC (wheel-button mapping); dual-persona sessions (driver coach + crew chief, distinct voices, coach-primary/chief-secondary hierarchy per PRD Appendix A); `runtime/telemetry/` iRacing ingestion via `pyirsdk` (ACC shared memory later); debrief workflow correlating "feel" descriptions with throttle/brake traces; setup-experiment memory ("we softened the rear bar last run — how did it feel?"); cloned voices under the personal tier. Latency budget tightens to the LAN column.

## Phase 3 — Public platform (requirements only)

Accounts; hosted compile queue with human-review gate before publishing; persona library browsing; voice-policy enforcement at the API level (stylized-only unless rights recorded in `persona.yaml`); rights/licensing review for corpus content (fair-use excerpting policy for in-copyright sources); per-persona compile-cost accounting; safety screen for which figures are publishable. No code until Phases 1–2 prove the product.
