OpenWispr
Star Download
From the project — reliability & personalization

Why we built OpenWispr's retry safety net the way we did

A first-person account of one bug we refused to ship again, the write-ahead fix that came out of it, and how the same don't-lose-it principle shows up again in how OpenWispr learns from you.

Download for macOS Get it on Google Play
OpenWispr/Journal/Why we never lose your audio
From the project This is a first-person account from the people building OpenWispr, about how we use our own app day to day — not a customer testimonial, and not a fictional user. Every feature named links to the source that implements it.
The bug we refused to ship again

A five-minute recording lived only in RAM. One failure took it with it.

Before OpenWispr's write-ahead audio store existed, a dictation's audio lived only as an in-memory sample buffer while it was being transcribed. That was fine right up until it wasn't: a transcription error, the recording activity getting destroyed, or the OS killing the process mid-attempt all had the same result — the audio was gone, and there was nothing the user could do to get it back. A long, carefully-thought-out recording and a one-line note failed exactly the same way: completely, with no trace.

That's a bad trade against a genuinely rare failure. Most dictations transcribe fine on the first try. But when one doesn't, losing the only copy of what you said turns an inconvenience into an actual loss of work — and it's precisely the moment a user has the least patience for the app failing them.

Write-ahead, not best-effort

The recording is durable before it's ever transcribed

The fix is a small, specific ordering guarantee: the audio is written to on-device storage before the first transcription attempt is made at all, on both the on-device and the cloud transcription paths — not after a failure is detected. Its lifetime is deliberately decoupled from any single attempt, and nothing in the app ever deletes it from an error-handling path. It's written to the app's persistent files directory rather than a cache directory, specifically because a cache is exactly what the OS is allowed to silently reclaim under storage pressure — precisely the moment a user would want their recording back.

The state tracking follows the same discipline. A sidecar file records only two things: the facts known when recording started, and — once a dictation actually lands — its result. There is deliberately no "currently transcribing" flag written to disk, because that kind of flag can lie: if the app is killed mid-transcription, a stale "in progress" marker would sit there forever, and any attempt to clean it up after the fact is itself a source of bugs. Instead, "in progress" is tracked only in memory. If the process dies, that in-memory state is simply gone — and the recording correctly, automatically reads as unfinished and offered back for retry, with no separate repair logic needed.

A retry that isn't just "try again"

If the first attempt failed, running the identical attempt again usually won't help

A transcription failure is often deterministic — the same audio through the same model tends to fail the same way twice. So when a saved recording gets retried, the app defaults to a different transcription engine than the one that just failed, rather than silently repeating the identical attempt and hoping for a different outcome. That's a small decision, but it's the difference between a retry button that's mostly theater and one that's actually a second, meaningfully different attempt.

Recordings that fail and get abandoned rather than retried immediately aren't deleted either — they sit on-device, listed as unfinished, retryable whenever you come back to them. They're only ever cleared out by an explicit, bounded retention window, and a settled (successfully transcribed) recording is treated completely differently from one that's still unresolved: retention is never allowed to be the thing that quietly loses a dictation you haven't gotten your words back from yet.

The same idea, one layer up: don't lose corrections either

OpenWispr's four personalization layers, in the order they cost the least

Once "don't lose the raw material" was solved for audio, the same instinct applies to something smaller but just as easy to throw away by default: every time you correct what the app produced, that correction is a signal about how you actually want to be transcribed. OpenWispr keeps that signal, gated entirely behind a single "Keep history" switch, in four layers that build on each other, cheapest first:

Personal vocabulary (L1) — corrected names and terms are remembered and applied right after transcription, plus fed back into the transcription model itself as a small decoding bias, so frequently-corrected words get heard correctly more often over time, not just fixed after the fact.

Correction corpus (L2) — every accepted dictation is recorded as what the pipeline produced versus what you actually kept, in a capped, rolling on-device log. When the two differ, that's a real correction, and it's the shared raw material the next two layers draw from.

Few-shot polish (L3) — when the optional LLM cleanup step is set to Medium or Full (not Light, and not Off, to keep the on-device model's context small), the two most similar past corrections are pulled from that log and shown to the model alongside the current dictation, so it matches how you've actually preferred your own text cleaned up — without any retraining. The matching is a straightforward keyword-overlap score, not a heavier embedding-based search, because the log is small enough that a simple scan is instant and doesn't need an extra on-device model just to do retrieval.

Periodic re-fine-tune (L4) — for anyone who wants to go further, the corrected samples can be exported from the app as a plain file. Actually retraining a model from that export happens outside this app entirely, in a separate, dedicated project — this app's job stops at handing you your own data in a usable form, not silently uploading it anywhere to be trained on.

What never leaves the device, and what does — only on request

One switch, one honest exception

All four personalization layers, like the audio store above them, live entirely on-device and are gated by the same "Keep history" preference: turn it off, and nothing is kept — including audio that's still sitting unfinished, which is purged outright rather than lingering. The one deliberate exception is L4's export, and it's opt-in in the most literal sense: the app writes your corrected samples to a file only when you explicitly ask it to, so you can move that file yourself if you choose to use it. Nothing about the retry safety net or the three learning layers beneath it depends on a server existing at all — the entire point of building it this way was that there's nothing to lose to a server that isn't there, and nothing that leaves your device unless you're the one who moved it.

Questions

Good questions, honest answers.

Did OpenWispr always keep audio if a transcription failed? +
No — this is a first-person account of a real gap that existed before the write-ahead audio store was built: audio used to live only in memory during transcription, so a failure, an app-process kill, or a crash could lose the recording entirely. The write-ahead store is the fix, not a feature that was always there.
Why write audio to disk before transcription even starts, instead of only on failure? +
Because coupling the audio's lifetime to a single attempt is exactly the bug being fixed. Writing it ahead of time, to persistent storage rather than a reclaimable cache, means its survival doesn't depend on that attempt succeeding, being retried correctly, or the app staying alive.
Why does retry switch to a different transcription engine by default? +
Because a transcription failure is often deterministic — retrying with the exact same model on the exact same audio tends to fail the same way again. Defaulting to an alternate engine on retry gives the recording a meaningfully different second attempt instead of repeating the first one.
What are the four personalization layers, in short? +
L1 personal vocabulary (corrected names/terms, plus a decoding bias fed back into transcription), L2 a capped on-device log of corrections, L3 few-shot examples pulled from that log into the optional LLM-polish prompt at Medium/Full polish, and L4 an explicit, user-initiated export of corrected samples for anyone who wants to retrain a model outside the app.
Does any of this leave the device without being asked? +
No. All four layers and the audio store are gated by the same on-device "Keep history" switch. The only path any of it leaves the device at all is L4's export, and that only happens when explicitly triggered — it is never uploaded automatically.
Journal
How we dictate commit messages and PR descriptions with OpenWispr
Compare
OpenWispr vs VoiceInk (failure-recovery UX)
Use case
One free, open-source app for macOS and Android

Start dictating privately.

Open source and entirely on your device. Android from Google Play, macOS from GitHub — no account, no sign-up, just a download.

Download for macOS Get it on Google Play
OpenWispr Voice to text, on your device.
GitHub Privacy Policy Features vs Wispr Flow vs superwhisper On-device Android dictation MIT © 2026