← Back to blog

Parallelize Reads, Not Writes

· 23 min read · AI · aiagentsclaude-codemulti-agentsoftware-engineeringvibecoding
Contents

Note

This is Part 2 of 4 of From Swarm to Protocol, a serialized edition of “From swarm to protocol: a year of vibing, from noob to pro”. The body below is reproduced from the complete edition with only navigational changes; stage numbers refer to the full arc.

The swarm, at least my iteration of it, didn’t survive contact with reality, so I killed it, and dropped down to a single cross-model pair: a Claude planner and a Codex implementer, later watched by panels of independent reviewers. This is the part where the errors stopped being loud and started being correlated, and where the most durable rule of the entire arc fell out.

Stage 5: The Pairs

After barely a week of commanding swarms of agents, it became rather clear that it was neither sustainable nor efficient; RPing as a switchboard operator was burning me out, QA was difficult, and I was burning through usage limits at a ridiculous rate.

So I did the unthinkable for someone who’d just set up parallel agent swarms — I killed it. All of it. Back down to just one or two agents at a time for a single task, with no more than two or three tasks at once.

It felt like a demotion, honestly. I’ll admit, at the time I felt like the God-Emperor of Agentkind commanding the swarm of 16 frontier models at once, only to fall to the level of some L3 engineer handling just a couple of bumbling idiot interns. However, I realized that me manually reviewing everything was too high a cap on throughput while also insufficient due to my lack of engineering experience, and, by extension, a lack of knowledge of specific/subtle techniques, architectural choices, and pitfalls (after all, it was my second or third week ever of building production software).

Traditionally, this is where you’d pull up Google and try to find analogues to your problem at hand, and, if it somehow didn’t exist, go on StackExchange, make a post about it, and get downvoted into oblivion with unhelpful snarky comments — or if you’re lucky, a comment that said “duplicate issue” with a link to a post from 15 years ago that was quite clearly outdated.

Anyways, I digress. But I thought of a solution to this: why not just get an agent to review it?! Well, two catches:

  1. it can’t be the same agent that wrote the plan or did the implementation, for obvious reasons with bias,

  2. and I’d have to review the plan anyways.

Well, I didn’t want to feel like I was slacking with just two or three tabs of Claude Code running, so I decided to get fancy and pair each instance up with a Codex agent. As for why I partnered it with Codex instead of another Claude instance? Well, you see I wanted an agent to adversarially review every single artifact outputted, whether that be a spec doc or a git diff — I had a gut feeling about how the models in the same family would tend to agree with each other more compared to using different models, mostly since models within the same family would have overlapping datasets and similar training methods, leading to minor shared biases, even if they were technically independent instances;1 I also did not want to use a lower-intelligence Claude model. Thus the natural choice was to just use the frontier model from Anthropic’s primary competitor, namely OpenAI’s flagship coding agent at the time — GPT-5.2-Codex.

Additionally, I moved the implementation to Codex. As for why? Well, I’ve encountered many anecdotes online that, in general, stated Codex was better at following instructions than Claude,2 and instruction-following is quite important for an implementation agent. Also, I was trying to split usage roughly evenly across both my Claude Max and GPT Pro subscriptions to make them last longer, though I was no longer running 16 parallel agents so that was probably moot. Unfortunately, token usage remained roughly the same per cycle. Buuuuuut… (big but), tokens were being used more efficiently — rather than being spent on ad hoc in-cycle management of agent swarms, it was being allocated towards an upgraded planning cycle which would ultimately lead to fewer bugs and thus fewer tokens would be spent on remediating these bugs.

Eventually, after a couple hours of playing around with the, now named, agent-pair framework, I converged on the following structure:

  • Claude, the planner, owns the framing, the audit, the design, the plan, the acceptance criteria. It does not write any code.
  • Codex, the implementer, runs its own independent audit before it ever lays eyes on Claude’s artifacts, picks apart Claude’s audit, spec, and plan docs looking for holes and gaps, and after all is reconciled, gets dispatched to implement in accordance with the plan doc.

