Key takeaways

  • Split cleanup into pattern work and judgement work. Pattern work belongs in code, where it is fast, free and auditable. In the published evaluation eight regular expressions resolved about a fifth of the transcripts needing an edit, with perfect restraint and no content loss.
  • The remaining edits are not harder patterns, they are decisions. Whether a repeat is a stutter or emphasis. Whether "period" is punctuation or a fiscal quarter. A pattern that cannot tell should decline, and a rules-only pipeline therefore has a ceiling.
  • The order is normative. Jargon and filler fixes run before the model; spoken emoji and the user's own replacement rules run after it, because the user wrote those and the model did not.
  • Restraint is a training property, not a size property. A hosted 9B model and an untuned 0.8B model scored identically on restraint, 89.7% each. Fine-tuning the 0.8B model moved its edit accuracy from 4.9% to 48.8%.
  • Add a do-nothing baseline before you trust any score. On an unweighted mean of restraint and edit accuracy it lands at exactly 50.0%, and four of the seven systems tested scored below it.

Disclosure: SpeakoFlow is our product, the rules layer and the model described here are ours, and I wrote the evaluation they are scored on, so read this as an informed account rather than a neutral review. A hosted frontier model remains better than our 0.8B model in unconstrained everyday use, and where it wins is stated below.

Dictation cleanup works best as two stages: deterministic rules for the fixes a pattern can make, then a small fine-tuned model for the fixes that need a decision about what the speaker meant. One model doing both jobs is slower, more expensive, and measurably worse at the half that matters most, which is knowing when to leave correct text alone.

This is the design behind SpeakoFlow's cleanup, and both halves are published: the rules layer under MIT and SpeakoFlow Mini under Apache-2.0. What follows is the reasoning, including the places the reasoning has a ceiling.

What does the recogniser already do for you?

More than most cleanup features assume, which means a lot of cleanup code is solving a problem that no longer exists. Punctuation, capitalisation, filler removal and number formatting are recogniser-side features now.

Say "the deposit is three hundred dollars, um, and the meeting is at nine thirty" into SpeakoFlow with AI cleanup switched off. What comes back is "The deposit is $300 and the meeting is at 9:30". No language model was involved. Start by finding out what your own pipeline already produces, because whatever it already handles is work you should not be paying for twice.

Key point

Before writing any cleanup, run a dozen real dictations through your recogniser with cleanup disabled and read the raw output. The residue is the actual job. It is much smaller than "clean up this transcript" suggests, and almost all of it is judgement rather than formatting.

Which fixes belong in code rather than in a model?

Anything where the correct output is a function of the input rather than an opinion about it. Removing "um" is a table lookup. Collapsing "wh wh wh what" is a loop. Spelling a company name the recogniser has never heard is a dictionary. Turning "thumbs up emoji" into an emoji is a lookup with one guard on it.

Sending those to a language model costs latency, costs money on every API call, and buys variance nobody wanted, because a model told to drop a filler word will sometimes rewrite the sentence around it. A deterministic stage gives you a property a model cannot: the same bytes out on every run, as a pure function of the text and its configuration, with no network and no randomness. The whole rules pass in our implementation costs about a millisecond.

The published rules layer is four functions over a string, arranged as pattern tables plus a written specification. Filler tables are per-language and deliberately not translations of each other, which is the subtlety most implementations miss: "um" is an English filler and the Portuguese word for "a" or "an", and "ha" is Spanish for "has". A single global filler list quietly corrupts text in every language it was not written for. A missed "um" is invisible; a deleted article is a bug report.

Writing these rules from memory does not work, and the repository publishes the audit that proves it, with the sentence that killed each discarded rule.

Rules a reasonable person would write, and the real sentence each one breaks. From the audit published in dictation-cleanup-rules.
The obvious rule The sentence that kills it
Replace the spoken word "comma" with a comma"the supplementary file is comma separated"
Replace the spoken word "period" with a full stop"for a period"
Collapse any doubled word"Alright, alright, alright"

The surviving rules are the ones that pass an audit against a set of transcripts that are already correct: each rule is checked on its own, and the check fails loudly if any single rule alters one of them. That is the bar a deterministic stage has to clear. It is allowed to decline; it is not allowed to damage.

The measured version of this argument comes from the benchmark. Eight ordered regular expressions resolved roughly a fifth of the transcripts that needed an edit, at perfect restraint across every already-correct transcript and zero content loss. That is the mechanical share of the task, and it is free. Any product shipping a model without a pattern layer in front of it is paying for inference on work a substitution does correctly.

Which fixes genuinely need a model?

The rest, and they are not harder versions of the easy ones. They need a decision that a pattern cannot express, and there are three clean examples.

  1. Stutter or emphasis. "the the deadline" is a stumble to collapse. "Alright, alright, alright" is not. The strings look the same to a rule.
  2. Homonyms in dictation commands. "period" is punctuation, a fiscal period, or a historical era, depending entirely on the sentence around it.
  3. An instruction, or something you said. A mid-clause "new line" is a layout command in one sentence and part of the content in another, and when the recogniser has supplied no punctuation there is nothing mechanical left to distinguish them.

