Skip to content

Voice commands

Two layers of recognition decide what your command does. The first layer is always on; the second is optional polish.

  • Prefix matching (always available, no model required) — recognises commands that start with a canonical verb: call, text, remind, cancel, play, pause, next, previous, open, launch. This is the baseline behaviour; everything in the tables below marked without an (intent model) tag works on a fresh install with no extra downloads.
  • Intent classifier (optional MiniLM model, ~23 MB) — also recognises natural paraphrases of the same intents ("ring Mom", "tell Dad I'll be late", "buzz me at 3"). It doesn't unlock any new actions — only new ways of asking for the same ones.

Both layers route to the same handlers, so the resulting action is identical. Install the intent model from Settings → Models → Intent only if you want the paraphrase support.


Phone actions

Call

Say Action
call Mom Place a call to that contact
dial John Same
phone Lisa Same
ring Mom (intent model) Same
give Dad a call (intent model) Same

Contact lookup is fuzzy — Mom, M, mom all match the same contact. Multi-word names work: John Smith.

SMS (and other messaging apps)

Say Action
text Mom hi Send SMS "hi" to Mom
text Mom saying I'll be late Send SMS "I'll be late"
message Lisa about dinner Send SMS "about dinner"
tell Mom I'm on my way (intent model) Send SMS "I'm on my way"
shoot Dad a text saying yes (intent model) Send SMS "yes"

The companion does multi-word contact-name resolution: text John Smith hi recognises John Smith as the contact (not just John) when the contact exists, and sends hi as the body.

What about WhatsApp / Telegram / Signal?

The companion recognises those names — saying "WhatsApp Mom on my way" lands cleanly on the confirm prompt ("action: whatsapp / details: [Mom] on my way"). But dispatch is gated: you'll get "Can't send to WhatsApp yet" instead of a real send.

Why: the deep-link path for those apps opens a pre-filled draft but doesn't actually send — you'd have to tap Send in the target app. There's no documented intent extra or URI parameter that triggers a real programmatic send from a sideloaded caller. The only path that would work is an AccessibilityService driving the Send button, which is a heavyweight permission (system-wide screen access). Deferred until that trade is worth making.

SMS is special because Android exposes SmsManager.sendTextMessage as a real system API. The other apps don't have an equivalent.

Tasks

Say Action
add task buy milk Create a new task "Buy milk"
new task call the dentist Same
add buy milk to my tasks Same
what are my tasks Lists your pending tasks
what are my completed tasks Lists completed tasks
completed tasks Same
complete buy milk Mark task done (substring match)
mark buy milk done Same
complete the last task Mark the most recent pending task done
done with the laundry (intent model) Same shape as complete X
delete task buy milk Permanently remove a task
remove buy milk from my tasks Same
scratch buy milk off my todo (intent model) Same

Tasks are separate from Reminders (which are time-triggered) and Notes (which are free-form text). The voice CRUD round-trips with the on-watch list (long-press DOWN from the chat screen) and the companion's Tasks tab. Substring matching means "complete milk" finds "Buy 2 percent milk"; if multiple tasks match, the reply asks which one. Say "complete the last task" / "latest" / "most recent" to act on the newest pending task without restating its text.

Alarms & timers

Alarms got a redo in companion v0.18.0 + watch v0.10.0 — they're no longer "fire-and-forget through the phone clock app". Each alarm has a destination (Phone / Watch / Both) and is tracked in Settings → ⏰ Alarms & Reminders → Alarms. Voice creates with the default destination (Phone unless you change it in that card); voice cancel works for the watch leg.

Say Action
set an alarm for 7am / wake me up at 6:30 Creates an alarm at the default destination
cancel the alarm / stop the alarm Cancels every pending watch alarm — phone alarms still ring and have to be dismissed in the system clock
cancel the 7am alarm Targets the alarm at that time (watch leg)
set a timer for 10 minutes / timer for 30 seconds Starts a countdown — the Clock app opens showing it
set a timer for 1 hour 30 minutes Compound durations are summed