With just a single agent-pair, coordinating seemed almost trivial after going through an entire week or so managing a complicated web of agents in a swarm — I only had to relay information between two agents, two panels. Moving terminal text back and forth between Claude and Codex before they both converged sometimes felt almost too mechanical. However, it was necessary as I was unaware of any better method for having the two models communicate with each other while also maintaining interactivity.

Now that I only had one agent implementing per pair, I was able to also more closely monitor exactly what it was implementing and stop it the moment I felt or saw it going off track, and easily rectify any issues, basically resolving the mid-flight visibility problem from the days of the swarm.

Anyways, over the course of running this structure for just a few days, I learned three things that made the lost parallelism worth eating:

  1. Two independent audits of the same problem may disagree. When neither agent has seen the other’s take yet, they can come back with different maps of the same territory. Sitting them down to reconcile — here’s what we agree on, here’s what we don’t, here’s where we just looked at different things — and forcing the two to correct each other allowed a more complete examination and, in many cases, caught completely incorrect architectures, prevented reimplementing something that already existed, and corrected data schemas, all before a single line of code was ever committed.

  2. Confining implementation to a single separate model kept context intact the whole way through, which turned the Stage 4 fixes for fragmentation and collisions from a behavioral fix into a structural fix: one model holding the entire context, with the schema contracts already locked into the reviewed plan, meant collisions and API contract mismatches just stopped — within a single Superpowers cycle, at least. Across separate cycles, contract drift could still sneak back in. Also, it actually didn’t restrict throughput as much as I thought it would. Locally yes, implementing a single task took longer due to the lack of parallelism, but this allowed me to parallelize by being able to more efficiently manage one or two more other agent-pairs while not reaching the level of mayhem that was coordinating eight or even 16 agents all at once on one or two tasks.

  3. With the consolidation of implementation back to a single agent on a separate model family, the PIC decreased further, but didn’t vanish completely — only now, instead of being several very obvious problems, it became just a couple of tiny, barely noticeable, yet critical, issues (more on that in a bit).

I was still a huge bottleneck in the entire system, having to step in at quite a few places, but the tedium of handing off several relays to a swarm of agents no longer existed. This really made coordination less of a hassle since I no longer had to frequently relay prompts to a swarm of agents during the implementation phase; I went from being actively involved in everything from planning through relaying multiple batches of prompts during implementation and review, to really only the planning and review phases, you know, the more important parts.

But — and you can probably already see the next pothole coming — this stage still had only me reviewing the finished work. The big new issue is something the swarm never had as the swarm was at least incoherent in obvious different directions. On the other hand, two agents, even if from two different model families, stewing in the same conversation reaching shared conclusions, will tend to be wrong together, often in very very subtle ways — resulting in correlated errors.3 There was that one time where a single memory leak caused by both Claude AND Codex misinterpreting an aliased type led to my MacBook going OOM and crashing; it took several crashes and a couple of hours before it was finally diagnosed and fixed. Despite using two different model families, the decorrelation afforded by doing so only had the strongest protections for the initial independent audits, and the moment I set them both on a convergence course, that protection evaporated.

The agents no longer disagreed chaotically — they just agreed a tad too easily.

Also, worth a tangent, during this stage I moved off of CLion4 and onto just having only terminal windows open, with documents and code being opened up in nvim whenever needed.

I won’t be writing out the long step-by-step list of actions anymore. From here on it’d be too long to be readable and won’t be all that useful as the flow chart will be a superior visual tool. In lieu of the sequential steps, here’s a table of who owns what and (more importantly) what each one isn’t allowed to touch:

Roles

RoleOwnsForbidden from
Claude — plannerframing, audit, design, plan, acceptance criteriawriting a single line of code
Codex — implementerits own independent audit, picking apart Claude’s docs, the implementationchanging the agreed scope or design
Mereconciling the two, the final review and merge callletting vibes count as verification

Gates

  1. Auditing the codebase (Codex + Claude)
  2. Design choices (manual + Codex + Claude)
  3. Design review (Codex)
  4. Plan review (Codex)
  5. Output review (manual)

