# MentorCore Decision Log

Decisions made during initial planning (2026-06-11), with rationale. Revisit a decision only with new evidence; record the change here.

## Product

| Decision | Choice | Why / alternatives rejected |
|---|---|---|
| MVP order | **General persona chat first**, racing second | Proves the name→sources→persona pipeline (the core asset) before sim integration; racing adds telemetry + push-to-talk on top of a working voice loop. Alternative (racing-first) front-loads integration work against an unproven pipeline. |
| Voice policy | **Tiered: cloned local, stylized public** | Keeps the personal use case (cloned coach/crew-chief voices) intact while removing right-of-publicity exposure from any public launch. "Cloned everywhere" deferred until a rights workflow exists; "stylized only" rejected as it guts the personal experience. |
| First persona | **Marcus Aurelius** | Long-dead, public-domain corpus (Meditations via Gutenberg), no rights issues, distinctive measurable style, and matches the dog-walk scenario. |
| Name | **MentorCore** | Matches the repo dir; EchoSage/PersonaLink/VeritasVoice/Proximal retired. |

## Technical

| Decision | Choice | Why / alternatives rejected |
|---|---|---|
| LLM strategy | **Hybrid: Claude API default, local optional** behind a provider Protocol | Persona fidelity (especially the judge) needs a strong model; the abstraction keeps the door open for MLX/Ollama on the M4 Mac Studio for fully private mode. API-only rejected (privacy ceiling); local-only rejected (fidelity + setup cost). |
| Model tiers | Sonnet 4.6 runtime / Opus 4.8 judge / Haiku 4.5 + Batches for bulk extraction | Runtime needs sub-second TTFT for the voice budget; the judge needs maximum quality and runs rarely; per-document style extraction is high-volume and tolerant, so cheapest model + 50% batch discount. IDs live in `shared/config.py` only. |
| Stack | **Python/FastAPI backend + Next.js PWA** | Python owns the STT/TTS/RAG ecosystem; Next.js matches the monorepo's launchd + Tailscale serving patterns and gives a phone-installable PWA. All-TypeScript rejected (weak local-audio ecosystem); all-Python rejected (poor mobile voice UX). |
| Vector store | **LanceDB** (fallback `sqlite-vec`) | Embedded and file-based — fits the "persona package is a directory" contract with no server process. Chroma/pgvector rejected as they add a service dependency. |
| Embeddings | `sentence-transformers` bge-small, local | Free, offline, sufficient for single-corpus retrieval; swapping later costs only a re-index. |
| STT | `faster-whisper` (distil-small.en default) | CTranslate2 performance on Apple Silicon; size configurable per latency budget. |
| TTS | **Kokoro** (stylized/generated) + **F5-TTS** (cloning) | Kokoro: Apache 2.0, real-time on Apple Silicon, 54 voicepacks — but cannot clone by design. F5-TTS: best open zero-shot cloning (5–15s reference audio) — **but CC-BY-NC 4.0, non-commercial**, hence hard-restricted to the `cloned-personal` tier in code. If commercial cloning is ever needed: licensed API (e.g. ElevenLabs) behind the same TTS interface. |
| Persona Package | Versioned directory; compiler writes (atomic build-then-symlink), runtime reads | The decoupling lets compiler and runtime phases proceed independently and gives rollback for free. Single most load-bearing invariant in the repo. |
| Eval holdout | ~10% of passages held out at compile, never indexed | The fidelity score is meaningless if the model can retrieve the ground truth it's judged against. Disjointness is a hard test, not a convention. |
| Voice latency trick | Sentence-streaming TTS (synthesize per sentence as the LLM streams) | Perceived latency = time to first audio, not full response; this is what makes <2s achievable. |
| Prompt caching | Stable persona prefix (identity + guards + exemplars) with a cache breakpoint; retrieval after it | Cuts per-turn cost and TTFT; a test asserts the prefix is byte-stable. |
| Persona capture tier (2026-07-17) | **`captures/<slug>/` — frontier-model-authored distillations as a checked-in compiler input**, merged by a `capture_import` stage; runtime still reads only the package | Distillation quality is capped by the extraction model; a frontier model authoring philosophy/style/exemplars directly is the highest-leverage artifact and must survive recompiles (hence git, not `personas/`). Alternatives rejected: hand-editing `personas/*/style/` (breaks the compiler-writes contract and dies on rebuild); a separate runtime path for captures (two contracts to maintain, bypasses the eval gate). Spec: `PERSONA-CAPTURE.md`. |
| Quote provenance tiers (2026-07-17) | `verified` (URL recorded at authoring) vs `model-memory` (quarantined from verbatim use) | Frontier-model quote recall is *almost* right — exactly the misattribution failure mode the zero-tolerance gate exists for. Trusting authoring-model memory rejected; verifying every quote against fetched sources at import time rejected as it re-does judgment mechanically and late. |

## Process

- The build plan file is `docs/BUILDPLAN.md` (no hyphen — keep references consistent).
- BUILDPLAN checkboxes are the live progress ledger; tick them as work completes.
- Tests run offline by default (FakeProvider + recorded fixtures); real-API tests live behind the `live` pytest marker.
- The original racing-focused PRD draft was overwritten during planning and was never committed; its content is preserved as PRD Appendix A. Commit docs before iterating on them.
