Skip to main content
This guide focuses on Coda, but the same patterns apply to the Mist model family, which shares Coda’s normalizer and spell() function.
Building a voice agent means asking an LLM to write text that will be spoken, not read. That’s a different job from what LLMs do by default: they’re trained on text and post-trained for grammatical correctness, so when you send their output to a TTS model, it tends to sound like written prose read aloud rather than natural, conversational speech. Your LLM has two problems to solve: sounding like a person, and producing text the normalizer can read cleanly. A drop-in system prompt at the end covers both.

Sound like a person

The goal is to make callers comfortable enough to talk naturally, which leads to better outcomes than a stiff, overly formal agent. You aren’t trying to fool anyone into thinking they’re talking to a human. Real speech meanders: fillers, restarts, soft pauses, the occasional “yeah, no.” Coda doesn’t accept SSML: no <break>, no <emotion>, no inline tags except spell(). Rime keeps the interface deliberately small. Coda reads the semantic content of what you send and shapes its emotional delivery accordingly, so the only levers you need are word choice and punctuation: exclamation marks and interrobangs for excitement, commas and ellipses for pacing.

Show, don’t tell

“Be conversational” doesn’t work as an instruction. Give the model concrete examples to pattern-match against in your system prompt. These pairs aren’t universal good/bad; the right register depends on call type, persona, and caller. But LLM defaults rarely sound natural, and best-practice patterns produce more realistic audio in most cases. Even on a formal call, people stumble and reach for filler words occasionally. A little of that texture goes a long way.

Disfluencies in the text itself

Have the model use “um,” “uh,” “so,” “yeah,” and “well” where a person would actually hesitate. Don’t reach for tags; write the disfluency into the text itself. Sprinkle, don’t stack: two “um”s in a row reads as a bug.

Punctuation is your only prosody tool

  • Comma. Short internal pause with a slight rise.
  • Period. Sentence end, falling pitch.
  • Question mark. Rising intonation.
  • Ellipsis. Hesitant or trailing pause. Use sparingly.
  • Semicolon. Somewhere between a comma and a period.
Keep sentences under 25 words. A long sentence without internal commas will sound breathless. Break it into two.

Personality as audible behavior

Replace adjectives like “friendly” or “warm” with observable speech patterns the model can imitate. “Friendly” is interpretation; “starts sentences with ‘yeah’” is instruction. Maintain a calm, even baseline. Save exclamation marks for moments that actually warrant them. A real support agent isn’t excited about every line.

Normalize cleanly

Rime’s normalizer handles most common formats natively: currency with symbols, full dates, clock times with minutes, phone numbers, percentages, and standard measurements. Pre-expand only the gaps below. See Text normalization and Pre-normalization for the full reference. Pass through as-is. Rime handles these natively:
Rewrite. See Pre-normalization for the specific patterns to expand before sending. You can run any tricky string through the /textnorm endpoint before shipping to confirm how Rime will read it.

Use spell() for IDs

When something needs to be read letter-by-letter (confirmation codes, account numbers, SKUs, vanity phone letters), wrap it in spell(). The function groups characters into chunks of three (or two) and handles symbols like @ and -.
Don’t use spell() for standard phone numbers (digit grouping is more natural without it) or for real words that happen to be uppercase. Avoid dashes inside numeric IDs; they cause awkward pauses. Use spaces or spell() instead.

Drop-in system prompt

A complete system prompt that bakes in all of the above. Paste it into your LLM’s system message and adapt it to your agent’s persona.
voice-system-prompt.md