← Back to blog

I Ran 16 Coding Agents. The Bottleneck Was Me

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

Note

This is Part 1 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.

At my peak delusion I was running sixteen coding agents across four screens and calling it engineering. This first installment covers the arc most vibecoders will recognize (i.e. one-shot prompts, plan mode, the swarm, and the week discipline started shipping as plain text) and it ends where the real problem finally shows itself — the failures didn’t lie in the agents themselves.

At the height of my AI delusion, I had sixteen coding agents running at once: four apiece across my laptop, an iPad I’d hauled from home, and a couple of monitors at the office. I was completely sure I’d cracked it. What I’d actually done was make myself the single slowest part of my own system. And this was real production code at a startup I’d recently joined, so the delusion wasn’t free: every confident wrong turn got paid for downstream, sometimes caught by someone else before I caught it.

This post is the story of my agentic workflow’s evolution through seven distinct stages, starting from the stereotypical vibecoder setup of VS Code with code on one half of the screen and Claude Code on the other half, to an organized and coordinated multi-agent system comprising models from multiple providers running completely from the terminal.

TL;DR — I once thought sixteen agents at once made me a genius. In fact, I was a fool. It made me the bottleneck and taught me that an agent saying “done” is a claim, not a fact. It only meant it stopped talking, not that anything worked. Thus, “done” doesn’t count until a merge, a reviewed diff, or a test suite that actually ran and meant something says so. The fix wasn’t more agents, but the boring engineering that grew out of agents blowing up prod: a spec, a reviewed plan, scoped writes, an independent panel, and a skeptical human who won’t wave anything through. To be completely honest, it trades quality for throughput: it won’t make the output excellent, just keep it from collapsing into vibecoded slop-spaghetti. Quality now at least has a floor that isn’t rock bottom. However, it can’t replace an engineer. It’s just a prosthesis. That is, it only extends your reach, never your judgment, so the human at the last gate matters more as the agents improve, not less. Strip the agents out and what’s left is just engineering.

TL;DR of the TL;DR — Vibecoding, and its (disastrous) results, made me rederive engineering from first principles. It was just engineering the whole time. Always has been.

Stage 1: The Vibes

My first foray into vibecoding agentically-assisted programming came at CalHacks 12.0 last year,1 where I got ahold of $150 of Anthropic API credits.

Over the weekend, my friends and I attempted, well, initially attempted, to build something that let an AI chatbot interact with multiple people in a single environment all at once.

We initially started building a Discord-esque chatroom to situate our chatbot within to demonstrate its ability to chat with many people simultaneously in the same place. Then, while that was being built out, we decided that a social deduction game not too dissimilar to Among Us2 would be a great way of demoing its multiplayer capabilities. The setup: A queue of 3 to 8 anonymized players would be dropped in a chatroom with a chatbot placed amongst them, and then multiple rounds of voting/deliberation would be given after some time, with the players winning if the chatbot gets voted out, and the chatbot winning if there is only one other player left.

“Wow! That sounds like a lot to build out, even with gen-AI helping,” some of you may be thinking (or the complete opposite for some of you), and indeed it was. Due to my gross dereliction of duty — choosing to play poker on the concrete floor with strangers the first night, and a victorious poker tournament the second night3 — we barely finished building out a semi-functional Discord clone with an LLM wrapper (that was undemoable due to, again, the internet being uncooperative) and a Figma mockup of the videogame.

Honestly, for being a bunch of non-CS sophomores, I’d say we did pretty well!

My “process” for vibecoding the demo at this point was that of the stereotypical vibecoder’s — VS Code split into two with one half displaying the code and the other half being Claude Sonnet 4.5 (wow how far we’ve come with flagship models). There was little to no prompt engineering being done here, just straight up one-shot prompts like “build me a fully functioning chatroom”.4 Needless to say, this was not at all an effective nor mature way of building with agents… which led to features not working as intended with extended debugging periods following the initial “completion” of each minor feature.

Process I

  1. Open up VS Code
  2. Open the Claude Code panel
  3. One-shot prompt the entire way through
  4. Review the output

Gates

  1. Output review (manual)

Wiring

flowchart TD
    YOU(["You"])
    AGENT["Claude"]
    REVIEW{"Eyeball it"}
    SHIP(["Ship"])

    YOU -->|"one-shot prompt"| AGENT
    AGENT -->|"code"| REVIEW
    REVIEW -->|"bugs / mismatch"| YOU
    REVIEW -->|"looks done"| SHIP

The board

ProblemStatus
“Done” isn’t done🟥 open

Stage 2: The Plans