Wiring

flowchart TD
    YOU(["You (juggling 2-3 pairs)"])
    PLANNER["Claude<br/>planner"]
    IMPLEMENTER["Codex<br/>implementer"]
    AUDIT_RECONCILE{"Audit reconcile"}
    BRAINSTORM["Claude<br/>brainstorm"]
    DESIGN_REVIEW{"Design review<br/>(Codex)"}
    PLAN["Claude<br/>plan"]
    PLAN_REVIEW{"Plan review<br/>(Codex)"}
    IMPLEMENT["Codex<br/>implement"]
    OUTPUT_REVIEW{"Output review<br/>(you)"}
    SHIP(["Ship"])

    YOU -->|"audit prompt"| PLANNER
    YOU -->|"audit prompt"| IMPLEMENTER
    PLANNER -->|"audit doc"| AUDIT_RECONCILE
    IMPLEMENTER -->|"audit doc"| AUDIT_RECONCILE
    AUDIT_RECONCILE -->|"/brainstorming"| BRAINSTORM
    BRAINSTORM -->|"spec doc"| DESIGN_REVIEW
    DESIGN_REVIEW -->|"revise"| BRAINSTORM
    DESIGN_REVIEW -->|"/writing-plans"| PLAN
    PLAN -->|"plan doc"| PLAN_REVIEW
    PLAN_REVIEW -->|"revise"| PLAN
    PLAN_REVIEW -->|"converged + dispatch"| IMPLEMENT
    IMPLEMENT -->|"code / PR"| OUTPUT_REVIEW
    OUTPUT_REVIEW -->|"bugs"| IMPLEMENT
    OUTPUT_REVIEW -->|"looks done"| SHIP

The board

ProblemStatus
Post-impl churn (PIC)🟨 down to a couple subtle-but-critical issues
Coordination🟨 reduced — still me, just less of me
Context fragmentation🟨 structural in-cycle, but still exists cross-cycle
Edit collisions🟩 structural, in-cycle
Mid-flight visibility🟩 closed — though only by dropping back to one or two agents I can reasonably watch
Token burn🟨 same cost, better spent
Correlated errors🟥 new — wrong together (→ Stage 6)

Stage 6: The Reviews

February 5th was quite the exciting day, as I remembered it. It was the day where both Claude Opus 4.6 AND GPT-5.3-Codex dropped within half an hour of each other’s release. The models were most definitely a welcome upgrade to 4.5 and 5.2, respectively, but Anthropic shipped another experimental feature right under the new frontier model releases — Agent Teams.

When Anthropic shipped Agent Teams, I initially tried it out by resurrecting the swarm from a couple of stages ago. It, uh, didn’t go very well. The issues mainly came from the overhead caused by running multiple Claude Code agents at the same time — it burned tokens inefficiently, which led to usage limits running out mid-run, and switching accounts was a bit annoying since I had to keep asking for a token each time I switched. It also wasn’t that much faster than just having Codex do all the implementation on its own for some reason.

Feeling like the feature was more of a novel experiment and less groundbreaking, I almost put it off. That is until I read the documentation a bit more thoroughly and I found the following example prompt:

Create an agent team to review PR \#142. Spawn three reviewers:
- One focused on security implications
- One checking performance impact
- One validating test coverage
Have them each review and report findings.

“Hmm, might as well try it and see, perhaps it can find both the subtle and the glaring flaws in the code implemented.” And thus, at the end of implementing this feature, I just copied and pasted that exact prompt into the planner, Claude, only replacing the PR number while also indicating which repo on which branch. Lo and behold, it outputted a list of issues to be fixed in the codebase, labeled by concern and marked with severity.

There was something lacking though as security, performance, and test coverage weren’t entirely everything that should be reviewed when shipping a feature — correctness against the specified design and implementation plan docs was pretty, nay, probably the most important thing that needs to be checked. Thus I changed it from three to four reviewers and added another line to the prompt:

- One validating correctness with respect to the design and impl docs above

