Branch blocks
Run a set of blocks only for requests that match a condition.
A Branch block holds its own list of blocks and runs them only for requests that match its condition. Every other request skips the branch, so an AI Judge or webhook inside it is only asked about the requests it's meant for.
To add one, open an approval pipeline, select Edit pipeline, then Add block and Branch. Add block to this branch adds blocks inside it. A branch can hold any type of block, including another branch.
When a branch runs
Give the branch a Name and use When to choose the requests it handles. When works like a Conditions block, with the same fields, such as Tool name, MCP server and the tool's arguments, the same operators and the same All and Any groups.
A request that matches enters the branch, and the blocks inside it run in order. They work just as they do anywhere else in a pipeline: an approval, denial or human-review outcome ends the whole pipeline, and Continue moves on to the next block in the branch.
A request that doesn't match skips the branch. None of its blocks run, and the request continues with the next block after the branch. In the editor, If conditions are met leads to Check the blocks in this branch, while Otherwise, skip this branch and check the next block shows where every other request goes.
Loading diagram…
Diagram source
flowchart TD
request["Request reaches the branch"] --> match{"Does When match?"}
match -->|"No"| skip["Skip the branch and check the next block"]
match -->|"Yes"| blocks["Check the blocks in this branch"]
blocks -->|"Approve or deny"| decision["Finish the request"]
blocks -->|"Ask a human"| review["Human review"]
blocks -->|"No block decides"| branchEnd["Human review with the branch's reviewers"]If withHuman can't evaluate the condition, for example because a field has the wrong type for its comparison, the request goes to human review with the pipeline default. It's never skipped, so a broken condition can't wave requests past the checks inside the branch.
An AI Judge or Webhook block inside a branch can include Earlier block results from blocks that ran before it, inside the branch or before it.
Example: Refund checks
Our agents send requests for everyday tools such as Read and Bash, and now and then for stripe.refund. We want every refund checked by our fraud service and an AI Judge, without making every file read wait for a model. We'll add a branch named Refunds to the organization pipeline, with When set to Tool name equals stripe.refund, and put three blocks inside it.
| Block | Type | What it does |
|---|---|---|
| Fraud service | Webhook | The service can approve, deny or continue. |
| Check refund eligibility | AI Judge | An Approve verdict continues to the next block. Deny and Escalate ask a human. |
| Approve small refunds | Conditions | Approves the refund when amount is less than 1000. |
The judge can send a refund to a person, but it can't approve one on its own. Our refund tool sends amount in cents, so 1000 is $10. We'll turn on Earlier block results for the judge so it can see what the fraud service found.
Read, Bash and every other tool skip Refunds, so they never reach the fraud service or the judge.
When nothing decides
If every block in a branch continues, the request goes to human review and the pipeline stops. The branch card says so: "If no block in this branch decides, the request goes to a human and the pipeline stops."
The branch's end card shows who that human is, for example "No block in “Refunds” decided." followed by Ask a human and the reviewers. Choose them with the branch's Escalation path, or keep Pipeline default. The branch's Reason explains why the request needs a human.
A request that enters a branch is always settled there. Blocks after the branch only run for requests that skipped it. For a branch in the organization pipeline, the same is true of the agent's pipeline. A branch inside another branch works the same way: when the inner branch runs out, the request goes to the inner branch's reviewers, and the rest of the outer branch doesn't run.
An Always block inside a branch decides every request that reaches it, so the blocks after it in that branch, and the branch's end, show Unreachable. Blocks after the branch aren't affected, because they still run for requests that skip it.
Example: A $50 refund
We'll select Payments review as the escalation path for Refunds, with the reason "No refund check decided, so Payments reviews the refund."
A $50 refund arrives with amount set to 5000. The fraud service continues, and the judge approves, which also continues. 5000 isn't less than 1000, so Approve small refunds doesn't match either. No block in Refunds decided, so the refund goes to Payments review with the branch's reason. Nothing after Refunds runs, including the agent's pipeline.
Reviewers inside a branch
A block inside a branch chooses its own reviewers. If it asks a human without naming an escalation path, it uses the pipeline default, not the branch's reviewers. The branch's Escalation path only applies when no block in the branch decides.
This means moving a block into or out of a branch never changes who reviews its requests.
Example: Judge reviews
Our judge asks a human when it denies or escalates a refund. We want Payments to handle those refunds too, so we'll select Payments review on the judge itself. Without that, they would go to the pipeline default.
Nested branches
A branch can contain other branches, as deep as your rules need. Each one has its own condition, blocks, reason and end.
Blocks are numbered by where they sit. The second block inside block 3 is 3.2, and if 3.2 is a branch, its first block is 3.2.1. The editor uses the same numbers when it reports a problem with a block.
Moving blocks
Dragging reorders blocks within the same list. To move blocks between a branch and the list around it, use the block's action menu.
| Action | What it does |
|---|---|
| Move into the branch above | Offered on a block that directly follows a branch. The block joins that branch as its last block. |
| Move out of this branch | Moves the block out of its branch, to just after the branch. |
| Duplicate branch | Copies the branch and every block inside it. |
| Remove branch, keep its blocks | Removes the branch and puts its blocks where it stood. |
Removing a branch that still holds blocks removes them too, so the editor asks first, for example "Remove “Refunds” and its 3 blocks?"
Moving a block into a branch limits it to requests that match the branch. Moving it out lets it check every request that reaches its new place.
Branch testing
Test request shows the route a sample takes through each branch. A branch the sample enters lights the line into its blocks. A branch it skips lights the Otherwise row, and its blocks are dimmed. If the sample runs out of a branch, the branch's end card is highlighted.
Test one sample that matches the branch and one that doesn't. Testing makes real AI Judge and webhook calls inside a branch the sample enters, and none inside a branch it skips. For a real request, the Audit log shows whether it entered or skipped each branch.
Example: Three samples
| Sample | Expected result |
|---|---|
Read | Skips Refunds. The fraud service and the judge aren't called. |
stripe.refund with amount 500 | If the fraud service and the judge pass it on, Approve small refunds approves it. |
stripe.refund with amount 5000 | If the fraud service and the judge pass it on, the refund runs out of Refunds and its end card shows Payments review. |
Limits
A pipeline can hold up to 1,000 blocks, counting every block inside its branches. Within that limit, branches can nest as deeply as you like. Each branch needs a condition, a Reason and at least one block before the pipeline can be saved.
In the JSON editor, a branch lists its blocks under config.blocks, and every block's id must be unique across the whole pipeline, including blocks inside branches. The API reference has the exact format.