Fast-forward a couple months and… poof. All of a sudden, I’m a college dropout! A founder I knew was looking for engineers to help build his startup. Disillusioned with a future in academia, I applied to the job, took a short interview, attended a week-long work trial, got my offer, and then clicked that button that submitted my withdrawal request from my alma mater UC Berkeley. That’s roughly when the hackathon toy problem became a production problem.

Given the velocity at which one is required to build and ship features in order to grow the company at a startup, the CalHacks workflow was no longer sufficient — I had to ship real production systems with a tiny team and not drown. When you’re tasked with “build a planning engine in two weeks,” you either start effectively wielding agents or you become the bottleneck — dead weight in a team of less than 10 people is not something I am keen on being.

Having learned my lesson from my previous vibecoding experience, I evolved my process into something a bit more appropriate, or at least what I thought of as appropriate, for the workspace.

Using the stereotypical vibecoder’s setup as a jumping-off point, I realized that building stable production software is completely different from building a demo, and that requires more than a well engineered prompt, but rather a well written plan for agents to follow.

Initially I tried writing out plans by hand but realized that, without the experience of decades+ of senior engineers, it was exceedingly tedious and slow, kinda what you don’t want at a growth-minded startup. Luckily, however, after just a couple of hours going about this the wrong way, I found out that Claude Code actually had a native planning feature built in! Huzzah! Now it can draft me plans and output a full spec doc ready for my review — IMO it is certainly much easier, at least in the sense of documentation, to review rather than to write.

With the reckless one-shots removed, the number of holes the agent left in their implementation, and with it the length of the cycle of prompt-till-no-visible-bugs, decreased by a non-trivial amount; a simple addition of a small gate before sending off an agent to implement was an incredibly cheap win. However, the bugs never really stopped coming — they only got more subtle. Every half-fix still bred the next one that failed in quieter, harder-to-spot ways; fix begetting bug begetting fix, like an ouroboros that ate its own tail and called it progress. This will be a recurring issue throughout the journey, though it does get smaller and smaller as we go along.

Process II

  1. Open up CLion (not VS Code anymore, working on C++)
  2. Open the Claude Code panel
  3. Prompt Claude to start plan mode
  4. Review the finished plan and make/prompt edits
  5. Send Claude off to implement
  6. Review Claude’s final output

Gates

  1. Plan review (manual)
  2. Output review (manual)

Wiring

flowchart TD
    YOU(["You"])
    PLAN["Claude<br/>plan mode"]
    PLAN_REVIEW{"Plan review"}
    IMPLEMENT["Claude<br/>implement"]
    OUTPUT_REVIEW{"Output review"}
    SHIP(["Ship"])

    YOU -->|"prompt"| PLAN
    PLAN -->|"spec doc"| PLAN_REVIEW
    PLAN_REVIEW -->|"revise"| PLAN
    PLAN_REVIEW -->|"approved"| IMPLEMENT
    IMPLEMENT -->|"code"| OUTPUT_REVIEW
    OUTPUT_REVIEW -->|"bugs / mismatch"| YOU
    OUTPUT_REVIEW -->|"looks done"| SHIP

The board

ProblemStatus
“Done” isn’t done — now called the vibecoding ouroboros🟨 smaller — a plan gate helps, but the bugs just get subtler

Stage 3: The Swarms

Ok, so, quick preface, some may view this stage as a regression from the previous stage, but I think it’s more fair to see this as a separate path that developed near-concurrently rather than a sequential progression from Stage 2.

Anyways, I was quick to realize that one agent shipping everything simply wasn’t going to do — implementation often took far too long, sometimes over an hour or so! This is when I decided to copy the setups I’ve seen on twitter X and Reddit and other places online, where people often had many agents running at once. I then had the brilliant idea to get two more screens (on top of my MacBook and the monitor it was connected to); at work I was able to procure a leftover monitor as an additional screen after the other work trialists left and brought an iPad I had from home with me. I then decided to run not just one, nor two, nor four agents at once, but four on each of my four screens — a MacBook, an iPad, and the two monitors — for a total of 16 concurrent. I recall once where I managed to cram eight agents on one single screen operating all at once! (I have no clue how I did it back then)

I usually split the 16 agents up into two teams, with one planning agent and seven implementation agents. The protocol was kept mostly the same except while planning I directed each planning Claude to parallelize it up to seven lanes for the implementation agents by outputting prompts to give to each agent (note this was BEFORE Claude Code Agent Teams was a thing, and far before it got out of the experimental phase). And yes, I believe there was once where I had all 16 on a single team! Doing this I was able to cut down implementation time from around an hour to around half an hour or less for most tasks.

