PROJECT · 3 hours
Learn to orchestrate multiple AI agents using the patterns from Brandon's production SDLC harness — parallel worktrees, dependency-ordered waves, and ordered merges.
The first time I ran my SDLC harness and watched eight agents rewrite eight independent learning modules in parallel — each in its own git worktree, none aware of the others — the result landed in under 45 minutes. Running the same spec sequentially takes closer to three hours and eventually runs into context limits.
That harness is now the production backbone for how I manage large rewrites. It runs on a Mac Mini
behind a Cloudflare Tunnel, dispatches tasks in dependency-ordered waves, and merges results into
main in a controlled sequence. This module is about the patterns behind it — not as abstract
architecture, but as decisions I made after running into real problems.
Think of a single agent like a skilled freelancer who can only take one project at a time. A multi-agent system is a coordinated firm: specialists work in parallel on different problems, and a project manager (the orchestrator) routes work, waits for results, and integrates outputs in a defined order.
The fundamental shift: You stop asking "how do I make the agent do X?" and start asking "which agent owns X, and what does it need from the orchestrator?" Ownership boundaries are the entire design.
Three problems that pushed me toward multi-agent patterns:
Context limits. A large spec rewrite has 10 tasks. One agent accumulates context from every task it does, eventually degrading in quality and hitting token limits. Eight parallel agents, each scoped to one task, stay focused.
Parallelism. Tasks 2 through 8 in my SDLC harness are independent (each touches different files). Running them sequentially means task 3 waits for task 2 to finish even though they share nothing. Parallel execution eliminates that dead time.
Failure isolation. If task 5 fails, I want to retry only task 5 — not re-run the whole spec. Agents that each own one task make retry logic trivial.
Before reading further: In your own work, name a recurring process that has independent subtasks that don't share files. That's the seam where multi-agent patterns pay off. If you can't name one yet, the rest of this module will sharpen what to look for.
Sub-agents are not background threads — they are separate Claude sessions, each with a fresh context window. They know nothing about your conversation, your project, or each other. The orchestrator is the source of truth; agents are specialists that execute exactly what they are told and write outputs to known locations.
Here is how coordination works in my SDLC harness:
The crucial rule: agents coordinate only through the orchestrator. They do not talk to each other directly. Their outputs — git commits and structured reports — are the only channel back to the orchestrator. Trying to have agents communicate peer-to-peer creates race conditions that are nearly impossible to debug.
Boundary rule: Each agent writes only to files it owns. If two agents need to modify the same file, the task split is wrong — fix the ownership boundary, not the agents.
Imagine five construction crews building separate wings of the same building. Each crew works from a blueprint excerpt covering only their wing. The project manager holds the master blueprint and inspects each wing before integrating it into the overall structure. Crews do not walk into each other's wings to make adjustments.
Parallel worktrees work exactly this way. Each agent has its own copy of the repository, works independently, and the orchestrator merges results in a fixed order.
Before writing any YAML or code, answer three questions: what files does this agent read, what files does it write, and what is it explicitly forbidden from touching?
In my SDLC harness, each implementation agent receives:
That constraint prevents the merge conflicts that caused two early parallel runs to require manual intervention to untangle.
The description field is what the orchestrator reads to decide when to delegate. Make it
concrete: triggers, expected inputs, and ownership constraints.
A sub-agent's only channel back to the orchestrator is its output — files written to known paths and a structured commit. In my harness, each agent writes a report to a predictable location, then commits. The orchestrator reads that report to determine pass or retry.
Pattern: Write outputs to deterministic file paths. The orchestrator reads files, not conversational output — this works reliably across process boundaries.
A meta-agent is an agent whose job is to generate configuration for other agents. Instead of writing every sub-agent YAML by hand, you describe what you need and a meta-agent produces a complete, ready-to-use config.
In practice I use this when a project introduces a new type of task I haven't automated before. Rather than designing the agent config manually, I describe the task to a meta-agent and iterate on its output.
The meta-agent pattern is most useful for bootstrapping. Once a sub-agent config is working well, it rarely needs to change. Think of the meta-agent as a one-time configuration accelerator, not a runtime dependency.
When to use it: When you are building a new category of agent (e.g., "review agent" vs "implementation agent") and want a well-structured starting point rather than writing the YAML from scratch.
This is the core pattern in my SDLC harness. Tasks are grouped into waves where all tasks in a wave are independent. The orchestrator launches all tasks in a wave in parallel, waits for all to complete, then merges results in task-number order before the next wave begins.
The key detail: merge_wave_results merges in task-number order, not completion order. This makes
the merge outcome deterministic — essential when tasks might touch overlapping config files.
Why ordered merges matter: If task 3 and task 5 both update a shared config file, whoever merges second wins. Fixing the merge order makes that outcome predictable and reproducible across runs.
When tasks have explicit dependencies — task B needs task A's output — run them sequentially. My harness uses this within a wave for structural tasks that must land before module rewrites begin:
The wave boundary is your sequencing primitive. Use it when earlier work creates a dependency that later work consumes.
The orchestrator itself can call the Anthropic SDK to handle failure modes that rule-based
if/else logic would miss. When a task fails, instead of pattern-matching on error strings,
I ask Claude Haiku to classify the failure and recommend a retry strategy:
A rule-based triage only catches failures you anticipated at design time. An LLM can classify novel error messages using general reasoning — useful the first time you hit a new failure mode you did not plan for.
Consider a spec where tasks 3 and 5 both modify metadata.json. You could run them in parallel,
but the merge will conflict. What are your options?
There is no single right answer — but thinking through it before you start is far cheaper than untangling a merge conflict after.
When eight agents are writing to eight worktrees in parallel, you need to know what is happening without reading eight separate terminal windows.
My harness logs four events per agent: started, checkpoint-passed, completed, and merged.
Four events per task are enough to reconstruct the full timeline of any run and detect which
agent stalled or failed.
One JSON line per event. No database, no server, no dependencies. To see what happened, read the
JSONL file. To build a timeline, sort by ts. To find stalled agents, look for started with
no matching completed.
The most common multi-agent failure mode I hit is two agents modifying the same file path. The fix is architectural (better task splits), but detection is simple:
Before scaling to 10+ agents: run 3 agents first and fix every conflict you find. A conflict that is small at 3 agents is catastrophic at 10.
When a wave produces fifty pages of logs, I use Claude Haiku to produce a one-paragraph summary of each agent's session. At current pricing, summarizing an eight-agent run costs well under $0.05 — less than the cognitive cost of reading the logs manually.
You now know the core patterns. This project asks you to build a minimal wave runner: dispatch two independent tasks in parallel, wait for both to complete, and merge results in task-id order.
Scope deliberately: A production wave runner has retries, worktree cleanup, and structured reporting. Build the simplest version that demonstrates the core pattern. You can extend it once it works.
Task dataclass with id, spec_excerpt, and output_pathrun_task(task) simulates async work and writes a result file to output_pathrun_wave(tasks) dispatches all tasks in parallel using asyncio.gathertask.id before merging — regardless of completion ordermerge_resultsTiered hints:
asyncio.gather(*[run_task(t) for t in tasks], return_exceptions=True) runs all tasks
and captures exceptions as return values instead of raising.isinstance(r, Exception) in the results list to detect failures before merging.sorted(results, key=lambda r: r["task_id"]) puts results in deterministic merge order.The critical line is sorted(..., key=lambda r: r["task_id"]). asyncio.gather returns results
in completion order, which is non-deterministic. Sorting by task id makes merge order reproducible
across every run.
1. In a dependency-ordered wave runner, results from a wave are merged in completion order. True or false?
Correct Answer: B
Results are always merged in task-id order. Completion order from asyncio.gather is non-deterministic — sorting by task id makes the outcome reproducible across every run, which matters when two tasks might touch overlapping files.
2. Two agents in a wave both modify the same metadata.json file. What is the correct fix?
Correct Answer: C
The root cause is a design problem: the task boundaries are wrong. Serializing or locking are workarounds that mask the flaw. Fix the ownership split so each file has exactly one owning agent — then parallelism is safe.
3. What is the primary communication channel between a sub-agent and its orchestrator in this harness?
Correct Answer: C
Agents write outputs — reports and commits — to deterministic file paths. The orchestrator reads those paths after each agent finishes. This avoids tight coupling and works reliably across separate processes.
4. You call asyncio.gather(return_exceptions=True). How does this differ from the default behavior?
Correct Answer: C
With return_exceptions=True, gather returns Exception objects as list elements rather than raising. This lets you inspect all results — including which tasks succeeded — before deciding how to handle failures.
5. The orchestrator calls Claude Haiku to classify a task failure. What does this provide that a rule-based if/else cannot?
Correct Answer: D
A rule-based triage only recognizes failures you enumerated when writing the code. An LLM can classify novel error messages using general reasoning — useful the first time you encounter a failure mode you did not plan for.
6. Which four events per agent does the observability logger track in this module's pattern?
Correct Answer: B
Started, checkpoint-passed, completed, and merged. These four events are enough to reconstruct the full timeline of any run and detect which agent stalled, failed, or was never merged.
7. In the worked solution, why is `sorted(raw, key=lambda r: r['task_id'])` necessary before calling merge_results?
Correct Answer: A
asyncio.gather returns results in the order tasks completed — not the order they were submitted. Task completion order is non-deterministic (affected by I/O, scheduling, etc.). Sorting by task_id makes merge order deterministic and reproducible.
Nice work. Here's what's next.
Back to this pathOr book the diagnostic call