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

# Variables

> Declare a value once, fill it during the call, and read it only where it is needed.

A variable is a named value the call holds onto. You declare what it holds,
something fills it during the call, and the prompts that need it name it.

On this page:

* [Quickstart](#quickstart) - declare, fill and read one value
* [Declare it](#1-declare-it) - every key a variable takes
* [Fill it](#2-fill-it) - a task, a pre-fetch, or the call itself
* [Read it](#3-read-it-where-it-is-needed) - placeholders, and the greeting
* [The types you can declare](#the-types-you-can-declare) - ten built in, plus lists and your own
* [Advanced](#advanced) - shapes, injection, and seeding a value locally
* [Troubleshooting](#troubleshooting) - the four that come up most

## Keep only the values the call needs

**Keep state small.** Declare a variable when a value must survive a task or
handoff, feed a later tool, or supply a needed call fact to a prompt. Before
adding one used by only one prompt, check whether the agent needs the fact at
all. Prefer fewer values that each represent one useful fact or result; do not
split a timestamp into date and time just because both fields are available.

For a clock, replace `current_date`, `current_weekday`, and `current_time` with
`current_datetime` and `current_weekday`. The timestamp already carries the
date and time. Keep the weekday only if the prompt must name it; reading the
clock's answer avoids asking the model to calculate it. If nobody needs the
weekday, keep just the timestamp.

Merge these entries into an existing package:

```yaml agent.yaml theme={null}
variables:
  current_datetime:
    type: str
  current_weekday:
    type: str

prefetch:
  - name: local_clock
    clock: now
    timezone: Europe/Madrid
    assign:
      - current_datetime: result.datetime
      - current_weekday: result.day_of_week
```

Use a shape for fields that travel together as a task result. Pre-fetch fills
plain values, so it cannot save the clock into a shape. Keep separate values
when they have different confirmation steps or different readers.

## Quickstart

Declare a value, save it when a task finishes, and read it in a later prompt:

```yaml agent.yaml theme={null}
variables:
  requested_service:
    type: str
    description: The service the caller chose for this booking.

agents:
  concierge:
    tasks:
      - name: choose_service
        when: Find out which service the caller wants.
        instructions: tasks/choose-service.md
        assign:
          - requested_service: result.service
```

```markdown tasks/booking.md theme={null}
Requested service: {{requested_service}}
If no service is recorded, ask which service the caller wants.
```

```sh theme={null}
unmute validate my-agent
```

That is the whole loop: **declare, fill, read.** The rest of this page is each
step in full.

<Note>
  The snippets below extend an existing package. Merge them into the matching
  blocks in `agent.yaml`.
</Note>

## 1. Declare it

A variable needs a name and a `type:`. Start with plain text:

```yaml agent.yaml theme={null}
variables:
  requested_service:
    type: str
    description: The service the caller chose for this booking.
```

That is the whole declaration. `requested_service` is the name you will use
everywhere else, and `str` means it holds text.

<ParamField path="type" type="string" required>
  A single-line Python type expression. Accepts `str`, `int`, `float`, `bool`, `Phone`,
  `Date`, `Time`, `Id`, `EmailStr`, `NameEmail`, a declared shape name, `Literal[...]`,
  `list[T]`, or `T | None`. The aliases `string`, `integer`, `number`, and `boolean` also
  work. See the [type grammar](/reference/variables#the-type-grammar) for composition
  rules and target limits.
</ParamField>

<ParamField path="description" type="string">
  A sentence explaining what the value means. Used to describe the task finish argument
  derived from this variable. Required for `source: conversation`; otherwise omit for no
  extra description. The saved value is still shared only through placeholders.
</ParamField>

<ParamField path="default" type="matching the declared type">
  A scalar starting value, checked against `type`. Shapes, lists, and `NameEmail` take no
  authored default. If omitted, a scalar or shape starts unset and a list starts as `[]`.
  An unset value renders as `none recorded yet.` in a prompt.
</ParamField>

<ParamField path="source" type="string">
  Accepts `call_start`, `conversation` (SLNG only), or a [system call
  fact](/reference/variables#sources). If omitted, the variable has no automatic call-fact
  source; a task, pre-fetch, or session payload can still fill it. Route support is
  checked for system sources. `conversation` requires a description and forbids a default;
  inbound code-target calls require a default for `call_start`.
</ParamField>

<ParamField path="confirm" type="string">
  The name of the task that must hear the caller agree to a pre-fetched value. Until
  confirmed, only that task can read it in a prompt and tools cannot silently use it. If
  omitted, the value is usable as soon as it arrives.
</ParamField>

A variable starts unset unless you give it a `default:`.

## 2. Fill it

Declaring a variable creates the box. Something still has to put a value in it.
There are three ways, and each has its own page:

| How                            | When it happens           | Written where                             |
| ------------------------------ | ------------------------- | ----------------------------------------- |
| A task's `assign:`             | when that task finishes   | on the [task](/build/orchestration/tasks) |
| [`prefetch:`](/build/prefetch) | once, before the greeting | in a top-level `prefetch:` entry          |
| `source:`                      | as the call starts        | on the variable itself                    |

The common one is a task. Add `assign:` to the task that learns the value:

```yaml agent.yaml theme={null}
agents:
  concierge:
    tasks:
      - name: choose_service
        when: Find out which service the caller wants.
        instructions: tasks/choose-service.md
        assign:
          - requested_service: result.service
```

```markdown tasks/choose-service.md theme={null}
Find out which salon service the caller wants. Use what they have already
said and ask only if the service is missing or unclear. Then finish with
that service.
```

`assign:` creates a finish argument named `service` from the
`requested_service` declaration. The destination already owns the type and the
description, so the task does not repeat them.

All assigned values are checked before any are saved. If one is invalid, the
task stays open so the model can correct its answer, and nothing changes.

## 3. Read it where it is needed

Saving a value does not put it in any prompt. A prompt reads a value only when
it names it:

```markdown tasks/booking.md theme={null}
Requested service: {{requested_service}}
Use it if present. If no service is recorded, ask which service the caller wants.
```

An unset value renders as `none recorded yet.`, so write the sentence to read
whole either way. The instructions above work before and after the earlier task
saves the service.

<Warning>
  The agent that owns a task needs its own placeholder to remember the result
  after the task returns. It gets a completion status and its own earlier
  conversation back, not the private conversation inside the task.
</Warning>

### Use one in the greeting

The greeting renders once, before anyone speaks, so it can only name a value
that is already settled: one supplied at session start, a `default:`, or a
prefetch assignment.

```yaml agent.yaml theme={null}
variables:
  caller_name:
    type: str
    source: call_start
    default: there

conversation:
  greeting:
    speaks_first: agent
    text: "Hi {{caller_name}}, how can I help?"
```

<Warning>
  Do not greet a caller by a name you have not checked with them. A name looked
  up from the number that called is a guess: people call from a partner's
  phone. Mark it with
  [`confirm:`](/build/prefetch#always-check-a-value-with-the-caller) and the
  greeting cannot use it until they say yes.
</Warning>

## The types you can declare

The type is what the call checks a value against before saving it. A task's
finish argument is built from it too, so the model is told what shape of answer
is wanted.

### Built-in types

| Type        | What it holds                                        |
| ----------- | ---------------------------------------------------- |
| `str`       | Free text, such as names and notes                   |
| `int`       | A whole number                                       |
| `float`     | A number that may have a decimal part                |
| `bool`      | `true` or `false`                                    |
| `Phone`     | An international phone number in E.164 format        |
| `Date`      | Date text in `YYYY-MM-DD` format                     |
| `Time`      | A 24-hour time in `HH:MM` format                     |
| `Id`        | A short identifier with a defined character set      |
| `EmailStr`  | A valid email address, saved as plain text           |
| `NameEmail` | A person and their email address, held as two fields |

`string`, `integer`, `number`, and `boolean` are aliases for `str`, `int`,
`float`, and `bool`.

Those ten cover most packages. The three forms below are for when they do not.

### A fixed set of choices

```yaml agent.yaml theme={null}
variables:
  booking_status:
    type: Literal["booked", "cancelled", "pending"]
```

Anything outside the set is refused where it enters, and the refusal lists what
was allowed.

### A value that may be absent

`T | None` says `null` is a valid answer, not a failure. Use it when a lookup
can honestly come back with nothing.

### Several values of the same type

`list[T]` holds many. See [Keep several records](#keep-several-records) below.

<Note>
  LiveKit and Pipecat support every type on this page. The SLNG target supports
  the basic scalars only: `str`, `int`, `float`, `bool`, and their aliases. It
  runs none of your package's code, so it has nowhere to check a shape.
</Note>

The [type reference](/reference/variables#the-type-grammar) has the exact
formats and composition rules. Your tools still check business rules, such as
whether a record exists and whether a slot is free.

Every type on this page appears once in
[`examples/customer-intake`](https://github.com/slng-ai/unmute/tree/main/examples/customer-intake),
one agent that collects a caller's details and hands them to a tool.

## Keep several records

Declare `type: list[T]` when later steps need several values of the same type.
A list starts as `[]`.

In a task's `assign:`, `variable+:` appends one item and `variable:` replaces
the whole list:

```yaml agent.yaml theme={null}
variables:
  notes:
    type: list[str]

assign:
  - notes+: result.note
```

Appending skips `null` and skips an identical structured item. See
[assignment rules](/reference/variables#assigning-a-task-result-to-a-variable),
and [state design](/best-practices/state-design#choose-a-current-value-or-a-list)
to choose between a current value and a list.

## Advanced

Three things you will not need on your first agent.

### Group fields into a shape

<Note>
  Skip this until you need it. A shape is worth declaring when several fields
  always travel together and one tool fills all of them at once. Until then,
  separate variables are simpler to read and simpler to prompt.
</Note>

A pre-fetch cannot fill a shape or a list. Use a task to produce grouped fields;
use separate scalar variables for clock and lookup pre-fetch results. A single
pre-fetch may assign several scalar variables from one result.

A shape defines your own reusable object type. Choose a name, then declare
each field and its type under `shapes:`. Set a variable's `type:` to that
name to store an object with those fields.

One shape can be used by several variables. Each variable holds its own
value, and a task assignment saves all of the object's fields together.

For example, this package defines an object type called `Appointment` and
uses it for the variable `appointment`:

```yaml agent.yaml theme={null}
shapes:
  - name: Appointment
    fields:
      - booking_id: Id
      - service: Literal["haircut", "hair-color", "blowout"]
      - date: Date
      - time: Time

variables:
  appointment:
    type: Appointment
    description: The latest appointment successfully saved by the booking tool.
```

A task assignment saves all of the object's fields together, and one shape can
be used by several variables, each holding its own value.

Use the short field form, `- field_name: Type`, or the long form with `name:`,
`type:` and `description:` when a field needs explaining to the model. See the
[shape reference](/reference/variables#shapes-groups-fields-into-a-named-type)
for both.

<Warning>
  Declare a field only when something in the package can fill it. A field no
  tool returns leaves the model inventing a value or sending an empty one, and
  it renders empty in every prompt that names it for the rest of the call.
</Warning>

#### Reading one field

`{{appointment}}` renders the whole object as compact JSON. `{{appointment.date}}`
reads one field, and a dotted path can follow nested shapes. To read fields
from a list item, save that item into its own variable first.

`NameEmail` is an object type too, so `{{contact.name}}` and `{{contact.email}}`
work without declaring anything under `shapes:`. It is refused as a shape name,
because it already means something.

For deciding which fields belong together, separating a requested change from a
saved result, and choosing one record or a list, see
[Designing declared state](/best-practices/state-design#group-fields-that-describe-one-result).

### Hand a value to a tool

Sometimes the tool needs a value the model does not need to read or type. Use
`inject:` for that argument:

```yaml tools/check_slots.yaml theme={null}
input:
  type: object
  properties:
    date:
      type: string
  required:
    - date
inject:
  - service: "{{requested_service}}"
local:
  handler: tools/check_slots.py
```

The model supplies `date`. The handler receives both `date` and the current
`service`. Keep injected arguments out of `input.properties` and
`input.required`.

If the value is missing or unconfirmed, the tool stops before it runs. The
error names the task that supplies or confirms the value when one is declared,
and otherwise asks the model to collect it.

Injection is not the same as keeping a value private: tool results and spoken
messages can still contain it. See
[Reduce context sharing step by step](/best-practices/context-scope).

### Try it locally

```sh theme={null}
unmute dev my-agent --var caller_name=Priya
```

`--var` works for a declared variable with `source: call_start` or no `source:`
at all. Values are parsed against the variable's type; pass JSON for an object
or a list, quoted for your shell.

A system source such as `from_number` comes from the phone route instead, and
your machine is not one. To exercise prefetch and confirmation locally, stand
in for the network:

```sh theme={null}
unmute dev my-agent --source from_number=+15005550006
```

Which facts a route supplies, and in which direction, is in the
[source reference](/reference/variables#sources).

## Troubleshooting

### A prompt renders `none recorded yet.` on every call

The variable is unset. Either nothing has filled it yet, or the prompt that
reads it runs before the step that fills it.

**Fix:** check which step owns the `assign:`, and write the sentence so it reads
whole either way. An unset value is normal early in a call, so the prompt should
say what to do about it:

```markdown theme={null}
Requested service: {{requested_service}}
If no service is recorded, ask which service the caller wants.
```

### The owning agent forgets a value after its task returns

A task returns a completion status and the <Tooltip tip="The agent that started a task. When the task ends, the caller goes back to it.">owner</Tooltip>'s own earlier conversation. It
does not hand back the private conversation inside the task.

**Fix:** put a placeholder in the **owner's** prompt too. Saving is not sharing.

### A tool refuses to run, naming a value

The tool reads that value through `inject:` or a webhook path, and the value is
empty or still unconfirmed.

**Fix:** the refusal names the task that supplies or confirms it. Run that step
first, or, when nothing supplies the value, have the model ask the caller.

### `--var` is refused for a variable

`--var` works only for a variable with `source: call_start` or no `source:` at
all. A system source such as `from_number` comes from the phone route, and your
machine is not one.

**Fix:** use `--source` instead, which stands in for the network:

```sh theme={null}
unmute dev my-agent --source from_number=+15005550006
```

## Where to go next

<Columns cols={2}>
  <Card title="Tasks" icon="list-checks" href="/build/orchestration/tasks">
    The usual way a variable gets filled: `assign:` on the task that learns it.
  </Card>

  <Card title="Pre-fetch" icon="timer" href="/build/prefetch">
    Fill a value before the greeting, and confirm the ones about the caller.
  </Card>

  <Card title="Context scope" icon="scissors" href="/best-practices/context-scope">
    Who can read what, and how to share less as the call goes on.
  </Card>

  <Card title="Designing declared state" icon="database" href="/best-practices/state-design">
    Which fields belong together, and when a list beats a current value.
  </Card>

  <Card title="Credentials" icon="key" href="/build/credentials">
    API keys and tokens, which are not variables.
  </Card>

  <Card title="Variables reference" icon="file-code" href="/reference/variables">
    Every type, field, source and assignment option.
  </Card>
</Columns>
