if is the only branching operation. It compares two values and runs its then chain or its else chain — and because a branch never rejoins, the branch it takes is where the tool ends.

Config

then and else sit on the operation, not in config, and they are the only place either word is legal — sending a then on any other kind is rejected. Both are optional: an if with no else does nothing on the false path.

The operators

There is no gte or lte, and no substring, prefix or regex operator. For >=, use lt and swap the two branches; for anything richer, compute the answer in a code operation and branch on what it published.

How values are compared

eq and neq try the values as they are, then as strings. So "5" equals 5, which is what makes a number that came back from an API as a string comparable. It is case-sensitive: "Gold" does not equal "gold". If your API is inconsistent about case, normalize it in a code operation rather than writing three branches. Compare a boolean against a JSON boolean"right": true, not "right": "true". right accepts any JSON value, and a boolean’s string form is not "true", so the string version never matches.
In the dashboard both sides of an if are plain text boxes, so a JSON boolean cannot be typed there. If you will edit the tool in the dashboard, have the code operation publish a string — "status": "open" — and compare against open, rather than publishing true and depending on how a boolean renders as text.
gt and lt need two numbers. Both sides are converted; a side that will not convert fails the operation rather than quietly taking the else branch, with `gt` needs two numbers, and cannot compare '7:26 PM' with '09:00'. That is deliberate: a comparison that cannot be made has no true side and no false side, and silently choosing one sent every caller down the wrong path with nothing to show for it. exists is about presence, not truth. A missing key, null, "", [] and {} are absent; 0 and false are present. To test whether a value came back at all, use exists — comparing a missing value against an empty string does not match it. in is membership, not substring. right may be an array, or a comma-separated string that is split for you: ["shipped", "delivered"] and "shipped, delivered" mean the same thing. Each choice is trimmed and compared as a string.

Branches do not rejoin

if is terminal in its chain: nothing may follow it, in the top-level list or inside a then or else. There is no operation both paths flow back into.
  • Work needed on both paths is written into both.
  • Work needed after the decision means the branch should come later.
  • A third path is a second if inside the first one’s else.
on_error: "continue" on an if therefore means something specific: the condition failed, neither branch runs, and the tree ends there. It is the only thing continue can mean on an operation nothing follows, and it is a reasonable thing to want — a comparison you would rather skip than apologise for.

The guard pattern

For an early exit, put the exit in then and the whole rest of the flow in else. This is the shape to reach for whenever a tool has a precondition.
Note the end_call with no config: it is the one kind whose config may be omitted, because there is nothing to put in it. Anything the tool must do before hanging up goes before it — see ending a call.

Why time and date comparisons fail

{{system_vars.time}} resolves to "7:26 PM" and {{system_vars.date}} to "Wednesday, 19 August 2026". They are written to be spoken, not compared. Neither converts to a number, so gt and lt fail the operation on them, and eq against "09:00" is never true. There is no date arithmetic in an if. Business-hours logic — “are we open?”, “is this within thirty days?”, “is it a weekend?” — belongs in a code operation over new Date(input.system_vars.now), which carries the agent’s own offset:
Publish open into tooldata, then branch on {"left": "{{tooldata.open}}", "op": "eq", "right": true}.

A worked example

Refund eligibility: a status the order must be in, and a window it must be inside.
Three things that make it work:
  • The outer if is the last operation in the top-level chain, and the inner one is the only operation in its then. That is the only way to write three paths.
  • days is compared with lt against the JSON number 30. If your API returns it as "30", that still works — gt and lt read both sides as numbers.
  • Every operation that could return a result is silent, so the tool is silent and each branch speaks for itself. Drop the silent on the refund POST and the agent would narrate the refund JSON on top of the sentence you wrote.

Next

The code operation

Where anything richer than one comparison belongs.

The operation tree

Terminal operations, on_error and derived silence.

Conditional handoffs

Routing decided by a tree rather than by the model.

Patterns

Guard-and-continue and the other recipes.