Mobile JQ Presence — the OS layer under the Living Voice
Status: strategy / not built. Authored 2026-07-08. This is the native-mobile companion to AR_GLASSES_LIVING_VOICE.md: where that doc is the glasses hardware layer under the Living Voice, this is the phone layer — how much of "JQ is with me, talks to me, and I can talk back without opening the app" iOS and Android actually permit. Like the AR doc, it is not a pull-forward from Workstream A (writing) or Odessa, and it inherits the Living Voice ethics/cost wall verbatim.
Where the mobile app is today: Expo 54 / RN 0.81 (apps/mobile), pre-release. expo-av (audio playback) is in; no push notifications, no on-device speech, no Siri/App-Intents wiring exist yet. So this is greenfield — a ceiling map, not a refactor.
Thesis in one line
You cannot build "an app that always listens and talks whenever it wants" — both OSes forbid it. What you can build is a set of OS-sanctioned hooks that compose into ambient presence, and the payload they all want — a short, cached, render-once voice clip — is exactly what the shipped Living Voice layer (sayInUserVoice, the nudge cadence engine, the audio-first player + MediaSession) already produces.
The two hard walls (both platforms)
- No always-on custom wake word. No background mic listening for "Hey JQ." iOS forbids it for third parties; Android technically allows it via a foreground service + persistent notification, but at heavy battery cost and real Play Store policy risk. Don't.
- No unprompted speech from a backgrounded app. JQ can't start talking on its own. Every proactive moment needs a sanctioned trigger: a notification, a scheduled local notification, a push, or a system-assistant invocation.
Everything below lives inside these walls.
A. JQ reaching / speaking to you (proactive)
| Hook | Can JQ "speak"? | Notes |
|---|---|---|
| Push notification (APNs/FCM) | iOS: yes, ~30s — a pre-rendered voice clip plays as the notification sound on the lock screen, app not running. Android: yes, more — a high-priority FCM push can start a foreground service and play full TTS/audio | The single most important channel. iOS caps the spoken part at a 30s custom sound (bundled or downloaded via a notification-service extension); Android is far more permissive |
| Local scheduled notification | Same as above, server-free | The existing occasion-aware cadence (morning intention / next-step / at-risk streak) schedules these locally — fires even offline |
| Live Activity / Dynamic Island (iOS) / ongoing notification (Android) | No voice, but present | A persistent "today's intention" surface, server-updatable via push |
| Lock-screen / home widget | Tap-to-hear | Passive presence: today's nudge visible, one tap plays the cached clip |
| CarPlay / Android Auto | Yes — audio-first | The context where "JQ speaks a nudge" is most natural; the shipped audio-first player + MediaSession maps straight onto the car now-playing surface |
Ceiling for "JQ talks to us anytime": short server-pushed spoken nudges (30s clip on iOS, richer on Android) + persistent visual presence. Not long unprompted conversations from the background.
B. You talking to JQ without opening the app
The sanctioned answer: borrow the system assistant as your microphone.
| Hook | Strength | What it gives |
|---|---|---|
| Siri + App Intents / App Shortcuts (iOS 16+) | Strong | "Hey Siri, ask JQ for my next step" → your intent runs and Siri speaks JQ's reply without visibly opening the app. Custom phrases auto-register. The real "voice interaction, app not running" |
| iPhone Action Button / Control Center | Strong | One press → a JQ shortcut → speak. No "Hey Siri" needed |
| Shortcuts automations (iOS) | Medium | "When I get home / connect CarPlay → ask JQ for a reflection." Context-triggered voice |
| Google Assistant / App Actions (Android) | Weaker / in flux | Google scaled back custom App Actions; the robust path is App Shortcuts surfaced to Assistant — more limited than iOS App Intents. Verify current state at build time |
Ceiling for "voice-in without opening the app": genuinely good on iOS (App Intents), noticeably weaker on Android right now. You ride the platform assistant; you don't get your own wake word.
Top integrations, ranked (feasibility × reuse of the shipped voice layer)
- Voice-clip-as-notification-sound — JQ's cached nudge plays on the lock screen. Reuses
sayInUserVoiceverbatim. iOS 30s cap; Android unbounded. The wedge. - Siri App Intent: "ask JQ" — spoken Q→A without opening the app (iOS). Highest-impact, iOS-only.
- Local scheduled nudges — the existing cadence engine, emitted as OS notifications. Offline, free.
- CarPlay / Android Auto audio companion — nudges + journal playback in the car; rides the audio-first player.
- Action Button / lock-screen tap-to-talk — instant capture or "talk to JQ."
- Live Activity "today's intention" — persistent ambient presence, push-updated.
- Home/lock-screen widget — glanceable nudge, tap to hear.
- Android foreground-service TTS — the one place JQ can speak a longer proactive line (Android's permissiveness).
- Shortcuts automations — location/time/NFC-triggered JQ reflections (iOS).
- Voice journaling via intent — "Hey Siri, tell JQ…" logs an entry in your voice, app closed.
iOS vs Android asymmetry
- iOS: weaker proactive (30s notification-sound cap), stronger voice-in (App Intents are excellent).
- Android: stronger proactive (foreground-service TTS, richer notifications), weaker voice-in (App Actions scaled back).
Design for both: the notification-sound nudge (#1) works everywhere; the Siri intent (#2) is the iOS standout; Android's edge is #8 (longer proactive speech).
What it costs in Expo terms
- Already have:
expo-av— playback is done. - Easy adds (config plugins, no ejection):
expo-notifications(rich push + local + custom iOS sound),expo-speech(on-device TTS fallback). - Needs a native module + dev build (not Expo Go): Siri App Intents / SiriKit, CarPlay / Android Auto, Live Activities. These are the high-value ones and the real engineering lift — a custom Expo config plugin wrapping native intent definitions.
Ethics & cost inheritance (non-negotiable)
Inherits the Living Voice load-bearing wall, unchanged:
- Consent v2 scope + the JQ Bridge permission graph for whose voice may speak.
- Read-only vs. generative bright line for any enduring/deceased voice.
- Cost guardrails: cache rendered clips (a notification sound is rendered once, reused forever), keep nudges short, rate-limit, never force re-render, reuse
tts-spend.ts. - Calm, not noisy — the mobile version of this rule is the sharpest: a phone that pings is the opposite of the brand. Proactive nudges must be rare, user-tunable, off by default, and honor OS Focus/Do-Not-Disturb (never request Critical/Time-Sensitive interruption levels for a nudge). The whole point is presence, not notification spam.
The wedge / recommendation
Build #1 + #2 + #3: voice-clip notifications (both platforms) + the Siri "ask JQ" intent (iOS) + local scheduled nudges. All three reuse the shipped voice layer, and together they deliver ~80% of the felt "JQ is with me" experience within the OS rules — no wake-word fantasy. Android voice-in (App Actions) and CarPlay are second-wave.
Sequencing
expo-notifications+ the voice-clip-as-sound path (#1) — the shared surface, both platforms.- Local scheduled nudges (#3) onto the existing cadence engine.
- iOS Siri App Intent "ask JQ" (#2) — the first native-module lift (dev build + config plugin).
- CarPlay / Live Activities / Android foreground-service TTS — second wave, per the asymmetry above.
See also
- AR_GLASSES_LIVING_VOICE.md — the glasses sibling of this doc; the audio-first player + MediaSession it references is the same layer these hooks play through.
- LIVING_VOICE_ROADMAP.md — the voice/presence spine (Track A = JQ, the forward voice) this is the phone hardware layer for.
- docs/mobile/REACT_NATIVE_STATUS.md — current state of the Expo app.