product

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/sayInUserVoice foundation, 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):

  1. Spec-first. Write the spine before code (this doc).
  2. 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).
  3. 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/me returns feedback (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 app already 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)

VoiceWhose wordsWhose voice you hearEmotional frame
Your own voicethe coach's approved encouragement (or the wearer's own dream nudge)the wearer's cloneyour best self, reminding you
Your coach's voicethe coach's approved encouragementthe coach's cloneyour coach, in your ear
Your teammate's voicea squad/cohort teammate's short encouragementthe 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)

NeedAlready shipsSource
In-ear playback on glassesbutton/voice → fetch nudge → session.audio.playAudio(mp3)apps/glasses/src/session.ts
Speak text in a user's own voicesayInUserVoice (cache-first, consent-v2, quota)jq-voice.ts
The nudge endpointGET /api/jq/voice/nudge?occasion=routes/jq-voice.ts
Per-client coach encouragementcoach-brief draft → approve → delivercoach-brief.ts
Delivered coach feedback (client-readable)GET /api/coaching/mefeedback[]routes/coaching.ts
Cohort + teammatescoaching cohorts (a team) + cohort leaderboardcoaching-cohorts.ts
Consent-scoped voicelovio_user_voices + consent_version + JQ Bridge graphlovio-voice.ts

What we build (small, per phase):

  1. 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.)
  2. Teammate recorded-clip encouragement — a cohort teammate records a short clip (explicit opt-in per clip), routed to a teammate's glasses. (Phase 1.)
  3. The coach-voice consent scope — a new "my clone may address my cohort clients" grant + client opt-in-to-hear, gating a sayInCoachVoice path. (Phase 2 — counsel-gated.)
  4. Program packaging for John — a cohort-level toggle bundle + the non-exclusive terms. (Phase 3.)

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_version scope (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.

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, momentum from 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 delivered coaching_seat_decisions row (mirrors jq_dream_nudges).
  • coach-brief.ts getLatestDeliveredEncouragement(clientUserId) — the client's latest delivered (approved, un-reversed) coach feedback + cohort name + program clone; null when not a coaching client.
  • jq-voice.ts getCoachingNudge(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 → same jq_voice_clips audio (no re-spend). Normalizer extracted to dependency-free jq-voice-text.ts with a golden test.
  • routes/jq-voice.ts GET /nudge — prefers the coaching nudge, falls back to getDreamNudge, stamps source: 'coach' | 'dream' + feature_key jq.coach_nudge. Zero glasses-app change (unchanged { text, dream_id, audio_url, ... } contract).
  • tsc --noEmit clean; jq-voice-text golden 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.sqlcohort_encouragements (from_user_id, cohort_team_id, audio_url, storage_path, is_active, ...), RLS-closed.
  • cohort-encouragements.tsrecordEncouragement (transcodes the recorded clip to MP3 at record time via toMp3/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). NoCohortError when the caller isn't in a cohort.
  • routes/coaching.tsPOST /api/coaching/encouragement (multer audio, 10MB), GET /encouragement/next (204 when none), GET /encouragement/mine, DELETE /encouragement/:id.
  • CohortEncouragement.tsx on /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 tsc clean; frontend lint clean (frontend typecheck fails only on a pre-existing workout-window test file).
  • Deploy gate: apply migration 478 before the backend deploys (routes SELECT cohort_encouragements).
  • Phase 1b ✅ BUILT 2026-07-12: GET /api/jq/voice/nudge now sometimes (TEAMMATE_NUDGE_RATE, 0.35) folds in a cohort teammate's recorded clip — source: 'teammate', the pre-recorded audio_url returned 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 whatever audio_url comes back; dream_id/dream_title are null, source/from_display_name are additive). Coach/dream nudges stay dominant.
    • WebM landmine — RESOLVED 2026-07-12 (server-side transcode). Teammate clips come off the browser MediaRecorder as WebM/Opus (or MP4/AAC on Safari), and whether Mentra's session.audio.playAudio decodes those is unverified — the glasses path is only known-good for HTTPS MP3. Rather than gamble, recordEncouragement now transcodes every non-MP3 clip to mono 128k MP3 once at record time (toMp3, bundled @ffmpeg-installer binary; temp-file in/out, not pipe:0). The stored audio_url is 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 a cohort-encouragements / transcode.mp3 service 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.

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

LayerReuse (shipped)Build
In-ear playbackglasses app loop (playAudio)— (Phase 0 needs zero glasses change)
Own voicesayInUserVoice (cache/consent/quota)
Encouragement textcoach-brief draft→approve→deliver; GET /api/coaching/me feedback[]coaching-aware nudge composer (cached)
Nudge endpointGET /api/jq/voice/nudgecoaching branch / coach-nudge route
Teammate voicecohort/team membership + JQ Bridge consentper-clip opt-in record + deliver (Phase 1)
Coach voiceclone infra + approve stepnew consent scope + sayInCoachVoice (Phase 2, counsel-gated)
Program packagingcoaching partner dashboard + rev-sharecohort voice-toggle bundle + terms (Phase 3)

8. See also

DREAMPRO GLASSES VOICE PROGRAM — Docs | HiveJournal