Skip to content

Troubleshooting

Most issues fall into one of three buckets: watch dictation isn't capturing audio, a specific command isn't routing correctly, or the companion isn't responding. Walk through the section that matches.


"Dictation isn't available" on the watch

Symptoms: you press select, the watch briefly shows the mic icon (or doesn't show it at all), then immediately bails with "Dictation isn't available".

This is almost always a stuck watch-side audio state or a BLE bridge issue. In escalating order:

  1. Restart the watch. Long-press the back button until it reboots. Fixes the most common case — the watch firmware's DictationSession gets wedged after a long idle and won't engage the mic until reset.
  2. Force-stop your BLE companion (microPebble or Core Devices). Settings → Apps → [companion] → Force Stop. Reopen.
  3. Toggle phone Bluetooth off then on. Forces the BLE stack to re-handshake.
  4. Re-pair the watch if 1–3 don't help. Settings → Devices in your BLE companion → unpair → pair again.

Wristotle Companion as of v0.1.0 automatically recreates its DictationSession after a FailureSystemAborted error, so this scenario should become rare after the first upgrade.


I have to wait several seconds after I stop talking — and pressing BACK says it didn't pick anything up

That gap between "I finished speaking" and "the watch shows the response" is two things stacked:

  1. Pebble's end-of-speech timeout. The Pebble OS dictation session waits a few seconds of silence after you stop talking before it closes the microphone. This is system-level and not something Wristotle controls.
  2. Phone-side transcription. Once the audio reaches your phone, the speech-to-text engine has to actually transcribe it. On-device Whisper takes a couple of seconds per query on most phones — varies with model size.

Press SELECT during dictation to commit early. SELECT closes the microphone immediately and sends what you've said so far. BACK cancels the dictation entirely, which is why pressing BACK got you the "didn't pick up" reply — the audio was thrown away, not failed to transcribe. This is Pebble OS behaviour, not Wristotle-specific.

To shrink the transcription part:

  • Switch to a smaller Whisper model under Settings → Models → Speech. tiny.en is noticeably faster than small.en.
  • Or configure a cloud / self-hosted STT endpoint under Settings → Models → Speech provider. Sub-second is realistic on Groq.

"Could not understand" / "no audio received"

Symptoms: dictation starts, mic icon appears, the watch shows "Could not understand. Try again" — but it happens every time, even when you speak clearly.

This usually means the audio bridge from watch → BLE → companion isn't delivering bytes. The recognizer is being invoked correctly but with an empty pipe.

What to try:

  1. Restart the watch (same as above — fixes wedged firmware state).
  2. Confirm Wristotle is the active voice provider:
    adb shell settings get secure voice_recognition_service
    
    Should print com.lazydevs.wristotle/com.lazydevs.wristotle.speech.service.WhisperRecognitionService. If not, re-run the activation command from the Install guide.
  3. Confirm RECORD_AUDIO is granted — Permissions tab in Wristotle.
  4. Try the Gboard mic test — open any text field, tap Gboard's mic icon, dictate. If Gboard works, Wristotle recognition is healthy and the issue is purely in the watch→BLE bridge. If Gboard also fails, there's a deeper Android audio-routing issue.

Random "Door opens" / "sigh" / "DING" in the chat

These are Whisper subtitle-annotation hallucinations on silence. Whisper was trained on subtitle data and emits annotation tokens when given near-silent audio (you press select but don't speak in time, or the mic is muffled).

As of v0.1.0 the companion filters these out before they reach the chat — purely-annotation transcripts become "no match" instead of phantom commands. If you're still seeing them after an update, make sure your installed APK is the latest version.


"Need the Intent classifier model" reply on the watch

Symptoms: you dictate something natural-sounding (what's on my calendar today, ring Mom, tell Dad I'm on my way) and the chat shows "Need the Intent classifier model. Install in the companion: Settings → Models."

Wristotle has two layers of voice recognition. The first is a prefix matcher that handles canonical verbs (call, text, remind, cancel, play, pause, next, previous, open, launch) — it's always on, no model required. The second is the Intent classifier (a ~23 MB MiniLM model) that recognises natural paraphrases of those intents. Calendar commands route through it entirely; nothing in calendar has a verb prefix.

To enable: Settings → Models → Intent classifier → Install. Once it's downloaded and active, calendar commands and natural paraphrases start working. Canonical-verb commands keep working with or without it.


Wristotle keeps mishearing the same contact or app name

If the same name comes through wrong every time ("Audible" → "Absorb", "YouTube" → "U-Tube"):

  • App names — open Settings → Learning → App aliases and add a row mapping the misheard phrase to the right package (e.g. absorb → Audible). Next time you say it, the alias takes priority over the fuzzy match. Wristotle also auto-suggests likely aliases based on past failures in the Saved aliases popup. Compound words are tolerated automatically — an alias stored as audiobook still matches when dictation transcribes audio book (and vice versa), so you don't need to add both spellings.
  • Contact names — open Settings → Learning → Contact aliases and pin the misheard phrase to the right contact (e.g. next → Mom for the classic "text mom""next mom" mishearing). Aliases beat the normal matcher, so this also disambiguates when multiple contacts share a first name. The alias keeps pointing at the same person even if you rename them in Contacts or their number changes.

If you suspect Whisper itself is the problem (mishearing in a way that's not specific to one name), try a different Whisper model from Settings → Models — bigger models (small.en, medium.en) are more accurate but slower on older phones.


My reminder ended up at the wrong time

Two common causes:

  • You didn't say a time. "Remind me to buy milk" with no time clause defaults to 30 minutes from now. The default is configurable: Settings → Reminders → Default time when none spoken (5 / 10 / 15 / 30 / 45 / 60 / 90 min).
  • The time was misheard. Whisper occasionally renders digits as words in unexpected ways ("five PM" vs "5pm"). Wristotle's time parser handles both forms but is occasionally tripped up by edge cases (e.g. "eleven 30" vs "eleven thirty"). If you suspect a parse, turn on Confirm before send — the watch will show "Action: remind / Details: Buy milk @ 5:30 PM" before it lands, and you can BACK to cancel.

I said "cancel the alarm" and my phone alarm is still going to ring

Voice cancel works for watch alarms — alarms whose destination is Watch or Both. Phone alarms can't be cancelled programmatically: the one Android API that's close (DISMISS_ALARM) only dismisses an alarm that's currently ringing, not a future scheduled one. So a phone-destination alarm has to be toggled off in the system clock app.

  • Pick a destination when you create an alarm. Companion → Settings → ⏰ Alarms & Reminders → Alarms card → set the default destination dropdown. Voice "set an alarm for 7am" honours it. The + Add alarm dialog lets you pick per-alarm.
  • The response tells you what happened: "Watch alarm cancelled." / "Watch alarm cancelled. Phone alarm still pending." (Both destination — phone leg untouched) / "No watch alarms to cancel." (only Phone alarms, or nothing pending).
  • Manage existing alarms in the companion's Alarms card — each row has a Delete button. Cancelled alarms vanish from the list (cancel = delete in v2; one-shot alarms don't need a "disabled" state).

I can't cancel a running timer by voice

This one's still a platform limitation. There's no DISMISS_TIMER intent at all. The "set a timer…" command opens the Clock app's Timers tab on purpose so you can cancel it there.

Tip: alarm vs. timer is decided by what you say — a duration ("for 10 minutes") is always a timer, a clock time ("7am") is always an alarm, even if dictation drops the word "timer".


A setting I changed in the companion doesn't seem to apply

The companion's Settings → Watch card is a remote control — the watch itself is the source of truth. When you toggle something there, the companion sends the change to the watch; it applies on the next message round-trip. If you flip a setting and immediately dictate a command, you might catch the watch on the old value.

To force a refresh: open Settings → Watch, tap the Refresh icon. The card re-fetches the live state from the watch. If a setting you saved isn't reflected, that's the fastest way to verify.


"Contacts permission required" / "SMS permission required" / similar

Each voice command needs different runtime permissions on the phone:

Command Permissions
call <name> Contacts + Phone
text <name> <body> Contacts + SMS
Calendar (read or create) Calendar
pause / next / previous / play <app> Notification Access
open <app> / play <app> (app launch) none beyond install

Grant from Permissions tab in the companion. The tab badges red when something's missing. After granting, the next dictation of that command should succeed without the error.

If you granted a permission but the error keeps appearing, force-stop the companion (Android Settings → Apps → Wristotle Companion → Force Stop) and reopen — Android sometimes caches old denials inside the running process.


"Confirm before send" toggle is on but my command didn't prompt

By design, the prompt only appears for action-taking commands — call, SMS, reminder, calendar create, open app, play media. Read-only commands (time, battery, what's on my calendar, list reminders, pause, next, previous, find phone) always run immediately, even with the toggle on. The point is the gate is asking "are you sure you want this action to happen?", not "are you sure you want to hear the time?".

If you wanted confirmation on a command in the read-only list, that's currently not supported. Open an issue if it's important.


pause / next / previous does nothing

Symptoms: play <app> works, but pause, next, previous don't seem to do anything.

Cross-app media control needs Notification Access granted to Wristotle. Without it, the companion can't reach the active MediaSession of other apps to send key events.

Grant via Permissions tab → Media Control (or from the first-run flow). After enabling, try again.


I just installed an app but open <app> / play <app> doesn't find it

Wristotle keeps its own on-device index of installed apps (not Android's, for permission-scope reasons). The index is built on demand. After installing or uninstalling an app, the index goes stale until you re-scan.

Settings → Learning → Installed apps → Scan installed apps.

You only need to do this when your installed-app list changes. The scan is fast (a few seconds) and runs entirely on the phone.


The companion goes quiet after the phone has been idle for a while

Symptoms: voice commands work for a session, then stop responding until you manually open the Wristotle companion.

Some Android OEMs (notably Xiaomi/MIUI, Huawei/EMUI, Samsung, OnePlus, Oppo) aggressively kill background services to save battery, including foreground services like the watch bridge. Workarounds:

  1. Exempt Wristotle from battery optimization. The first-run flow asks for this; if you skipped it, go to Android Settings → Apps → Wristotle Companion → Battery → Unrestricted.
  2. On MIUI/EMUI, also enable Autostart and pin the app to memory (steps vary by device — search "autostart <your phone>").
  3. Confirm the foreground notification is showing — if it's been swallowed, the OS likely killed the service.

There's no Android API to override this on every OEM; you're at the mercy of the device-specific power manager.


My Pebble 2 / Pebble 2 HR says the companion isn't loaded after updating the watch app

Symptoms: on an original Pebble 2 or Pebble 2 HR, after updating the watch app to v1.2.0 the app reports that you need to load the companion even though it's running, and voice commands that need the phone stop working.

This was a bug in watch v1.2.0: the on-watch speaker support was enabled on hardware that doesn't have a speaker, and the memory it reserved stopped the watch from opening its messaging channel to the phone. Update the watch app to v1.2.1 or later — it limits the speaker to Pebble Time 2 and restores the original Pebble 2 / Pebble 2 HR to normal. Re-install from your watch app store (or sideload the v1.2.1 .pbw from the release page).


open <app> (or play <app>) reports success but the app doesn't appear

Symptoms: the watch shows "Opened Spotify" or "Playing Audible" in chat, but the app doesn't come to the front (or starts playing).

On Android 14+, a background-launch restriction can prevent a third-party app from foregrounding a different app when the trigger comes from a non-user gesture (the watch dictation, in this case). The confirm prompt fires, the dispatch runs, the companion reports "Opened …" — but the OS quietly blocks the activity start.

There's now a clean fix for open <app>: grant Wristotle the "Display over other apps" permission. Under Settings → Apps → Special access → Display over other apps on most devices, or via the button in Wristotle → Permissions → Reliable background app launch. Once granted, cold-start open <app> from the watch foregrounds reliably (Android's BAL evaluator has a SYSTEM_ALERT_WINDOW-holder exemption). Wristotle never actually draws an overlay — only the grant matters.

For play <app>, the grant gets the target app open, but auto-resuming playback depends on the target app accepting commands from a sideloaded caller — most major media apps (Spotify, Tempo, etc.) only accept Google Assistant. See voice commands → which apps work for the full investigation.

Workarounds:

  • Open the app once manually, then use voice. While the app is in a recent state it usually launches reliably from the watch.
  • Apps with foregroundServiceType="mediaPlayback" — Spotify, Audible, Pocket Casts, Plexamp, most audiobook/podcast apps — bypass the restriction and launch reliably even from cold.

play <app> plays the wrong app (or no app)

Symptoms: you say "play Audible" and YouTube starts (or nothing plays).

The companion does fuzzy matching against the local app index. Common causes:

  • The index hasn't been scanned yet. Settings → Learning → Installed apps → Scan installed apps.
  • The app you said isn't installed (maybe uninstalled and the index is stale). Re-scan.
  • Whisper misheard the app name — e.g. "Audible" transcribed as "absorb" or "absolved". We handle some of these via reverse substring match ("absorbed" → "Absorb"), but truly garbled mishearings can't be auto-bridged. Open Settings → Learning → App aliases and pin the mishearing to the right app (e.g. absolved → Audible).
  • The app exists but doesn't publish a MediaSession — some apps (notably non-music apps and homebrew Pebble-era apps) won't respond to media-key events. We can launch them; we can't make them play if they don't register their session with Android.

Watch dictation works in another app but not in Wristotle

If whisperIMEplus / Gboard mic / voice search transcribe fine but the Pebble watch's dictation fails, the issue is in the watch ↔ BLE-companion handoff, not in Wristotle.

Try restarting both apps + the watch in this order: 1. Force-stop the BLE companion (microPebble / Core Devices). 2. Reboot the watch. 3. Reopen the companion. Verify the watch shows as connected. 4. Try dictation.


I'm not sure what's wrong — how do I file a useful bug report?

Open Wristotle Companion → Settings → Diagnostics → "Copy diagnostics + open Codeberg issue". This bundles app version + device info + permission state + active models + recent conversation rows + last ~200 lines of in-app logs into a markdown report, copies it to your clipboard, and opens the new-issue page on Codeberg. Paste, fill in a brief description, submit.

See Bug reports for what's in the bundle and what's redacted.


Why isn't my dictation being transcribed by Whisper?

If you're on Core Devices (formerly rePebble) as your BLE companion, the dictation pipeline is handled by Core Devices itself (local / cloud / hybrid — configurable inside Core Devices' settings). The transcribed text reaches Wristotle but the audio never does, so Wristotle's on-device Whisper isn't used.

This is a Core Devices design choice we can't override. Switch your BLE companion to microPebble if you want Wristotle's Whisper in the path (and the audio-capture feature, which depends on it).

You can confirm which companion you have from Settings → Models → Whisper — when running under Core Devices the card shows an explanatory banner instead of model controls. Other features (Call / SMS / Reminders / Open / Play app) work normally under both companions.


Some features are missing or greyed out (Whisper, audio replay, model download, Speech provider)

You're almost certainly on Core Devices rather than microPebble. The two BLE companions differ in exactly one thing — who transcribes your speech — and several Wristotle features depend on Wristotle being the transcriber:

  • On-device Whisper transcription — under Core Devices, it transcribes with its own engine (local / cloud / hybrid).
  • Cloud / self-hosted Speech provider — same audio-path dependency: the picker still appears in Settings, but the chosen endpoint is never invoked because the watch audio never reaches Wristotle on Core Devices.
  • Dictation audio replay (last 5) — the card is hidden; the audio never reaches Wristotle to save.
  • Whisper model download / selection — shows an explanatory banner instead; no model is needed.

Everything else — every voice command (call, text, reminders, calendar, notes, tasks, alarms, open/play app, media control) — works identically on both, because the command handling all happens phone-side after the words are recognised. See the full microPebble vs. Core Devices comparison in Features. To get the transcription-dependent features, switch your BLE companion to microPebble.


I imported a .bin and it says "Not a whisper.cpp model"

The Imported models picker checks the first four bytes of the file against the ggml/GGUF magic header before copying it in. The error includes those bytes — e.g. "first 4 bytes 0x25504446 (\"%PDF\") — expected \"lmgg\", \"GGUF\"" tells you the picker got a PDF instead of a model.

The two formats Wristotle accepts:

  • "lmgg" — whisper.cpp's GGML_FILE_MAGIC written as a little-endian uint32. This is what every ggml-*.bin file from ggerganov/whisper.cpp on HuggingFace starts with. (Yes — the constant is named 0x67676d6c // "ggml" in the source but the bytes on disk read as lmgg.)
  • "GGUF" — the newer unified ggml container. Read as ASCII bytes in order.

If the picker rejects a real whisper.cpp model with "lmgg" magic the file is genuinely corrupted or truncated — re-download. If the picker rejects something else (.nemo for Parakeet, .onnx, .safetensors, .tflite), the format isn't supported by Wristotle's whisper.cpp loader regardless of what file extension you gave it.

Browse compatible models: the "Browse whisper.cpp models on HuggingFace →" link in the same Imported section opens a filtered HuggingFace search.


I imported a model but my next dictation didn't use it

Two places to check:

  • Was it auto-activated? When you import a model and no other model was active before (fresh install, or you deleted everything), the import auto-activates. When something else was already active (e.g. tiny.en from the catalog), the import joins the list but stays inactive — you have to tap the new row in Settings → 🧠 Models → Imported to make it the active one.
  • Did you switch BLE companions? Whisper imports are only used under microPebble. Under Core Devices, dictation goes through Core Devices' own pipeline and never reaches Wristotle's Whisper. See Some features are missing or greyed out.

If you restored a backup and the active model can't be found, that's a known limitation: imported .bin files are too large to fit in a backup ZIP and aren't preserved across devices, but the active-model selection IS. Re-import the model from your storage on the new device, or pick a catalog model as the active one instead.


"Test connection" in Speech provider fails

The button posts 200 ms of silence to your configured endpoint and reports the response. Common failures:

  • API key rejected (401) — the bearer key field is wrong or empty for a provider that requires it. Verify in the provider's console (Groq → API Keys, OpenAI → API keys page). Re-paste — beware accidental whitespace around the key.
  • rate-limited (429) — you hit the provider's quota. Wait, or pick a less constrained provider.
  • server error (5xx) — the provider or self-hosted server is having a bad day. Re-test after a moment.
  • request failed with no HTTP status — DNS / connect / read timeout. Most often: the URL is wrong (typo in the host), the self-hosted server isn't actually exposing HTTP (some MCP-like servers default to stdio), or your phone isn't on the same network as the homelab. For Tailscale-exposed boxes, verify the Tailscale app on the phone is connected.
  • HTTP 404 or Not Found — the base URL probably doesn't include the /v1 suffix the provider expects. Most providers use /v1/audio/transcriptions and Wristotle appends only the /audio/transcriptions part, so your base URL needs to end at /v1.

If Test connection works but real dictations fail, the provider may be rate-limiting longer payloads — short test silence passes their fast path while a real 5-second utterance trips a quota. Check the provider's logs.


"Test connection" says "endpoint reachable (no transcript on silence)" — is that bad?

No — that's the success state for a clean configuration. Silence is sometimes legitimately transcribed to an empty string by Whisper-family models, and an empty response is proof the endpoint accepted the request shape, the auth was valid, and a real provider responded. If you'd rather see a transcript, the button isn't the place — dictate something via the watch or via a phone mic icon and the conversation history will show what came back.


Speech provider is set to "Cloud primary" but my dictation looks like local Whisper

A few possibilities:

  • The cloud call failed and the local recognizer kicked in. That's the fallback design — Cloud primary tries the configured endpoint first, falls back to your active Whisper model on any error. Check adb logcat -s HttpRecognizer:* CompositeRecognizer:* WhisperRecognizer:* during a dictation and look for "primary failed (…); falling back to secondary".
  • Speed. Cloud transcription on Groq runs sub-second; on a slow link it can be slower than local. The transcript still came from the cloud — there's no UI indicator of which side ran.
  • You're on Core Devices. Under Core Devices the picker is silently inert because audio never reaches Wristotle. See Some features are missing or greyed out.
  • The phone has no network. Cloud primary tries the cloud, fails on timeout, then falls back to local. The user sees a 100% local dictation.

To confirm what actually ran, tail logcat as above. The cloud success path emits "uploading N bytes to https://…" followed by "transcribed in N ms: '…'". The Whisper path emits "inference start: N samples" followed by "inference done: '…'".


"Home Assistant not set up." / Home Assistant doesn't respond

The watch tells you exactly which part is missing — the reply is specific by design:

  • "Home Assistant not set up." — the base URL or the access token is empty. Fill both under Settings → 🏠 Home Assistant. Until they are, Wristotle makes no network request at all.
  • "Say what? …" — the wake word ("home assistant" / "hey home assistant" / a custom word) was heard but no command followed. Say the whole thing in one breath: "hey home assistant turn off the lights."
  • "Home Assistant: token rejected." — the long-lived token is wrong or expired (HTTP 401/403). Create a fresh one in HA: your profile → Security → Long-lived access tokens → Create, and paste it in.
  • "Home Assistant: can't reach it." — the URL is unreachable: server down, wrong host/port, or your phone is off the network that can see HA (try the same URL in the phone's browser). LAN / Tailscale / self-signed http:// are all fine — you just have to be on a network that routes to it.
  • "conversation endpoint not found (404)" — the server answered but the path is wrong or the Conversation integration isn't enabled in HA. Enter just the base address (e.g. http://homeassistant.local:8123) — Wristotle appends /api/conversation/process itself — and make sure Conversation is set up in HA.

The command is picked up by the classifier (routes to Home Assistant) before any of this — so these replies confirm the voice routing works and the problem is purely the connection/config. Nothing sensitive (URL or token) is ever written to logs or the Diagnostics report (it shows only (set) / (empty)).


Conversation history disappeared

Two retention controls can wipe it:

  • Settings → Conversation → History → Retention — shortening the window auto-prunes entries older than the new limit. Defaults to 10 days.
  • Settings → Conversation → History → Clear all — wipes everything immediately.

If neither was changed but history is gone, the app's private storage may have been cleared (Android settings → Apps → Wristotle Companion → Storage → Clear data).


"Audio capture" toggle is off but I want the recordings

By default Wristotle does NOT save the raw recording of your dictations — audio is materially more sensitive than transcripts so it's opt-in. To enable:

Settings → Conversation → Audio → Save dictation audio (toggle on).

After that, each dictation's WAV is saved in the app's private storage and an inline play button appears on the matching chat row. Only the last 5 recordings are kept; older ones are auto-deleted.

To delete all saved audio without flipping the toggle: same card → Delete saved audio.

microPebble only

The audio-capture card is hidden under Core Devices — and there's no way around it. Audio capture works by saving the bytes as Wristotle's on-device Whisper transcribes them, which only happens on the microPebble dictation path. Under Core Devices, dictation is handled by Core Devices' own engine and only the finished text reaches Wristotle, so there's no audio to save. If you don't see the card, that's why. (Same boundary as on-device Whisper transcription itself.)


Tasks

The watch shows fewer tasks than I have

The on-watch Tasks list caps at 16 rows. If you have more than that — especially when you pick the "All" filter and the count includes completed tasks — older entries get dropped from the wire response. The companion's Tasks tab is the full list; use the watch view as a quick-action surface.

Two ways around it: keep the pending list short by clearing completed work (use the "Clear all" button or "delete task X" by voice), or just open the companion to see everything.

Completed tasks show [x] instead of a checkmark

That's a Pebble font limit. The Unicode character (U+2713) isn't in Pebble's built-in GOTHIC fonts; it renders as a fallback rectangle ("tofu"). The [x] prefix is plain ASCII so it always renders cleanly and reads unambiguously as "checked" in a list.

Tapping SELECT on a completed task in the watch list does nothing

By design for v1. SELECT-to-complete only applies to pending rows. To remove a completed task, either:

  • Say "delete task " from the watch, OR
  • Use the trash icon on the companion's Tasks tab.

Re-opening a completed task via voice isn't shipped yet — use the filled-checkmark icon on the companion's Tasks tab to move it back to Pending.

completed tasks shows my completed list instead of completing a task

That's intentional. Saying "completed tasks" (or "done tasks" / "finished tasks") is interpreted as a filter on the list query — same as "what are my completed tasks" — because the past-tense verb in that position is acting as a list modifier, not a command verb. To mark a task complete, name it explicitly: "complete buy milk" / "finish buy milk" / "done with buy milk".

"No task matching ''" when the task obviously exists

The match is substring case-insensitive over your pending tasks. If you say "complete dentist" and your stored task is "Call the dentist", the substring "dentist" is in the task text — that's a hit. If you say "complete buy 2% milk" but the stored task is "buy milk", the substring "buy 2% milk" is NOT in "buy milk" — no hit, and you get the "No task matching" reply.

Two common causes:

  • Whisper transcribed your speech differently than what's stored. Look at the watch chat — if you said "complete buy milk" but the transcript reads "complete by milk", that's why it didn't match.
  • The target wording diverged from the stored task. Voice the stored text more literally, OR use the shortcut: "complete the last task" / "latest" / "most recent" picks the newest pending task without text matching.

"Multiple matches: A, B, … — which one?"

Your target matched more than one pending task. Re-say with more text so only one row substring-matches. Example: pending list has "buy milk" and "buy bread"; "complete buy" matches both → "Multiple matches: buy milk, buy bread — which one?". Re-say "complete buy milk" and the row disappears.

"Add task to call the dentist" creates a meeting instead

Fixed in companion v0.9.0 — earlier versions had CreateEvent ("schedule a call with X") match before AddTask in the routing pipeline because both contained "call". If you still see this on the latest companion + watch pair, capture a diagnostic bundle (Settings → Diagnostics) and file an issue.


Watch notes view shows only some of my notes

The on-watch Notes list caps at 6 entries — the same Pebble outbox-budget reasoning as the Tasks cap. The companion's Notes tab has every note you've ever saved (subject to the keep-last-N retention setting in Settings → Notes). The watch view is the quick-glance surface; the companion is the archive.

If you want more notes accessible from the watch, lower the keep-last-N setting in the companion so the 6 most-recent stay relevant.


Backup & Restore

"Restore failed" with no other detail

The Restore dialog wraps the underlying error in the message. Common causes:

  • Not a Wristotle ZIP. The Open dialog accepts application/zip but doesn't verify the contents until the import runs. A random ZIP picked by mistake fails with "Backup is missing manifest.json".
  • Corrupted download. A backup pulled from a flaky cloud sync can land truncated. Re-download from the source.
  • Out-of-space. Restore needs enough cache space to stage the extracted contents (roughly the ZIP's size again, more if audio is included). Free up space and retry.

If none of those fit, capture a diagnostics bundle right after the failure (Settings → Diagnostics) and file an issue.

"Wrong password — please try again"

The dialog re-shows itself with that line in red. Re-enter and unlock. If you don't remember the password, the backup cannot be restored — Wristotle does not store passwords and there is no recovery path. AES-256 isn't bruteforceable on consumer hardware for any sensible password.

"Update Wristotle Companion to restore this backup"

The backup was created by a newer version of Wristotle Companion than the one you're running now. The result dialog will name the specific entity (e.g. "notes" or "conversations") that was too new to decode. Update the app and try again.

Restore says everything is a "duplicate"

That's the expected result when you restore your own freshly-exported backup onto the same device — every row dedupes against itself. The Total · Duplicates · Failed columns will all line up; Imported is zero. If you want to actually test a restore, delete a few rows on the phone first, then restore.

Audio is in the backup but I don't see play buttons after restore

Two possibilities:

  • The note row's audio basenames don't match any extracted file — likely if you restored a partial backup (created without "Include audio recordings"). The Note rows came back but Wristotle has no audio file to point at, so the play button stays hidden.
  • The audio collision-rename happened silently. If a local audio file with the same name already existed but with different bytes, the importer keeps the local copy and renames the incoming one to <basename>-imported-<exportedAtMs>.wav. Look in the companion's notes-audio directory if you're surprised by the count.

Restoring a backup that does include audio onto a wiped install should produce play buttons on every note row that had them at export time.

I want to be sure my backup is good before wiping the phone

Two ways:

  1. Restore it back onto the same phone before wiping. Every row should report as a duplicate; failures should be zero. That confirms the ZIP decrypts (if encrypted) and all entities decode.
  2. Inspect the plaintext ZIP on a desktop. If you exported without a password, unzip extracts it and jq reads each data/*.json. Counts in the manifest should match jq '.rows | length' on each data file.

Ask Agent

Replies are full of <think>…</think> blocks instead of the answer

You're pointing Ask Agent at a reasoning model — Qwen3, DeepSeek-R1, QwQ, or a derivative — and the inference server is dumping the model's chain-of-thought into the response body alongside the actual answer. Wristotle reads the OpenAI-compat choices[0].message.content verbatim and shows it on the watch, so you see the reasoning as text.

The fix lives on the inference-server / request side, not in Wristotle (every reasoning-model family handles this differently — encoding all of them in the client would be brittle whack-a-mole). Pick whichever applies to your stack:

  • vLLM running Qwen3 / DeepSeek-R1 / QwQ: start the server with --enable-reasoning --reasoning-parser deepseek_r1 (or the per-model parser name from the vLLM docs). With that flag, the server moves the chain-of-thought into a separate reasoning_content field that Wristotle doesn't read — content becomes the clean answer.
  • OpenAI-compatible chat-template-kwargs support (some servers): pass "chat_template_kwargs": {"enable_thinking": false} in the request. Wristotle doesn't have a UI for adding arbitrary fields to the request body yet, so this path requires a proxy in front of your server that injects the kwarg.
  • Append a system-prompt instruction in Settings → ✨ Ask Agent → System prompt: Provide only the final answer. Do not include <think>…</think> blocks or any chain-of-thought. This works sometimes on Qwen3 but is unreliable — the models were trained to think and often ignore the instruction. Use the server flag instead if you can.
  • Switch to a non-reasoning model. Qwen3-14B-Instruct, Llama-3.3, Mistral-Small, GPT-4o-mini — anything without "reasoning" / "R1" / "thinking" / "QwQ" in the name avoids the issue entirely. Reasoning models are valuable for hard tasks but rarely improve the kind of one-shot Q&A Wristotle's voice loop is built for.

Anthropic's Claude (with extended thinking) and OpenAI's o-series handle this server-side — <think> blocks never appear in the wire response for those providers.

Ask Agent answers nothing useful — "I don't have access to real-time information" or stale facts

The base model only knows what was in its training corpus. For news, sports, today's weather elsewhere, etc. the model has no live data unless you:

  • Add a web-search MCP server under Settings → 🔌 MCP servers — Brave, Tavily, Exa, or a self-hosted search MCP. With at least one search MCP enabled, the agent loop can pick it as a tool and feed fresh results back to the LLM.
  • Point at a search-grounded endpoint — Perplexity Sonar models (via OpenAI-compatible mode at https://api.perplexity.ai) have search baked into the model itself; no MCP needed.

Per-model web-search defaults vary; the MCP path is the universal answer.