This was possibly by far the best upgrade throughout this vibecoder -> agentic engineer arc, especially in terms of the effort-to-reward ratio. Now, with a team of dedicated adversarial reviewers with absolutely no involvement in the planning/implementation, I was able to gain another look from a different perspective that would ultimately reveal shortcomings caused by the two agents in the pair converging. That is, the correlated errors were now being discovered much more easily.

Anyways, while working on the core engine, I soon thought of another necessary addition to the standard team of four. From working with these coding agents, I found that, when writing C++, they typically defaulted to only using C++11 or older features and idioms, with some amount of C++14/17 stuff thrown in. This isn’t too much of a shocker as a vast majority of their C++ training data was made up of legacy codebases, with a majority of them even being older than me. Obviously, since we weren’t dealing with a legacy codebase but rather building from scratch, we opted to utilize more modern (and memory-safe) features of C++20/23. We had to include directions in the repo’s AGENTS.md and CLAUDE.md to conform to C++20/23 standards — something we wanted confirmed at the end of implementation. This led to the inclusion of a dedicated idiomaticity checker whenever we were touching C++ code with the following prompt:

- One checking conformance to modern C++20/23 coding standards

Here I realized that it was very easy to add reviewers and modify their scopes whenever dictated by what was being implemented. Touching React modules? How about a specific reviewer for React? Making UI changes? How about a dedicated reviewer with Playwright to poke through your frontend? And so on…

After the planning agent collects the results of the team, it was fairly simple just to verify the issues and nits found were real and then copy-paste the issues over to the implementation agent for them to fix. Codex’s fixes were applied and passed back and another round of review by fresh reviewers commenced. I initially started off passing fix -> review -> fix back and forth until complete convergence but then later dropped to just 2 or 3 rounds tops. 5

Eventually, though, as the models got better and more work came onto my plate, I dropped down to just one round and a quick verification of whether everything highlighted in the original review was fixed. This was mainly a judgment call on my end as a tradeoff with throughput, and I only really dispatched multiple rounds when stuff like security or data schemas were touched, or when multiple gross errors/new revelations were found that naturally may have needed multiple rounds to reconcile.

This was the first time deep review gates (albeit agentic) existed at the end of my pipeline, and the catch rate embarrassed everything that came before. For example, the formerly common API contract drift problems, even between independent cycles, that plagued past stages were now able to be nipped very early before going live since some of the independent reviewers would, often unprompted, check against the other side of the contract. Though, this again was mostly behavioral and not structural.

