operations is an ordered list. It runs top to bottom when the model calls the
tool, and it is where everything the tool actually does lives.
Sending operations replaces the tree entirely. There is no partial update
of a tree — PATCH /v1/tools/{tool_id} leaves out fields you omit, but a tree
you do send is the whole tree. Read the current one, change it, send it back.
One operation
Every operation is{kind, config, on_error?}, and kind decides the rest. An
operation is a union of eleven shapes, one per kind, each carrying only its own
fields — so a field belonging to another kind is rejected, not ignored. A
publish_fields on a say is refused at save with
422 operations.0.say.publish_fields: Extra inputs are not permitted, and a
then on an http the same way.
config is required on every kind but end_call, which has no settings.
The eleven kinds
Which fields exist on which kinds
silent, publish_fields and background_execution exist on http, code
and frontend_rpc and on nothing else, because those three are the only kinds
that produce a result. There is nothing for the other eight to hide, to publish
out of, or to stop waiting for.
then and else belong to an if in exactly the same way.
silent: true still runs the operation and still publishes its fields; it only
keeps the response away from the model. Use it for a noisy intermediate lookup —
a lookup whose raw JSON the agent has no business narrating.
on_error
abort (the default) stops the tool. Nothing after the failing operation runs,
nothing further is published, and the model is told the tool could not be
completed — the agent then apologises to the caller in its own words.
continue logs the failure and moves to the next operation in the same
chain. Two consequences worth knowing:
- A failed operation publishes nothing. Anything a later template expected from it resolves to empty instead. Validation cannot see this, because it checks what an operation declares, not whether it ran.
- On an
if, there is no next operation — anifis terminal.on_error: "continue"on one means neither branch runs and the tree ends there. That is a real outcome to reach for, and it is the only thingcontinuecan mean on an operation nothing follows.
The branching rule
if, handoff, end_call and transfer are terminal in their chain, and
a chain is the top-level list or any then / else list. Nothing may follow
one there — a tree that puts something after one is refused at save, naming the
operations that came after it.
Branches do not rejoin. There is no operation that both paths flow back into. So:
- If work must happen on both paths, write it into both branches.
- If work must happen after the decision, move the branch later instead.
- For an early-exit guard, put the rest of the flow inside the guard’s
else.
end_call, not after
it. By the time an end_call returns, the session is already closing.
Derived silence
If no operation in the tree can return a response, the tool is silent whateversilent says — including operations inside if branches. A tool
whose whole tree is one say "Your appointment is confirmed" says that once, rather than
saying it and then having the model improvise a second sentence on top.
You cannot turn derived silence off, and there is nothing to untick. If the
agent should add a closing line, add a generate_reply
operation that states what to add: it is visible in the tree, it says what it
will say, and it costs the same turn.
When a tool is not silent, the model is handed the ordered outputs as JSON —
{"responses": [{"operation": "http", "response": { … }}, … ]} — or the literal
string "Done." when a tool that could have returned something happened not to.
A run that reaches end_call tells the model nothing either way; there is
nobody left to narrate to.
Timeouts are in seconds
Every timeout in a tree is in seconds, never milliseconds. A caller is on the line while an operation runs, so a long timeout is not a patient tool, it is a hung call. Each one is capped:
If an endpoint is genuinely slow, the answer is the tool-level
long_running_task flag — the agent keeps talking while the work runs — not a
120-second timeout. See overview.
A three-branch tree
Look the account up, then take one of three paths. Notice that the secondif
is the only operation in the first one’s else: that is how you get a third
branch, since nothing may follow an if in the same chain.
- The
httppublishestierintouserdata, so the rest of the conversation — and the next tool — can read it.balance_duegoes totooldata, because nothing outside this run needs it. - The
httpissilent, and it is the only operation that could return anything. So the whole tool is silent: each branch speaks for itself and the agent adds nothing after. - The enterprise branch runs two operations. A branch is an ordinary chain — it can be as long as you like, and the same terminal rule applies inside it.
Next
Templating and data
The six template roots, the two stores, and
publish_fields.Conditionals
Every comparison operator, and why time comparisons fail.
Testing and publishing
Run the draft tree and see what each operation did.
Patterns
Lookup-then-answer, guard-and-continue, escalation.