ToCoach
Personal ProjectReplaces $1,500–4,000/month of human sales coaching with on-demand voice role-plays against AI buyer personas — 24/7, zero marginal cost, with an accountability engine that locks features if you skip practice.
The problem it solves
Sales skill only improves one way: realistic practice with expert feedback. But human role-play coaching costs $75–200 an hour, has to be scheduled days out, and the written debrief lands 30+ minutes after the moment that mattered. The solo founders who most need reps get the least access to them.
And there's the part nobody solves: accountability. When practice is uncomfortable, people avoid it — they browse the dashboard, reread lessons, and quietly stop doing sessions. No coaching product notices. This one does, and it responds.
What I built
A local-first desktop app where you talk — out loud — to an AI prospect that pushes back like a real buyer: it interrupts, drops competitor names, and escalates when your answers are weak. Speech recognition and synthesis run on-device; the only external call is the LLM, and even that routes through a subscription CLI so marginal cost is zero. After every session, a fleet of LLM pipelines runs the entire coaching back-office — scoring, debriefs, curriculum, buyer research — while a coach brain maintains a deep model of the student and personalizes everything that follows.
capture
(on-device)
(CLI gateway)
(on-device)
pattern learning
next session
Key features
- Role-play that feels adversarial — 16 scenarios × 12 personas × 3 difficulty tiers (500+ configurations) with adaptive pressure tactics that escalate on weak answers and ease on strong ones.
- Feedback in seconds, not next week — rubric-scored drills, generated exams (0–100), and 4-dimension debriefs with line-by-line coach rewrites, delivered the moment a session ends.
- A coach that remembers you — a ~44-field deep student profile (confidence baseline, fear patterns, price confidence, objection habits, core internal blocks) updated after every session and injected into every future prompt.
- Accountability with teeth — five escalating response levels driven by real presence data, per-feature privilege locks, and behavioral pattern detection that catches avoidance before it becomes quitting.
- Self-improving curriculum — every session generates vocabulary and communication insights; every 5th session authors a new scenario variant targeting current weaknesses; every 14th session the coach audits its own strategy.
System internals
| Component | What it does |
|---|---|
| process split | Electron main process orchestrates 7 backend modules (accountability, training gates, life-ops, onboarding brain, mission calendar, coach persona, telemetry); the renderer is a Vite + React 18 app with 40+ screens. 103 ipcMain.handle endpoints exposed through a single contextBridge API of ~107 methods — the renderer never touches Node directly. |
| LLM gateway | Zero direct SDK calls anywhere. Every one of the 31 pipelines flows through one callClaude() helper that spawns the Claude CLI and pipes the prompt via stdin — chosen specifically so Windows shell operators in user text can never become injection. |
| voice — STT | Two independent paths: in-renderer Whisper tiny.en (ONNX, q8) fed by raw PCM captured at 4096-sample blocks and linearly resampled to 16 kHz; plus a main-process whisper.cpp path running base.en for higher accuracy. Guards for sub-0.3s clips and muted graphs; the recognizer returns errors as values, never throws. |
| voice — TTS | Kokoro-82M (ONNX, q8) decoding through Web Audio with a duration+5s safety timer, falling back to the OS Web Speech API when the model isn't ready — the prospect always speaks. |
| data layer | 34 SQLite tables (better-sqlite3, local-first): sessions, messages, daily metrics, vocabulary, coach intelligence/research/insights, presence, privileges, rewards, milestones, training gates, exam attempts, mission calendar, and a whitelisted-column deep student profile. |
| accountability | Absence-driven levels: 1 day = warning · 2–3 = lockdown with mandatory session · 4–6 = level drop, re-earned over 3 consecutive days · 7+ = recommit protocol. Feature privileges lock on their own schedules (quick meetings and mail mode after 2 days, stranger mode after 3, advanced difficulty after 4) and restore via streaks. |
| behavior detection | Per-screen dwell tracking catches "The Browser" (2 days of ≥10 min reading with zero sessions → info-avoidance lock) and "The Quitter" (3+ abandoned sessions in a week → the next 5 sessions require 8 full exchanges). |
| packaging | electron-builder → NSIS (Windows), DMG (macOS), AppImage (Linux); voice models auto-download on first run and ship as extra resources thereafter. |
Tech stack
Impact
Reliability & safety engineering
- The never-brick rule: no blocking UI ever waits on a model call. Every LLM call runs in an isolated child process with a 90-second hard timeout, and heavy post-session generation is deferred off the interaction path entirely.
- 5-stage JSON recovery: structured outputs survive messy model responses — strip code fences → direct parse → greedy outermost-braces match → all candidate blocks largest-first → fail loudly with a truncated preview. Imperfect output degrades to a retry, never a broken screen.
- Deterministic fallbacks with authority: every accountability overlay, punishment, and reward renders from deterministic templates — the discipline system keeps working when the AI is down, which is exactly when a user is most likely to test it.
- Injection-safe by construction: prompts travel over stdin (never argv or a shell), the deep-profile writer accepts only whitelisted columns, and the renderer is sandboxed behind the contextBridge with no direct Node access.
- Privacy as architecture: transcripts, scores, and the student profile live in a local SQLite file on the user's machine. Voice never leaves the device; the only network egress is the LLM prompt itself.
Decisions worth talking about
Why on-device STT and TTS
Whisper and Kokoro run fully local. Audio latency drops to near-zero, privacy is absolute — your fumbled pitches never leave the machine — and the product works on a plane. Two STT paths exist deliberately: a fast in-renderer tiny.en for live turns, and a main-process base.en when accuracy matters more than milliseconds.
Prompts over stdin, not argv
Every LLM call spawns the CLI and writes the prompt to stdin. The boring reason: on Windows, argv strings pass through cmd.exe parsing, and a user typing shell metacharacters mid-role-play would become a command injection. The gateway design closed that class of bug permanently — and gave every pipeline identical timeout, auth-failure, and recovery behavior for free.
The accountability engine is deterministic on purpose
Punishment levels, privilege locks, and behavior-pattern rules are plain code with exact thresholds — AI only garnishes the messaging. A discipline system that silently stops enforcing when an API is down teaches users that consequences are negotiable. This one physically can't.
Single-threaded WASM was the right trade
Multithreaded ONNX inference wants SharedArrayBuffer, which wants cross-origin-isolation headers — a day of debugging taught me that lesson. The renderer path pins inference to one thread: slightly slower transcription, zero deployment fragility across three operating systems.