Now, don’t get me wrong, there’s still a bunch of issues that this agent-pair + reviewer framework (we’ll call this framework agent-pair+ from now on) has, some new and some old:

  1. The observant amongst y’all may have noticed that the independent panel is comprised of agents from the same model family as the planner — every single reviewer was either Opus or Sonnet, depending on what the planning agent was feeling at the time (this nondeterminism was also an issue, albeit small). This reintroduced the same-model bias discussed previously, only now it affected the review layer rather than the entire setup. One could perhaps use an entirely separate near-frontier model for reviewing (say, Gemini or DeepSeek for example) but that would mean a third $200 subscription (or, in the case of DeepSeek, API tokens) on top of the two Anthropic and OpenAI subscriptions, and it was hard to justify another subscription for a model family whose flagship was inferior (in software development contexts) to even the second-class frontier models of Anthropic or OpenAI — I didn’t think that the cross-model bias reduction was significant enough to balance against the lowered intelligence.6 Eventually though, throwing a third model into the mix would be smart if I were forced to use tokens, as many of these near-frontier models were way cheaper than their frontier counterparts. Also, I think it’s worth mentioning why the panel would have been the right home for a third family of models, provided that there was another family of models that were just as capable. Recall that the benefits of decorrelation only lasted a half cycle since they degraded through continuous back and forth till convergence, after which it disappears. The independent panel exists for one round and then gets restaffed with new agents the next so this convergence problem is moot.7

  2. Instead of having to meticulously review the entirety of the implementation myself, I had to now review the reviewers and then still run a quick pass over the implementation anyways! Sometimes the panel would not only emit real findings, but also unnecessary nits (which, to their credit, did usually flag) and false positives. These were most noticeable when the original planning agent that spawned them was able to flag these nits and false positives, while also occasionally downgrading overplayed issues. Fortunately though the review panel was a rather cheap addition that ran on its own and took usually around 10 or so minutes, and the benefits gained from having another 4 pairs of metaphorical eyes reviewing the code — like being able to cover more ground and also knowing more, and sometimes better, than me — made this issue feel more like a justifiable tradeoff rather than a strict downside.

  3. The largest issue by far is the same issue that plagued, not only this stage, but also the entirety of Stages 1 through 5 — namely, the issue of breadth. Every stage so far, including this one, only focused on improving the output of a single unit of work with better specs, richer plans, tighter review, fewer correlated mistakes, but none of them made the unit any bigger. An agent-pair+ was pretty much able to ship out an isolated small to medium sized feature on its own. Any larger and you’ll risk the spec doc being too high level and the implementation plan being too big for a single agent to handle. A bandaid fix for this was to manually direct the planner to split the implementation plan into multiple plans such that the implementer can execute in sequence. This kinda worked but wasn’t very elegant and still suffered from the spec doc being too high level. In the case where I didn’t do this, I had to split the development of features over multiple cycles, either over the same pair with repeated compaction of context or multiple independent pairs that required me to both determine how to organize as well as manually relaying context between them. That is, context fragmentation was a systemic issue with no resolution (yet). This ultimately led to me getting sloppy and complacent over time, and occasionally caused me to forgo the manual review gates I had set up.

  4. And finally, of course, we were still racing through usage limits at F1 rates.

Despite these failures, the main takeaway of the agent-pair+ framework:

Parallelize reads, not writes.8

Reviewers are allowed to be orthogonal to one another even when they land in the same space since they’re each looking for different things, and even when reading the same thing, they’re at least coming from different angles. Also read collisions simply aren’t a thing the way write collisions are (duh). So the context-fragmentation tax and the collision issues that killed my Stage 3 and 4 swarms simply didn’t apply to them.

Looking back, I have come a long way from last October where I just kept prompting a single agent to death until the task looked done, and now, through just a couple of weeks of being tossed through the rings of fire of startup engineering culture, I’ve created a workflow that mimics a real engineering process with gates and active reviews nearly everywhere — and I used this agent-pair+ workflow nearly every time I did any sort of agentic development throughout the next four or so months.

Roles

RoleOwnsForbidden from
Claude — plannereverything from Stage 5, plus spawning the review panel and collecting its findingswriting a single line of code
Codex — implementerthe implementation, and applying the review’s fixeschanging scope; reviewing its own work
Review panel — independent Claude agentsadversarial review by concern (correctness vs docs, security, performance, tests, idioms)any hand in planning or implementation — they only ever read
Meverifying the findings are real, routing fixes, the final merge gatemerging on vibes; treating “done” as a fact

Gates

  1. Auditing the codebase (Codex + Claude)
  2. Design choices (manual + Codex + Claude)
  3. Design review (Codex)
  4. Plan review (Codex)
  5. Output review (manual + independent review Claude agents)

Wiring

flowchart TD
    YOU(["You"])
    PAIR_CYCLE["agent-pair+<br/>(audit → design doc → plan doc → implementation)"]
    PANEL["Claude spawns review panel"]
    R1["correctness vs docs"]
    R2["security"]
    R3["performance"]
    R4["test coverage"]
    R5["optional domain reviewer(s)<br/>(C++ idioms, React, Playwright...)"]
    FINDINGS{"Collect + verify real"}
    MERGE_GATE{"Merge gate (you)"}
    SHIP(["Ship"])

    YOU -->|"task prompt"| PAIR_CYCLE
    PAIR_CYCLE -->|"PR"| PANEL
    PANEL --> R1
    PANEL --> R2
    PANEL --> R3
    PANEL --> R4
    PANEL -.->|"per domain, as needed"| R5
    R1 --> FINDINGS
    R2 --> FINDINGS
    R3 --> FINDINGS
    R4 --> FINDINGS
    R5 -.-> FINDINGS
    FINDINGS -->|"real issues"| PAIR_CYCLE
    FINDINGS -->|"converged (1-3 rounds)"| MERGE_GATE
    MERGE_GATE -->|"bugs"| PAIR_CYCLE
    MERGE_GATE -->|"clean"| SHIP

