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

> ## Agent Instructions
> Unmute compiles to exactly three targets. Pipecat and LiveKit are code targets: compile writes a Python project you run. SLNG is a hosted target: compile writes a deployment body and SLNG runs the agent, so it has no `unmute dev`. Those three are the only values `provider` accepts in `targets.yaml`. Deepgram and ElevenLabs appear in these docs as model vendors, which is not the same thing as a target, and `slng` is both.
> The Go structs in `internal/spec` and `internal/ir` are the schema truth. Check a field against them, or run `unmute validate`, rather than against what you remember.

# Transfers on LiveKit

> The only route with both cold and warm transfer, over a Twilio SIP trunk.

`examples/salon-concierge` carries the cold half of this on its LiveKit target,
over a Twilio SIP trunk. Warm transfer compiles on no other route today, and no
shipped example declares it, so the warm snippets below are worked examples
rather than quotes from a package you can run.

On this page:

* [Two transfers, side by side](#two-transfers-side-by-side) - the block, and every key it takes
* [The trunk has to allow it](#the-trunk-has-to-allow-it) - three Twilio settings a new trunk does not have
* [The connection](#the-connection) - the four SIP names this route reads
* [Run it](#run-it) - compiling, deploying, and the lines the log prints

```yaml targets.yaml theme={null}
targets:
  livekit:
    provider: livekit
    version: "1.8.1"
    sdk_language: python
    connection: twilio_sip
    deployment_region: eu-central
    models:
      detector:
        provider: livekit
        model: turn-detector-mini
```

```yaml agent.yaml theme={null}
destinations:
  billing_line: BILLING_PHONE_NUMBER
  supervisor_line: SUPERVISOR_PHONE_NUMBER
```

The route is `connections/twilio_sip.yaml`, below. The two desks are in
`agent.yaml`, because the same desk answers whichever target places the call.
Each one names an environment variable rather than a number, a rule
[Human transfers](/transfers/overview) states in full.

## Two transfers, side by side

```yaml agent.yaml theme={null}
escalations:
  send_to_billing:
    when: The caller asks about an invoice, a refund, or a charge they do not recognise.
    cold:
      destination: billing_line

  escalate_to_supervisor:
    when: The caller is unhappy with how something was handled and asks for a manager.
    warm:
      destination: supervisor_line
      briefing: |
        Lead with the caller's name and which stylist they saw.
        Say what they are unhappy about and what you already offered them.
        Ask whether they can take the call now.
      ring_timeout: 25s
      on_unavailable: return_to_caller
```

**Cold** is a SIP REFER. The agent asks the carrier to hand the caller's
existing leg somewhere else. The caller leaves the room and the session ends.
If the REFER fails, the caller stays with the agent and `on_unavailable`
applies.

**Warm** uses LiveKit's own prebuilt warm transfer task. The caller waits on
hold, the supervisor's line rings, the agent briefs them, and only then are the
two connected. `briefing` is free text, not a mode: the call transcript is
passed along on its own, so write what the person needs on top of it. Every
failure, no answer, decline, voicemail, failed dial, comes back as one error
and `on_unavailable` decides.

The person who answers hears the handover in the first sentence: who is on
hold, what they want, what was tried, then one question they can answer. If the
conversation has too little detail, the agent says that plainly instead of
inventing a briefing.

### Every key a transfer block takes

`destination:` is the only key either block requires. The entry's own `when:`,
and the choice between `cold:` and `warm:`, work the same on every route and
are stated on [Human transfers](/transfers/overview).

<ParamField path="destination" type="a destinations entry name" required>
  Which desk to reach. The name is resolved in the `destinations:` block above,
  so the model never sees a number.
</ParamField>

<ParamField path="briefing" type="string">
  What to tell the person while the caller waits. It belongs to a `warm:` block
  and is refused in a `cold:` one. Left out, the person still gets the
  transcript, which rides along on its own.
</ParamField>

<ParamField path="ring_timeout" type="a positive Go duration, e.g. 25s">
  How long the destination rings before it counts as unavailable. It reaches
  both shapes on this route. Left out, LiveKit is sent no value and its own
  platform default stands.
</ParamField>

<ParamField path="on_unavailable" type="return_to_caller | hangup" default="return_to_caller">
  What happens when the destination does not take the call. On this route no
  answer, a decline, voicemail and a failed dial all arrive as one failure, so
  one setting covers them.
</ParamField>

`ring_timeout` covers ringing only. Once the person answers, LiveKit has no
post-answer timeout and the caller stays on hold until the consultation ends.
The agent asks again when the person does not decide, but that prompt is a
mitigation rather than a hard time limit.

## The trunk has to allow it

Cold transfer is a SIP REFER, so the trunk must permit transfers. In the trunk's
**General settings**, three things have to be right, and a new trunk has none of
them:

| Setting                       | Value                              |
| ----------------------------- | ---------------------------------- |
| Call Transfer (SIP REFER)     | enabled                            |
| Enable PSTN Transfer          | ticked, for any `tel:` destination |
| Caller ID for Transfer Target | see below                          |

Twilio's transfer mode has a third value, `sip-only`, which allows SIP
destinations and refuses PSTN ones. A phone number destination needs the PSTN
box ticked.

**Caller ID decides whether the transfer connects at all.** Transferee presents
the original caller's number to the destination, which is the nicer behaviour
and works where the receiving carrier accepts a number your account does not
own. Transferor presents your own Twilio number, and is what to use when the
transfer crosses a border. Getting this wrong fails every transfer instantly,
with `486 Busy Here` and a zero second, zero cost call leg in Twilio's log.
[The caller ID section](/telephony/twilio#caller-id-for-a-transfer-target) shows
how to tell that apart from a destination that is genuinely busy.

Caller ID for the transfer target is that trunk setting, never per call.
Transfers to emergency numbers are not supported, and the referred leg keeps
billing per minute trunking charges.

LiveKit Phone Numbers cannot transfer. Use a SIP trunk whose provider supports
REFER; this example uses Twilio Elastic SIP Trunking.

## The connection

Four standard SIP names, not Twilio specific ones, because the same generated
code dials through any SIP carrier with them:

```yaml connections/twilio_sip.yaml theme={null}
transport: sip
carrier: twilio
environment:
  sip_address: SIP_TRUNK_HOSTNAME
  sip_username: SIP_AUTH_USERNAME
  sip_password: SIP_AUTH_PASSWORD
  from_number: SIP_FROM_NUMBER
```

The first two lines are the route: this is the file that says the call arrives
over SIP and Twilio carries it. The four names below are yours, and the compiler
carries whatever you write through verbatim.

Those settings belong to the route rather than to this page. Which keys each
route accepts is in
[Connection configuration](/reference/connections-yaml#which-environment-keys-a-route-accepts).
Where each Twilio value is found is in
[LiveKit over Twilio](/telephony/livekit-twilio).

## Run it

```sh theme={null}
unmute compile examples/salon-concierge
```

The build carries the route, every required environment variable, and the
transfer capability the route supports. The emitted `README.md` walks the trunk
setup for it.

Inbound on this route is tested against a deployment: SIP needs the carrier to
reach signalling and media at a routable address, which a laptop behind a home
router cannot offer. Deploy the generated project, finish the trunk setup
above, and call your number to reach a transfer.

Cold transfer needs an existing SIP caller leg, so it cannot be tested from a
browser session or the LiveKit Agent Console. Warm transfer can start there,
because it dials the person itself.

That difference also controls startup checks. A cold destination environment
name is required only after a real SIP job is identified, before the greeting;
it does not block WebRTC startup. Warm transfer is always available to the
browser agent, so its destination and the selected connection's SIP address,
username, password, and caller number remain required in `REQUIRED_ENV` and
`compose.dev.yaml`.

Keep `lk agent logs` open. A warm transfer prints the control, the conversation
message count handed to the briefing, and then either `warm transfer merged`
or `warm transfer unavailable`. A cold transfer prints either
`cold transfer completed`, `cold transfer failed`, or
`cold transfer skipped: no phone caller in the room`. A dial line with no final
warm line means the consultation is still running.

## Where to go next

<Columns cols={2}>
  <Card title="Pipecat on Twilio" icon="phone" href="/transfers/pipecat-twilio">
    Cold transfer on Pipecat with nothing hosted by you.
  </Card>

  <Card title="Targets" icon="boxes" href="/targets/overview">
    Compare Pipecat, LiveKit, and SLNG.
  </Card>
</Columns>