Alarm vs. timer is decided by what you say: a relative duration ("for 10 minutes") is always a timer; a clock time ("7am", "6:30") is always an alarm — even if dictation drops the word "timer". Watch alarms ring on your wrist via Pebble's wakeup_service (vibration + dismiss) and can be cancelled programmatically. Phone alarms still go through Android's AlarmClock intent — set-only by Android's API, so we can't cancel them; the response text says so when relevant. Timers still open the Clock app deliberately (it's the only way to see + cancel a running timer — there's no DISMISS_TIMER intent). All three pass through Confirm before send when you have it on.

The on-watch list view is reachable two ways: pick Alarms from the Quick menu chooser, or bind SELECT / long-press UP / long-press DOWN to Alarms in the chat-surface shortcuts settings.

World clock

Tells you the current time in another city or country. Resolved on the phone from Android's built-in time-zone database — no network needed.

Say Action
what time is it in Tokyo Current time there + day and offset, e.g. "It's 7:42 AM in Tokyo (Wed, 13h ahead)"
time in London / what's the time in New York Same, for any city the zone database knows
what time is it in Japan / in California Country names and common abbreviations (UK, NYC, LA) resolve too

Plain "what time is it" (no place) is still answered instantly on the watch for your own clock — only the "in <place>" form goes to the phone. If a place can't be matched the watch says so, so you can try the city name instead of the country (or vice-versa).

Calculator

Quick arithmetic, worked out on the phone — no network, no calculator app.

Say Result
what's 25 plus 17 / 100 minus 30 = 42 / = 70
12 times 8 / 96 divided by 4 = 96 / = 24
what's 15% of 80 = 12
20% off 50 = 40 (a discount)

Speak the numbers as digits ("fifteen percent" works less reliably than "15 percent"). Operator words — plus, minus, times, divided by — and % of / % off are understood. Dividing by zero or an expression it can't parse comes back as a short "couldn't work that out" rather than a wrong answer.

Ask Agent (LLM passthrough)

Say Result
ask agent <question> / ask claude <question> Routes the question to your configured LLM and reads back the answer. e.g. "ask agent what's the capital of France"
ask agent search github for repos named wristotle When MCP servers are configured, the LLM can pick + invoke their tools across multiple rounds. The watch shows a → github.search_repos status line in the hint bar while each tool runs
ask the agent translate hello into spanish The the is optional; hey agent … also works. Built-in subjects: agent / claude / ai / llm / assistant / bot / chatbot / chatgpt / gpt
Jarvis, what's the weather? Wake-word form — bare "<custom> …" at the start. Custom trigger words only — see "Custom trigger words" below. Built-in subjects like agent / ai deliberately don't bare-match (too generic)

Custom trigger words. Settings → ✨ Ask Agent → Custom trigger words lets you add your own (one per line). They work both with the verb form ("ask jarvis …") and on their own as wake words ("Jarvis, …"). Added in v0.16.0.

Setup. Open Settings → ✨ Ask Agent and either:

  • Pick Anthropic and paste a Claude API key from console.anthropic.com, or
  • Pick OpenAI-compatible and point the endpoint URL at any service that speaks the OpenAI Chat Completions wire shape — OpenAI itself, OpenRouter (covers Claude / GPT / Gemini / Llama / Qwen with one key), a local Ollama / llama.cpp server, etc.

The system prompt field is pre-filled with a watch-friendly baseline ("Reply in 1-2 short plain-text sentences…") that keeps responses readable on the Pebble's tiny chat surface. Edit it freely — extend with persona instructions, change tone, etc. The "Reset to default" button puts the baseline back.

