Worktrees and Parallel Loops
By the Syncfusion DevTutorials team · Last updated
Two agents edit the same file at the same time and one fix is lost. Give each agent its own worktree, land the lanes through a merge queue, and see why your review time is the real limit.
Two Agents, One Folder
The first chapter built one loop with one agent. This chapter makes loops bigger. The first way to go bigger is to run several agents at the same time.
That is faster. It is also how fixes get lost.
Watch Two Agents Collide First
Two agents work on eight tickets at once. They share one folder. Some pairs of tickets touch the same file.
Two agents, one folder
Press Run it in the Loop Lab. Look at the Lane column and the red list under the table:
- In the same tick, lane 1 and lane 2 both write the same file.
- The last one to write wins. The other fix is gone.
- But the lane that lost its fix still says "done". Its checker passed before the file was overwritten.
In simple words: two people typed in the same document at the same moment, and one of them lost their work without knowing.
TL;DR
- Running several agents at once is called parallel work. Each agent works in its own lane.
- Agents in one folder can overwrite each other's files. That is a collision.
- A git worktree gives each agent its own copy of the repo, on its own branch.
- A merge queue brings the lanes back to
mainone at a time, and re-runs the tests when two lanes touched the same file. - More agents means more fixes to review. Your review time, not the tool, is the real limit.
A Real-World Example
Think about two students writing one group report.
If they both type into the same file at the same time, chaos follows. One saves, then the other saves over it. A whole page vanishes.
So they work smarter. Each one writes their part in their own copy. When a part is done, they hand it to the editor. The editor adds the parts to the final report one at a time. If two parts changed the same paragraph, the editor reads them together before adding the second one.
Now map this to a loop:
- Each student is an agent, in its own lane.
- Their own copy is a worktree.
- The editor adding parts one by one is the merge queue.
- Reading two changes to one paragraph together is a rebase, followed by running the tests again.
What You Will Learn
In this lesson, you will learn:
- What parallel lanes are.
- Why two agents in one folder collide.
- What a git worktree is.
- What a merge queue does.
- Why review time limits how many agents you should run.
In one folder, two agents overwrite one file. In worktrees, each has its own copy, and the merge queue lands them in turn.
In the lab, the eight tickets come in pairs that touch the same file, such as #3 and #4 in status.ts. Each version of the work shuffles the pairs, but keeps each pair together, so you can always see what a collision does.
Lanes: Several Agents at Once
A loop with two agents deals the tickets out like cards: ticket 1 to lane 1, ticket 2 to lane 2, ticket 3 to lane 1, and so on. Both lanes work at the same time.
The next box deals eight tickets into two lanes, and counts the ticks. A tick is one turn for every lane at once.
Two lanes finish in half the ticks
const tickets = ["#3", "#4", "#1", "#19", "#5", "#7", "#8", "#15"];
const agents = 2;
const lanes = Array.from({ length: agents }, () => []);
tickets.forEach((t, i) => lanes[i % agents].push(t));
lanes.forEach((lane, i) => console.log("lane " + (i + 1) + ": " + lane.join(", ")));
console.log("ticks with 1 agent: " + tickets.length);
console.log("ticks with 2 agents: " + Math.max(...lanes.map((l) => l.length)));
Expected output:
lane 1: #3, #1, #5, #8
lane 2: #4, #19, #7, #15
ticks with 1 agent: 8
ticks with 2 agents: 4
Half the time. But look at the pairs in each tick: #3 and #4, #1 and #19. In this project, each pair touches the same file.
Why One Folder Collides
When two agents share one folder, they share every file. If both change status.ts in the same tick, the second save replaces the first.
Same file, same tick: the last write wins
const files = { "status.ts": "original" };
const tick = [
{ lane: 1, ticket: "#3", file: "status.ts", change: "fix for #3" },
{ lane: 2, ticket: "#4", file: "status.ts", change: "fix for #4" },
];
for (const edit of tick) files[edit.file] = edit.change; // both write the same file
const lost = tick.filter((e) => files[e.file] !== e.change).map((e) => e.ticket);
console.log("status.ts now holds: " + files["status.ts"]);
console.log("lost fixes: " + lost.join(", "));
Expected output:
status.ts now holds: fix for #4
lost fixes: #3
The worst part: lane 1 already told its state file that #3 was done. The loop's memory now says something that is not true.
Worktrees: A Copy for Each Agent
A git worktree is a second (or third, or fourth) working folder for the same repo. Each worktree has its own branch. Edits in one worktree do not touch the files in another.
So each agent gets its own worktree. They can all edit status.ts at once, because each one edits its own copy.
| Tool | How each agent gets its own copy |
|---|---|
| Claude Code | claude --worktree <name>, or isolation: worktree in a subagent's front matter |
| Codex | A scheduled task can run in a new worktree instead of your local project |
| Cursor | Automations and cloud agents run in the background, away from your local files |
In the lab's loop file, it is one setting:
parallel:
agents: 2
worktrees: true
Worktrees: same file, separate copies
const worktrees = { lane1: { "status.ts": "original" }, lane2: { "status.ts": "original" } };
worktrees.lane1["status.ts"] = "fix for #3";
worktrees.lane2["status.ts"] = "fix for #4";
console.log("lane 1 copy: " + worktrees.lane1["status.ts"]);
console.log("lane 2 copy: " + worktrees.lane2["status.ts"]);
console.log("lost fixes: none");
Expected output:
lane 1 copy: fix for #3
lane 2 copy: fix for #4
lost fixes: none
Each lane changed its own copy of status.ts. Both fixes are safe, waiting to be merged.
The Merge Queue
Separate copies solve the collision. But the work still has to come back to main. A merge queue does that one change at a time:
- Take the next finished fix.
- If an earlier fix already changed the same file, rebase: put this fix on top of the newest
main. - Run the tests again.
- Merge. Then take the next one.
A merge queue lands fixes one at a time
const finished = [
{ ticket: "#3", file: "status.ts" },
{ ticket: "#4", file: "status.ts" },
{ ticket: "#1", file: "format.ts" },
];
const touched = new Set();
for (const fix of finished) {
const rebase = touched.has(fix.file);
console.log("merge " + fix.ticket + (rebase ? " (rebased, tests re-run)" : ""));
touched.add(fix.file);
}
Expected output:
merge #3
merge #4 (rebased, tests re-run)
merge #1
Every fix lands. The second change to status.ts costs one extra test run. That is cheap compared with a lost fix.
In the Timeline, look for the Merge queue line under the table. A fix marked "rebased" shared a file with an earlier fix. Its tests ran again before it landed.
Review Is the Real Limit
Four agents finish work twice as fast as two. But every fix still needs a person to read it. That is the outer loop you will meet in a later lesson.
Four agents, one reviewer
Four agents finish 8 fixes in 3 ticks. One reviewer can check 4 fixes per run. The red gauge shows the rest waiting. The agents are not the limit any more. Your review time is.
Fixes made vs fixes a person can review
const fixesPerAgent = 2;
const reviewCapacity = 4; // one reviewer, per run
for (const agents of [1, 2, 4, 8]) {
const made = agents * fixesPerAgent;
const waiting = Math.max(0, made - reviewCapacity);
console.log(String(agents).padStart(2) + " agents: " + made + " fixes, " + waiting + " waiting for review");
}
Expected output:
1 agents: 2 fixes, 0 waiting for review
2 agents: 4 fixes, 0 waiting for review
4 agents: 8 fixes, 4 waiting for review
8 agents: 16 fixes, 12 waiting for review
A good rule: run only as many agents as you can review the work of. Unread fixes are comprehension debt that keeps growing.
If the review queue keeps growing, do not add agents. Add reviewers, make the tickets smaller, or slow the loop down. A fix nobody reads is a fix nobody understands.
Lab Exercise: Give Each Agent a Worktree
Give each agent a worktree
Open the loop file. Under parallel:, keep agents: 2 and change worktrees: false to worktrees: true.
Press Check my loop. On all 3 versions, there must be 0 collisions, and the merge queue must land every fix.
Common mistakes
| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
| Shared folder | "They work on different tickets, so it is fine." | Different tickets can touch the same file. Use worktrees. | One fix is overwritten and lost. |
| No merge queue | "Each lane can push to main when it is done." | Land lanes one at a time, with tests after each. | Two good fixes combine into a broken main. |
| Skipping the rebase test | "Each fix passed on its own." | Re-run the tests when two fixes share a file. | The pair breaks something neither broke alone. |
| Too many agents | "More agents, more done." | Match agents to review capacity. | Unreviewed fixes pile up. |
Exercises
Each exercise ends with a value on the last line. Press Check to compare it with the expected answer.
What to Remember
- Parallel agents work in lanes, at the same time.
- In one folder, two agents can overwrite the same file. One fix is lost.
- A git worktree gives each agent its own copy and branch.
- A merge queue lands lanes one at a time, and re-runs the tests after a rebase.
- Review time is the real limit. Run only as many agents as you can review.
Further Reading
- Loop Engineering, by Addy Osmani: worktrees as a building block for parallel agents.
- Loop engineering: designing loops you can walk away from, by CodeRabbit: worktrees for parallel agents.
- git worktree, in the Git documentation: the command itself.
Next: Connectors, where the loop says "here is the fix" but nothing changes, and you connect it to a tracker, CI and chat.