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

# Task groups

> Run several tasks in a fixed order.

A task group is an ordered sequence of tasks. Use one when the order must be a
runtime guarantee. A task that runs inside a group is called a step of that
group.

On this page:

* [Declare a group](#declare-a-group) - steps, in order
* [Every key a group takes](#every-key-a-group-takes) - all seven
* [Skip a step whose work is already done](#skip-a-step-whose-work-is-already-done) - `skip_when_confirmed:`
* [What stops a group](#what-stops-a-group) - unserved, handoff, finish
* [Context between steps](#context-between-steps) - shared or isolated

## Declare a group

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

agents:
  appointment_desk:
    instructions: instructions.md
    think: reasoning
    speak: voice
    tasks:
      - name: identify_customer
        instructions: tasks/identify-customer.md
        tools:
          - lookup_customer
        assign:
          - customer_id: result.customer_id

      - name: select_appointment
        instructions: tasks/select-appointment.md
        tools:
          - check_slots
        assign:
          - selected_slot: result.selected_slot

      - name: finalize_appointment
        instructions: tasks/finalize-appointment.md
        tools:
          - book_appointment
        assign:
          - booking_status: result.booking_status

    task_groups:
      - appointment_flow

task_groups:
  appointment_flow:
    when: The caller wants to book, reschedule, or cancel an appointment.
    steps:
      - identify_customer
      - select_appointment
      - finalize_appointment
    context_scope: shared
    then: return
    merge: results
```

These three tasks have no `when:`. The group decides when they run, so they
need no trigger of their own. A task an agent runs on its own does need one,
and a task with no `when:` that no group lists is refused, because nothing
would ever run it.

Each `steps` entry names a task defined in the package, under some agent's
`tasks:`. It does not have to be the agent that runs the group: the group is
what makes each step reachable. `assign:` derives each task's finish arguments
and saves the values needed later.

### Every key a group takes

<ParamField path="steps" type="list of strings or objects" required>
  One or more task names, in execution order. An object requires `task` and may set
  `skip_when_confirmed` to a variable that task confirms. Omit that condition to run the
  step every time. An empty or absent steps list is refused.
</ParamField>

<ParamField path="when" type="string">
  The situation the model reads to decide whether to run the group. Omission is accepted
  but supplies no trigger guidance, so write one.
</ParamField>

<ParamField path="announce" type="string">
  One fixed spoken line, with no `{{placeholders}}`, when the group starts. Omit for no
  announcement.
</ParamField>

<ParamField path="context_scope" type="string" required>
  Accepts `shared` or `isolated`. There is no default. Each member still applies its own
  `context.history`; an isolated group cannot be widened by a member’s `full`.
</ParamField>

<ParamField path="then" type="string" required>
  Accepts `return`, `transfer`, or `end`. There is no default.
</ParamField>

<ParamField path="then_target" type="string">
  Required with `then: transfer`: an existing agent name. Refused for `return` or `end`;
  there is no inferred destination.
</ParamField>

<ParamField path="merge" type="string">
  Only `results` is accepted. Omission also means `results`.
</ParamField>

## Skip a step whose work is already done

A step is a bare task name, or an item that says how the group treats it:

```yaml agent.yaml theme={null}
    steps:
      - task: identify_customer
        skip_when_confirmed: customer_id
      - select_appointment
      - finalize_appointment
```

The group skips that step when the named variable is confirmed at the moment
the group starts, and runs it otherwise. A bare step always runs. This is what
makes a second booking on one call cost nothing extra: the caller is identified
once, and the group knows it.

`skip_when_confirmed:` names a variable the skipped step itself confirms, with
[`confirm:`](/reference/variables#confirm-marks-a-value-the-caller-has-to-agree-to)
on the variable. Confirmed here is a real mark on the variable, set when the
step named under `confirm:` saves a value. It is not the same as a prompt
sentence saying the caller was verified: the compiler reads the mark, never the
prose. Anything else is refused: a variable nobody confirms would leave the
step running forever or never, and a variable another step confirms would let
this group skip somebody else's work.

There is a side effect, and it is the point. A step some group names this way
withdraws the confirmation of the values it confirms every time it is entered,
including on its own outside the group. So a caller correcting their number
re-verifies it, and the next group run does not skip the step on the strength
of the number they just replaced. Values derived from a withdrawn one follow it.

## What stops a group

A group stops when a step ends unserved, whichever `context_scope` it has: the
later steps do not run, and the owner is handed the unserved status. Running
the next step would answer a question nobody asked.

A handoff can arrive while a tool that ends the step is still running, or in
the same response as one. Either way the step's own work is committed first,
and the caller moves after. The booking is not lost on the way out.

A request the caller makes at a [`reset`](/best-practices/context-scope) or an
isolated boundary reaches nobody: the step that hears it has no way to pass
words on other than
[`unserved_request`](/build/orchestration/tasks#what-returns), and an isolated
step's context does not carry back. Keep the request in the caller's own words
in `unserved_request` and let the owner act on it.

## Context between steps

`shared` lets later tasks inherit the group's running conversation. `isolated`
starts every member without inherited group conversation.

Each task still applies its own `context.history`. Omitted history means
`messages`. An isolated group also prevents a member's `full` policy from
recovering conversation outside that group.

Task results are private. The next task sees only neutral completion status,
the conversation allowed by its history choice, and saved values explicitly
referenced in its prompt.

```markdown tasks/select-appointment.md theme={null}
Find an available slot for customer {{customer_id}}.
```

The group owner gets its original context back plus `completed` or `unserved`.
It reads saved values through its own placeholders.

A member task may hand off directly to another agent. That ends the current
task and skips the remaining group steps.

## LiveKit status

Task groups use a LiveKit API that upstream marks experimental. The package
still validates and compiles normally.

## Where to go next

<Columns cols={3}>
  <Card title="Handoffs" icon="users" href="/build/orchestration/handoffs">
    The shape that does not return: a lasting change of role.
  </Card>

  <Card title="Choosing a structure" icon="git-branch" href="/build/orchestration/choosing-a-structure">
    Compare tasks, task groups, and handoffs.
  </Card>

  <Card title="Making a task actually run" icon="list-checks" href="/best-practices/step-scoping">
    Why a task you declared never runs, and how to fix it.
  </Card>
</Columns>