Add to those the cases where the recogniser was accurate and you were not: you corrected yourself mid-sentence, you restated a name more precisely, or you said a word that transcribed perfectly and was still the wrong word. "In the mourning" is spelled correctly. It is also not what you meant.

The model stage should be allowed to fail. If it times out, errors, or comes back empty, discard its output and carry the text from the previous stage forward. Model output is advisory. Treating it as required is how a cleanup feature turns a working dictation into nothing at all.

Why is the order of the stages not negotiable?

Because two of the rule stages fix things the model would otherwise reason about, and two of them assert things the model has no right to overrule. So the model sits in the middle, and reordering changes the output.

Five stages, and where the model goes
1. Vocabulary correction
Before the model. Fix jargon and proper nouns the recogniser rendered as ordinary words, so the model never has to guess at them. Skip this stage entirely if your recogniser accepts a bias or hot-word prompt, because steering the decoder is strictly better than repairing its output.
2. Filler and stutter removal
Before the model. Deletion only. It must not add, reorder or re-case a single word it is not deleting, and it has no notion of a sentence.
3. The model
The middle. The only stage allowed to be wrong in an interesting way. Everything around it is a table.
4. Spoken emoji expansion
After the model. Off by default. The trailing keyword is mandatory, which is the single constraint that stops the word "fire" becoming an emoji.
5. The user's replacement rules
After the model, last. The user wrote these and the model did not. Someone whose company is spelled ChargeBee must win against whatever the model produced. Run this first instead and the model gets to undo it.

Stage 1 also carries the clearest limit in the whole design, and it is worth knowing before you build on it. Its scan looks at windows of up to three words, so a spoken acronym longer than three words cannot match cleanly: "Chat G P T" comes out as "ChatGPT T". That is the strongest argument in the repository for biasing the recogniser's decoder rather than running stage 1 at all. Put the work at the cheapest layer that can do it correctly, and sometimes that layer is upstream of your rules entirely.

Why a small fine-tuned model rather than a big prompted one?

Because the property you most need cannot be bought with parameters. In the published evaluation, a hosted 9B model scored identically to an untuned 0.8B model on restraint, 89.7% each, at eleven times the size. A hosted frontier model scored ten points worse on restraint than the 0.8B fine-tune.

What did move the numbers was training. Against the identical untuned base at the same file size, quantisation, prompt and hardware, edit accuracy went from 4.9% to 48.8%, a gain of 23.4 points overall with a 95% confidence interval of +16.3 to +30.3. Restraint went from 89.7% to 92.6%, which this set cannot resolve, so the defensible claim is that restraint did not fall.

4.9% → 48.8%edit accuracy, same base model, fine-tuned
89.7%restraint for both a 9B hosted model and an untuned 0.8B
833 MBthe shipped reference build, Q8_0

The mechanism is straightforward. A fine-tuned model has the convention in its weights. A prompted model has to be told the convention on every single call, and then has to follow it. Training on a written specification beats prompting for it, and at 0.8B that difference is first-order rather than marginal.

Examples of a boundary work. Instructions about the boundary do not.

There is a practical corollary if you are prompting rather than training. Two settings do most of the damage. Capping output tokens turns a long transcript into lost content, and leaving reasoning enabled fails every already-correct case, because any reasoning text in the output means the output is no longer byte-identical to the input. Neither failure has anything to do with cleanup ability.

Output tokens divided by the rows each model got exactly right, ranked, with the 0.8B fine-tune lowest at 310 tokens.
310 output tokens for each row it gets exactly right. Every model produced within a few percent of the same total output, so raw token counts separate nothing. Lower is better.

How do you tell whether your cleanup is helping?

Score restraint and edit accuracy separately, then put a baseline next to them that returns every input unchanged. Without that baseline you cannot read your own numbers.

Restraint is the share of already-correct transcripts your system returns untouched. Edit accuracy is the share of transcripts needing a change that came back exactly right. Take the unweighted mean of the two and the do-nothing baseline lands at exactly 50.0%, so anything under 50 is worse than shipping nothing. Four of the seven systems tested landed below it, and two of those look safe if you read only the restraint column.

Three practical notes from building this evaluation, each of which cost something to learn.

  1. Use exact string match, and no partial credit. Measuring how much strictness costs is better than arguing about it: forgiving every formatting-only miss moved the leaders by about three to four points each and changed the top three not at all.
  2. Report both halves next to any aggregate. A model can reach a respectable mean from either direction, and the mean alone hides which. A restraint-only ranking is actively misleading, because it promoted a 9B model above a hosted frontier model that beat it overall by a resolved margin.
  3. Do not trust a model as a judge without checking agreement first. Four frontier models from four vendors adjudicated the same disputed cases here and agreed on too few of them to use, so the choice of judge would have moved the result more than the answers did. That tier was dropped rather than tuned, and no judge score was published.