When I got to this point, I saw myself “handling” the 16 agents and thought to myself: “Wow, I am such a genius!” as one would typically say at the top of Mount Stupid on the Dunning-Kruger curve.5

The Dunning-Kruger curve: confidence plotted against competence, peaking early before the valley of despair.
The “Dunning-Kruger Curve” — note how early the peak comes.

For disjoint work, the throughput gain was significant. However this led to five compounding problems:

Telephone switchboard operators connecting calls by hand at Fort Richardson, 1950.
Telephone switchboard operators connecting calls by hand at Fort Richardson, 1950.
  1. Coordination: I was the coordination layer and I don’t scale — textbook Mythical Man-Month, except that every message was routed through me rather than amongst the agents themselves.6 The “swarm’s” speed was capped not by the agents but by how fast I could read, summarize, and re-prompt. The entire time I felt like I was a 20th century switchboard operator.

  2. Context fragmentation: Context was fragmented across independent agents. Each agent saw its slice and made locally reasonable decisions that were globally incompatible. The canonical disaster: two agents touching opposite sides of an internal API, each “fixing” the contract toward its own caller. Both diffs looked clean in isolation and they merged without issue. However, when it was deployed onto a dev mirror (yes I did NOT push to prod :D), it took a while to figure out why data didn’t make it through an internal service boundary. Yikes, a rather important bridge sabotaged by agents unaware of each other’s plans.7

  3. Edit collisions: I did not have agents work in separate worktrees — they were all working on the same one and only local checkout I had on my machine, leading to an abundance of race conditions which slowed stuff down a lot when different agents tried to edit the same file.

  4. Mid-flight visibility: I lost the ability to see what each individual agent was doing throughout implementation; it was hard to examine exactly what and how each line of code was being edited. Whether or not an agent was going sideways was now mostly based off of vibes while it was running and its final output.

  5. Token burn: And the worst of all — it took up a helluva lot of tokens. Sixteen agents all at once on the top-tier Opus 4.5 model drained accounts relatively quickly.

my friends and me routing more than a dozen operators each, if I had any friends that is.

A bridge built toward the middle from both ends, the two halves meeting at different heights and failing to line up.
Two agents, each “fixing” the API contract toward its own caller. Both halves passed their own tests.

I want to especially flag the first two problems, because it took me embarrassingly long to see them as structural problems rather than vigilance problems. The clean tell: the obvious fix for the edit collisions — giving every agent its own worktree — would’ve done nothing for the first two. I would’ve still been the router, each agent still seeing only its own slice; the collisions were a config problem, but the first two were the topology:

The swarm wasn’t failing because I wasn’t paying enough attention — it was failing because I’d made myself the coordination layer.

This will come back later…

Process III

  1. Open up CLion
  2. Set up 4 displays and open up 4 tabs of Claude Code on each
  3. Prompt Claude to start plan mode
  4. Instruct Claude to plan for parallelization
  5. Review the finished plan and make/prompt edits
  6. Signal to planning agent for the first batch of prompts
  7. Dispatch to each agent their prompt
  8. Review each PR generated at each step of the stage and either merge or pass back issues
  9. Signal to planning agent for next batch of prompts
  10. Repeat 7 through 9 until done
  11. Review their final output

Gates

  1. Plan review (manual)
  2. Output review (manual)

Wiring

flowchart TD
    YOU(["You, the switchboard"])
    PLANNER["Planning agent"]
    PLAN_REVIEW{"Plan review"}
    A1["Agent 1"]
    A2["Agent 2"]
    A7["Agents 3-7"]
    OUTPUT_REVIEW{"Output review"}
    SHIP(["Ship"])

    YOU -->|"prompt + parallelize"| PLANNER
    PLANNER -->|"plan doc"| PLAN_REVIEW
    PLAN_REVIEW -->|"revise"| PLANNER
    PLAN_REVIEW -->|"approved"| YOU
    YOU -->|"lane 1"| A1
    YOU -->|"lane 2"| A2
    YOU -->|"lanes 3-7"| A7
    A1 -->|"code"| OUTPUT_REVIEW
    A2 -->|"code"| OUTPUT_REVIEW
    A7 -->|"code"| OUTPUT_REVIEW
    OUTPUT_REVIEW -->|"bugs / next batch"| YOU
    OUTPUT_REVIEW -->|"looks done"| SHIP

The board

ProblemStatus
The ouroboros🟨 still there
Coordination — I’m the router🟥 new
Context fragmentation🟥 new
Edit collisions🟥 new
Mid-flight visibility🟥 new
Token burn🟥 new

