# 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](/docs/web-app/approval-pipelines), 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](/docs/web-app/conditions-blocks), 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.

```mermaid
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](/docs/web-app/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](/docs/reference/approval-pipelines) has the exact format.
