Skip to main content

03.25Practice

Multi-agent orchestration for AI coding agents

How AR Labs orchestrates AI coding agents on one body of work: a task graph decides what is ready, each task gets a worktree, overlapping work waits its turn.

A dependency graph on a board dispatches tasks onto four parallel lanes; two overlapping tasks wait in single file on one lane.A small dependency graph on a board dispatches tasks onto four parallel lanes, each its own worktree; two overlapping tasks wait in single file on one lane.PRACTICE03.24THE GRAPHDECIDESWHAT RUNSDRAWINGENG-03.24SUBJECTmulti-agent orchestrationSHEET24 OF 26 · AR LABSGRAPHWT1WT2WT3WT4TASKTASKTASK 1TASK 2TASKONE DRIVERRE-READS IT
On this page
  1. The problem it answers
  2. How it works
  3. The bottleneck is checking, not writing
  4. A worked example
  5. What exists today

When AR Labs runs several AI coding agents (AI agents that write code, such as Claude Code or Codex) on one body of work, a task graph, all tracked tasks and the dependencies between them, decides what may run, not a plan held in any agent's head. Each task runs in a fresh agent session, on its own branch in its own checkout, and tasks whose specs declare overlapping files are run one after the other. A single stateless driver re-reads the graph on every pass, so a crashed session loses no record of what is done. The practice is for anyone whose parallel agents overwrite each other or lose track of finished work.

There are two kinds of fan-out in our work. Research and audit fleets, where several agents read and report, follow the briefing and synthesis protocol described under Orchestrate. This page covers the other kind: several agents writing code toward one goal.

The problem it answers

Parallel coding agents fail in predictable ways. Two agents edit the same file and one overwrites the other. An agent starts work that depends on code another agent has not finished. An orchestrating agent keeps the plan in its own context, and when that context fills or the session dies, nobody knows which work is done. A long-lived agent "seat" accumulates habits, and its reviewer inherits them. And the fleet produces work faster than anyone can check it.

How it works

FIG. 01
flowchart TD
  G["Task graph with blocking edges"] --> F["Ready frontier: blockers landed"]
  F --> O{"Specs declare overlapping files?"}
  O -->|"yes"| SR["Serialise those siblings"]
  O -->|"no"| P["Eligible to run"]
  SR --> P
  P --> W["Worktree per task, cut from the target branch"]
  W --> S["Fresh session per step"]
  S --> R["Proposal recorded on the task"]
  R --> G

The graph is the plan. Work is split into vertical slices, each a task in a Beads dependency graph with explicit blocking edges. The ready frontier is every task whose blockers have landed. A guard refuses to move a task into implementation while any declared blocker is unmerged, because a branch cut before its dependency lands is built on code that does not contain it.

One worktree per task. Each task gets a branch named after it and a git worktree cut from its integration target at dispatch. Every later step of that task reuses the same worktree, because it carries the earlier steps' commits. Read-only reviewers need no worktree. See worktree isolation.

Overlap is serialised, not merged after the fact. Each task's spec lists the files it expects to change. When ready siblings under one parent declare overlapping files, the driver runs them one after the other and records which sibling is waiting behind which.

Fresh sessions, not persistent seats. Each step of each task runs in a new agent session that reads the task's records and the step's procedure. Nothing carries over in the agent's memory. That keeps the driver's decision a pure function of recorded state, and it keeps the reviewer independent of whoever wrote the code. Persistent seats, long-lived agents that keep their learned context between tasks, carry habits forward along with knowledge.

Claims and dead letters. A driver that starts work on a task records a claim, and claims never expire on their own: when a driver meets another holder's claim, it stops and asks a person whether that holder is still working. Three failed attempts at one step park the task in a dead-letter state until a person clears it. Neither rule lets two agents quietly work the same task.

Integration is its own step. When every child of a container task has landed, a fresh agent session writes a reconciliation of what was built against what was planned, and the container's acceptance scenarios must each appear in a test that passes. In the deepest autonomy level, children land on a campaign branch rather than main, so dependent work can stack and a person reviews the whole campaign before it reaches main.

Briefs travel by reference. A dispatched agent receives facts and a pointer to the step's procedure, never a paraphrase of it (see unattended agent runs). Routing and briefing rules for every agent launched follow model routing and agent economics.

The bottleneck is checking, not writing

Our survey of software factories, from session managers and orchestrators to the in-house systems large companies have described in public, found that throughput is limited by verification, review and merging, not by the number of agents. Most of the systems it surveyed keep a person at the merge. Our design follows from that: one pull request per task so each merge stays readable, a cross-model review before a person sees anything, and serial integration. More agents are added only when the checking side has room for their output.

A worked example

Illustrative example, with generic names. A campaign search-3 has four slices. search-3.1 is the tracer bullet; 3.2 and 3.3 both depend on it; 3.4 depends on 3.3.

On the first pass only 3.1 is ready. It runs spec, build and review in its own worktree, and a person merges it. On the next pass 3.2 and 3.3 are both ready. Their specs declare files: 3.2 expects to change the query parser and its tests, 3.3 the ranking module and the query parser. The parser overlaps, so the driver dispatches 3.2 and rests 3.3 with the reason "serialised behind search-3.2". When 3.2 lands, 3.3 is cut fresh from main, which now contains 3.2's parser changes, so it builds on them instead of conflicting with them. 3.4 waits on its blocking edge throughout. When all four have landed, a fresh session writes the reconciliation and the campaign's own acceptance tests run.

What exists today

The driver, the worktree placement, file-scope serialisation, claims and dead letters are in use in our coding pipeline. The executor currently dispatches sessions one at a time, so the serialisation rules decide order rather than run work side by side; concurrent dispatch, a merge queue and a fleet view are the subject of our software factory design.