# Branch a flow

> Put a condition on an edge so a step runs only when it holds, and write the "otherwise" branch.

This guide puts a condition on an edge, so a step runs only when it holds, and writes the "otherwise" branch.

An edge with a `when` condition fires only when the condition holds. Two edges out of one step, with opposite conditions, make an if/else.

## Before you start

- **A pack with the tools** from [Write a flow](https://docs.kindgi.com/v0.1.9/guides/flows/write-a-flow.md) and [Pass data between steps](https://docs.kindgi.com/v0.1.9/guides/flows/pass-data-between-steps.md): `acme.get-order`, `acme.hold-order` and `acme.confirm-order`.
- **A runtime such as `kindgi dev`,** with Kindgi 0.1's CLI.

## Branch on a condition

Ask your coding agent: "I want a flow that holds an order over 1000 USD and confirms the rest." Kindgi's skills in your pack tell it how ([How it knows Kindgi](https://docs.kindgi.com/v0.1.9/start/coding-agents.md)). For reference, the flow looks like this. This guide calls it `acme.review-order`:

- **TypeScript**

  `flows/review-order/index.ts`

  ```ts
  import { defineFlow } from '@kindgi/sdk/define';

  const isLarge = {
    op: 'gt',
    left: { path: 'nodeOutputs.order.total' },
    right: { literal: 1000 },
  } as const;

  const defined = defineFlow({
    id: 'acme.review-order',
    version: '0.1.0',
    name: 'Review an order',
    description: 'Holds a large order for review and confirms the rest.',
    nodes: [
      {
        id: 'order',
        kind: 'tool',
        ref: 'acme.get-order',
        inputMapping: { orderId: { path: 'runInput.orderId' } },
      },
      {
        id: 'hold',
        kind: 'tool',
        ref: 'acme.hold-order',
        inputMapping: {
          orderId: { path: 'runInput.orderId' },
          reason: { literal: 'Over 1000 USD' },
        },
      },
      {
        id: 'confirm',
        kind: 'tool',
        ref: 'acme.confirm-order',
        inputMapping: { orderId: { path: 'runInput.orderId' } },
      },
    ],
    edges: [
      { id: 'e1', from: '$start', to: 'order' },
      { id: 'e2', from: 'order', to: 'hold', when: isLarge },
      { id: 'e3', from: 'order', to: 'confirm', when: { op: 'not', child: isLarge } },
      { id: 'e4', from: 'hold', to: '$end' },
      { id: 'e5', from: 'confirm', to: '$end' },
    ],
  });

  if (defined.kind === 'err') throw new Error(defined.error.message);

  export default defined.value;
  ```

- **Python**

  `flows/review_order.py`

  ```python
  from kindgi import Flow

  IS_LARGE = {
      "op": "gt",
      "left": {"path": "nodeOutputs.order.total"},
      "right": {"literal": 1000},
  }

  review_order = Flow(
      id="acme.review-order",
      version="0.1.0",
      name="Review an order",
      description="Holds a large order for review and confirms the rest.",
      nodes=[
          {
              "id": "order",
              "kind": "tool",
              "ref": "acme.get-order",
              "inputMapping": {"orderId": {"path": "runInput.orderId"}},
          },
          {
              "id": "hold",
              "kind": "tool",
              "ref": "acme.hold-order",
              "inputMapping": {
                  "orderId": {"path": "runInput.orderId"},
                  "reason": {"literal": "Over 1000 USD"},
              },
          },
          {
              "id": "confirm",
              "kind": "tool",
              "ref": "acme.confirm-order",
              "inputMapping": {"orderId": {"path": "runInput.orderId"}},
          },
      ],
      edges=[
          {"id": "e1", "from": "$start", "to": "order"},
          {"id": "e2", "from": "order", "to": "hold", "when": IS_LARGE},
          {"id": "e3", "from": "order", "to": "confirm", "when": {"op": "not", "child": IS_LARGE}},
          {"id": "e4", "from": "hold", "to": "$end"},
          {"id": "e5", "from": "confirm", "to": "$end"},
      ],
  )
  ```

- **Java**

  `src/main/java/acme/flows/ReviewOrder.java`

  ```java
  package acme.flows;

  import com.kindgi.pack.Flow;
  import java.util.Map;

  public final class ReviewOrder {
    private static final Map<String, Object> IS_LARGE = Map.of(
        "op", "gt",
        "left", Map.of("path", "nodeOutputs.order.total"),
        "right", Map.of("literal", 1000));

    public static final Flow FLOW = Flow.define("acme.review-order")
        .version("0.1.0")
        .set("name", "Review an order")
        .set("description", "Holds a large order for review and confirms the rest.")
        .node(Map.of("id", "order", "kind", "tool", "ref", "acme.get-order",
            "inputMapping", Map.of("orderId", Map.of("path", "runInput.orderId"))))
        .node(Map.of("id", "hold", "kind", "tool", "ref", "acme.hold-order",
            "inputMapping", Map.of(
                "orderId", Map.of("path", "runInput.orderId"),
                "reason", Map.of("literal", "Over 1000 USD"))))
        .node(Map.of("id", "confirm", "kind", "tool", "ref", "acme.confirm-order",
            "inputMapping", Map.of("orderId", Map.of("path", "runInput.orderId"))))
        .edge("e1", "$start", "order")
        .edge(Map.of("id", "e2", "from", "order", "to", "hold", "when", IS_LARGE))
        .edge(Map.of("id", "e3", "from", "order", "to", "confirm",
            "when", Map.of("op", "not", "child", IS_LARGE)))
        .edge("e4", "hold", "$end")
        .edge("e5", "confirm", "$end")
        .build();

    private ReviewOrder() {}
  }
  ```

- **Scala**

  `src/main/scala/acme/flows/ReviewOrder.scala`

  ```scala
  package acme.flows

  import com.kindgi.pack.scaladsl._

  object ReviewOrder {
    private val isLarge = Map(
      "op" -> "gt",
      "left" -> Map("path" -> "nodeOutputs.order.total"),
      "right" -> Map("literal" -> 1000))

    val flow: Flow = Flow("acme.review-order")
      .version("0.1.0")
      .set("name", "Review an order")
      .set("description", "Holds a large order for review and confirms the rest.")
      .node(Map("id" -> "order", "kind" -> "tool", "ref" -> "acme.get-order",
        "inputMapping" -> Map("orderId" -> Map("path" -> "runInput.orderId"))))
      .node(Map("id" -> "hold", "kind" -> "tool", "ref" -> "acme.hold-order",
        "inputMapping" -> Map(
          "orderId" -> Map("path" -> "runInput.orderId"),
          "reason" -> Map("literal" -> "Over 1000 USD"))))
      .node(Map("id" -> "confirm", "kind" -> "tool", "ref" -> "acme.confirm-order",
        "inputMapping" -> Map("orderId" -> Map("path" -> "runInput.orderId"))))
      .edge("e1", "$start", "order")
      .edge(Map("id" -> "e2", "from" -> "order", "to" -> "hold", "when" -> isLarge))
      .edge(Map("id" -> "e3", "from" -> "order", "to" -> "confirm", "when" -> Map("op" -> "not", "child" -> isLarge)))
      .edge("e4", "hold", "$end")
      .edge("e5", "confirm", "$end")
      .build()
  }
  ```

`e2` fires when the order's total is over 1000, and `e3` when it isn't. The condition is defined once and wrapped in `not` for the other branch.

Run it:

- **TypeScript**

  ```sh
  pnpm exec kindgi runs start --flow=acme.review-order --input='{"orderId":"A-200"}'
  ```

- **Python**

  ```sh
  uv run kindgi runs start --flow=acme.review-order --input='{"orderId":"A-200"}'
  ```

- **Java**

  ```sh
  ./kindgiw runs start --flow=acme.review-order --input='{"orderId":"A-200"}'
  ```

- **Scala**

  ```sh
  ./kindgiw runs start --flow=acme.review-order --input='{"orderId":"A-200"}'
  ```

`--flow` is the flow, and `--input` its input. It prints the run:

```json excerpt
{
  …
  "flowId": "acme.review-order",
  "status": "completed",
  …
  "output": {
    "status": "held",
    "orderId": "A-200"
  },
  …
}
```

`A-200`'s total is 1250, so the order is held. With `A-100`, whose total is 42.50, `e3` fires and the order is confirmed.

## What the journal records

Every edge the run evaluates is in the journal with its decision. A step whose edge didn't fire is skipped and has no entries:

- **TypeScript**

  ```sh
  pnpm exec kindgi runs journal <run-id>
  ```

- **Python**

  ```sh
  uv run kindgi runs journal <run-id>
  ```

- **Java**

  ```sh
  ./kindgiw runs journal <run-id>
  ```

- **Scala**

  ```sh
  ./kindgiw runs journal <run-id>
  ```

`<run-id>` is the run's id. It prints:

```json excerpt
{"sequence": 4, "kind": "edge.evaluated", "payload": {"edgeId": "e2", "decision": true}, …}
{"sequence": 5, "kind": "edge.evaluated", "payload": {"edgeId": "e3", "decision": false}, …}
{"sequence": 6, "kind": "step.started", "nodeId": "hold", "payload": {"input": {"reason": "Over 1000 USD", "orderId": "A-200"}}, …}
```

This shows one entry per line. The command prints them as one JSON document.

## Conditions

A condition is JSON. Each operand is a `{ path }`, with the same roots as `inputMapping`, or a `{ literal }`:

| Operator                        | Shape                     | True when                                                                    |
| ------------------------------- | ------------------------- | ---------------------------------------------------------------------------- |
| `eq` `ne` `lt` `lte` `gt` `gte` | `{ op, left, right }`     | the comparison holds. `lt` and the others compare two numbers or two strings |
| `in` `notIn`                    | `{ op, value, set }`      | `value` is, or isn't, an element of the array `set`                          |
| `exists` `notExists`            | `{ op, value }`           | the path resolves, or doesn't                                                |
| `truthy` `falsy`                | `{ op, value }`           | the value is truthy, or falsy. A missing path is falsy                       |
| `and` `or`                      | `{ op, children: [...] }` | all, or any, of the children hold                                            |
| `not`                           | `{ op, child }`           | the child doesn't hold                                                       |

> **Caution: A missing value**
>
> When a path doesn't resolve, `eq`, `lt`, `lte`, `gt` and `gte` are false and `ne` is true. So for the "otherwise" branch, write `not` around the condition, as `acme.review-order` does, rather than a second comparison: it covers exactly what the first edge doesn't.

## Branch on an agent's answer

To branch on what an agent decided, give the agent a [typed answer](https://docs.kindgi.com/v0.1.9/guides/agents/typed-answer.md) and compare one of its fields: `{ path: 'nodeOutputs.<step>.output.<field>' }`. [Wait for an approval](https://docs.kindgi.com/v0.1.9/guides/flows/wait-for-an-approval.md) branches on `nodeOutputs.hold.output.held`, a boolean the agent returns.

## When no branch fires

A step none of whose incoming edges fired is skipped, and so is every step only it leads to. If no edge reaches `$end`, the run still completes, with no output. Cover every case with your conditions, or bring the branches back together, as [Join branches](https://docs.kindgi.com/v0.1.9/guides/flows/join-branches.md) shows.

## Next

- [Join branches](https://docs.kindgi.com/v0.1.9/guides/flows/join-branches.md)
- [Repeat steps in a loop](https://docs.kindgi.com/v0.1.9/guides/flows/loop.md)
- [Give an agent a typed answer](https://docs.kindgi.com/v0.1.9/guides/agents/typed-answer.md)
