Running workflows side by side

Fan a phase out into lanes, each running another workflow, then merge the results.


Some work splits cleanly. Ship the same feature on Android and iOS. Try three approaches and compare. Run a security audit alongside the implementation. A workflows step does that: each lane runs another of your project's workflows, at the same time, in its own container on its own branch.

- type: workflows
  onLaneFailure: fail        # fail (default) or continue
  lanes:
    - workflow: android-feature
    - workflow: ios-feature

- type: prompt
  prompt: |
    The lane branches are fetched locally and described in
    .peractor/lane/lanes.json. Merge both, reconcile the shared API
    client, then run the suite and fix what breaks at the seams.

With one lane it is not parallelism at all but reuse — a shared test-pass or security-audit workflow dropped into ten pipelines instead of copy-pasted phases.

How the phase splits

The phase stops at the step, the lanes run, and it resumes at the next step once every lane has settled.

Nothing uncommitted survives that gap, and neither does the agent's session. The steps after the fan-out start fresh, and they learn about the lanes from what is put in front of them, not from memory.

What the next step gets

  • Every lane branch fetched as a local ref, so git diff, merge, and cherry-pick between them just work — they share a fork point.
  • .peractor/lane/lanes.json listing each lane's id, branch, status, whether it changed anything, and its summary.
  • .peractor/lane/<id>/report.md for each lane.

There is no mechanical merge action. The agent merges with plain git, guided by your prompt.

Lane names

A lane is named after the workflow it picks — Android feature becomes android-feature, with -2 and -3 when the same workflow appears twice. The name ends that lane's branch and labels its rows in the run, and you can type your own over it — which is what tells three lanes on one workflow apart.

A lane already running keeps the name it started with, so renaming a workflow mid-run is safe.

When a lane fails

onLaneFailure: fail — the default — fails the whole step if any lane dies. Shipping two platforms out of three and calling it done is what that prevents.

continue lets the survivors carry on, and the failed lane is still fetched and listed, because half-finished work is sometimes worth salvaging.

While the step is open, each settled lane has a Run again button — not only failed ones, so a result you dislike can be redone. Once the step has finished you retry the whole phase instead.

Reviews and the board

Gates, questions, and previews are honoured per lane: a lane that needs you stops on its own while its siblings keep going, and you are notified per lane.

More than one lane can move the task's status. Which status a phase sets is configured per workflow, so lanes running different workflows are usually moving the task on different boards; where two really do drive the same board, the last to finish wins. Set ownsStatus: false on a lane — or on the component workflow itself — for one whose progress should not show on the task at all.

What it costs

Lanes are real runs. Three lanes are three runs' worth of compute, counted against your quota, each taking a slot like any run — so a concurrency limit smaller than the lane count simply runs them a few at a time.

A lane's workflow may fan out again, bounded by one limit on the peak number of containers a single run can need. The editor totals it live and will not save over it, circular references are refused, and a lane can only reference a workflow in the same project.

Trying several approaches to one task and comparing them is a different feature: start parallel attempts from the task itself, each with its own workflow, prompt, model, or persona, and read the comparison panel when they settle. See The run lifecycle.