unmute init writes, one key at a time.
On this page:
- Quickstart - one task, start to finish
- Every key a task takes - all nine
- Say what success looks like - let a tool’s outcome end the step
- Choose conversation history - what the task can read
- What returns - and what does not
- Advanced - lists, sharing, starting a step
- Troubleshooting - the two that come up most
Quickstart
A task is one entry under an agent’stasks:. This one identifies the caller
and saves what it learns:
state.py
agent.yaml
name, prompt), when to run it
(when), what it can use (tools), and what it saves (save).
Declare a task
state.py
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 flow; it cannot be attached elsewhere
by bare name.
list of strings or objects
Tools loaded by the package. Each item is a bare tool name, or a block that starts
- name: <tool> and adds confirm: and outcomes: for this step only. Omit for no
ordinary tools in this task; it does not inherit its owner’s tools.list of strings
Lines said to read the call back before the tool runs. The tool runs on the caller’s
yes. Lines read
{{input.x}}. Needs a then: done outcome on the same tool.list of objects
What the step does with each result of this tool, top to bottom, first match wins.
See Say what success looks like.
list of one-key pairs
A typed match on the result, such as
- status: booked or - status: [booked, moved].null
Matches every result no
if: above matched. Last, and at most one.list of one-key pairs
Pairs of
variable: result.field saved only when this outcome matches.list of strings
Lines said for this result. Several lines are alternatives. Lines read
{{result.x}}.string
Accepts
done or failed. Omitted means the step goes on.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.object
What the step does as it opens:
say, listen, call and with. Omitted means the
model writes the opening turn. See Start a step.list of strings
Bare names of handoffs an agent defines. Omit for no handoffs from this task.
list of strings
State fields the model fills from the conversation when it starts the task, such as
the service the caller asked for. Omit to fill none.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.tasks:, no flows: and no transfers: 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 afinish call. The model uses it to say the task is done and
to hand over the values in save:. A tool outcome with then: done is
different: the tool’s own result ends the task, so the model does not have to
make that call. The next section covers it.
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 save:. The model can still call finish.
Say what success looks like
How does the task know it worked? Without an outcome, the model decides. It runs a tool, reads the result, and callsfinish. With an outcome that says
then: done, the tool’s own result decides. You name the tool in the step’s
tools: and say what each result 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
save_booking returns a result that matches the if:, the step says the
line, saves its save: rows from that result and ends. The result never
reaches the model. Anything else falls to the next outcome, or goes back to the
model when none matches. That is what keeps a failed booking a conversation
rather than a saved one.
An outcome is how a step reacts to one tool result. These rules hold for all of
them:
- Top to bottom, first match wins. An outcome starts with
if:orelse:.else:takes every result noif:matched, comes last, and appears once. then: doneends the step as completed.then: failedends it as failed, and a flow stops unless the step is non-blocking. With nothen:the step goes on. Only a step’s outcomes takethen:.- Order them
then:first. Write the outcomes withthen:, then those without, thenelse:.else:cannot saythen: done. Anelse:withthen: failedcannot follow an outcome withoutthen:. - No
then:needssay:. Itssave:readsresult.only. - Saving. A
then: doneoutcome with nosave:of its own saves the step’ssave:rows from the tool’s result. A step cannot mix done outcomes with and without their ownsave:. confirm:needs a done outcome. The tool entry needs at least onethen: doneoutcome. The read-back is said before the tool runs, and the tool runs on the caller’s yes.- What a line reads.
{{state.x}}reads State. Aconfirm:line reads{{input.x}}. An outcomesay:reads{{result.x}}. A line with no tool, such as a greeting or a nudge, reads State only.
if: 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.
A tool in a step is not always a bare name. A top-level tools: entry can
carry the same outcomes: for every agent that uses the tool; see
Tools in the agent file.
On the slng target, a task takes one outcome with then: done
for now, because SLNG supports one finishing tool per task today. It may carry
up to 5 say: lines. SLNG says them itself when the tool’s result matches. A
line may read {{state.x}} for a whole saved value, or {{result.x}} for a
field of the tool’s result. SLNG ends a step only as done, so then: failed is
refused, and so is a confirm: read-back. The tool needs a known output: a
typed local tool, or a hosted slng: tool with a mirror.
agent.yaml
result.<field> in the step’s save: names an output property of every tool
that ends the step, so one tool returning appointment and another returning
nothing is refused.
Four things follow from a task that ends on its tool:
- The ending tools close. Once one of them succeeds, none of them 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
finishcall 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
finishwhen the step is complete. It then says that the named tools end the step by themselves, and thatfinishis 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 unless the outcome
has a
say:. The next step of a flow 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.- Your instructions file, as written.
- The appended finish rule. Unmute adds it after your text. It names the
finishcall, lists the values fromsave:, 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. - The values your placeholders name. Each
{{state.variable}}in your instructions is replaced with the saved value. No other saved value is added. - The history you chose with
context.history. When omitted, it ismessages.
then: done outcome, 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
none recorded yet.
A dotted reference selects one field and does not send its siblings:
Choose conversation history
context.history is optional. When omitted, it is messages.
Tasks compile on Pipecat, LiveKit and SLNG. SLNG takes every history value
except
summary. Twilio refuses tasks, including every task history value.
agent.yaml
tasks/reschedule-booking.md
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: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 saves nothing. 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.
A finished task will not run again until the caller speaks
A task the owner already ran is refused, not re-run, while the caller has said nothing since it returned. A completed result with no new caller turn above it is the model reading its own finished work as a fresh request: the owner reruns the whole step, repeats its announcement, and answers nothing. The refusal arrives before the step’sstart: runs, so a refused call is silent.
Nothing to configure. Write the owner’s prompt to expect it: a bare “yes” above a
completed task is the caller answering the question that task already asked and
acted on, so the owner should read its saved values and reply.
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 flow steps. See
Handoffs.
Advanced
Append and project values
Append one result to a list with+:
state.py
agent.yaml
save: row may select a nested field:
agent.yaml
? after the result field, as - appointment: result.appointment?. A
path like result.appointment.id reads part of a value the step already
returns, so it takes no ?.
Share a task across agents
Define a task once. Another agent can attach it by name:agent.yaml
Start a step
By default a task’s first turn is a model request.start: replaces that turn
with something said or run in code. It takes four keys, and each is optional.
say:is said as the step opens. Several lines are alternatives.listen: truewaits for the caller after the line, with no model request.call:andwith:run a tool in code first.with:lists the tool’s inputs, such as- day: state.requested_day.
agent.yaml
listen: true
and no say: is a warning, not a refusal: the caller hears nothing until they
speak, which is occasionally what you want and usually a mistake.
A call: runs the tool before the model speaks:
agent.yaml
with: value with no ? is one the tool requires. A value with ? lets the
call run without it. The compiler checks both against the tool’s input schema.
Then the step does one of four things:
- A required value is empty: the call does not run, and the step says
say:and listens. - The value is already confirmed: the step is skipped.
- The tool has a
confirm:line: the read-back is said instead ofsay:, and the tool runs on the caller’s yes. - Otherwise the result goes through the tool’s
outcomes:, and the line the outcome picks is said.
say:, tell the model not to repeat it with a second “let me
check” line.
Task or second agent
Try it
my-agent is the package unmute init wrote in
Your first agent. With a task in it:
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 flow 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’ssave: 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
Everysave: 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
Flows
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
save:, and how to type it.