Keep a damage figure outside the score too. Content damage, the share of transcripts that lost a word the speaker actually said, sits at 10.7% for SpeakoFlow Mini. It stays out of the aggregate because any penalty weight for it would be arbitrary, and it stays published because losing a word is the failure users notice.

Restraint and edit accuracy as paired bars per model, showing several models with high restraint and almost no editing ability.
A model can look restrained and still fix almost nothing. Restraint is free on the transcripts whose correct answer is the input, which is why it is never reported here without edit accuracy beside it.

How does two stages compare with one hosted model doing everything?

Under one fixed short prompt with reasoning off, the two-stage local setup matches a hosted frontier model on this task: a 5.8 point gap with a 95% interval of -1.5 to +12.9, which contains zero. That is a tie, not a win. Only on passages of 500 words and up is the gap resolved, at +20.9 points.

Two approaches to the same job, as measured on 30 August 2026. Latency figures are not comparable across the local and hosted boundary and no latency win is claimed.
  Rules plus a 0.8B local model One hosted frontier model
Overall score70.7%65.0%
Restraint92.6%82.4%
Passages of 500+ wordsAhead by a resolved marginWeakest band
Pattern fixesAbout a millisecond, deterministicCharged as inference on every call
Unanticipated casesWeakerBetter, and noticeably so
Languages other than EnglishUntestedBroad coverage
Network requiredNoYes

Where the hosted model genuinely wins is worth spelling out. Real dictation produces oddballs no category anticipated, and on those a much larger model with more general understanding reads the situation better and fixes it better. It was also not tested at its best here: both systems got the same short prompt with no reasoning budget, which is the configuration the small model was trained for. And on the subset remaining after an internal audit removed defective scored cases, the hosted model's edit accuracy comes out ahead, 55.9% against 54.4%.

So the finding is not that small beats large. It is that a specification in the weights beats the same specification in a prompt, and that the pattern work should never have gone to a model in the first place.

Frequently asked questions

Why not just send the transcript to one good language model?

Because a model told to drop the word um will sometimes rewrite the sentence around it. Deleting a filler word is a table lookup, collapsing a stutter is a loop, and spelling a company name is a dictionary. Sending those to a model costs latency, costs money on an API, and buys variance nobody asked for. In the published evaluation, eight regular expressions handled about a fifth of the transcripts that needed an edit with perfect restraint and no content loss.

Why not use rules for everything?

The remaining edits are not harder versions of the easy ones, they need a decision a pattern cannot express. Whether a repeated phrase is a stutter to collapse or emphasis to keep. Whether the word period is punctuation, a fiscal period, or a historical era. Whether a mid-clause new line is an instruction or something the speaker said. A rule that does not match should decline, and that is what it does.

Does the order of the stages matter?

Yes, the order is normative and reordering changes the output. Vocabulary correction and filler removal run before the model, because they remove problems the model would otherwise have to reason about. Spoken emoji expansion and the user's own find-and-replace rules run after the model, because the user wrote those and the model did not. A user rule saying their company is spelled ChargeBee must beat whatever the model produced.

Why a small fine-tuned model instead of a big prompted one?

Because restraint turns out to be a training property rather than a size property. In the published evaluation a hosted 9B model scored identically to an untuned 0.8B model on restraint, 89.7% each, and a hosted frontier model scored ten points worse than the 0.8B fine-tune. Fine-tuning the 0.8B model took its edit accuracy from 4.9% to 48.8% at the same file size, prompt and hardware.

How do I tell whether my cleanup is actually helping?

Score two things separately: the share of already-correct transcripts your system returns untouched, and the share of transcripts needing a change that come back exactly right. Then add a baseline that returns every input unchanged. On an unweighted mean of those two rates that baseline scores exactly 50.0%, so any system below 50 is worse than leaving the text alone. Four of the seven systems tested landed below it.

Can I use the rules layer without the model?

Yes. The rules layer is published on its own under an MIT licence with reference implementations in Python and TypeScript, a written specification, the pattern tables, and a conformance suite of 79 fixed input and output pairs that both implementations pass. It has no model, no weights and no GPU requirement, and the whole pass costs about a millisecond.

Abhishek Barali

Maintainer of SpeakoFlow, a free and open-source local-first voice layer for Windows, macOS, and Linux. Every score quoted here was read off the published SpeakoFlow Mini model card on 30 August 2026, and the rule behaviour and audit from dictation-cleanup-rules. Corrections are welcome in GitHub issues.

Both stages, running on your own machine

SpeakoFlow ships the rules layer and the cleanup model together, offline. The rules layer is also usable on its own in any project, in Python or TypeScript.

Windows, macOS, and Linux. MIT licensed. No account, no telemetry.