Skip to main content
A task is one step of a call. It has its own instructions and tools. When it finishes, control returns to the agent that called it. That agent is the owner. Inside a task group, each task is one step of the group. To the model, a task is one more entry in its function list, next to the agent’s tools. Calling it switches to the task’s prompt and tools. When the task finishes, the owner’s prompt and tools come back. Use a task when one bounded piece of work needs a smaller prompt or a smaller tool list. Use a task group when several tasks must run in order. Use a handoff when a different agent should own the rest of the call. New to tasks? Your first task adds one to the package unmute init writes, one key at a time. On this page:

Quickstart

A task is one entry under an agent’s tasks:. This one identifies the caller and saves what it learns:
agent.yaml
Four keys carry it: what it is (name, instructions), when to run it (when), what it can use (tools), and what it saves (assign).

Declare a task

agent.yaml

Every key a task takes

Two are required. The rest are how you shape the step.
string
required
A lower snake case name, unique across all agents in the package. To reuse an existing task, write its bare name instead of defining it again.
string
required
Path to the task’s Markdown prompt inside the package. No prompt is inferred.
string
The situation the model reads to decide whether to start the task. If omitted, the task is a definition only and must be used in a task group; it cannot be attached elsewhere by bare name.
list of strings
Names of tool files loaded by the package. Omit for no ordinary tools in this task; it does not inherit its owner’s tools.
list of one-key pairs
Pairs of variable: result.field, including dotted result paths. Use variable+ to append one list item. Omit to save no values; the task can still finish. The destination variable already owns the type and description, so the task does not repeat them.
list of objects
Tools whose successful results finish the task automatically. Each entry requires tool and a non-empty success list of one-key output field/value pairs. Values must be declared output enum choices; a list means alternatives. If omitted, the model ends the task by calling its generated finish tool.
list of strings
Names from the top-level handoffs catalog. Omit for no handoffs from this task.
string
A fixed spoken line with no {{placeholders}}. Omit it for no fixed announcement. Required when opening is listen.
string
Accepts generate or listen. Omitted means generate, so the model writes the opening turn. listen speaks announce and waits for the caller without a model request.
object
The history fields. Omit for history: messages. A returning task restores its owner’s earlier context and adds only completion or unserved status.
string
A models.think entry name. LiveKit only. If omitted, use the entry agent’s think profile, even when another agent defines the task.
There is no tasks:, no task_groups: and no escalations: key on a task. A task cannot run another task or reach a person directly, and that is structure rather than a rule to remember: there is no key to write it in.

How a task finishes

Every task gets a finish call. The model uses it to say the task is done and to hand over the values in assign:. The finish: key is different: it names tools whose own result ends the task, so the model does not have to make that call. The next section covers the key. The finish call for this example takes customer_id and customer_name. It checks every value before saving any of them. If one value is invalid, the task stays open and no value changes. A task that saves nothing omits assign:. The model can still call finish.

Say what success looks like

How does the task know it worked? Without finish:, the model decides. It runs a tool, reads the result, and calls finish. With finish:, the tool’s own result decides. You name the tools that end the task, and what a success from each one looks like. This also removes a model request. A task that runs a tool and then asks the model to call finish spends a whole request on a decision the tool already made. The saving grows with the number of tasks a call runs through.
agent.yaml
When one of those tools returns a result in which every success: field holds one of its allowed values, the task saves its assign: from that result and ends. The result never reaches the model. Anything else is an ordinary result: it goes back to the model and the task stays open. That is what keeps a failed booking a conversation rather than a saved one. A success: item is field: value, or a list of values that are alternatives. Items on different fields are all required. Every value has to be one the tool declares in its own output enum:, so a typo is a refusal at compile time rather than a task that never ends by itself. result.<field> in assign: names an output property of every listed tool, so one tool returning appointment and another returning nothing is refused. Four things follow from a task that ends on its tool:
  • The listed tools close. Once one of them succeeds, none of the task’s finish: tools runs again in that run of the task. A new booking is a new request from the caller, and a new run of the task.
  • The finish call is still there. The model uses it for a request the task cannot serve, for values the task already holds, and for recording a result whose save was refused. That repair keeps the values the tool returned, so a reference the tool handed back is never retyped by the model.
  • The appended text changes. The text Unmute adds to your instructions still tells the model to call finish when the step is complete. It then says that the named tools end the step by themselves, and that finish is not to be called after one of them succeeds. Your own instructions should not contradict it.
  • The owner speaks. The task says nothing after its tool. The next step of a group opens, or the owner speaks once when control returns, so the acknowledgement belongs in the owner’s prompt.

What the model gets for a task

