Connect by JBRH Open Connect

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.

Status
Available What this means
Audience
both
Channels
phone
In the app
#/calls, #/phone-advanced
Last verified
Product version
6.3.2

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.

  1. Trigger — the PSTN delivers a call to a number the workspace owns.
  2. External event — the carrier posts to the answer URL, signed, with Direction=inbound.
  3. Authentication and workspace resolution — the signature is checked and the handler enters the owning workspace *first*.
  4. Ingest — the carrier's own webhook shape is normalised in voice_carriers.py, the only file that speaks a provider's dialect.
  5. Canonical record — a Call row is created with ring time, direction and line; call_events stores the event once, keyed on the signature nonce.
  6. Reasoning — the turn-based engine writes a reply per turn; the realtime engine listens and speaks at once.
  7. Knowledge, memory and rules — voice_engine.brief() renders persona, line purpose, contact facts, standing instructions, knowledge and hard rules, once per call, and voice_region.plan writes the language rule into it from the evidence about the caller.
  8. Autonomy and approval — budget_refusal is asked before the call is answered; guardrails are re-checked on every sentence spoken.
  9. Action through a provider — the carrier reads out the reply, or a Gemini Live model speaks directly into the LiveKit room.
  10. Result — the call ends; hangup_by is attributed to caller, model, owner, engine, carrier or agent.
  11. Relationship, timeline and memory — summary, lead capture, memory write and follow-up, all through voice.end_call.
  12. 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#

StageWhat you seeWhat changesWhat can fail
Carrier receives the callNothing yet — the caller hears ringingNothing in ConnectThe number is not linked to the voice application, so no webhook is ever sent
Webhook, signature, workspaceNothingcall_events gains one row, nonce-guardedA signature checked outside workspace scope reads the credentials as empty and answers 403 before pickup
Line resolvedRinging continuesThe channel_routes row supplies purpose, hours, permissions and capabilitiesHours closed or line switched off — a refusal brief, not a fault
AnswerThe greeting, ideally at onceCall row opened; greeting.prewarmed records whether a warmed line was usedA greeting that cannot be synthesised ends the call with engine_error {at: greeting}
ConversationQuestion, answer, interruptioncall_turns, turn_timing and barge_in eventsModel refuses the session; watchdog nudge cancels a generation in flight; caller not heard
EndingThe line clearshangup_by, outcome, disposition kept separateA status callback that ends the call on *any* status — a ringing callback once hung up on a live caller
DigestA summary appears shortly afterSummary, lead, memory, follow-up; facts.digest moves off pendingA call with under two caller lines and five caller words is never summarised, on purpose
CostThe call appears in usageAudio and text tokens metered separately; duration charged when no tokens are reportedA 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-voice worker 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 Call row exists for every call the carrier logged as answered. A Completed on their side with no row on yours is a defect every time.
  • hangup_by names 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_answer or not_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.