Voice input should exploit local context
Before system-wide apps like Wispr Flow and
Superwhisper ate dictation, coding agents rolling
their own /voice input took pains to achieve typo-free input. They did so by
exploiting local context. System-wide dictation apps have more local context,
not less, and can build integrations in cases where the OS itself cannot expose
context. So, they should support context-aware dictation, pulling in structured
high-value data instead of what they support today — selected text, clipboard
content, and a hand/auto-maintained dictionary. They shouldn’t just do this in
coding, where coding agents’ /voice provides reference implementations, but in
Slack, email, meeting notes, and Docs — and eventually in prompts to agents
that write legal contracts and fill electronic health records (EHRs).
The reference implementation: coding agents
For example, open-source coding agent Aider has a clever detail in its
voice.py.
Instead of simply passing in the user’s recorded speech to a speech-to-text
(STT) model
for transcription, Aider does “vocabulary biasing” by pasting in the past chat
turns as a prompt:
transcript = litellm.transcription(
model="whisper-1", file=raw_wav_recording, prompt=history, language=language
)
This is a trick from ye olde Whisper
docs:
“By submitting the prior segment’s transcript via the prompt, the Whisper model
can use that context to better understand the speech and maintain a consistent
writing style. However, prompts do not need to be genuine transcripts […].
Fictitious prompts can be submitted to steer the model to use particular
spellings or styles.” The chat history contains identifiers and jargon to bias
recognition towards the right spellings.
But Aider’s intentional minimalism (the whole voice.py is under 200 LoC) is
just the simplest way to vocabulary-bias transcription with local context. A
certain de-minified coding agent’s source instead seeds requests to an
enterprise STT API with
“keyterms” — vocabulary hints
about words that are especially easy to mistranscribe — from the most
error-prone bits of local context. It draws keyterms from three broad buckets:
- Coding terminology (
symlink,grep,gRPC,dotfiles). - The project and branch names.
- Recent files.
The file itself looks something like this:
# Provides domain-specific vocabulary hints so the STT correctly transcribes
# coding terminology. Skip words never spoken aloud as spelled, e.g., "stdout"
# -> "standard out".
GLOBAL_KEYTERMS = (
"MCP", "symlink", "grep", "regex", "localhost", "codebase", "TypeScript",
"JSON", "OAuth", "webhook", "gRPC", "dotfiles", "subagent", "worktree"
)
def split_identifier(name: str) -> list[str]:
# Split camelCase/PascalCase/kebab-case/snakeCase/path identifiers into
# their spoken words:
# "feat/stream-grok-responses" -> ["feat", "stream", "grok", "responses"]
# Drop fragments <= 2 chars to avoid wasting keyterm slots.
spaced = re.sub(r"([a-z])([A-Z])", r"\1 \2", name)
return [
w
for w in (part.strip() for part in re.split(r"[-_./\s]+", spaced))
if 2 < len(w) <= 20
]
def file_name_words(file_path: str) -> list[str]:
# Strip directory and extension, then split the stem:
# "packages/tui/src/component/dialog-retry-action.tsx" -> ["dialog", "retry", "action"]
stem = re.sub(r"\.[^.]+$", "", basename(file_path))
return split_identifier(stem)
def get_voice_keyterms(
git_branch: str | None = None,
project_name: str | None = None,
recent_files: dict[str, object] | None = None
) -> list[str]:
terms: dict[str, None] = dict.fromkeys(GLOBAL_KEYTERMS)
# Kept whole: the project name is semantically a single keyterm, and
# matched as one.
if project_name and 2 < len(project_name) <= 50:
terms[project_name] = None
if git_branch:
for word in split_identifier(git_branch):
terms[word] = None
# dicts preserve insertion order, so the top files in recent_files get
# preserved in the sliced terms.
if recent_files:
for file_path in recent_files:
if len(terms) >= MAX_KEYTERMS:
break
for word in file_name_words(file_path):
terms[word] = None
return list(terms)[:MAX_KEYTERMS]
Other apps
The most obvious target for context-aware dictation is Slack. Extract top
members, channels, and custom emoji names from the workspace, and let this cache
resolve spellings (is it @Ishan or @Ishaan?). Similarly, you can ensure
dictation and live transcription never get an email recipient or meeting
attendee’s name wrong — keyterm all of the To: field or the attendee list.
Intelligent keyterming in an editor like Docs/Coda/Notion is more sophisticated than the static reference implementation in coding. Aider’s prompt prefixing (send the ~200 tokens before the cursor to Whisper) is a fine first pass, and keyterming the top N uncommon words in the doc is a reasonable second pass.
Legal contract drafting and EHR filling are similar to the coding reference implementation, being forms of technical writing themselves. Clinical terminology is filled with Greek/Latin constructions that STT will reliably get wrong. Legal writing is similarly stuffed with Latin phrases. Each benefits from global keyterm lists that come online when the cursor is in the right app (Epic for EHRs, MS Word for legal drafting). Each also benefits from local context. The active medication and diagnoses list in EHRs is (1) full of difficult names, and (2) structured for extraction. A legal drafting project has (1) enough parties and authorities that a mistranscription-free sentence is a surprise rather than expected, and (2) collects them all in a structured way.
(I expect dictation app integrations into many of these apps will not really live in the OS layer because of PII-handling requirements. Fine — the integration lives as a partnered relationship in Epic, but nothing about the principles of using domain context changes.)
Now, a stock response to any careful-deliberate-design proposal like this one is to gesture at Rich Sutton’s bitter lesson. Here, you’d say something like: “soon, LLMs will be strong enough and fast enough that you can run one over a broken STT transcript and fix it near-instantly.” True, but two responses. First, no amount of intelligence gets those spellings right which depend on local context — a genius would still need to ask me how my first name is spelled. The glue work of threading in local context is unavoidable if you want a pleasant dictation experience. Second, the fact is that today, dictation often gets technical terms wrong. Until GPUs are cheap enough and strong LLMs are small enough to proofread every technical STT transcription, hand-tuning a short global keyterms list is a week’s ticket.