Skip to main content
Two node types make decisions in a flow. branch_on_reply decides based on what the customer just said. conditional decides based on what the flow already knows. They are not interchangeable, and knowing which one you need is most of the work.
Configuring a branch_on_reply node from the canvas is coming soon. The inspector’s Step panel renders configuration for send_message, send_amb_primitive, wait_for_reply, integration, and assign_to_agent — not for branch_on_reply. The canvas can create the node, and it renders whatever match clauses and fallback already exist on it, but there’s no UI yet for authoring or editing them. Match clauses and fallbacks are API-only today. The one exception is the composite “Message with options” shortcut, which drops in a branch_on_reply with its fallback hardcoded to loop and max_retries: 2 — everything below still applies to that node once it exists, you just can’t hand-author a new one from the canvas yet.

How a match clause matches

A branch_on_reply node holds a list of match clauses, each pointing at a destination node. Three clause types exist: The first clause that matches wins. If none do, the fallback runs.
Reach for list_picker_item whenever the choice matters. It matches on a stable identifier, not displayed text — the same caveat covered in Channel-native nodes: quick replies on a Simple Message hand back their label text and nothing else, so a text_equals branch built on one breaks the moment someone edits the copy. A List Picker doesn’t have that problem.

What the fallback actually does

Three outcomes exist for “nothing matched,” and only one of them notifies anyone:
Re-poses the same node, up to a configured retry cap. Once the cap is hit, the flow ends.
Hands the turn to the AI agent — not to a person — once consecutive unmatched replies reach the inbox’s threshold (one miss, by default). The agent picks the conversation up from there and is then subject to its own escalation grounds, so a human is reached only if the agent cannot finish either.If the agent cannot be reached at all, this degrades to a real human handoff rather than failing silently. See AI-to-human handoff for what a handoff costs.
Ends the flow. Nothing is assigned, and nothing is logged as an escalation — this is functionally identical to the flow simply running out of graph.
An unset fallback is a silent dead end. The flow simply stops: nobody is assigned, nothing is logged as an escalation, and the customer is left without a reply. Set one deliberately.agent is usually the one you want — it is what stops a gap in your branch list from costing you a human touch. Note that it reaches the AI agent first; routing straight to a person is assign_to_agent, whose name is unhelpful here and does mean a human.

conditional

conditional branches on a variable rather than a reply — a true_next_node_id and a false_next_node_id, chosen by comparing a variable to a fixed value.
Today that comparison is a strict equality check only. No greater-than, less-than, or contains — those aren’t built yet. And like set_conversation_attribute, add_label, and delay, there’s currently no library block for conditional on the canvas (see Canvas basics). The engine evaluates it correctly wherever a graph contains one; the gap is only in adding one through the builder itself.

Captured variables and Liquid

set_conversation_attribute writes a key and a value onto the conversation — a conversation attribute, not an entry in the execution’s variable bag. Reading a captured attribute back through {{ variables.<key> }} is coming soon. Liquid’s variables scope is populated only by last_reply, the trigger, and two internal counters the engine itself writes — nothing set_conversation_attribute writes reaches it. So a later node’s {{ variables.order_status }} will not resolve a value you captured with set_conversation_attribute, and conditional can’t read one either, since it compares against that same variables scope. The available context today is narrower than it might look:
  • contact exposes name, email, and phone_number only — no custom contact attributes yet.
  • conversation exposes id.
  • variables exposes last_reply, the trigger, and two internal counters the engine writes — not anything written by set_conversation_attribute.
A reply’s raw text is itself a captured variable — variables.last_reply — which is what branch_on_reply and conditional are actually reading when they evaluate a match.

Routing a reply back into the flow

One more thing worth knowing if an agent or the AI sends a rich feature manually, outside a running flow: a send_amb_primitive node can be marked to route replies globally, so a customer tapping a List Picker that a human sent by hand still lands on the right branch_on_reply node in a published flow, rather than falling through as an unhandled message. This starts a run only when no person owns the conversation; while one does, the reply is left to them.

Next

Channel-native nodes

Why List Picker returns a stable identifier and quick replies don’t.

Node types

The full node set, including what each field configures.

Testing and simulation

Where the simulator’s own matching diverges from production.

AI agents

What happens once the agent fallback hands a turn over.