The voice worker
The voice worker is the process that holds a realtime call. The SDK runs each call in its own process, every idle process warms greetings, and every live worker heartbeats its load and the calls it is holding. If nothing has checked in for 90 seconds, admission refuses new calls before a row exists — rather than sending a caller to a fleet that is not there.
One process per call#
The worker is not a server that multiplexes conversations. The SDK runs each call in its own process, which is what makes a single bad call survivable: a session that wedges takes its own process with it and no other caller notices.
Idle processes are not idle. prewarm_fnc runs in every one of them, fetching the workspace's greeting lines and synthesising each into that process's cache, so a call landing in a warm process starts speaking immediately. Process initialisation is given 45 seconds for this, because the default bound was shorter than the warm-up's own 30-second budget — a mismatch that made every restart warm nothing.
heartbeat- One-minute load per core, plus the calls this worker is holding. Sent on every beat, and read by admission.
- The heartbeat file
- Forgets a process not seen for an hour, so a worker that died does not hold a slot in the fleet's arithmetic forever.
drain_timeout- 180 seconds. A deploy stops taking new calls and lets the ones in progress finish.
- The service unit
ops/connect-voice.serviceallows 210 seconds before systemd kills the process — deliberately longer than the drain, so a clean drain is never cut short by the supervisor above it.
What the worker is and is not responsible for#
It holds the audio, speaks, and reports. It does not decide anything about the business: every fact it uses arrives in the brief rendered once per call, and every sentence it says is posted back so that the guardrails can be re-checked on Connect's side, where the authority lives.
It also does housekeeping that would otherwise never happen. The stale call sweep runs from the worker heartbeat as well as from the engine tick — the second path was added because a browser call has no worker to heartbeat at all, and one such call sat active for 24.78 hours before anything closed it.
When no worker has checked in#
This is the failure worth designing against, because it is the one where the screen and reality disagreed. A worker that has not checked in means an inbound call reaches nothing at all — and the Phone screen used to still say "ready", so nobody knew until a customer said so.
| Fleet state | Admission | Caller experience | Where it shows |
|---|---|---|---|
| Workers checked in, capacity free | Accepted | A normal call | — |
| Nothing checked in for 90 s | Refused not_ready before a row exists | The call is not answered | Line health, as a Needs You item |
| All live workers over the load threshold | Refused | The call is not answered | Line health |
| A worker draining after a deploy | New calls go elsewhere | Unaffected, if another worker is up | — |
| A worker that died | Forgotten after an hour | Unaffected once other workers are up | — |
Refusing before a row exists is deliberate. It means the phone follow-up drain sees a line that is not ready and retries in an hour, rather than recording a failed call against a promise that was never attempted.
Deploying without cutting a call#
Stop admitting new calls to the workers being replaced.
Result Calls in progress continue; new ones land on the rest of the fleet.
Let the drain run its 180 seconds.
Result Conversations end naturally instead of being cut mid-sentence.
Let systemd's own 210-second allowance be the backstop rather than the schedule.
Result A drain that legitimately needs its full time is never killed halfway by the process supervisor.
Confirm a worker has heartbeated before treating the deploy as finished.
Result A fleet with nothing checked in refuses calls, and the drain that retries follow-ups only helps if the fleet comes back.
Questions#
Does a worker restart drop the call it was holding?
A drain lets calls in progress finish, which is what the 180-second window is for. Separately, a model session that ends mid-call can be resumed — session resumption plus context compression let the plugin reconnect carrying the conversation — and where a session is restarted, model_session_restarted goes on the record.
How do I know whether a worker is running without shell access?
Line health is the customer-facing answer. It names four causes and one of them is a worker that has not checked in; the finding appears in Needs You for both audiences and drains by itself when a worker comes back.
Why does the greeting warm-up matter to the worker's design?
Because it is the only reason an idle process does work. Warming moved the inbound greeting from about three seconds to none, and it is why process initialisation gets 45 seconds rather than the default — a warm-up that times out leaves a process that will synthesise every greeting live.