The prompt a task runs on has four parts. You write the first one.
  1. Your instructions file, as written.
  2. The appended finish rule. Unmute adds it after your text. It names the finish call, lists the values from assign:, and gives the model a way out for a request the task cannot serve. You do not write “call finish when you are done” yourself.
  3. The values your placeholders name. Each {{variable}} in your instructions is replaced with the saved value. No other saved value is added.
  4. The history you chose with context.history. When omitted, it is messages.
For the task declared above, the appended rule reads:
With finish:, a sentence is added between those two paragraphs. It says the named tools end the step by themselves and that finish is not to be called after one of them succeeds. On the Pipecat target the call has a longer name that includes the task’s name. The rule is the same.

A task’s contract

Four keys make a task that knows what it saves, when it is done, and when it can be skipped. They live in three places.

Read a saved value

Saving and sharing are separate choices. Put a placeholder in the prompt that needs the value:
tasks/booking.md
The task receives saved state only through the values its prompt names. Its history may separately contain facts spoken earlier. A description does not send a value to the model. An unset value renders as none recorded yet. A dotted reference selects one field and does not send its siblings:
See Variables for shapes, dotted paths, append assignments, and confirmation.

Choose conversation history

context.history is optional. When omitted, it is messages. A task needs a code target, Pipecat or LiveKit. On SLNG a task cannot run at all today, so every history value is refused there, messages included. Tasks on SLNG are coming.
agent.yaml
tasks/reschedule-booking.md
This reset task sees those three saved values because its prompt names them. It does not receive the sentence that triggered the task. If the new date was not saved, the task must ask for it again. History controls model input; it does not redact speech already retained in a different context. Use reset when the receiver should inherit no transcript. The step-by-step context guide shows how to save the request first and keep the owner informed after the task finishes.

What returns

The owner gets its pre-task context back plus one neutral status:
or:
Task arguments, tool results, private task turns, and the text of an unserved request do not cross back. The owner reads saved values through its own prompt placeholders. Every generated finish call includes an optional unserved_request. A task uses it when it cannot complete the request with its tools or handoffs. A non-empty value skips all assignments. The owner sees the unserved status and asks the caller what they need; it does not receive the private request text. After a tool reports success, call finish right away with the result to save. Let the owner confirm it once, using its own placeholders. If the task waits for another turn, it may receive a new request before saving the first result. An unserved status does not undo an action already completed by a tool. Better still, let the tool end the task. See Say what success looks like. For requests that another agent can handle, a task may declare handoffs:. Taking one ends the task and any remaining task-group steps. See Handoffs.

Advanced

Append and project values

Append one result to a list with +:
agent.yaml
An absent item adds nothing. Repeating the same structured item does not add a duplicate. Plain values may repeat. An assignment may select a nested field:
agent.yaml
The whole-object assignment establishes the result shape. The compiler then checks the nested path and derives its type.

Share a task across agents

Define a task once. Another agent can attach it by name:
agent.yaml
Task names are unique across the package. Share a task only when both agents should be able to run it: a task within reach is a task the model may choose.

Open by listening

A task’s first turn is a model request. When the task opens with one fixed question, opening: listen speaks that question and waits, and makes no request:
agent.yaml
The line is the announce: line, spoken once and recorded as the task’s own first turn, so a caller answering it is answering something the task can see. The default is generate: the model opens the task from its instructions. A listening task with no announce: is a warning, not a refusal: the caller hears nothing until they speak, which is occasionally what you want and usually a mistake.

Announce a task

announce: is an optional fixed line spoken as the task starts. Omit it for a silent transition. If you add one, tell the model not to repeat it with a second “let me check” line:
agent.yaml

Task or second agent

Try it

my-agent is the package unmute init wrote in Your first agent. With a task in it:
In the dev page the task shows as its own row, labelled HANDOFF, so you can see it was entered. The dev loop explains the rest of that page. The salon is the shipped example with tasks, a task group and handoffs. These commands need a clone of the unmute repo:

Troubleshooting

The model does the task’s work itself and never enters it

The agent holds every tool the task holds, so the model takes the short route and the task’s assign: never runs. unmute validate warns and names the tool to move. Fix: take the tool off the agent and leave it on the task. A task the model can bypass is a task it will bypass.

A task saves nothing on a call where it clearly ran

Every assigned value is checked before any is saved. If one value is invalid, nothing changes and the task stays open so the model can correct it. Fix: check the destination’s type against what the tool actually returns. An object or array output landing on a plain destination is refused at compile time; a wrong value at run time keeps the task open instead.

An older package uses a retired key

Ordinary tool input: and output: schemas are unchanged. Run unmute validate after updating an older package; retired keys produce a located error with migration advice.

Where to go next

Task groups

Run several tasks in a fixed order. This is the next page.

Making a task actually run

What to give a task so the model enters it, and what to take away from the owner.

Designing declared state

Choosing what to save with assign:, and how to type it.