Almost every “AI coding” demo shows you the same thing: one agent, in one chat, editing one file at a time while you watch. That is fine for a snippet. It is the wrong shape for a feature.
baro takes a goal, plans it into a dependency graph of small stories, and runs a fleet of coding agents in parallel — a dozen or more at once — over the same repository. The interesting engineering isn’t the agents. Agents are becoming a commodity. The interesting part is everything around them: how do you let twelve agents write code on one repo at the same time without them overwriting each other, fighting over the working tree, or shipping a tangle of half-merged changes?
Here is how baro does it — and two production bugs that taught us the gap between “works on my machine” and “works in the cloud.”
Before any code is written, an architect turns your goal into a set of stories — small, independently shippable units of work — and a dependency graph between them. “Add a Vitest setup” comes before “write tests for the parser.” “Test the parser” and “test the formatter” depend on nothing in common, so they can run side by side.
That graph is the whole game. baro walks it in topological levels: everything with no unmet dependency runs at once, the next level unlocks as its dependencies land, and so on. On a typical run that means three to twenty agents executing in parallel, each on its own story, with the graph deciding who waits for whom.
The naïve way to run parallel agents is to point them all at the same working directory. Don’t. Two agents running npm install and editing src/index.ts in the same folder will race each other into a corrupted mess.
Instead, every story gets its own git worktree — a real, separate checkout backed by the same .git, branched off the run-branch HEAD:
git worktree add -b baro-wt/<run>/<story> <dir> HEADEach agent commits in total isolation in its own directory. It cannot see, or stomp on, another story’s uncommitted work. When a story passes, its branch is merged back onto the run branch:
git merge --no-ff -m "baro: merge story S4" baro-wt/<run>/S4Most merges are trivial because the DAG kept genuinely independent work apart. When two stories do touch the same lines, the first merge attempt fails, baro aborts it cleanly, and retries favouring the merging story:
git merge --no-ff -X theirs -m "baro: merge story S4" baro-wt/<run>/S4If even that can’t resolve it, baro refuses to fabricate a merge — it aborts, leaves the run branch clean, and preserves the story’s branch so the work is recoverable rather than silently dropped. The guiding rule: never lose an agent’s work, and never commit a mess.
Worktree isolation works beautifully for git-tracked files. Then we shipped the cloud tier, where each run starts from a fresh clone, and a test-writing run started failing with a maddening error:
bash exited with status 1: Command failed: npx vitest run
fatal: not a git repositoryHere is what was happening. A setup story would run npm install to add the test runner. The dependent stories — which the DAG correctly made wait for it — then branched off the run branch and ran vitest… and couldn’t find it.
The reason is subtle and, once you see it, obvious: a git worktree only checks out tracked files. node_modules is gitignored. So the setup story’s install landed in its worktree, the merge-back carried the package.json change over git — but not the installed packages — and every dependent worktree got a package.json that lists vitest and a node_modules that doesn’t contain it.
It had worked locally for months. Of course it had — locally everyone shares one working directory, so the first install populated the one node_modules and everybody saw it. The isolation we added for correctness had quietly removed the accidental shared state we were relying on.
The fix is to make dependency directories genuinely shared again — on any stack, not just npm. baro discovers package roots by their manifests (package.json, pyproject.toml, composer.json…) at any depth, including monorepo subpackages like frontend/, and symlinks each worktree’s install dir to a single shared location:
# every worktree's node_modules → one shared dir
worktree/frontend/node_modules -> shared/frontend/node_modules
worktree/.venv -> shared/.venvNow whoever installs first writes through the symlink into one place, and every other agent sees it — exactly like the old shared workspace, but without giving up worktree isolation for the code itself. (We were also careful that those symlinked dirs never get committed back, at any depth.)
A colleague pinged us: “I did two or three runs and now they’re all gone.” His credits had been charged — the work had clearly happened — but the run records were nowhere. Worse, it wasn’t just him. Every recent cloud run was missing, while a handful of old ones survived.
Three things had stacked up. Run records were persisted on a throttled timer (fine — persistence is for history, not the hot path), there was no flush on shutdown, and every deploy restarts the control plane with a SIGTERM. So any run sitting in the throttle buffer at deploy time was dropped. That alone was bad.
But the real culprit was one line of log we almost scrolled past:
[dynamo] Error: Pass options.removeUndefinedValues=true
to remove undefined values from map/array/set.A run record has a lot of optional fields — repo, githubToken, finishedAt, prUrl, diff — that are undefined until they’re set. The DynamoDB document client, by default, throws when you ask it to marshal an undefined. So the write threw, the error was swallowed by a fire-and-forget call, and the record only ever lived in memory — until the next restart erased it. The old runs survived because they predated the fields that introduced the undefineds.
The fix was three layers, smallest first:
removeUndefinedValues so the write actually succeeds.SIGTERM, so a deploy can never drop a just-finished run.The lesson we keep relearning: in a distributed system, a swallowed error on a fire-and-forget write is indistinguishable from success, right up until a restart proves otherwise.
The same worktree machinery gives us a feature we like a lot. You can point baro at any public repo with no GitHub connection at all: it clones read-only, drops the origin remote so the agents never try to push, runs the full fleet in an isolated sandbox, and returns a git diff you can read — and download as a .patch — instead of a pull request. See what baro would do to a codebase before you give it a single token.
Try a sample run → — one click, no setup, see the diff in about a minute.
If there is one thing to take from this, it’s that the model doing the typing is the least differentiated piece of a multi-agent coding system. Swap it tomorrow. What actually decides whether parallel agents help or hurt is the orchestration around them: a dependency graph that parallelizes the right work, isolation that lets them run without colliding, dependency sharing that survives that isolation, a merge strategy that never loses work, and durability that survives a deploy.
That is the part we’ve been building. If you want to watch a fleet of agents fan out across a repo and hand you back a pull request — or just a diff — you can try it at baro.rs. Thanks for reading. 🙏
Different is better than better.
We're organizing hackathon
We're organizing a hackathon — compete using the Mozaik framework.