say speaks a
fixed line, generate_reply asks the model to phrase one, and add_message
puts something into the model’s context without anyone hearing it.
All three are templated, so
{{args.*}}, {{tooldata.*}}, {{userdata.*}},
{{system_vars.*}}, {{vars.*}} and {{secrets.*}} all resolve — see
templating and data. None of them carries
silent, publish_fields or background_execution: they return nothing, so
there is nothing to hide, publish or detach.
say
text is refused at save. A say whose text is
three spaces speaks nothing, so it fails at the request that tried to store it
rather than on a live call.
generate_reply
instructions is not the line. It is what you want said; the model writes the
words, in the language it has been speaking and in the agent’s voice. Write it
as a direction to the model, not as dialogue:
- Good:
"Confirm the booking, read back the date and time, and ask if they want a reminder." - Bad:
"Your booking is confirmed for {{args.date}}."— that is asay.
generate_reply when the phrasing depends on what just came back, and
for say when it does not. generate_reply costs an extra model call in the
same turn; say costs nothing but TTS.
add_message
tooldata or userdata, where you can address them
by name. add_message is for context the model should weigh.
wait_for_playback
say and generate_reply each take wait_for_playback (default false).
Ticked, the tree does not continue until that line has finished playing.
Leave it off. The archetypal say exists precisely to cover the next
operation’s latency:
on_error then answers for.
Two places do not need the flag at all. A say before an
end_call and a say before a
transfer both finish playing on their own: those two
operations wait for outstanding speech before they act.
Silence is derived
A tool in which no operation can return a response is silent whatever thesilent flag says, and that includes operations inside if branches. So a tool
whose whole tree is one say says that line once, rather than saying it and then
having the model improvise a second sentence on top.
That is the common case and it is deliberate: without it, the model would be
handed a bare "Done." after the tool ran and would talk over the line the tree
just delivered.
If you do want a closing line, add a generate_reply that states what to add.
Do not try to turn the derived silence off — it is derived from the tree, not
from a box.
http result is hidden, the say line is spoken verbatim, and the
generate_reply is the one sentence the model contributes.
Realtime agents reword a script
On a realtime (speech-to-speech) agent, a
say is not a script. Realtime
models have no separate text-to-speech step to hand exact words to, so the
platform asks the model to say the line instead — and it usually will, in
roughly those words. It may reword, reorder or trim.If the wording has to be exact — a quoted price, a legal line, a compliance
disclosure — put that agent on a cascade model stack, where say hands the text
straight to TTS. See models.Worked example: lookup with a filler
The filler covers the HTTP call, and the model phrases the answer from what came back.wait_for_playback anywhere: the filler is doing its job precisely because
the tree keeps moving while it plays.
Worked example: a disclosure that must land first
Here the order genuinely matters — the caller has to have heard the line before the recording starts — so the flag goes on, and the tool setsdisable_interruptions so an interrupted line cannot count as a heard one.
Next
Ending the call
end_call, and how to word a goodbye the platform can keep.Templating and data
Where
{{tooldata.*}} and {{userdata.*}} come from.Tools overview
silent, disable_interruptions and the other tool-level flags.Patterns
Complete trees you can paste, including lookup-then-answer.