MCP tools (optional). Settings → 🔌 MCP servers lets you connect any HTTP MCP server (e.g. GitHub's MCP server at https://api.githubcopilot.com/mcp/ with a GitHub PAT). When at least one MCP server is enabled, AskAgent runs an agent loop — the LLM picks tools, the companion executes them, and the loop continues for up to 5 rounds before answering. The watch shows a status line for each tool call.

Like Weather, this uses the network. Your API key and queries go directly from the phone to your chosen LLM provider — see Privacy → AskAgent queries for the data flow.

Home Assistant

Forward a spoken command to your own Home Assistant instance. Unlike Ask Agent, there's no LLM — Home Assistant runs its own natural-language understanding server-side (its conversation agent) and acts on your devices; Wristotle just sends the words and speaks back the reply.

Say Result
hey home assistant turn off the kitchen lights Sends "turn off the kitchen lights" to HA's conversation agent; its spoken reply comes back on the watch
home assistant, is the garage door open? The verb is optional — the built-in subjects (home assistant / hass) are specific enough to open a command on their own
tell home assistant to lock the front door ask / hey / tell all work as the lead-in; a trailing "to" is stripped
Jarvis, turn off the lights Wake-word form — add your own trigger words (see below)

Custom trigger words. Settings → 🏠 Home Assistant → Custom trigger words lets you add your own (one per line), used as wake words ("Jarvis, …") or with a verb ("tell jarvis to …"). If a word is also an Ask Agent trigger, the card flags it — a shared word routes to Home Assistant.

Setup. Open Settings → 🏠 Home Assistant and enter:

  • Base URL — your HA address, e.g. http://homeassistant.local:8123 or a domain. LAN / Tailscale / self-signed http:// all work; no path needed (/api/conversation/process is added for you).
  • Long-lived access token — in HA, your profile → Security → Long-lived access tokens → Create.

Until both the URL and the token are set, the command answers "Home Assistant not set up." and nothing leaves the phone — no request is even attempted. Once configured, if something's still off you get a specific reply instead of a generic failure, so you can tell what to fix:

Watch reply What it means
"Home Assistant not set up." Base URL or token is empty — fill both in Settings
"Say what? Try 'hey home assistant turn off the lights'." The wake word was heard but no command followed it
"Home Assistant: token rejected." The long-lived token is wrong or expired (HTTP 401/403)
"Home Assistant: can't reach it." The URL is unreachable — server down, wrong host/port, or off your network
"conversation endpoint not found (404)" URL reachable but the path is wrong, or the Conversation integration isn't enabled in HA

This is a network-using command; the command text goes only to your own server — see Privacy → Home Assistant commands for the data flow. To hear the reply spoken on the watch, enable Home Assistant under Settings → 🔊 Speech. Companion v1.16.0.

Weather

The first voice command that uses the network. Triggered only when you ask — see Privacy → Weather queries for the data flow.

Say Result
weather in Tokyo / what's the weather in London / weather in India Conditions + temperature for any city, country, or common abbreviation
what's the weather (no city) Your current-location forecast, if you've granted the optional Location permission
is it raining / what's the temperature Same as above — the keyword set covers weather / forecast / raining / sunny / temperature

By default the phone queries open-meteo.com — free, no API key, no registration. In Settings → Weather you can switch to OpenWeather and paste your own free-tier API key (more direct control over your quota). Same screen has the °C / °F toggle (defaults to your locale's convention). The bare "what's the weather" form needs the Location permission — grant it from Permissions → Weather (Location). Until you do, that form replies with a hint pointing you back to the location-qualified version.

Sports

Scores, schedules, and standings — companion-side and network-using, like Weather. Triggered only when you ask. See Privacy → Sports queries for the data flow.

Supported sports:

Sport Leagues / competitions
Soccer Premier League, La Liga, Serie A, Bundesliga, Ligue 1, MLS, FIFA World Cup 2026 (national teams)
Basketball NBA
American football NFL
Baseball MLB
Ice hockey NHL
Formula 1 Race weekends — practice, qualifying, sprint, race — plus driver & constructor standings
Cricket Indian Premier League, Big Bash League, Cricket World Cup

New leagues can be added without an app update, so this list may grow between releases.

Say Result
when do the Warriors play next The team's next fixture — opponent, date, and time
did Arsenal win / what was the Lakers score Their most recent result
live score for Manchester City The in-progress score, or the last result if no game is live
Premier League table / NBA standings League standings (top rows)
National League West standings / AL East A specific division's table
MLB standings The leader of every division

Formula 1:

Say Result
when is the next F1 race The next Grand Prix and its date
who won the last race The most recent completed session's result, labelled with which session it is
who won the last qualifier / F1 qualifying results The qualifying result
who got pole Pole position (qualifying winner)
F1 sprint results The sprint result
last race standings The full finishing order of the last race
F1 standings / driver standings Driver championship table
constructor standings Constructor championship table

Every F1 answer is labelled with its session (e.g. "Spanish Grand Prix · Qual"), so you always know which result you're seeing. "Who won the last race" gives the most recent completed session — so mid-weekend you still get a result (qualifying) rather than a "not run yet".

Cricket (IPL, Big Bash League, Cricket World Cup):

Say Result
IPL score / did RCB win The latest match result
BBL standings / did the Scorchers win Big Bash League table / a team's last result
IPL standings / IPL table The points table
next IPL match The upcoming fixture

FIFA World Cup 2026 — ask by national team (all 48 are covered):

Say Result
when does Argentina play next The team's next World Cup fixture
did Brazil win / what was the USA score Their most recent result
live score for France The in-progress score (or last result if no game is live)

Group standings aren't available yet — ask for scores, fixtures, and results.

Favorites, sport priority + on/off live in Settings → Sports:

  • Favorite teams — save the teams you follow so "did we win?" works without naming one.
  • Sport priority — a reorderable list. When a team name is shared across sports (e.g. "City" → Manchester City vs Oklahoma City), the sport higher in this list wins.
  • Turn sports on/off — untick any sport you don't follow. An excluded sport returns no info and never matches a spoken name (so "India" won't pull up a cricket team if you've switched cricket off).

Favorites, sport priority, and your on/off choices are all included in Backup & Restore (under Settings, no secrets — they travel by default).

Keeping sports data up to date

The list of leagues and teams (and the occasional fix when a sports site changes something) is delivered as a small data file that's separate from the app. That means new leagues and corrections can reach you without an app update.

  • Automatic — the companion refreshes this data on its own in the background, picking up changes within about a day of your next sports question. You don't have to do anything.
  • Update now — if you'd rather not wait, go to Settings → Sports → Update sports data. The button is enabled only when a newer version is available; tap it to pull the latest immediately. Check again re-checks on demand, and it shows the version you're on.

Data comes from a public sports service with no key or account required. If it's ever unavailable, the app falls back to a bundled list so name resolution keeps working offline.

Reminders

Say Action
remind me to pick up milk at 5pm Create a 5 PM reminder titled "Pick up milk"
wake me up at 7 Same — no task spoken, so titled "Wristotle Reminder"
buzz me at 3pm (intent model) Same, titled "Wristotle Reminder"
ping me in 20 minutes (intent model) 20 min from now
set timer for 15 minutes 15 min from now

Time parsing handles word-form numbers ("ten thirty" → "10:30"), relative ("in 20 minutes", "tomorrow at noon"), and absolute ("at 5pm", "next Friday at 3"). A bare hour ("at 8") resolves to the next time that o'clock comes around — 8 PM if you say it in the afternoon, not tomorrow morning; say "8 am" / "8 pm" to pin the half explicitly. This is consistent whether you're creating, rescheduling, cancelling, or asking ("is there a reminder at 8?").

At the reminder time your watch buzzes and shows the reminder. This needs a recent Pebble app — confirmed on microPebble 1.10.0+ with companion v1.12.1+; on older versions the reminder still lands in the watch's timeline but may not vibrate.

Persistent reminders

For things you really don't want to miss — meds, important calls — add a "persistent" flavour to any reminder. The watch pin fires as usual at the scheduled time, and your phone keeps re-firing a notification every few minutes until you tap Stop or hit the max-nags limit.

Say Action
persistent reminder to take meds at 9pm Persistent reminder at 9 PM titled "Take meds"
nag me to call mom at 5 Same shape, "Nag me" as the prefix
keep reminding me to drink water at noon Same shape
remind me persistently to check the oven at 6 Modifier on the usual "remind me" prefix

What you'll see:

  • On the watch — same timeline pin as a plain reminder. Fires once at the scheduled time.
  • On the phone — a "Persistent reminders" notification channel posts at the same moment and re-fires every 10 minutes (default; configurable in Settings → Reminders) until you tap Stop, swipe past the max-nags limit, or cancel the reminder by voice. Body reads "Persistent reminder · N more nags after this" so you can see how many remain.

In Settings → Reminders → Persistent reminders you can tune the nag interval (5 / 10 / 15 / 30 min) and the max number of nags (3 / 5 / 7 / 10).

Tapping a nag's body opens Wristotle but keeps the chain alive — useful for "I see this one, but keep reminding me." Tap Stop when you're done.

cancel reminder by voice also kills the phone-side nag chain, so you don't have to chase down the notification separately.

List reminders

Say Action
what are my reminders Lists your pending reminders
is there a reminder at 2pm Answers for that hour — "1 reminder around 2 PM" or "No reminder around 2 PM"
do I have any reminders (intent model) Lists them

Reminders stay listable for 24 hours after they fire, then auto-clear.

Cancel

Say Action
cancel reminder / cancel that Cancel the most recent reminder
cancel the gym reminder Cancel the reminder whose title matches "gym"
cancel my 5pm reminder Cancel the reminder at that time
scratch that reminder (intent model) Cancel the most recent

A named target that matches nothing reports "No reminder matching …" rather than cancelling the wrong one. (Cancelling a calendar event isn't supported — you'll get "I can only cancel reminders, not meetings".)

Reschedule

Say Action
push my reminder to 6pm Move the reminder to a new time
snooze for 10 minutes Reschedule relative to now
reschedule my gym reminder to noon (intent model) Move the matching reminder

The new time is relative to now ("in 10 minutes" = 10 minutes from now).

Calendar

Reads and writes your phone's calendar (via Android CalendarContract), not the watch timeline. Needs the Calendar permission (granted in the first-run flow or the Permissions tab). All calendar phrasings are natural-language, so they route through the intent model.

Say Action
when is my next meeting (intent model) Read your next upcoming event
what are my next 3 meetings (intent model) Read the next 3 upcoming events
what's on my calendar today / …tomorrow (intent model) Read that day's events
do I have a meeting on May 25 (intent model) Read events on that date
schedule a meeting tomorrow at 3pm (intent model) Create a 60-minute event
create an event dentist appointment July 29 at 3pm (intent model) Create an event titled "Dentist appointment"
add a meeting with Alex Friday at noon called standup (intent model) Create an event titled "standup"
book a half hour meeting Monday at 9 (intent model) Create a 30-minute event

Reads cover a 30-day window and recurring events expand to individual occurrences. For creates: the time is required. Naming the event: just say it after "create an event" — "create an event dentist appointment tomorrow at 3pm" titles it "Dentist appointment". Saying "called …" / "titled …" works too (handy if the name itself contains a date or time word). If you don't name it, it defaults to "Wristotle Meeting" (or "Meeting with \<name>" when you name an attendee). Dates can be relative ("tomorrow", "next Friday") or spoken ("July twenty ninth", "the 29th"); duration defaults to 60 minutes unless you say otherwise ("for one hour", "30 minutes", "half hour"). After creating, the watch shows back what was added so a misheard time is visible. Deleting events by voice isn't supported yet — for now, delete them in the Calendar app. (Creates go through the Confirm before send gate if you've turned it on.)

Morning brief

A single voiced rollup of what's on your plate today — meetings, alarms, reminders, pending tasks, notes created today, and unread messages from your messaging apps.

Say Action
morning brief Aggregate every section into one watch-chat reply
what's on my plate today / what's my day / what's coming up today Same
summary of today / daily summary / today's rundown Same
brief me / brief me on today Same

Order in the reply: Meetings → Alarms → Messages → Reminders → Tasks → Notes. Empty sections drop out — if you have no pending tasks the Tasks line just doesn't show.

Choose what's included. From Settings → 🔔 Notifications → Morning Brief you can turn any section on or off (all on by default). A section you turn off is skipped entirely — for example switching off Messages & notifications also stops the brief reading your notification tray. (companion v1.13.0+) Each section is short by design (the watch chat surface tops out around 280 characters), so a packed day folds into +N more tails: the first three meetings by start time, the first three alarms, the first four messaging apps, and so on. Reminder / Task / Note counts are intentionally just the count — voice "list my reminders" / "list my tasks" give the detailed views.

Unread messages read live from your notification tray via the Notification Access permission you already grant for media control. The brief recognises the common messaging apps (WhatsApp, Telegram, Signal, Slack, Discord, Teams, Messenger, Gmail, AOSP / Google / Samsung Messages); anything else still counts toward a trailing +N other so you know how full your tray is. If Notification Access isn't granted, the Messages line simply doesn't render.

Default behavior (snapshot): only notifications currently sitting in your tray show up. A notification you've already dismissed never appears.

Optional persisted log (companion v1.6.0+): turn on Settings → 🔔 Notifications → Include dismissed notifications to also catch notifications you swiped or cleared earlier today. Wristotle records app id + conversation key + timestamp only — never the body, title, or sender. The log auto-prunes after 7 days; Clear log in the same card wipes it on demand.

Conversation count vs message count. The brief deduplicates by conversation so five SMS from the same friend reads as one. When a thread is chatty enough that the raw count matters, the brief shows both: "Messages: 1 conversation (5 msgs) from WhatsApp." When the counts match (one buzz per conversation) the parenthetical drops and the line stays calm.

"good morning" on its own won't trigger the brief — too greeting-shaped to risk false positives. The trigger phrases above all require a "brief / day / today / plate" anchor.


Notes

Notes are stored on the phone (in the companion's private storage), browsable from the on-watch Notes list (long-press UP from the chat) and from the Notes tab in the companion. Dictation audio is saved alongside each note so you can play it back from the companion. See Settings → Notes for retention controls.

Create a new note

Say Action
note to self: pick up bread Creates a note "Pick up bread"
take a note: wifi password is 1234 Creates a note with that body
make a note that the meeting moved to room C Creates a note "The meeting moved to room C"
save a note: tracking number 9876 Same
write this down: call back in the morning Same
jot this down: groceries — milk, eggs, bread Same
remember to renew the parking permit (intent model) Same

Lead-ins recognised: note to self, noted, (make/take/save/store/keep/add) a note, add to my notes, for my notes, jot (this/that/it/down), write (this/that/it/down), write down, remember, plain note(s). Connector words (that, to, about, down, the) and trailing punctuation (:, ,, ., ;, !, ?, -) after the lead-in are absorbed so the body starts with the substance.

Append to your most recent note

Say Action
append: room c at 3pm instead Adds "Room c at 3pm instead" to the last note
append to my note: also milk Same
add to my last note: speaker is bob Same
add to the previous notes that the room changed Same
amend my last note: tracking number 1234 Same — amend works as a synonym (Whisper often mishears append as amend)
amend to the notes: door code is 9876 Same

add requires one of previous / last / latest / recent to distinguish "add to my last note" (append) from "add a note" (create). append and amend are unambiguous on their own, so the qualifier is optional with those verbs.


App + media control

These two intents share an on-device installed-app indexSettings → Learning → Installed apps → Scan installed apps populates it. You only need to re-scan when you install or uninstall an app.

Open any app

Say Action
open Spotify Launch Spotify
launch Maps Launch Maps
start Audible Launch Audible
fire up Settings (intent model) Launch the system Settings app
switch to Messages (intent model) Launch Messages

The companion finds the app by fuzzy matching the spoken name against the local index — exact match → prefix → contains → reverse-contains (handles Whisper hearing "absorbed" → "Absorb").

Make cold-start open <app> reliable: grant 'Display over other apps'

Without it, Android 14+'s Background-Activity-Launch (BAL) restriction silently blocks Wristotle from foregrounding a target app when triggered by a watch dictation — the companion reports "Opened …" but the target stays in the background.

Granting "Display over other apps" to Wristotle (under Settings → Apps → Special access → Display over other apps on most phones, or via the button in Wristotle → Permissions → Reliable background app launch) adds Wristotle to Android's BAL allow-list. Wristotle never actually draws any overlay — only the grant matters. Once granted, cold-start open <app> foregrounds reliably across YouTube, WhatsApp, Spotify, Maps, and so on.

The grant is optional. If you'd rather not grant it, the fallback is: open the target app once manually so it's in a recent state; subsequent open <app> from the watch usually succeeds while it's warm.

Media playback

Works across most media apps — see "Which apps work?" below.

Say Action
play Resume the currently-active media app
pause Pause the currently-active media app
play pause / toggle music (intent model) Toggle play/pause on the currently-active media app — useful when you don't remember the current state
play Spotify Launch Spotify and start playing
play Audible Launch Audible and start playing
pause YouTube Pause YouTube specifically (even if Spotify is on top)
next / next song / skip Skip to next track
previous / last song Previous track
rewind 10 seconds / rewind Seek backward 10 s
skip ahead 30 seconds / fast forward Seek forward 30 s
go back 2 minutes Seek backward 2 min

For play <app>, pause <app>, next <app>, previous <app> to work, the named app must:

  1. Be installed on the phone, AND
  2. Be in the AppIndex (run a Scan from Settings).

Generic phrasings (play music, pause the song, next track) intentionally fall through to whichever session is currently active — they don't require an app name match.

Which apps work for play <app> (cold-start from watch)?

When the target app is force-stopped, locked, or not currently in any media-control state, play <app> reliability depends on how the target app declares its media-playback service — this is an Android-side constraint, not Wristotle's:

  • Works reliably: apps using the modern Media3 / MediaSessionService pattern with foregroundServiceType="mediaPlayback". Examples: Audiobookshelf, Spotify, Pocket Casts, Plexamp, most audiobook + podcast apps.
  • May fail silently: apps using older androidx.media.MediaButtonReceiver plumbing or no declared service type. Android 14+'s Foreground-Service-Background-Start (FGS-BS) rules block the target app from starting its own playback service when invoked from a cold-state background broadcast. Examples observed: YouTube Music (and YouTube-family forks).

Wristotle has no way to bridge the play step for sideloaded callers — the failure happens inside the target app's process after our command is delivered, and target apps gatekeep their playback API to known-signed callers (Google Assistant, Android Auto). We've confirmed empirically that the documented workarounds don't help:

  • Granting Wristotle "Display over other apps" does make open <app> cold-start work (the BAL restriction on Wristotle's side); see the tip above. It does not make play <app> auto-resume.
  • MediaBrowserService binding is rejected by both Tempo (onConnectionFailed from media3's default onGetSession) and Spotify (onGetRoot rejection). The bind never delivers a controller.
  • Intent.ACTION_MEDIA_PLAY_FROM_SEARCH dispatches but the receiver inside the target does the same gatekeeping check; the app opens, playback doesn't start.
  • RoleManager.ROLE_ASSISTANT taking over the device's assistant slot doesn't help — the exemption that lets Google Assistant bypass these checks is START_ACTIVITIES_FROM_BACKGROUND (signature-only, reserved for /system/priv-app), not the role itself. Sideloaded VIA holders get the slot but not the privilege. Full UX cost, zero functional gain.

The play step depends on the target app explicitly accepting commands from a sideloaded caller, and basically none do — that requires either being on the app's signing whitelist or being a system-privileged install. For now, play <app> opens the app reliably (with the "Display over other apps" grant), but you may still need a tap on the target app to start playback, depending on whether that app has a saved last-session it auto-resumes.

Notification Access required

Cross-app media control needs Notification Access granted to Wristotle. The first-run permission flow asks for it; you can also grant/revoke from Permissions tab → Media Control.


Speak on watch

Optional, experimental, off by default. Lets the watch read replies aloud through its built-in speaker — emery (Pebble Time 2) and diorite (Pebble Round 2) only; every other model silently drops the audio.

Configure under Settings → 🔊 Speech:

  1. Flip the master "Speak on watch" toggle.
  2. Pick which intents should speak — grouped by noun (Reminders / Tasks / Notes / Alarms / Calendar / Lookups / Ask Agent / Morning brief / …) with action-level chips for Create / List / Cancel / Complete / …. Nothing speaks until at least one chip is on. Bulk Select all / Deselect all for quick changes.
  3. Pick an engine under Mode:
    • Local only — your phone's Android TextToSpeech engine (Google TTS, Samsung TTS, GrapheneOS speechservices, etc.). No network. Time-to-first-audio ~4 s on most devices.
    • Local primary — Android first, HTTP fallback on init / synth failure.
    • Cloud primary — any OpenAI-compatible /v1/audio/speech endpoint first (OpenAI's tts-1, self-hosted Piper, OpenedAI Speech, LiteLLM, …), Android fallback on transport / 4xx / 5xx failure. Time-to-first-audio ~1 s with OpenAI.
  4. If you picked Local primary or Cloud primary, fill in the HTTP block (Base URL, optional API key, model, voice). Empty defaults by design — paste whichever endpoint you want.
  5. Hit Test to verify with a free-form phrase before relying on it for real queries.

Hardware gate matters: if your watch isn't a Pebble Time 2 or Pebble Round 2, the toggle is purely cosmetic — the chunks land but the watch has no speaker to play them through.


Saved codes

Settings → 🎟️ Saved codes (companion) + menu → Codes (watch). Keep your loyalty cards, membership barcodes, and QR codes on your wrist — scannable offline, right at the checkout.

  • Add oneScan a card with the phone camera (a real scanner, no Google Play Services), or Enter manually (type the number/text and pick the format).
  • Formats — QR, Code 128, EAN-13, UPC-A. Most loyalty and product cards are one of these.
  • Name + voice alias — give each code a name and an optional spoken alias, so you can pull it up hands-free.
  • On the watch — long-press UPCodes → pick one. It renders full-screen with the name on top and the backlight forced on; point your scanner at it.
  • Edit any code's name, alias, contents, or format later. Backup & Restore carries codes and their voice aliases.

Recall by voice

Say Action
show my qr code Show your QR code on the watch
show my barcode Show your barcode (Code 128 / EAN-13 / UPC-A)
show my clubcard (intent model) Show the code you named or aliased "clubcard"
pull up my gym pass (intent model) Same, by name

If more than one code matches (e.g. several QR codes for show my qr code), Wristotle reads the names back so you can pick by saying one.


On-device stats

Settings → 📊 Stats. A snapshot of how you've been using Wristotle, computed locally from the conversation history and the existing on-device stores — nothing leaves your phone. The card renders an "on-device · private 🔒" chip in the header to make that explicit.

Six panels:

  • Streak header — "Day N with Wristotle" plus tiles for total voice queries, words dictated, and approximate dictation time (135 WPM).
  • Top intents (last 30 days) — what you've been asking for, sorted by count. Unknown and Error don't get top billing.
  • When you talk to Wristotle — 24-hour activity chart in your local time, peak hour, busiest day of week.
  • How well it works — success rate over the last 30 days and lifetime, plus the NLU classifier's average confidence.
  • Where Wristotle stumbles — the three lifetime intents with the lowest success rate, severity-tinted, with an absolute X of Y succeeded breakdown. Tap-through is Settings → 🎓 Learning — adding phrasings is the canonical fix.
  • On your phone — current counts for notes, completed tasks, pending reminders, and alarms. These are current state (deleting a note reduces the count), not lifetime totals.

The first six panels unlock once you have at least 10 lifetime queries; before that, the card collapses to a single "Day N — come back later" stub so a fresh install doesn't read as a wall of zeros. Conversation retention is 10 days by default (configurable under Settings → 💬 Conversation), so the 30-day window degrades gracefully to whatever's still on disk.

No telemetry, no analytics, no cloud round-trip. The Stats card is a different lens on data you already have — not a new collection point.


Local watch commands

These run entirely on the watch — no phone needed, no network. They're handled by src/c/input/commands.c in the watch app.

Say Action
what time is it / time Show current time on the watch (add "in <place>" for world clock)
battery / battery level Show battery percentage
steps / step count Show step count
vibrate / buzz Test vibration
find phone / ring my phone Ring the phone to locate it

Local commands still log to the phone's Conversation history (so you can review what you asked the watch), but they're handled fully on-device.

Find my phone rings from the companion app on the alarm volume (and vibrates), so you can hear it even when your media volume is muted — and it briefly turns the alarm volume up so it's actually loud. Tap, Stop, or swipe the "Finding your phone" notification to silence it (it also auto-stops after 30 s). (companion v1.14.0+ / watch v1.4.0+; older versions fall back to the previous tones, which follow the media volume — you can switch the source in your Pebble app's settings.)