- Quickstart - declare, fill and read one value
- Declare it - every key a variable takes
- Fill it - a task, a pre-fetch, or the call itself
- Read it - placeholders, and the greeting
- The types you can declare - ten built in, plus lists and your own
- Advanced - shapes, injection, and seeding a value locally
- 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, replacecurrent_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:
agent.yaml
Quickstart
Declare a value, save it when a task finishes, and read it in a later prompt:agent.yaml
tasks/booking.md
The snippets below extend an existing package. Merge them into the matching
blocks in
agent.yaml.1. Declare it
A variable needs a name and atype:. Start with plain text:
agent.yaml
requested_service is the name you will use
everywhere else, and str means it holds text.
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 for composition
rules and target limits.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.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.string
Accepts
call_start, conversation (SLNG only), or a system call
fact. 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.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.
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:
The common one is a task. Add
assign: to the task that learns the value:
agent.yaml
tasks/choose-service.md
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:tasks/booking.md
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.
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, adefault:, or a
prefetch assignment.
agent.yaml
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
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
agent.yaml
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 below.
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.examples/customer-intake,
one agent that collects a caller’s details and hands them to a tool.
Keep several records
Declaretype: 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:
agent.yaml
null and skips an identical structured item. See
assignment rules,
and state design
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
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.
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:
agent.yaml
- field_name: Type, or the long form with name:,
type: and description: when a field needs explaining to the model. See the
shape reference
for both.
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.
Hand a value to a tool
Sometimes the tool needs a value the model does not need to read or type. Useinject: for that argument:
tools/check_slots.yaml
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.
Try it locally
--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:
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:
The owning agent forgets a value after its task returns
A task returns a completion status and the ’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 throughinject: 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:
Where to go next
Tasks
The usual way a variable gets filled:
assign: on the task that learns it.Pre-fetch
Fill a value before the greeting, and confirm the ones about the caller.
Context scope
Who can read what, and how to share less as the call goes on.
Designing declared state
Which fields belong together, and when a list beats a current value.
Credentials
API keys and tokens, which are not variables.
Variables reference
Every type, field, source and assignment option.