The board

ProblemStatus
Post-impl churn (PIC)🟨 deep review gates catch most, but still exists
Coordination🟨 still me (→ cracks at scale, Stage 7)
Context fragmentation🟨 structural in-cycle; cross-cycle drift now occasionally caught in review, behavioral only
Edit collisions🟩 structural, in-cycle
Mid-flight visibility🟩 closed
Token burn🟨 review adds cost, but high payoff
Correlated errors🟨 caught by the independent review panel, buuuuut…
Same-model review panel🟨 new — the panel is one model family; cross-checking can’t reach a blind spot they all share (→ a third family, when tokens force it)

Previous: Part 1 — I Ran 16 Coding Agents. The Bottleneck Was Me
Next: Part 3 — Three Rules for Agent Teams

In a hurry? Continue from this exact point in the complete edition.

The combined list of references and supplementary reading can be found here.

Footnotes

  1. Closest formal analogue I found is LLM-as-judge work: Cohere’s PoLL paper reports panels drawn from different model families reduce intra-model bias. That doesn’t prove Claude/Codex coding-agent decorrelation, but it matches the failure shape I was seeing.

  2. Caution This was forum lore plus my own experience at the time, not a benchmark — and it mostly held when instructions were persistent rather than ephemeral (c.f. Stage 7’s jumping-the-gun problem). Treat it as a workflow choice, not a model-leaderboard claim.

  3. A line in this video — about an agent left looping until it “got stuck [in] a local minimum and improvements plateaued” (~18m in) — is what sent me digging, and there’s an empirical observation that rhymes with it: feed a single model its own output in a loop and the trajectory tends to go contractive, collapsing toward a stable attractor with shrinking dispersion (Tacheny, 2025 — measured but not proven, and only on single-model self-loops). It’s a loose analogy for why two agents dialoguing in the same long conversation drift to a shared answer too: both look like a trajectory contracting into a basin. Perhaps the introduction of a fresh agent reintroduces enough velocity to climb out of the well?

  4. Due to it being bloated, typical for JetBrains’s products.

  5. Note This was mostly because of two things: a) I was lazy and it took too long for more than a couple of cycles, and b) most finding after the second or third rounds were just tiny nits and seldom any blocking issues. Eventually, I dropped down to just one round of review and fixes then a quick re-review by the planner agent to verify that the reviewer’s issues were fixed and folded in properly.

  6. Caution This squares with OpenRouter’s Fusion result that even a same-model panel — Opus 4.8 + Opus 4.8, synthesized by Opus 4.8 — jumped from 58.8% solo to 65.5%, which suggests a meaningful chunk of panel lift comes from the synthesis step itself, not just model-family diversity. Not proof a third family is useless; just enough to make the cost/benefit harder to justify here.

  7. Caution I didn’t engineer the fresh-each-round part: every time I invoked Agent Teams it spun up new teammates with fresh context windows, so the panel got restaffed whether I asked for it or not. Do note that this wasn’t actually guaranteed in the docs… though, the architecture (new session IDs, freshly loaded context per spawn) makes it the natural behavior, and it held every time for me — call it observed, not promised. Either way, the thing keeping decorrelation from degrading across rounds fell out of a framework default, not any cleverness of mine. A “happy little accident”, quoth Bob Ross.

  8. Tip I later found I’d half-reinvented a wheel: Cognition argued against tightly coupled multi-agent coding, while Anthropic showed multi-agent systems shine for breadth-first research. (Cognition’s own follow-up later landed on basically this split.) My rule of thumb became: parallelize reads freely; parallelize writes only when ownership is partitioned (c.f. Stage 7).