Cover image for Shipping a feature milestone as dependency-ordered waves of AI agents

Shipping a feature milestone as dependency-ordered waves of AI agents


June 15, 2026

Cover image for Shipping a feature milestone as dependency-ordered waves of AI agents
Hero: a dependency graph showing the 4-wave chain with a parallel fork at Wave 2, labeled "2 of 5 genuinely parallel"

I'm looking at a milestone of 5 issues. The instinct is to fan everything out to parallel agents at once. Get them all running, collect the PRs, merge everything clean, done.

I open a text file and start drawing the dependency graph.

Most of it can't be parallel.

What the graph said

The milestone was the second one of the production build: patient registration and global patient search. Five issues to ship:

  • A CI hardening task with two deprecated actions to bump

  • A flaky end-to-end auth test to fix at the root

  • A server-state library adoption

  • A patient registration form (the heavy story)

  • A global patient search finalize

I drew the arrows. Flaky test fix had to land before the data library. The data library had to land before both downstream stories. The CI action bump was independent of everything.

The dependency chain looked like this:

`` CI action bump (independent) flaky-test-fix → data-library → { registration form || patient search } ``

Only 2 of the 5 issues were genuinely parallel. The rest had to sequence.

The insight landed early: "parallel agents" sounds like the obvious play, but the dependency graph usually says more about what's actually parallel than the instinct does.

Flowchart: the 4-wave pipeline with dependency ordering, chokepoints, parallel fork, and "merge + verify" gates between waves

Wave 0: Fix the signal you're about to build on

I started with the flaky auth test. Three of the remaining four PRs would pass through the same end-to-end gate. Left unfixed, every one of them would false-flake under load.

The root cause: the test suite was re-signing in per test. The auth provider throttles rapid repeated sign-ins, which showed up especially during back-to-back local runs. The fix was architectural: sign in once at the start of the suite, cache the session in a storageState file, reuse that file across every subsequent test. One auth call per run instead of one per test.

Gate clean. Only then did I release the next wave.

The rule that crystallized: fix the signal you're about to build on before you build on it. A flaky CI gate is infrastructure debt that compounds on everything downstream.

Wave 1: The chokepoint merge

The data library was a chokepoint. Both the registration form and the patient search needed it mounted and live in the app before they could run.

If I'd tried to parallelize the library adoption with either downstream story, they'd have collided on the provider setup: two agents both wiring the same root component, stepping on the same mounting code. Even with worktree isolation, one would have to rebase on the other's work before anything could merge clean.

The clean move was to treat it as a gate: library merges first, then the parallel pair unlocks. One worker agent adopted the library, migrated the hand-rolled fetch hooks, exposed a clean mutation surface. One PR, merges clean. Gate opens.

Wave 2: The parallel fork

With the library on main, the two stories were genuinely independent. I launched two agents, each in its own git worktree. One built the patient registration form. One finalized the global patient search. Neither was touching the other's files in theory.

In practice, one file overlapped. A shared layout component both stories touched. When the first PR merged and the second agent rebased on the updated main, the overlap resolved cleanly on its own. No manual intervention.

The worktree isolation was what kept the rest of it clean. Without it, both agents would have been committing to the same checkout, and even a one-file conflict would have required stopping to untangle rather than just running a rebase.

The orchestrator closes every loop

After each wave merged, I stopped before releasing the next one. Pulled the latest main, re-checked the state of the task tracker, confirmed the merged PR was green end-to-end, then opened the next wave.

This sounds procedural. It is. It's also the step that's easiest to skip. Stacked-wave orchestration where you launch each wave before the previous one is verified produces layered problems: wave 3 builds on a broken wave 2, wave 2 built on a broken wave 1, and when CI finally surfaces the issue it's three layers deep instead of one.

The orchestrator's job isn't just to launch workers. It's to verify between merges and close every loop before the next one opens.

This is the same pattern from the prototype, grown up

Post 007 in this series covered a similar instinct: one orchestrator chat coordinating per-phase executor chats, each committing its own slice of work. That was a throwaway prototype in a single repo checkout, built in one afternoon with no CI gates, no Linear lifecycle to track, no production constraints.

This is the same instinct in a production context: real PRs, real CI gates running on every commit, git-worktree isolation per parallel agent, an explicit dependency graph drawn before anything launches, and merge-by-merge verification throughout.

The pattern is the same. The production constraints sharpened every step of it.

Comparison: Act 1 throwaway prototype sub-chats (single checkout, fan-out, no CI gates) vs Act 2 production wave orchestration (isolated worktrees, dependency-ordered waves, CI gate between each merge)

The result

The milestone hit 100%. Five PRs merged clean, in four waves, three days ahead of the target. The one file conflict that came up resolved on a rebase with no manual work.

What this milestone made clear: the developer-time bottleneck on this project is gone. Shipping five issues in a day isn't what requires care anymore.

What requires care is the part before any agent runs: mapping the dependency structure, finding the true parallels vs the apparent ones, identifying the chokepoints that have to clear before anything else can start, and staying in the verification loop between merges instead of fire-and-forgetting the whole batch.

That's the part where the value lives now.


#BuildInPublic #ClaudeCode #AIAgenticEngineer #AgenticAI