# The greeting

The first sentence is chosen per line and synthesised while the phone is still ringing, so it plays the instant the caller picks up rather than after a pause. The caller's own audio is muted for its length, and the model is told the opening has already been said — which is what stopped the voice introducing itself twice. If it cannot be said at all, the call is ended rather than left silent.

- **Status:** Available
- **Audience:** both
- **Channels:** phone
- **In the app:** #/calls
- **Last verified:** 2026-09-10
- **Canonical:** https://connectbyjbrh.com/docs/phone/greeting/

## Chosen, not improvised

The greeting is a known sentence rather than a generated one. That is partly a quality decision — the first thing a caller hears is the line least worth leaving to chance — and partly a mechanical one: the realtime model refuses `say()`, so anything spoken before the conversation starts has to be audio Connect already holds.

Which sentence is used depends on the line and the call. Greetings are cached under a key made of the workspace, the TTS model, the voice, the style, the language and the text itself, so a change to any of those is a different greeting rather than a stale one. The closed-line message is warmed the same way under `kind: closed`, which is why a refused call answers immediately instead of pausing to synthesise an apology.

The script the greeting is spoken in is the language the call opens in. The brief opens in the line's language; a remembered language for a returning caller is then spoken from the first reply, and a whole sentence in another language is what overrides it. A mixed line is not a switch on its own — unless the contact's city or state had already made that language likely, in which case two or three words of it move the call.

## Synthesised during the ring

Warming is why the greeting is instant. Each idle worker process fetches the workspace's greeting lines and synthesises them into its own cache before any call arrives; when a call lands in a warm process the audio is already there. The measured effect on the inbound greeting was **3.0 seconds down to none**.

**A warm hit** — The audio plays at pickup. The call's greeting event records `prewarmed` so you can tell afterwards.
**A miss** — Live synthesis fills in and the call still starts. The warm-up hook never raises — a failure to warm must not be a failure to answer.
**A refused synthesis** — Retried with backoff when the refusal is one that passes, such as a rate limit or a server error. A bad key is not retried, because it will not pass.
**A cold restart** — Every process warms on its own schedule, staggered rather than simultaneous.

The stagger exists because of a specific outage: until September 2026 every restart warmed nothing. Each idle process asked for the same sentence with the same key in the same millisecond, the model's per-minute quota refused one of them, and the greeting was given up for the life of that process. Spreading the processes out and retrying a passing refusal fixed it, and `refused` is now counted separately from `failed` so the two are never confused again.

## The muted first sentence

The caller's audio is muted for the length of the greeting, and the instructions given to the model state that the opening has already been said, ask for one thought and one question, and cap the reply at under twenty-five words. That combination came out of an audit of twenty-four production calls plus two controlled ones, and it is worth quoting because the numbers moved together.

| Behaviour | Before | After |
|---|---|---|
| Re-introductions per call | 1–4 | 0 |
| Words per reply, median | 23–40 | 19 |
| Stacked questions per call | 2–4 | 0 |
| Reply median | — | 2.9 s, with no latency regression |

The mechanism behind the first row is simple and easy to get wrong: a model that cannot hear the greeting being played has no way to know it happened, and will open the conversation by introducing itself. Telling it, in the instructions, that the opening is done is the whole fix.

## When the greeting cannot be said

`_say` asks the TTS once more when the refusal is one that passes. If the line still cannot be spoken — and the model in use takes no generated line in its place — the worker posts `engine_error {at: greeting}` and ends the call with `hangup_by=engine`, rather than leaving somebody holding a silent phone.

That ending is deliberately loud in the record. The engine writes `facts.silent_failure` from the event, `voice.no_conversation` says so, and the outcome is `not_reached`. Before this chain existed, five inbound calls on a single day sat silent and were filed as "the caller hung up before speaking" — the kind of row nobody investigates.

> **Note** Ending the call is the better failure. A caller who hears nothing concludes the number is broken and may not try again; a call that ends cleanly at least tells them to redial, and tells the business that something is wrong through line health.

## Questions

### Can I change the greeting per line?

Yes — the greeting is part of what a line resolves, and the cache key includes the workspace, voice, style, language and the text, so a change produces a different warmed entry rather than a stale one. Changes take effect as processes warm the new sentence.

### Why does the caller sometimes hear a pause before the greeting?

That is a warm miss: the process holding the call did not have the audio cached, so it was synthesised live. The call still starts, and the greeting event on the record says which of the two happened, so a pattern of misses is visible rather than anecdotal.

### Does Connect greet an outbound call the same way?

The greeting mechanism is the same audio-first one, but the situation is not: on an outbound call the person answering has not chosen to speak to anyone yet. The opening rules that cap length and forbid re-introduction apply either way, and consent and blocks are checked long before the call is placed.

## Related

- [Greeting warm-up](https://connectbyjbrh.com/docs/phone/greeting-prewarm/)
- [Answering inbound calls](https://connectbyjbrh.com/docs/phone/inbound-calls/)
- [Calls that were never a conversation](https://connectbyjbrh.com/docs/phone/not-a-conversation/)
- [Connect's own voice](https://connectbyjbrh.com/docs/phone/connect-voice-tts/)
- [Telling the voice how to speak](https://connectbyjbrh.com/docs/phone/speaking-guide/)
- [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

- AGENTS.md §9a — greeting warm-up and the greeting key (via docs-source/sources/PHONE.md §8)
- PROJECT-STATE.md — the 2026-09-06 gap audit of 24 production calls (via docs-source/sources/PHONE.md §10)
- `backend/voice_worker/agent.py` — `prewarm`, `_say` and the greeting event
- `backend/app/voice_style.py` — `greeting_key`
