Agent node - exits
Exits are how an Agent node finishes, and how the flow knows what to do next. Each one you declare becomes a branch handle on the node in the flow canvas.
They are the hardest part of a multi agent to get right, and the most common cause of a flow that stalls or routes unpredictably.
Exits
Exits are how a node finishes, and each one becomes a wireable branch on the node in the canvas. This is the section that turns an agent into a step in a procedure.
Not sure whether you want an exit or a variable? An exit decides where the conversation goes next; a variable decides what the rest of the flow knows. They're not alternatives - most steps want both. See Variables and exits - which one do I want? before designing a node's branches.
There are three kinds you can add, plus one the system provides.
Outcome branch
The agent picks one named result.
The workhorse. You give it a name and a description, and the agent picks whichever branch matches the situation when it decides its job is done. Up to five per node.
| Field | What it's for |
|---|---|
| Branch name | A short snake_case identifier - billing_issue, eligible, max_retry. This is the wiring handle and what shows in logs. |
| Description | Natural language telling the agent when this outcome is true, e.g. "user confirmed the refund". |
The description is what the agent matches on. The name is only a label. Vague descriptions produce wrong branches; sharp, mutually exclusive ones produce reliable ones.
- Make them mutually exclusive. "if the intent is a return" and "if the intent is an exchange" don't overlap. "if the customer seems unhappy" and "if there's a problem" do, and the agent will waver between them.
- Describe the condition, not the destination. Write "if the user wants a different size", not "go to the exchange handler". The canvas owns where it goes.
- Cover the unhappy paths. Add branches for "outside what this step handles" and "the user asked for a human", or those get forced into one of your main branches.
- Keep the set small. Two to five is the sweet spot. Needing ten means the node is doing too much.
Example - a returns router:
| Branch name | Description |
|---|---|
return | if the customer wants to send an item back for a refund |
exchange | if the customer wants to swap an item for a different size or variant |
out_of_scope | if the request is about anything other than a return or an exchange |
max_retry | when the customer can't decide after 3 attempts |
Gotchas
- Names are forced to lowercase
snake_caseas you type, because the agent picks from them as a fixed list of options. - Some names are reserved and can't be used:
on_error,error,fallback,success,default,response. Avoidin_progresstoo - see below. - Renaming a branch is safe. Wired edges follow the branch itself, not its name, so you won't orphan a connection.
- Two branches with the same name won't both work - the first one wins. The drawer flags the duplicate.
Every turn the agent answers: which branch, or "not yet"
Worth knowing, because it explains most of what a node does.
On every turn, the agent makes an explicit choice: take one of your branches, or report that it's still in progress. "Still in progress" is a reserved outcome - it isn't a branch you create, it has no handle on the canvas, and there's nothing to wire. It simply means not finished, ask again.
That single mechanic explains several behaviours:
- A node that keeps talking hasn't failed - it's choosing "not yet", every turn, because nothing in the conversation has satisfied a branch description. The fix is in the descriptions or the Instructions, not the wiring.
- Your descriptions are the deciding input. They're presented to the agent alongside each option at the moment it chooses, which is why the description - not the name - is what actually drives routing.
- A node can't half-exit. Each turn resolves to exactly one branch or none at all.
- If the agent names something that isn't one of your branches, it's ignored and the node carries on rather than stalling - so a typo in a branch name shows up as a step that never finishes, not as an error.
Don't name a branch in_progress; that value is reserved for this.
Response exit
Reply once, then continue (single-turn).
The node says one thing and hands straight back to the flow. The agent makes no choice - there's nothing to match on, so there's exactly one place to go next.
Use it for a step whose whole job is to say something: acknowledge, summarise, explain a result the previous node produced. It's the cheapest and most predictable exit there is, and it's a good instinct whenever you catch yourself writing a node with a single branch called done.
You can add one response exit per node.
Tool exit
Finish by calling a tool - routes to a single next step.
The node finishes by calling a tool you nominate, and the flow leaves along that tool's branch.
The distinction from an outcome branch is worth getting right:
- An outcome branch is the agent reporting a conclusion. "It's a return."
- A tool exit is the agent taking an action that ends the step. "Raise the ticket." "Transfer to a human."
Reach for a tool exit when the action and the outcome are the same event, and you want the flow to continue from whatever that action produced. Escalation is the classic case: the agent calls the escalation tool, and that is the ending.
Only tools attached to this node can be used as a tool exit - the picker won't offer anything else.
Gotchas
- If the tool fails, it isn't treated as an exit - the agent stays in the step and can try something else. A tool exit is not a guaranteed ending.
- If the agent calls two exit-tools at once, the first one you listed wins.
- A handful of internal names are reserved and can't be used as tool exits:
end,complete_goal,remember,handoff,end_call.
on_error (system)
Runs only if the agent run fails - wire it to a safe recovery step.
Always present, never editable, and shown under a System divider because it isn't an exit you design - it's the emergency path.
The agent never chooses on_error. It fires in exactly two situations:
- The run failed - the underlying model call errored or timed out.
- The agent ran out of steps without picking an exit - it kept working, kept calling tools, and never concluded.
That second one is worth internalising, because it's the one that catches real flows. A node with badly-overlapping branch descriptions, or instructions that never say when to stop, can go round in circles and land here.
Wire it. Send it to something graceful - an apology plus a human handoff, or a retry step. An unwired on_error means an unlucky run has nowhere to go.
What does not take on_error: a tool erroring, the agent picking an odd branch name, or the user saying something confusing. Those are all recovered inside the step. If you want the flow to react to a business failure - "not eligible", "user declined", "payment failed" - that's a branch, not an error path.
Retry is not an exit kind - and how to actually build one
max_retry appears in most well-built nodes and in every example on this page, which makes it look built in. It isn't. There is no retry exit, no attempt counter, and no setting anywhere that limits how many times a step re-asks. max_retry is just an outcome branch that people conventionally name max_retry - you could call it gave_up and it would behave identically.
Which matters, because a retry ceiling only exists if you build both halves of it:
- The branch - an outcome branch named
max_retry, described as "when the customer still hasn't given a valid postcode after three attempts". - The instruction that reaches it - "If they can't give a valid postcode after three attempts, finish on
max_retry."
Add only the branch and it will never fire - nothing counts attempts on your behalf. Add only the instruction and the agent has nowhere to go when it gives up. The pair is what makes a bounded step.
The count is the agent's judgement, not a counter. It's working out "have I asked three times?" from the conversation, so treat it as about three, not exactly three. If you need a hard guarantee, count it in a variable and branch on that.
Wire max_retry somewhere genuinely different - a human handoff, a simpler fallback question, an apology and an exit from the flow. Wiring it back into the same node just rebuilds the loop you were trying to escape.
The same "convention, not a kind" point applies to any branch name you'll see repeated - out_of_scope, declined, completed. They're ordinary outcome branches with useful names.
Choosing between them
| You want the node to… | Use |
|---|---|
| Decide between two or more outcomes | Outcome branches |
| Say one thing and move on | Response exit |
| End by doing something (escalate, raise a ticket, transfer) | Tool exit |
| Give up after too many attempts | An outcome branch named max_retry, plus an instruction that reaches it |
| Survive a crash or a runaway loop | on_error - already there, just wire it |
Designing the right exits
Getting the exits right is the first design decision in a node, and everything else follows from it. Instructions, variables and wiring are all downstream of "what are the ways this step can end?"
Start from the Goal
The Goal says what this step is trying to achieve. The exits enumerate the ways that can resolve - including badly. So the two should read as a matched pair.
Aligned. Goal: "Confirm whether the customer wants to go ahead with the refund, and record their decision."
| Branch | Description |
|---|---|
refund_confirmed | the customer has clearly agreed to the refund |
refund_declined | the customer has decided against the refund |
escalate | the customer asks for a human, or disputes the amount |
max_retry | the customer won't give a clear answer after 3 attempts |
Every branch is a way that Goal can end. Nothing is missing, nothing is spare.
Not aligned. Same Goal, but the exits are visa, mastercard, amex, paypal. None of those is a way the Goal resolves - they're a property of the payment. The Goal is about a decision; the exits describe a card. That mismatch is the signal, and the fix is a variable.
Two checks that fall out of this:
- An exit with no corresponding way for the Goal to end is probably data pretending to be control flow.
- A way the Goal can end with no exit is a gap. Most commonly the customer declining, or asking for a human.
Exits are outcomes, and outcomes are domain-specific
Nothing about branch names is generic. return and exchange are just what the examples on this page happen to use. Yours should say what actually happened in your business:
| Step | Sensible branches |
|---|---|
| Confirm a refund | refund_confirmed, refund_declined, escalate |
| Verify identity | verified, verification_failed, customer_abandoned |
| Offer a retention deal | offer_accepted, offer_rejected, wants_to_cancel_anyway |
| Book an appointment | slot_booked, no_slots_suitable, needs_callback |
| Triage a fault | resolved_by_self_service, needs_engineer_visit, needs_replacement |
Specific names make the canvas readable and the logs meaningful. completed and failed on every node tell you nothing when you're reading a run back.
When it's an exit, and when it's a variable
The single question: would the flow go somewhere different?
| The step establishes… | Exit or variable | Why |
|---|---|---|
| Whether a refund was approved | Exit | Approved and declined lead to genuinely different work. |
| Which card the customer paid with | Variable | Interesting to record, but the next step is the same either way. |
| Whether the customer is eligible | Exit | The whole point is that eligible and ineligible diverge. |
| Their delivery address | Variable | It's information the flow carries forward, not a fork. |
| Which of 200 products they're asking about | Variable | Always a variable. See below. |
| Which department they need - 4 of them, each with its own flow | Exit | Four outcomes, four destinations. That's routing. |
| Their reason for cancelling, from a long list | Variable | Unless a couple of specific reasons trigger different handling - then a small set of exits plus a variable for the detail. |
That last row is the common shape in practice: a few exits for the paths that differ, plus a variable holding the detail. You don't have to choose.
Why an exit must never be an entity
The tempting move is one branch per possible value - a branch per product, per country, per reason code. It reads naturally and it breaks in five separate ways:
- There's a hard ceiling of five branches. An entity with twelve values cannot be expressed, so the design fails immediately rather than gradually.
- Every branch is an edge you draw and maintain. Forty values means forty edges, and most of them going to the same place.
- Selection gets worse as the list grows. The agent is choosing from your descriptions. Five distinct outcomes are easy to tell apart; thirty near-identical ones are not, and accuracy drops exactly where you'd least notice.
- New values need a flow change. Add a product line and you're editing and republishing the flow. A variable absorbs new values with no change at all.
- The canvas stops being readable - and readability is the reason to build a multi agent rather than a single agent. A node with thirty branches has given that up.
Three diagnostics, any one of which settles it:
- Would the branches all go to the same next node? Then they aren't outcomes - they're a value.
- Could a new value appear next month without you touching the flow? Then it's a value. Outcomes are a closed set you decide; entities are open.
- Can you write a genuinely distinct description for each? If they'd all read "the customer said X" with only X changing, that's one outcome with a variable in it.
The correct shape is one branch and one variable:
| Branches | product_identified, product_unclear |
| Variable | product_name - Agent fills this |
One edge to wire, any number of products, and a later node reads the variable to do whatever it needs.
When the branches really do go ten different places
A related but different problem, and the diagnostics above won't catch it: what if the ten destinations are genuinely ten different places? Nothing is masquerading as an entity here. The design isn't wrong - it just doesn't fit in one node.
You'll discover it at the sixth branch, when you find the limit is five. Read that as a signal rather than an obstacle. Even if fifteen branches were allowed, one node choosing between fifteen similar descriptions is far less accurate than one choosing between four - so you'd have traded a build-time error for silent misrouting, which is worse because it looks like it's working.
First ask whether you need a flow at all. A multi agent exists to enforce an order. Ten independent use cases have no order between them - nobody checks a balance and then reports a lost card. If that's what you have, this isn't a flow problem, it's a routing problem: give each use case its own single agent with a clear trigger and let routing choose. That scales to any number, needs no canvas, and handles "actually, something else" mid-conversation without you wiring anything.
Build a flow router only when you need something routing won't give you - entitlement gating (only offer what this customer may use), a fixed script (regulated or brand-mandated wording), or a guaranteed menu (the same options in the same order every time).
If you do need it, use two levels. Group the destinations into a few categories, and let each category node choose among its own items. No node then has more than four or five branches, and twelve destinations fit comfortably.
It's also better for the customer. A ten-item menu is hard to read and unusable on a phone call; two questions with four options each beats one question with ten.
Or classify into a variable and dispatch on it. When the destinations are many and the routing is mechanical, split the work: let the agent do the part it's good at - understanding what someone meant - and let the flow do the dispatch.
The Agent node gets two exits, identified and unclear, and writes the choice to a variable. A Condition node then routes on that value, one arm per destination.
The payoff is what happens next quarter: adding an eleventh service is a new condition arm and a line in the agent's instructions. The node's exits never change, so you never hit the ceiling, and the dispatch is deterministic rather than a judgement call re-made on every conversation.
| Situation | Do this |
|---|---|
| Independent use cases, no fixed order | Don't build a flow router. Single agents with triggers. |
| Entitlement gating, a script, or a guaranteed menu, with ~6-12 destinations | Two levels. Better accuracy, better on voice. |
| Many destinations, mechanical dispatch, list will grow | Classify to a variable, dispatch with a Condition node. |
| Genuinely two to five outcomes | One node with branches. That's what they're for. |
Whichever shape you pick, remember the routing happens on the canvas. Writing "if they choose 3, send them to the appraisal agent" in a node's Instructions does nothing at all - see You cannot route to another agent from the Instructions.
Other ways a step can end
Not everything that ends a node is an exit. These don't take a branch, and knowing they exist saves a confusing debugging session when a step finished and no edge fired.
| What happens | What it looks like |
|---|---|
| The call is transferred or hung up (voice) | If the step, or a tool it calls, transfers the call or ends it, that's terminal. The call leaves; the flow does not continue to a next node. |
| Routing pulls the customer out of the flow | If someone says something clearly unrelated to the procedure, your assistant's routing can move them out of the flow entirely rather than trapping them in it. The flow stops - no exit is taken and no branch fires. |
| The node is waiting (not an ending) | The most common "nothing happened". The agent replied without choosing an exit, so the step is still in control and waiting for the next message. It looks stalled and is working as designed. |
That middle row is worth sitting with, because it contradicts a reasonable assumption. A flow is not a cage. A customer who abandons the procedure to ask something else can be routed away from it. Design for the conversation resuming elsewhere rather than assuming every run reaches one of your exits.
Every exit has to go somewhere
Each exit you add appears as a handle on the node in the flow canvas, and the flow follows whichever one the agent takes. An unwired exit is a dead end: if the agent picks it, the run has nowhere to continue. The canvas highlights unwired branches - clear them all, including on_error, before you publish.
The exit a node took isn't readable later
An exit steers the flow, and that's all it does - no downstream node can find out which exit an earlier node finished on. So if a later step needs to know that this one concluded return rather than exchange, write that decision into a variable as well as taking the branch.
The alternative - having a later node work the decision out again from the conversation - will sometimes disagree with the first one, which is a difficult class of bug to see.
You cannot route to another agent from the Instructions
This is worth stating plainly because it's an easy assumption to make. Writing "mark this goal complete and route to @Some Other Agent" in an Agent node's Instructions does not route anywhere. Inside a multi agent, the canvas owns the order - always.
The correct shape is: add a branch for that outcome, and on the canvas wire that branch to the node you want. The agent's job is to say which outcome happened; the wiring decides where that goes.
Problems
| Pattern | What goes wrong |
|---|---|
| A branch with no instruction that reaches it | Never fires. max_retry is the usual victim - the branch exists, nothing tells the agent to take it. |
| Overlapping descriptions | The single biggest cause of a node that "routes randomly". If two descriptions can both be true of the same message, the choice between them is a coin flip. |
| Descriptions that name the destination | "go to the refund handler" describes the wiring, not the situation. The agent is matching on what's true now. |
| A branch per possible value | Exits used as data. See Variables and exits. |
| Only happy-path branches | The customer who wants a human, or asks something off-topic, gets forced down a branch that doesn't fit. |
| Anything left unwired | A dead end. Includes on_error, which is the one people forget. |
Expecting on_error for business failures | "Not eligible" is an outcome, not an error. Give it a branch. |
| Ten branches | The node is doing too much, and the limit is five anyway. |
How to think about it
Design the exits before you write the Instructions. The exits are the step's contract with the rest of the flow - what it promises to tell the outside world. Once they're right, the Instructions almost write themselves, because each one has to reach a branch.
Write descriptions as conditions, mutually exclusive. The description is what the agent matches on; the name is only the wiring handle. Two descriptions that can both be true is a bug you'll experience as flakiness.
Walk the list backwards from each branch. For every exit, find the sentence in the Instructions that causes it. No sentence, no fire.
Every ending needs a home, including the bad ones. Unhappy paths, the give-up path, and on_error. A run that reaches an unwired exit has nowhere to go.
Two to five branches. Below two, consider a response exit. Above five you can't add more anyway - and that ceiling is a design signal, not an obstacle to work around.