Inbound phone call, end to end
A call reaches the carrier, the carrier calls Connect, and the handler enters the owning workspace before it reads a single setting. The line is resolved, a calls row is opened, the voice answers under that line's persona and rules, and the ending is attributed, costed and turned into a summary, a lead and a follow-up. Each stage can fail on its own, and each has its own recovery.
The chain, for one ringing number#
This is the same chain every flow in Connect runs; what changes is what each stage means when the trigger is a person dialling a number and waiting on the line. The clock is the difference. An email that takes four seconds longer costs nothing; four seconds of silence on a live call is a caller deciding nobody is there.
- Trigger — the PSTN delivers a call to a number the workspace owns.
- External event — the carrier posts to the answer URL, signed, with
Direction=inbound. - Authentication and workspace resolution — the signature is checked and the handler enters the owning workspace *first*.
- Ingest — the carrier's own webhook shape is normalised in
voice_carriers.py, the only file that speaks a provider's dialect. - Canonical record — a
Callrow is created with ring time, direction and line;call_eventsstores the event once, keyed on the signature nonce. - Reasoning — the turn-based engine writes a reply per turn; the realtime engine listens and speaks at once.
- Knowledge, memory and rules —
voice_engine.brief()renders persona, line purpose, contact facts, standing instructions, knowledge and hard rules, once per call, andvoice_region.planwrites the language rule into it from the evidence about the caller. - Autonomy and approval —
budget_refusalis asked before the call is answered; guardrails are re-checked on every sentence spoken. - Action through a provider — the carrier reads out the reply, or a Gemini Live model speaks directly into the LiveKit room.
- Result — the call ends;
hangup_byis attributed to caller, model, owner, engine, carrier or agent. - Relationship, timeline and memory — summary, lead capture, memory write and follow-up, all through
voice.end_call. - Audit, usage and Needs You — audio-token cost is booked, the call is on the timeline, and anything needing a person is queued.
Stage by stage, with the failures beside them#
| Stage | What you see | What changes | What can fail |
|---|---|---|---|
| Carrier receives the call | Nothing yet — the caller hears ringing | Nothing in Connect | The number is not linked to the voice application, so no webhook is ever sent |
| Webhook, signature, workspace | Nothing | call_events gains one row, nonce-guarded | A signature checked outside workspace scope reads the credentials as empty and answers 403 before pickup |
| Line resolved | Ringing continues | The channel_routes row supplies purpose, hours, permissions and capabilities | Hours closed or line switched off — a refusal brief, not a fault |
| Answer | The greeting, ideally at once | Call row opened; greeting.prewarmed records whether a warmed line was used | A greeting that cannot be synthesised ends the call with engine_error {at: greeting} |
| Conversation | Question, answer, interruption | call_turns, turn_timing and barge_in events | Model refuses the session; watchdog nudge cancels a generation in flight; caller not heard |
| Ending | The line clears | hangup_by, outcome, disposition kept separate | A status callback that ends the call on *any* status — a ringing callback once hung up on a live caller |
| Digest | A summary appears shortly after | Summary, lead, memory, follow-up; facts.digest moves off pending | A call with under two caller lines and five caller words is never summarised, on purpose |
| Cost | The call appears in usage | Audio and text tokens metered separately; duration charged when no tokens are reported | A session that restarted bills only the survivor unless every session is merged |
Answering is not one behaviour#
Which engine the workspace has chosen changes what happens between the greeting and the goodbye, and nothing else in the chain.
- Turn-based (carrier path)
- A webhook carries one heard sentence, Connect writes a reply, the carrier reads it out. Simple, provider-driven, and paced by the carrier's own speech detection.
- Realtime
- The number is linked at the carrier to an inbound trunk, a dispatch rule spawns the
connect-voiceworker on the room, and the model listens and speaks at once. The worker holds no business logic: it is handed a brief and posts back every sentence, every event and the ending.
The language the call is held in is decided in the same place, before anybody speaks. The greeting is pre-synthesised, so it opens the call; from the first reply onwards voice_region.plan governs. A pinned language holds the call. A preferred_language remembered from an earlier call is spoken from that first reply, and only a whole sentence in another language overrides it. Where nothing is remembered but the contact's city or state maps to a region, the call opens in the line's language, does not assume the regional one, and moves to it the moment the caller uses it — two or three words, mixed with English or not. With nothing on file, it takes a whole sentence. Nothing is offered and no phrase is quoted, because on a production call a quoted sample was read out to the caller word for word.
The realtime floor is the model's first token plus its end-of-turn detection — 3.3 s median reply on the best measured call, not the one to two seconds people expect from a demo. Connect's default model reaches first audio in about 0.6 s on the host, against 1.2–2.2 s for the 2.5 native-audio model, which is why the default is what it is.
What counts as the call having worked#
Not "the caller hung up politely". A verified inbound call has all of these, and the audit is worth running against the carrier's own log rather than against your impression of the day:
- A
Callrow exists for every call the carrier logged as answered. ACompletedon their side with no row on yours is a defect every time. hangup_bynames somebody other than the fallback. Before attribution existed, every ending that was not the caller's read as the agent's.- The outcome is a conversation word, not
no_answerornot_reached— those two mean the call rang and nobody picked up, or never rang at all. - The cost is booked from measured audio tokens, or from duration when the session reported none. Never zero.
- Anything promised on the call exists as a follow-up with a due time, not as a sentence in a transcript.
Questions#
Why does the caller sometimes hear the greeting and then silence?
Historically two causes, both fixed and both worth recognising. A host-less action URL meant the carrier had nowhere to send the next turn. And the <Gather> was the last element in the document, so when nothing usable was heard the carrier ran off the end of the XML and hung up. A <Redirect> now follows the gather, back to the turn handler, which answers empty speech rather than dropping the line.
Is the call recorded?
No. Recording is a foundation capability: it is asked of the provider through telephony.can rather than assumed, and it is not enabled on the live carrier. What you get instead is the transcript, the summary and the turn timings, which is what most reviews of a call actually need.
What happens if the daily budget is already spent when someone calls?
budget_refusal is asked before the call is answered, and a refused inbound call gets the spoken closed-line message in the business's own name — not silence, and not a dropped line.