> For the index of every page in Orbital's docs, read https://docs.runorbital.dev/llms.txt.

# Run steps in parallel

> Run independent review steps at once and collect their results

Run independent steps at the same time with a fan-out, then collect their results with a fan-in. In practice, independent work means reviews.

```toml title="parallel-reviews.toml"
[steps.reviews]
kind = "fan_out"
max_parallel = 2
join = "steps.collect"

[steps.reviews.clarity]
prompt = "Review the goal for clarity."
outputs.assessment = "text"
next = "steps.collect"

[steps.reviews.risks]
prompt = "Review the goal for risks."
outputs.assessment = "text"
next = "steps.collect"

[steps.collect]
kind = "fan_in"
prompt = "Summarise these reviews: {{ context.parallel.results['0'].context.assessment }} and {{ context.parallel.results['1'].context.assessment }}"
```

Orbital keeps each branch's context apart, but not its files. Two branches that write to the same checkout will fight over it.

## How the fan-out works

`reviews` is a [fan-out](/reference/step-kinds/#fan-out), a step with `kind = "fan_out"`. Each step inside it starts a branch, and a fan-out needs at least two. A fan-out can also name steps written elsewhere in the file, as `branches = ["steps.clarity", "steps.risks"]`, and each of those starts a branch too. Up to `max_parallel` branches run at a time, four by default. Instead of `next`, a fan-out has `join`, which names the [fan-in](/reference/step-kinds/#fan-in) where the branches meet. Each branch runs from its step until it reaches that fan-in. A branch may not hold a gate, and its steps may not loop, set `thread` or send the run out of the branch.

Each branch keeps its own context, so `clarity` cannot read what `risks` produced. After the fan-in, results arrive under `parallel.results` in the order the fan-out names its branches, the `branches` list first and then the steps inside it in file order, with each branch's step, outcome, failure reason and context. Each branch agent's final prose is at its dotted ID, such as `context.response.reviews.clarity`. The fan-in prompt reads `{{ context.parallel.results['0'].context.assessment }}`. The index is quoted, and the complete example validates that spelling. A fan-in without a prompt is only the meeting point.

![The editor shows a fan-out step with a maximum of three, three parallel steps below it, and a prompted fan-in step that combines their findings.](/screenshots/workflow-editor-roles.webp?v=3ff23d4252)

*Every branch leaves the fan-out and meets again at the fan-in.*

A fan-out can also ask a [decision model](/workflows/decide-with-a-model/) which branches to start, and takes its default branches when none can answer.

Set `max_parallel` to the number of steps your harness can run at once. If branches must write, give them separate files and review the combined result after the fan-in.

:::caution
If a branch fails and the fan-out has no route for the failure, the run is Paused. It waits for you and names the failed branches. Add a route that tests `outcome == 'failed'` on the fan-out when partial completion has a safe recovery. See [Runs](/runs/).
:::

## Before you start

You need a harness you have signed in to that can run two steps at once, and a project in Orbital. Any project works. It does not need to be a Git repository, unless you run the example with Codex, which needs one. See [Projects and folders](/projects/).

The branches share a folder, and their prompts ask only for a review of the goal. When branches must not change a shared folder, say so in their prompts. With Codex, a style can also give them a read-only sandbox with `codex.sandbox_mode = "read-only"`.

## 1. Download the example

[Download the example](/parallel-reviews.zip) and unpack it into `~/.orbital/workflows/`. Keep every relative path.

![reviews forks into clarity and risks; both meet at collect, which summarises them and reaches done.](/parallel-reviews.svg?v=e668a01a0b)

```toml title="parallel-reviews.toml"
version = 3
entry = "steps.reviews"

[steps.reviews]
kind = "fan_out"
max_parallel = 2
join = "steps.collect"

[steps.reviews.clarity]
prompt = "Review the goal for clarity."
outputs.assessment = "text"
next = "steps.collect"

[steps.reviews.risks]
prompt = "Review the goal for risks."
outputs.assessment = "text"
next = "steps.collect"

[steps.collect]
kind = "fan_in"
prompt = "Summarise these reviews: {{ context.parallel.results['0'].context.assessment }} and {{ context.parallel.results['1'].context.assessment }}"
next = "steps.done"

[steps.done]
kind = "terminal"
```

[Download parallel-reviews.toml](/examples/parallel-reviews/parallel-reviews.toml)

## 2. Validate it

```sh
orbital validate ~/.orbital/workflows/parallel-reviews.toml
```

Validate again whenever you change where a branch starts or ends.

## 3. Run it

Choose **New task**, pick any project, and choose the `parallel-reviews` workflow and your harness. One project serves every example. Write the goal both reviews should assess, and start the run.

Both reviews run. Their `assessment` outputs stay inside `parallel.results`. The `collect` step summarises both before `done`.

## Related

- [Fan-out](/reference/step-kinds/#fan-out) and [fan-in](/reference/step-kinds/#fan-in): what the two steps take and do.
- [Parallel branches](/reference/history-and-threads/#parallel-branches): the exact rules for branches, their context and their results.
- [Pass context between steps](/workflows/pass-context/): why a branch cannot read its sibling's outputs.
- [Give steps a style](/styles/configure-steps/): give each branch its own style.
