> ## Documentation Index
> Fetch the complete documentation index at: https://docs.upliftai.org/llms.txt
> Use this file to discover all available pages before exploring further.

# How it works

Here's everything that happens on one call, from the first ring to the record you read afterwards.

## A call is a room

Every conversation (web, outbound, inbound) is a **session**, and a session is a **room** the agent joins. Start with outbound, the busiest path:

```mermaid theme={null}
sequenceDiagram
    participant You as Your server
    participant Uplift as Uplift AI
    participant Agent
    participant Phone as Customer's phone

    You->>Uplift: POST /realtime-assistants/{id}/call
    Uplift-->>You: callId (= sessionId), status dispatched
    Note right of Uplift: The session record exists from here,<br/>whether or not anyone answers
    Uplift->>Agent: opens the room, agent joins
    Note right of Agent: Warm-up: connects to STT, LLM and TTS<br/>and prepares the greeting. Mic stays muted.
    Uplift->>Phone: dial
    Phone->>Agent: answer — the agent is already warm
    Note over Agent,Phone: Both in the room. Everything below this section<br/>is identical on web, inbound and outbound.
```

The agent joins **before** the dial, so a fast pickup never lands on a cold agent. The muted mic means ringback tone and carrier menus can't confuse it. Inbound is one sentence: the customer calls your number and the agent answers. On web, [your server mints a token](/api-reference/starting-a-conversation/create-a-web-session-token) and the browser joins with any LiveKit client. The agent joins as soon as the browser does.

The `sessionId` you get back is the key to everything later: [status while live](/api-reference/sessions-%26-call-records/get-a-sessions-status), [transcript and outcomes after](/api-reference/sessions-%26-call-records/get-a-calls-transcript-and-outcomes).

## Inside a turn

The caller speaks, and three models run as a streaming pipeline. The numbers match the four legs in [Where the time goes](#where-the-time-goes):

```mermaid theme={null}
sequenceDiagram
    participant Caller
    participant STT
    participant LLM
    participant TTS

    Caller->>STT: speaks
    STT-->>STT: 1. text keeps up with the voice
    Caller-->>STT: goes quiet
    Note over STT,LLM: 2. End of turn — a short silence decides<br/>the caller is done. The one real gate.
    STT->>LLM: the finalized turn
    LLM->>TTS: 3. first sentence, while the rest is still generating
    TTS->>Caller: 4. first audio, 100–200ms after the request
    LLM->>TTS: the rest of the reply
    TTS->>Caller: the rest of the audio
    Caller--)TTS: a real interruption stops playback here
```

Nothing waits for anything to finish. **STT** emits text as the caller talks. The **LLM** starts replying from the finalized turn, and the first sentence goes to **TTS** while the rest is still generating. Turn-taking is voice-activity based: the agent decides the caller is done from short silences, tuned for Urdu conversation rhythm.

Underneath, we run on **LiveKit, deployed in Pakistan**. That's what lets us call with Pakistani phone numbers. Our GPUs sit close by too, so end-to-end latency is about as low as you'll get here.

## The edge cases we already handle

Real calls are messy. We handle the mess in-house, so you only think about the prompt, the conversation flow, and your tools:

* **Interruptions, the Urdu way.** Listening noises like "جی جی" and "اچھا ٹھیک ہے" never stop the agent, no matter how often. A real "رکیں" or "نہیں" cuts it off immediately. A "ہیلو؟" mid-sentence doesn't stop it. That caller wants to hear more, not less.
* **Voicemail.** On outbound we detect the answering machine, mark the call, and hang up. You don't pay to talk to a beep.
* **Silence handling.** A silent caller gets one "are you still there?" in the conversation's language, then a graceful hangup. Never while a tool is running.
* **Phantom answers.** `answeredAt` is stamped on real human speech, not on the carrier saying the call connected. A pickup where nobody ever speaks is classified `silent_pickup`, not a conversation.
* **Bad lines.** If the caller audibly spoke but the transcription never caught a turn, the agent greets anyway instead of holding dead air.
* **Dial failures** come back classified (`wrong_number`, `busy`, `no_answer`, `unreachable`), with the carrier's SIP code preserved.

## Ending the call

The agent can always end the call. It's a built-in tool, nothing you configure. When it does, it records why, in the LLM's own words:

* `order confirmed`
* `customer not interested`
* `wrong number`
* `customer not available, will call back later`
* `customer self harm detected`. This one really happened: in a farming-agent test the caller said they had drunk the chemical, and kept asking why they shouldn't. The LLM caught it and ended the call.

The goodbye finishes playing before we drop the call. Nothing gets cut mid-sentence. On [session reads](/api-reference/sessions-%26-call-records/get-a-sessions-status), `endedBy: agent` tells you the agent ended it.

## When the agent calls your tools

Mid-conversation tool calls run while the caller waits. By default they wait in silence. Turn on background noise and ambient room tone covers the gap.

A tool gets a **10-second budget**. On timeout the agent gets an error and keeps talking, so a slow tool never drops the call.

Every tool call, with arguments and result, is on the [session detail](/api-reference/sessions-%26-call-records/get-a-calls-transcript-and-outcomes) afterwards.

## Where the time goes

Every turn has four legs:

1. **STT**: time to finalized text
2. **End of turn**: deciding the caller is done
3. **LLM**: time to first token
4. **TTS**: time to first audio. Uplift AI TTS first byte is **100–200ms**.

We expect end-to-end turn latency **usually under 1 second**. Bigger prompts or reasoning-heavy models stretch it. We've seen 2–3 seconds in some cases. The two you control are the **LLM** and the **TTS**: model choice and thinking budgets dominate. I recommend `gemini-2.5-flash`. It's the fastest — see [Provider & model reference](/voice-agents/assistants/providers-and-models). The thinking knobs trade intelligence for exactly this latency.

## After the call ends

We upload the recording and a structured report, and the post-call pipeline runs: the **transcript** is assembled, the **scorecard** grades the call, **conversions and callbacks** are extracted. Only then do [webhooks](/voice-agents/webhooks/overview) fire. So by the time `call.completed` reaches you, everything it announces is already readable via the API.

Every dial attempt leaves a session record and runs this pipeline, connected or not (a no-answer just skips grading). The transcript appears seconds after hangup.

The whole life of a call, in the timestamps you'll see in the JSON:

```mermaid theme={null}
flowchart TB
    subgraph live["While it's live — poll the session status"]
        direction LR
        C["createdAt<br/>session exists"] --> RG["ringingAt<br/>carrier is ringing"] --> CN["connectedAt<br/>carrier says connected"] --> AN["answeredAt<br/>a human actually spoke"] --> EN["endedAt<br/>hangup"]
    end
    EN --> P["Post-call pipeline<br/>transcript, then scorecard,<br/>then conversions and callbacks"]
    P --> D["Session detail is complete"]
    D --> W["Only now: call.completed fires"]
```

The gap between `connectedAt` and `answeredAt` is where phantom answers live. The gap between `endedAt` and `call.completed` is why an immediate read can be empty.

## Us vs you

| We handle                                | You bring                        |
| ---------------------------------------- | -------------------------------- |
| Telephony, numbers, media transport      | The prompt and conversation flow |
| Interruptions, barge-in, turn-taking     | Provider and model choice        |
| Voicemail detection, silence, bad lines  | Tools the agent can call         |
| Recording, transcripts, grading pipeline | Webhooks that receive outcomes   |
| Retries, scheduling, escalation plumbing | Campaign logic and audiences     |
