# Telling the voice how to speak

Two blocks of text are assembled for every call. `style_block` turns the delivery settings — speed, energy, warmth, pauses, fillers, formality — into lines of instruction. `behaviour_block` turns the conduct settings — role, goal, tone, rules, what to ask and never ask, objections, escalation, opening and closing — into more of them. Both are directions the model follows, approximates, or occasionally ignores.

- **Status:** Available
- **Audience:** both
- **Channels:** phone
- **In the app:** #/phone, #/behaviour-advanced
- **Last verified:** 2026-09-10
- **Canonical:** https://connectbyjbrh.com/docs/phone/speaking-guide/

## Two blocks, and a third thing that is not a block

The realtime engine is speech to speech. There is no synthesiser sitting between the model and the line with a pitch dial on it, so a setting can only reach the call in one of two ways: as a parameter the session is opened with, or as a sentence in the instructions.

| Kind | Assembled by | Examples |
|---|---|---|
| Session parameter | `voice_style.session_options` | Model, voice, pinned language code, temperature, reply length, end-of-speech silence, interruption thresholds, noise cancellation, the silence and hang-up timers |
| Style instruction | `voice_style.style_block` | Speed, pitch, energy, warmth, emotion, pauses, fillers, formality, humanness, accent, pronunciation |
| Behaviour instruction | `voice_style.behaviour_block` | Role, goal, tone, style, knowledge, hard rules, what to ask and never ask, objection handling, escalation, opening, closing, proactiveness, language switching, memory use |

The Lab shows which kind each control is, on the control itself. That is not decoration: a session parameter is applied by the runtime and will be there on every turn of the call, while an instruction is a request. Knowing which one you just changed tells you whether to expect a difference or to go and measure for one.

## What a direction can actually influence

The human voice profiles are the clearest statement of this, because their schema labels all 86 parameters by kind. `engine` is a session parameter. `prompt` is a direction the model follows fairly reliably — a filler rate, a politeness marker, a level of formality. `coarse` is a direction it approximates: you will get the general shape and not the specific value.

Anything the stack genuinely cannot set is in `UNSUPPORTED` and is shown as unsupported rather than as a slider that does nothing. That list covers pitch in hertz, level in decibels, jitter, formants, exact pause lengths and a deliberate delay before answering. A control that cannot move the thing it names is worse than no control, because somebody spends an afternoon moving it.

> **Note** A style instruction competes for the same character budget as everything else in the prompt, and characters cost first-token time. A direction that changes nothing measurable is not free — it makes every reply slightly slower for the life of the line.

## Which guide wins

`voice_style.resolve` layers settings from the broadest to the narrowest, and the narrowest wins:

1. The workspace's own settings.
2. The default profile.
3. A profile chosen by purpose — `inbound`, `service`, `transactional` or `promotional`. There is no campaign model here; per campaign means per purpose.
4. A profile attached to a specific line number.
5. A profile on the contact, held as `contact.facts.voice_profile` — one person who should always be spoken to a particular way.
6. The Lab's draft, on a test call only. This is why a Lab call can sound unlike production without anything being wrong.

Whatever comes out of that resolution is snapshotted onto `call.quality` when the call ends, so the review judges the settings that actually ran. If somebody edits a profile an hour later, the old call still reports what it was given, not what the profile says now.

## Budgets, because every character is time

Instruction size is on the record as `session.instructions_chars`, and the relationship is not subtle: about 7,500 characters produced a 1.2–1.8 s first token, and about 9,600 produced 2.3–3.4 s. The budgets exist because of that measurement.

**Knowledge** — 4 facts, 2,000 characters
**Memory** — 700 characters
**Contact block** — 600 characters
**A human voice profile block** — Under roughly 2,500 characters

A behaviour block written like a policy document will silently spend the whole latency budget of the line. Write the rules that change what the voice does and delete the ones that describe what it would have done anyway.

## Where the language rule comes from

One part of the brief is authored in neither block. `voice_region.plan` writes the language rule itself, from the evidence about the person on the other end, and it replaces the language line the behaviour block would otherwise carry. The behaviour block's switching setting still decides whether a pinned language is held or followed; everything else about which language is spoken is written from what is known, with a different bar in each of four tiers:

- **Pinned**, with switching set to anything but `follow` — the call is held there, any language is understood, and no evidence moves the reply.
- **A remembered `preferred_language`** — spoken from the first reply; a whole sentence in another language overrides it, never a switch mid-sentence, never mixed scripts unless the caller mixes them.
- **Likely, from the contact's city or state** — the call opens in the line's language and does *not* assume the regional one, but two or three words of it, mixed with English or not, move the call into it and keep it there.
- **Nothing on file** — the call opens in the line's language and moves only on a whole sentence.

Reconciled on 2026-09-10 against `backend/app/voice_region.plan` at `f5ea518`, read from the code rather than the docstring above it, which still describes an offer the code no longer makes. Nothing in the rule offers a language or quotes a phrase to try: on a production call an earlier brief's sample phrase was read out to the caller word for word, and a mid-call steer cannot reach a Gemini 3.1 session, so the opening line is the whole lever. [Detecting the caller's language](/docs/phone/language-detection/) has the tiers in full.

## Questions

### I set the speaking speed and nothing changed. Is it broken?

Speed is a style instruction, not a session parameter — the model is asked to speak at that pace and approximates it. If the pace matters more than anything else on the line, a `chirp3-hd` voice on the carrier path takes an actual speaking rate, because there the audio is synthesised rather than generated.

### Why does my Voice Lab test sound different from a real call?

The Lab's draft sits at the narrowest layer of the resolution order, so it overrides everything, including a profile pinned to the line number or to the contact. A real call resolves without it. Save the draft as the profile if you want production to match what you just heard.

### Can one customer always be spoken to formally?

Yes — a profile on the contact, held as `contact.facts.voice_profile`, wins over the line's and the workspace's. It resolves per call, so it applies whether they rang in or Connect rang them.

## Related

- [Voice profiles](https://connectbyjbrh.com/docs/phone/voice-profile/)
- [Human voice profiles](https://connectbyjbrh.com/docs/phone/human-voice-profiles/)
- [The Voice Lab](https://connectbyjbrh.com/docs/phone/voice-lab/)
- [What a setting cannot change](https://connectbyjbrh.com/docs/phone/model-limits/)
- [Regional speaking style](https://connectbyjbrh.com/docs/phone/regional-speech/)
- [What every character of a prompt costs on a live call](https://connectbyjbrh.com/research/prompt-size-and-first-token/)

## What this page is based on

- Connect phone source pack — `docs-source/sources/PHONE.md` §6 and §7, the Voice Lab and instruction size
- Connect phone source pack — `docs-source/sources/PHONE.md` §11, human voice profiles and their schema
- Reconciled 2026-09-10 against `f5ea518` — `backend/app/voice_region.plan`, read from the code rather than its docstring
