DreamPro Glasses Voice Program (John Rowley)
Codename / sellable brand: the DreamPro Glasses Voice Program — a non-exclusive offering John Rowley can extend to his DreamPro cohorts: program encouragement delivered in-ear through AR glasses, in your own voice, your coach's voice, and your teammate's voice. The motivational frame is "your team and your best self, pulling you toward the finish line."
Status: Plan / not yet built. This doc is the spine — the three voices, the per-voice consent model (the crux), the reuse map, phasing, and the honest gaps. It is the commercial application of the 'Integrate' primitive on top of three shipped layers: the
glasses app, the Living Voice clone/sayInUserVoicefoundation, and the DreamPro Coaching System (coaching seats, cohorts, the coach-brief generator, channel economics).Companion specs: Coaching System · Coach Seat · Channel economics · AR Glasses · Living Voice.
Decisions locked (2026-07-12, with the founder):
- Spec-first. Write the spine before code (this doc).
- Defer coach-clone-to-client. v1 leads with the wearer's own voice (+ optional teammate recorded clips, explicit opt-in per clip). A coach's cloned voice speaking to a client crosses a consent bright line and is gated behind a new consent scope + counsel review (§3, §5-Phase 2).
- Non-exclusive. John gets a program, not a lockup — the same primitive powers any coach/cohort; John's is the first packaged deployment.
0. The core insight (why this is low-risk)
We are not building a new voice stack, a new glasses app, or a new coaching engine. All three ship today. The program is a bridge between them:
- The glasses app (
apps/glasses) already plays an in-ear nudge from the wearer's active dream, spoken in the wearer's own cloned voice, on button-press or voice-command — cache-first, consent-gated, degrading gracefully when there's no clone (session.ts·hivejournal.ts). - Living Voice already gives us
sayInUserVoice(userId, text)— cache-first (never re-synthesizes a line), consent-v2-gated, TTS-quota-gated (jq-voice.ts). - The Coaching System already generates per-client encouragement: the
coach-brief cron drafts a personalized nudge per seat, a coach
approves/edits it, and delivery stamps it
(
coach-brief.ts). The client already reads it:GET /api/coaching/mereturnsfeedback(the last 5 delivered, coach-approved decisions) +momentum+cohort(routes/coaching.ts).
So the encouragement text already exists and is already coach-approved. The program's job is to route it into the ear, in the right voice, under the right consent. That's the whole build.
0.5 Positioning & privacy — lead with the ear, not the lens
These glasses are a coach in your ear, not a camera you wear. Every time we describe the product — to John, to a coach, to a cohort — the hero is the in-ear voice: your best self, your coach, and your teammate pulling you toward the finish line, hands-free, mid-day. The camera (used elsewhere for things like reading a paper form) is a bonus capability on hardware you're already wearing for the voice — never the reason to put the glasses on, and never the lead of the pitch. If a sentence about these glasses opens with the lens, rewrite it to open with the ear.
That ordering isn't just marketing — it's the privacy posture, by construction:
- The audio layer is private by design. Only the wearer hears the nudge, and nothing about the world is recorded to deliver it. In-ear encouragement is the least-invasive thing a wearable can do.
- No ambient, always-on, or passive recording. Ever. The nudge fires on
button-press or voice-command (as the shipped
glasses appalready does) — not on a hot mic listening to the room. - Any camera use is explicit-capture only — the wearer deliberately triggers a single scan of a document in front of them; the image is processed for its content and not retained beyond that. A lens on your face is exactly what people fear about smart glasses, so it stays opt-in, per-capture, and non-ambient by rule.
- Consent is the load-bearing wall (§3). The own-voice / teammate-clip model is private-by-default; the coach-cloned voice is deferred behind a new consent scope + counsel review precisely because presence in someone's ear is intimate.
Sell the intimacy of the ear and the discipline of the privacy model together — they're the same story.
1. The three voices (what John is actually selling)
| Voice | Whose words | Whose voice you hear | Emotional frame |
|---|---|---|---|
| Your own voice | the coach's approved encouragement (or the wearer's own dream nudge) | the wearer's clone | your best self, reminding you |
| Your coach's voice | the coach's approved encouragement | the coach's clone | your coach, in your ear |
| Your teammate's voice | a squad/cohort teammate's short encouragement | the teammate (recorded clip → later, their clone) | your team, pulling with you |
The pitch to a cohort member is one sentence: at the moment you'd normally quit, you hear your best self, your coach, and your team telling you to take the next step. The finish-line framing is John's; the delivery is ours.
One primitive, three holders. This is exactly the "Integrate" shape: a piece of encouragement text becomes aware of you (tied to your dream/seat) and is spoken back to you through the glasses. Which voice speaks it is a holder choice — the same swappable-holder pattern the Coach Seat already runs (DREAMPRO_COACHING_SEAT_SCHEMA.md).
2. Reuse map (what's shipped vs. what we build)
| Need | Already ships | Source |
|---|---|---|
| In-ear playback on glasses | button/voice → fetch nudge → session.audio.playAudio(mp3) | apps/glasses/src/session.ts |
| Speak text in a user's own voice | sayInUserVoice (cache-first, consent-v2, quota) | jq-voice.ts |
| The nudge endpoint | GET /api/jq/voice/nudge?occasion= | routes/jq-voice.ts |
| Per-client coach encouragement | coach-brief draft → approve → deliver | coach-brief.ts |
| Delivered coach feedback (client-readable) | GET /api/coaching/me → feedback[] | routes/coaching.ts |
| Cohort + teammates | coaching cohorts (a team) + cohort leaderboard | coaching-cohorts.ts |
| Consent-scoped voice | lovio_user_voices + consent_version + JQ Bridge graph | lovio-voice.ts |
What we build (small, per phase):
- Coaching-awareness in the nudge — when the wearer is a coaching client, fold their most-recent delivered coach feedback into the nudge line (still their own voice). No new consent. (Phase 0.)
- Teammate recorded-clip encouragement — a cohort teammate records a short clip (explicit opt-in per clip), routed to a teammate's glasses. (Phase 1.)
- The coach-voice consent scope — a new "my clone may address my cohort
clients" grant + client opt-in-to-hear, gating a
sayInCoachVoicepath. (Phase 2 — counsel-gated.) - Program packaging for John — a cohort-level toggle bundle + the non-exclusive terms. (Phase 3.)
3. The consent model (the load-bearing wall)
This program inherits the Living Voice bright line verbatim and adds one new scope. Read this before scoping any code.
Today's rule (shipped): sayInUserVoice will only speak in a clone whose
owner consented at v2+ — and it only ever speaks that owner's words back
to that owner. JQ speaking to you in your voice is the whole authorized
surface. Nothing today authorizes one person's clone to address a different
person.
Per-voice consent:
-
Own voice (Phase 0). No change. The wearer already consented (v2) for JQ to speak in their own voice; folding coach-approved text into that line is still their voice, their ears. The coach's words are already approved by the coach through the shipped review queue. Ships without a new bright line.
-
Teammate voice (Phase 1). A teammate records an actual clip (not a clone) with an explicit, per-clip "share this with my cohort teammate" opt-in. Recording your own encouragement and choosing to send it is ordinary user-generated content — no clone, no synthesis, no new scope. The clip is scoped to the cohort relationship (rides the existing team membership + the JQ Bridge consent graph for who may hear it).
-
Coach voice (Phase 2 — DEFERRED, counsel-gated). Speaking a coach's cloned voice to a client requires a new consent scope that does not exist today:
- Coach grant: "my voice clone may be used to speak my approved
encouragement to my cohort clients." A distinct
consent_versionscope (or a typed grant on the clone), separate from the v2 self-scope — a coach consenting to JQ-for-me must not silently authorize coach-voice-to-clients. - Client opt-in: the client chooses to hear the coach's voice (vs. their own). Default off.
- Words stay coach-authored + approved. The coach voice may only speak text that went through the coach-brief approve step — never an autonomous line. This keeps the wellness-lane guardrail and the "no synthetic words put in a coach's mouth" rule intact.
- Counsel review before any coach/client-facing surface ships, same as the Speech Mirror and Legacy Channel gates.
- Coach grant: "my voice clone may be used to speak my approved
encouragement to my cohort clients." A distinct
Cost + calm (inherited, non-negotiable): cache every rendered line
(sayInUserVoice already does), keep nudges short, opt-in-to-play, never
autoplay, never a nag. ElevenLabs spend is the platform's biggest AI cost —
the render-once cache is load-bearing here, not optional.
4. The buildable-now slice (Phase 0, own-voice coaching-aware nudge)
The honest first surface, shippable with no new consent decision:
Extend GET /api/jq/voice/nudge (or add GET /api/jq/voice/coach-nudge) so
that when the wearer is a coaching client with a delivered coach feedback line,
the nudge text is composed from that coach-approved encouragement (+ the
wearer's cohort/momentum for tone) instead of — or blended with — the generic
dream nudge. Then it's spoken through the existing sayInUserVoice path in
the wearer's own voice, and the existing glasses loop plays it in-ear.
Concretely:
- Read the client's context the same way the dashboard does: delivered
feedback[0],cohort,momentumfrom the coaching layer. - Compose one short second-person line (cheap model, cached per
seat+decision+occasion — mirror
composeNudgeText's cache discipline so a given approved feedback line is synthesized at most once). - Return the same
{ text, audio_url, cached, ... }contract the glasses client already consumes — zero glasses-app change for Phase 0.
This delivers "your best self, carrying your coach's words" on day one, on 2025 hardware, and proves the loop before we touch the coach-voice bright line.
5. Phasing
Phase 0 — Own-voice, coaching-aware nudge — ✅ BUILT 2026-07-12
The §4 slice. Coach-approved encouragement, spoken in the wearer's own voice, through the shipped glasses loop. No new consent.
- Migration
477_jq_coach_nudges.sql— stable-text cache keyed to the deliveredcoaching_seat_decisionsrow (mirrorsjq_dream_nudges). coach-brief.tsgetLatestDeliveredEncouragement(clientUserId)— the client's latest delivered (approved, un-reversed) coach feedback + cohort name + program clone; null when not a coaching client.jq-voice.tsgetCoachingNudge(userId, occasion)— composes one short own-voice line from that encouragement (claude-haiku-4-5), cached per(user, decision, occasion)→ same delivered decision → same words → samejq_voice_clipsaudio (no re-spend). Normalizer extracted to dependency-freejq-voice-text.tswith a golden test.routes/jq-voice.tsGET /nudge— prefers the coaching nudge, falls back togetDreamNudge, stampssource: 'coach' | 'dream'+feature_key jq.coach_nudge. Zero glasses-app change (unchanged{ text, dream_id, audio_url, ... }contract).tsc --noEmitclean;jq-voice-textgolden test green (6/6).- Deploy gate: apply migration 477 to prod before the backend deploys (the route SELECTs
jq_coach_nudges). Runtime verification still pending — needs a coaching client with a delivered decision + a v2-consented voice clone to drive the end-to-end nudge.
Phase 1 — Teammate recorded-clip encouragement — ✅ BUILT 2026-07-12 (web)
Cohort teammates record short opt-in clips; a teammate hears one (a "your team is with you" moment). Broadcast to the cohort (any teammate may hear it, never the recorder), web-first delivery. No clone, no synthesis — UGC scoped to the cohort relationship.
- Migration
478_cohort_encouragements.sql—cohort_encouragements (from_user_id, cohort_team_id, audio_url, storage_path, is_active, ...), RLS-closed. cohort-encouragements.ts—recordEncouragement(transcodes the recorded clip to MP3 at record time viatoMp3/ffmpeg so the glasses path can decode it — falls back to the original bytes + logs a service error if ffmpeg fails),getNextTeammateEncouragement(random draw over the recent active pool, excludes self; no per-listener seen-tracking in v1),listMyEncouragements,retractEncouragement(soft delete + storage cleanup).NoCohortErrorwhen the caller isn't in a cohort.routes/coaching.ts—POST /api/coaching/encouragement(multeraudio, 10MB),GET /encouragement/next(204 when none),GET /encouragement/mine,DELETE /encouragement/:id.CohortEncouragement.tsxon/dashboard/my-coaching(shown only when in a cohort) — MediaRecorder record→preview→share, a teammate clip player (opt-in-to-play<audio controls>, never autoplay), and retract-your-own. 30s cap.- Backend
tscclean; frontend lint clean (frontend typecheck fails only on a pre-existingworkout-windowtest file). - Deploy gate: apply migration 478 before the backend deploys (routes SELECT
cohort_encouragements). - Phase 1b ✅ BUILT 2026-07-12:
GET /api/jq/voice/nudgenow sometimes (TEAMMATE_NUDGE_RATE, 0.35) folds in a cohort teammate's recorded clip —source: 'teammate', the pre-recordedaudio_urlreturned as-is (no TTS, no consent gate, works even for a wearer with no voice clone); falls through to the coach → dream nudge otherwise. Zero glasses-app change (the app plays whateveraudio_urlcomes back;dream_id/dream_titleare null,source/from_display_nameare additive). Coach/dream nudges stay dominant.- WebM landmine — RESOLVED 2026-07-12 (server-side transcode). Teammate clips come off the browser
MediaRecorderas WebM/Opus (or MP4/AAC on Safari), and whether Mentra'ssession.audio.playAudiodecodes those is unverified — the glasses path is only known-good for HTTPS MP3. Rather than gamble,recordEncouragementnow transcodes every non-MP3 clip to mono 128k MP3 once at record time (toMp3, bundled@ffmpeg-installerbinary; temp-file in/out, notpipe:0). The storedaudio_urlis therefore an MP3 that serves both the in-ear glasses path and the 2D<audio>player. Recipe verified against the shipped binary (WebM/Opus → valid mono MP3). If ffmpeg ever fails, the original bytes are stored (web-playable, glasses-uncertain for that one clip) and acohort-encouragements / transcode.mp3service error is recorded so it's visible in/dashboard/admin/errors. Still worth an on-device confirm that MP3 plays cleanly in-ear once the Mentra Live arrives.
- WebM landmine — RESOLVED 2026-07-12 (server-side transcode). Teammate clips come off the browser
Phase 2 — Coach voice (DEFERRED, counsel-gated)
Design + review the new coach-voice consent scope (§3), then a
sayInCoachVoice-style path that speaks a coach's approved text in the coach's
consented clone to opted-in clients. Do not start until counsel signs off
on the scope.
Phase 3 — Program packaging for John
A cohort-level toggle bundle (which voices are on for this cohort) + the non-exclusive partner terms (which mirror the Coaching System rev-share model — John gets a program he can turn on for his cohorts, not an exclusive lockup of the primitive). Fold into the coaching partner dashboard.
6. Honest gaps & risks (so John isn't oversold)
- Coach voice is the emotional headline and the one we're deferring. v1 leads with own-voice + teammate clips; be clear with John that "your coach in your ear" is Phase 2, behind a consent scope + counsel review, not launch-day.
- Voice clones require setup + v2 consent. Own-voice only works for wearers
who've cloned their voice and consented at v2. Cohort onboarding must include
the voice-setup step, or the nudge degrades to the shipped "set up your JQ
voice first" fallback (
session.ts). - Hardware reach. Wave-1 audio glasses (Ray-Ban Meta class / Mentra Live) are the target; no on-glasses app layer for ~18 months — this rides the companion-phone + Bluetooth-audio path (AR_GLASSES_LIVING_VOICE.md). Most cohort members won't own glasses yet — the same nudge plays through the phone's audio-first player today, so the program isn't glasses-gated.
- Cost. Every distinct line is an ElevenLabs synthesis on first play. The render-once cache makes repeat plays free, but a cohort of N clients each with a fresh daily line is N syntheses/day — keep lines short + cached, and lean on the existing per-user TTS quota gate.
- Wellness-lane guardrail carries over. Coach-voiced (Phase 2) lines may only speak coach-approved text — no autonomous medical/injury/nutrition advice put in a coach's mouth (same posture as the Coaching System).
- Non-exclusive terms need the same simple partner-agreement discipline as the Coaching System (rev-share %, brand usage, what happens to cohort data if the partnership ends).
7. Reuse-vs-build summary
| Layer | Reuse (shipped) | Build |
|---|---|---|
| In-ear playback | glasses app loop (playAudio) | — (Phase 0 needs zero glasses change) |
| Own voice | sayInUserVoice (cache/consent/quota) | — |
| Encouragement text | coach-brief draft→approve→deliver; GET /api/coaching/me feedback[] | coaching-aware nudge composer (cached) |
| Nudge endpoint | GET /api/jq/voice/nudge | coaching branch / coach-nudge route |
| Teammate voice | cohort/team membership + JQ Bridge consent | per-clip opt-in record + deliver (Phase 1) |
| Coach voice | clone infra + approve step | new consent scope + sayInCoachVoice (Phase 2, counsel-gated) |
| Program packaging | coaching partner dashboard + rev-share | cohort voice-toggle bundle + terms (Phase 3) |
8. See also
- DREAMPRO_COACHING_SYSTEM.md — the coaching spine (seats, cohorts, coach-brief, economics) this program delivers through the glasses.
- AR_GLASSES_LIVING_VOICE.md — the hardware layer + the "Integrate" framing this is a commercial application of (integration #3, JQ ambient nudges).
- LIVING_VOICE_ROADMAP.md — the voice-clone / consent-v2 / read-only bright line this inherits.
- apps/glasses/README.md — the Mentra Live app the nudge plays through.