Stage 4: The Superpowers

After just a day or two of cosplaying a switchboard operator with the agent swarm, I realized that the plans Claude was writing were too high-level for any sort of reasonable execution/coordination outside of a one-shot attempt; these high level spec docs lacked a detailed implementation plan, which was a major contributor to the fragmentation and edit-collisions that plagued the swarm.

As luck would have it, as I googled “top 10 must have skills and mcps for vibecoding,” and browsed through several clearly AI-generated slop skills, I stumbled upon obra’s Superpowers. Let me tell you, it was like finding gold in a pile of gravel.

I then immediately went /plugin marketplace add obra/superpowers-marketplace, added the skill, and immediately went right into using it. It was a complete game changer — the brainstorming and writing-plans subskills provided crucial improvements to the workflow! Instead of having a single one-shotted high level spec doc, brainstorming actually ran me through most of the big architectural and design choices and recommendations with reasoning — I actually had a say in what the agent was going to output before it wrote a doc with no reasoning behind why it chose that specific design. Additionally, it produced a detailed implementation plan with specific lines of code and files to edit that stipulated test-driven development rather than requiring me to add it in myself or prompting the agent to add it. Though, I still had to instruct it to parallelize the plan afterwards.

Now, agents stopped sprinting headfirst into implementation with some high level plan and half-assed prompts with vague implementation directions and instead had a properly written and tailored spec doc along with a detailed implementation plan to work off of — “done” started meaning something in the rough neighborhood of done, and the gargantuan ouroboros of bug -> fix -> bug had significantly decreased to just a short period of post-implementation churn (calling it PIC, from now on) — the terrible plague that afflicted many vibecoders was slowly turning into a myth (or a fable, if you’d prefer), well for me at least.

Within a single Superpowers cycle from brainstorm -> PR, context fragmentation mostly stopped as every agent worked off the same detailed implementation plan and thus their local decisions stopped colliding. An additional win was that each agent’s file assignment was in the plan itself so they didn’t wander off onto another agent’s assigned file — though, nothing actually stopped them from colliding since they were still sharing the same worktree; there was only prose keeping them in line. Sure this probably was enough but it wasn’t exactly as persistent as an agent skill, so it was still very fragile — when a session autocompacted, it would often forget what file it was permitted to edit and veer off of its assignment. For the most part though, fragmentation and collisions mostly stopped, and now only really existed between separate Superpowers cycles and random freak edge cases.8

It still had problems, though. Most importantly, I’m still the bottleneck in terms of distribution and coordination between agents in large swarms — plans and spec docs still had to be manually reviewed, though, again, it was much easier to answer a couple of design questions and review a full doc rather than materialize it yourself; mid-flight visibility still hadn’t been solved — while Superpowers spec and planning docs did cut down on the chances of an agent going sideways, it could still happen; and tokens were still being burned at an incredible rate, requiring nearly two $200 Claude Max subscriptions’ worth of weekly usage.

However, the part that stuck with me wasn’t Superpowers itself, nor any of the subskills it provided. It was realizing one thing:

You can ship discipline as plain text.9

There was no bespoke harness and no deterministic push in general; just a couple of short, well-written directives dropped into the context of every agent were enough to make sure what was implemented was as intended and reduced the effects of each independent agent having independent contexts.

Process IV

  1. Open up CLion
  2. Set up 4 displays and open up 4 tabs of Claude Code on each
  3. Prompt Claude with /superpowers:brainstorming
  4. Answer all of its design questions
  5. Review the finished spec doc and make/prompt edits
  6. Prompt Claude with /superpowers:writing-plans, with specific instructions for parallel implementation
  7. Review the finished plan doc and make/prompt edits
  8. Signal to planning agent for the first batch of prompts
  9. Dispatch to each agent their prompt with /superpowers:executing-plans
  10. Review each PR generated at each step of the stage and either merge or pass back issues
  11. Signal to planning agent for next batch of prompts
  12. Repeat 9 through 11 until done
  13. Review the end output

Gates

  1. Design choices (manual + Claude)
  2. Design review (manual)
  3. Plan review (manual)
  4. Output review (manual)

Wiring

