> ## 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.

# Outbound calls

> Make the agent dial out, and give it the values it needs before the phone rings.

An outbound agent starts the call. That changes two things: the channel has to
declare it, and the agent usually needs to know who it is calling before the
first word.

No shipped example dials out: `examples/salon-concierge` declares
`outbound: false`, and every other package is browser only. So the snippets and
commands below are a worked example named `my-agent`, an agent that calls a
customer about an appointment. Everything in them is authorable as shown; there
is just no package in the repository to run them against.

On this page:

* [Declare the direction](#declare-the-direction) - `channels:`, key by key, for an agent that dials
* [One route per target](#one-route-per-target) - a connection file per target, key by key
* [Place a call](#place-a-call) - where the request comes from on each route
* [Give the call its values](#give-the-call-its-values) - `call_start` variables, and `--var` locally
* [Values the route supplies](#values-the-route-supplies) - what the runtime fills in, and which routes fill it

## Declare the direction

```yaml agent.yaml theme={null}
channels:
  phone:
    kind: telephony
    inbound: false
    outbound: true
```

Without `outbound: true`, this target has no outbound capability at all: no
route reads a destination number for it.

### Every key a channel takes

A telephony channel writes both directions, whichever one it uses.

<ParamField path="kind" type="realtime_audio | telephony" required>
  `telephony` is a phone call. `realtime_audio` is the browser channel, and it
  takes none of the keys below.
</ParamField>

<ParamField path="inbound" type="true | false" required>
  Whether this agent answers calls. `false` on an agent that only dials.
</ParamField>

<ParamField path="outbound" type="true | false" required>
  Whether this agent places calls. It is also what a warm transfer needs, since
  a warm transfer dials its destination.
</ParamField>

<ParamField path="required_controls" type="list of control names">
  What the route has to support: `cold_transfer`, `warm_transfer`,
  `dtmf_send`, `dtmf_receive`, `hold`, `hangup`, `voicemail_detection`,
  `ivr_navigation`. A route that cannot do one is refused when you validate.
</ParamField>

<ParamField path="on_voicemail" type="hangup | leave_message">
  What the agent does when it reaches a voicemail box. It needs
  `outbound: true` and a route that detects voicemail, which today is LiveKit
  `sip`.
</ParamField>

## One route per target

Dialling out is the carrier's job, so each target needs a connection that can
reach your carrier account. An agent on both targets rides two different
mechanisms, so it needs two connection files holding the same three names:

<CodeGroup>
  ```yaml Pipecat theme={null}
  # connections/twilio_voice.yaml
  transport: cloud-websocket
  carrier: twilio
  environment:
    account_sid: TWILIO_ACCOUNT_SID
    auth_token: TWILIO_AUTH_TOKEN
    from_number: TWILIO_PHONE_NUMBER
  ```

  ```yaml LiveKit theme={null}
  # connections/twilio_connector.yaml
  transport: connector
  carrier: twilio
  environment:
    account_sid: TWILIO_ACCOUNT_SID
    auth_token: TWILIO_AUTH_TOKEN
    from_number: TWILIO_PHONE_NUMBER
  ```
</CodeGroup>

Same three names, different `transport:`. `from_number` is the caller identity
the person you dial sees. Neither target says anything else about the route.

### Every key a connection takes

<ParamField path="transport" type="sip | connector | cloud-websocket | daily-sip" required>
  The mechanism that carries the call. Every one of the four can dial out. The
  target's provider decides which of them it accepts.
</ParamField>

<ParamField path="carrier" type="twilio | telnyx | plivo" required>
  The carrier account that places the call. Telnyx and Plivo dial out through
  the LiveKit `sip` route only.
</ParamField>

<ParamField path="environment" type="route key to environment variable name">
  Which variable holds each of the route's account values. Dialling out always
  needs `from_number`, plus either `account_sid` and `auth_token` or the SIP
  trio, depending on the route. [Which environment keys a route
  accepts](/reference/connections-yaml#which-environment-keys-a-route-accepts)
  has the set per route. The names on the right are yours, and every one is
  UPPER\_SNAKE.
</ParamField>

See [`connections/<name>.yaml`](/reference/connections-yaml) for the whole file.

## Place a call

A phone call reaches an agent that is deployed, so placing one is the
deployed platform's job, not a local command. Compile `my-agent`, deploy it
to the target's platform, and use the **Place an outbound call** section of
the generated `build/<target>/README.md`. It prints the exact request for
your route, with the trunk, project, and agent names already filled in:

* Pipecat `cloud-websocket` places the call with one request to Twilio's own
  API, carrying markup that names the deployed agent.
* Pipecat `daily-sip` places it with one request to the Pipecat Cloud
  dial-out endpoint, through the trunk your carrier setup created.
* LiveKit `sip` and LiveKit `connector` place it by dispatching this agent to
  a new room with job metadata, and the worker dials with the carrier's trunk
  settings inline.

Every one of the four surviving telephony routes can dial out. What differs
is the request shape, which is why the README is the one place to copy it
from: it already carries your own values.

## Give the call its values

An outbound call usually knows things before it starts: who is being called,
about what. Declare them as `call_start` variables:

```yaml agent.yaml theme={null}
variables:
  customer_id:
    type: string
    source: call_start
    description: CRM id of the customer this call is about. Used by the booking tools, never spoken.
  name:
    type: string
    source: call_start
    description: Customer's first name, used in the greeting and the prompt.
  appointment_time:
    type: string
    source: call_start
    description: Appointment start in spoken form, for example "tomorrow at 3 pm".
```

Locally, `--var` supplies them:

```sh theme={null}
unmute dev my-agent --target pipecat \
  --var customer_id=cus_1042 \
  --var name=Ada \
  --var "appointment_time=tomorrow at 3 pm"
```

In production the same values ride the target's own dispatch payload, as one
flat JSON object. Each generated `build/<target>/README.md` prints the exact
spelling for its platform.

That is the whole relationship: **`--var` is the local stand in for the dispatch
payload**. Same names, same types, same variables.

Values are checked against the declared type, and an undeclared name is refused
rather than quietly ignored.

### Every key a variable takes

<ParamField path="type" type="type expression" required>
  What the value is, written as one line: a built-in such as `string`, a
  `Literal[...]` set, a `list[...]`, or a shape you declared. [The type
  grammar](/reference/variables#the-type-grammar) has all of them.
</ParamField>

<ParamField path="source" type="call_start | conversation | a system call fact">
  Where the value comes from. `call_start` is the dispatch payload, which
  `--var` stands in for. `conversation` is a value the model records mid-call.
  The eight system facts are listed below. Left out, a task's `assign:` fills
  it.
</ParamField>

<ParamField path="default" type="a value of the declared type">
  What the value is before anything supplies one. Required on a `call_start`
  variable when the channel is also inbound, because an inbound call carries no
  dispatch payload.
</ParamField>

<ParamField path="confirm" type="step name">
  The step that has to hear the caller agree before anything acts on this
  value. Until then it renders in that step's prompt and nowhere else. See
  [`confirm:`](/reference/variables#confirm-marks-a-value-the-caller-has-to-agree-to).
</ParamField>

<ParamField path="description" type="string">
  What the value is, for a reader and for the model. Required on a
  `source: conversation` variable, because the model reads it to know what to
  record.
</ParamField>

## Values the route supplies

Some variables are filled in by the runtime, not by you:

```yaml theme={null}
  dialed_number:
    type: string
    source: to_number
```

System sources include `to_number`, `from_number`, `direction`, `call_id`,
`stream_id`, `session_id`, `carrier`, and `connection`. Seeding one with
`--var` is refused, because the runtime owns it.

**Which routes supply them, and in which direction, differs per fact.** Both
LiveKit routes supply every system source, on both directions. The two
Pipecat Twilio routes supply a smaller set: `pipecat cloud-websocket` fills
`to_number` on an outbound call, which is exactly the `dialed_number` example
above, while `pipecat daily-sip` supplies no `to_number` at all. A route that
does not grant a fact refuses the declaration at validation, rather than
leaving an empty string at call time. [The full grid](/build/prefetch#where-it-works)
in the pre-fetch reference has every fact, route and direction.

Declaring the fact as a `prefetch:` entry instead keeps one package compiling
on every route. Where the route supplies nothing, the entry is skipped and the
variable keeps its default, rather than the package being refused. [Where it
works](/build/prefetch#where-it-works) covers that shape, under "The number an
outbound call carries", and what a `cloud-websocket` call has to carry for it
to arrive.

## Where to go next

<Columns cols={2}>
  <Card title="Inbound calls" icon="phone-incoming" href="/telephony/inbound-calls">
    Take a real call, once the route is deployed.
  </Card>

  <Card title="Get your Twilio details" icon="key" href="/telephony/twilio">
    Which console value fills which name.
  </Card>

  <Card title="Variables reference" icon="braces" href="/reference/variables">
    Types, sources, and where each value can be used.
  </Card>
</Columns>
