Skip to main content
A task is a step that saves values to the call state, on its own prompt. The rules live in the core, so LiveKit and Pipecat behave the same.

Quickstart

agent.py
The main agent now has a choose_service tool. When the model calls it, a step runs on its own prompt. The step saves requested_service and hands the call back. A value outside the three services is refused, and the model asks again.

Why use unmute for this

A task is data. You describe the step, and the library checks it at startup, runs it the same way on both frameworks and saves the values it collects. A broken flow fails the deploy, not the call.

Name a task

A task or group name is lower snake case. It becomes the prompt site task:<name>. A prompt site is the place in the agent where a prompt is used: the main prompt, or the prompt of one task step. Three names are taken:
  • finish and go_back_to_step, the step’s own tools.
  • summary, because a handoff summary uses the prompt site task:summary.
A task is also a tool of the main agent. A task with the same name as another tool of the agent, like end_call, is refused when the agent is built. This is the same on LiveKit and Pipecat.

Define a task

Every spec class is immutable and refuses keys it does not know. They are Pydantic models, so you can build them as literals or from JSON with model_validate. Import them from unmute_core.tasks. A step that checks the caller’s phone number looks like this:
agent.py
The main fields of a TaskSpec are:
  1. name: lower snake case.
  2. instructions: the step’s prompt.
  3. when: when the main agent starts it.
  4. tools: the tools the step may call.
  5. assign: the values it saves.
Tasks reference lists the rest.

Instructions

A step’s instructions may hold {{name}} placeholders and plain text, including JSON.
  1. A lone }} is plain text. {"a": {"b": 1}} passes.
  2. A {{...}} that is not a valid placeholder is refused, and so is a {{ that never closes.
  3. Blank instructions are refused.
  4. Spoken lines, announce lines, locked arguments and the greeting keep a stricter rule: they take only placeholders.

Assign

One value a step saves. An Assign has a variable and a path. The path says where the value sits in the step’s result, like ("order", "id").
An empty path, (), saves the whole result, into a State value typed as the tool’s output model. The finish parameter is the field itself for a one-part path. For a longer or an empty path it is the variable name. Two assignments may not share a finish parameter. append adds one entry onto a list instead of replacing it. This is Unmute’s +. optional lets the step finish without the value. This is Unmute’s ?. Otherwise a served finish must carry it, unless it appends.

Modes

TasksSpec.mode is status or record. Everything on these pages describes record mode. status mode keeps Unmute’s older behaviour: a run hands the main agent a status, and a hand-back saves nothing.

Run the tasks

TaskPlan

TaskPlan holds the tasks and groups of one agent, checked once and read by every call. It checks the spec against the State and the tools. It raises ValueError and names the first task, group or value that does not fit. TaskPlan.check_then_targets(known) refuses a group that transfers to an agent the adapter was not given. The adapters call it when they are built.
  1. check_native_tools(names) refuses a task that lists a native tool the agent does not have. It raises ValueError and names the first task and the missing tool.
  2. check_handoff_tools(names, fix=) refuses a task whose handoffs names a tool the agent has no handoff for. fix is the end of the message. Say how to add the tool.
  3. Both take task= to check one task. Without it, they check every task.
  4. The adapters call them when the agent is built. Call them yourself when you write your own host.

CallTasks

CallTasks holds the tasks of one call: the main agent’s task tools, and every run they start. Both adapters build their own from the spec, once per call: read it as agent.tasks on LiveKit and flows.tasks on Pipecat. Build one yourself only when you write your own host. CallTasks.empty(state, tools=...) gives the tasks of an agent that has none. Use it in your own host when it needs a stack and you have no tasks. TasksSpec.owners() returns each State value that a task saves, with the first task that saves it. The save tool for values no task saves uses it. See State.

Troubleshooting

ValueError names a task, group or value when the plan is built

TaskPlan checks every name a task reads against the State and the tools. Fix: read the message. It names the first thing that does not fit, like a {{name}} the State does not have, a tool the task does not list, or a group that transfers to a target then_targets does not hold.

A task has no when, and nothing can start it

A task with no when must be a step of a group, or be entered by a via tool. Fix: add a when, put the task in a group, or add a via tool.

A task name is refused

finish, go_back_to_step, the built-in tool names and summary are taken. Fix: rename the task or group. In the salon tutorial: Book step by step. Every argument: Tasks reference.

Where to go next

Task groups

Run several tasks in order, and decide what follows.

Tasks reference

Every argument of every task class.

Tools

The tools a step lists in tools.