flowchart TD
    YOU(["You, still the switchboard"])
    BRAINSTORM["Claude<br/>brainstorm"]
    DESIGN_CHOICES{"Design choices"}
    DESIGN_REVIEW{"Design review"}
    PLAN["Claude<br/>plan"]
    PLAN_REVIEW{"Plan review"}
    A1["Agent 1 (+TDD)"]
    A2["Agent 2 (+TDD)"]
    A7["Agents 3-7 (+TDD)"]
    OUTPUT_REVIEW{"Output review"}
    SHIP(["Ship"])

    YOU -->|"/brainstorming"| BRAINSTORM
    BRAINSTORM -->|"design Qs"| DESIGN_CHOICES
    DESIGN_CHOICES -->|"answers"| 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 -->|"approved + parallelized"| YOU
    YOU -->|"lane 1"| A1
    YOU -->|"lane 2"| A2
    YOU -->|"lanes 3-7"| A7
    A1 -->|"code"| OUTPUT_REVIEW
    A2 -->|"code"| OUTPUT_REVIEW
    A7 -->|"code"| OUTPUT_REVIEW
    OUTPUT_REVIEW -->|"bugs / next prompt"| YOU
    OUTPUT_REVIEW -->|"looks done"| SHIP

The board

ProblemStatus
Post-impl churn (PIC)🟨 smaller again — real plans + TDD
Coordination🟥 still me
Context fragmentation🟨 held by the plan, in-cycle (fragile)
Edit collisions🟨 held by the plan, in-cycle (fragile)
Mid-flight visibility🟥 still tricky at scale
Token burn🟥 still brutal

Next: Part 2 — Parallelize Reads, Not Writes

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

The combined list of references and supplementary reading lives in the complete edition.

Image credits:

  • The “Dunning-Kruger Curve” (Stage 3) — figure generated by the author; free to reuse.
  • Switchboard operators (Stage 3) — Fort Richardson telephone operators connecting calls, 1950; photographed and released by David Bedard, U.S. Army (Joint Base Elmendorf-Richardson, 2012), via Wikimedia Commons; public domain.
  • Bridge that doesn’t meet (Stage 3) — widely circulated “bridge fail” meme; original source/author unknown. Used pending any rights claim and removed on request.

Footnotes

  1. Yes, that one — the one where the internet was absolutely cooked for the entire duration.

  2. Well, a better description would be a multiplayer version of the online game Human or Not.

  3. Launched my SF tech-bros poker arc, but that’s a story for another time.

  4. In fact, my first commit on that project was indeed a real-time chat websocket module in Python.

  5. The cartoon “Mount Stupid” curve isn’t from the original Kruger & Dunning (1999) paper, which only showed low performers overestimating — not the full rise-then-fall arc. But it isn’t pure meme either: Dunning’s own later work with Sanchez (2018, 2020) found a “beginner’s bubble” where confidence outruns competence early in learning, and Lackner et al. (2023) found overconfidence peaking at intermediate knowledge. So confidently writing the curve off as fake would itself be a nice little Mount Stupid moment — as, quite possibly, is this footnote.

  6. Note Fred Brooks’s The Mythical Man-Month is widely considered to be the “Bible of Software Engineering” since “everyone quotes it, some people read it, and few go by it.” Oh the irony of being the guy lifting this part straight out of Wikipedia. Anyways, within the book, Brooks states that communication paths grow quadratically with respect to the number of participants, or, to be more exact, n(n1)n(n-1) (if only I could do a double footnote, but we’re using directed edges to count instead of undirected edges as in the source material). My agents never talked to each other, so all that quadratic coordination demand funneled through one human channel instead: sixteen agents, one serial me. Theoretically speaking, if one were to just treat me like a node surrounded on all sides by the nn agents represented as nodes, the number of (directed) edges would actually only be 2n2n; looking closer with more critical thinking, it turns out this model abstracted too much and neglected the fact that relaying (going to the sender, copying, looking up where the recipient is, going there, and then pasting) takes a nonzero amount of time, and since each agent is in a different place, I still have n(n1)n(n-1) possible routes for me to traverse! Even if we managed to reduce the travel time to near zero, I am still limited by the fact that I can only relay messages to one recipient at a time, which makes it strictly worse than just having the agents talk to each other, in terms of communication efficiency that is.

  7. Note Conway’s Law: a system’s interfaces mirror the communication structure that built it. Two agents that never talked shipped two incompatible halves of one contract — an org-chart fracture rendered in code.

  8. Caution We’ve since moved away from a singular local checkout — Superpowers now gives each cycle its own implementation worktree. That’s per cycle, not per agent, though: the parallel agents inside a single cycle still shared that one worktree, so intra-cycle collisions were still only held off by prose.

  9. This is basically the idea behind what Anthropic formalized as Agent Skills back in late 2025, popularized by Vercel in late January 2026 by launching the skills CLI tool. For those who (somehow) don’t know what this is, a skill is just a directory of plain-text instructions and metadata (a SKILL.md file).