Some mornings I watch a pull request move through our whole pipeline before I’ve finished my coffee. An agent opens it. A second agent reviews it. By the time I sit down, the only thing still running is the test suite. That gap, the minutes between “reviewed” and “safe to merge,” has become the slowest part of how we ship.
This is the third post in a series about how Chris and I build Vycari. The first was about the issue tracker and what belongs in it once agents do the work. The second was about the pipeline that works through the tracker and never merges its own pull requests. This one is about what those pull requests wait on, and how the answer changed the shape of the code.
It didn’t used to be the tests. Plenty of us optimized CI over the years, but we optimized it by tiering it. A few minutes of unit tests at your desk before you pushed. A longer run in CI once you did. A slow integration suite that went off every few hours, or overnight. When it came back red, you bisected your way to whichever pull request broke it. That held up because a human was going to review the change anyway, and that took an hour or two at best.
Agents changed that arithmetic. One agent opens a pull request. Greptile reviews it in a couple of minutes. Human and machine are both waiting on tests now. If the full suite takes thirteen minutes, that’s thirteen minutes with nothing else in the way. Multiply that by the pull requests moving through Pepper on a given day and you need a merge queue to keep them from stepping on each other. I’ve finished a change by hand, pushed it, and found myself forty minutes behind a queue of agent pull requests, each one blocked on the same thirteen minutes. Before agents I would never have set up a merge queue for a two-person project. The idea would have been funny. It’s now part of my standard repository setup.
The tiering trick can’t save us either. We still run a slow suite overnight, but it can’t stand between a bug and production, because we ship on green, or near enough: every merge builds an image, and production picks up whatever is current on a fifteen-minute cycle. Call it half an hour from merge to running, and that’s the whole window I have to find out something is wrong. A suite reporting at midnight would be telling me about a bug I shipped at nine that morning, with a few hundred agent pull requests stacked on top of it. Twenty minutes of CI used to be unremarkable. It feels like an eternity now, because everything around it got faster and it didn’t.
The Repository Gets Harder to Hold
Making the suite faster buys time without touching the deeper problem: the repository keeps getting harder to reason about as it grows. A human engineer joining a large codebase builds intuition for its boundaries over weeks. They learn which corners are scary and which are safe to touch without asking anyone. An agent doesn’t get that runway. It arrives cold on every task, reading whatever context you’ve given it and inferring the rest from the code next to it. The bigger the repository, the more code there is to infer from, and the easier it is to miss a contract something else depends on.
I tried to buy that intuition back with context files: hierarchical instructions at every level of the tree, skills describing the traps in one corner of the code. All of it helps, and none of it is free. Every context file is one more thing to write and keep current. Even a well-oriented agent has to hold the whole shape of the repository in its head to change anything that crosses a boundary.
None of this means monorepos are a mistake. They’re a fine pattern for people who already know where the walls are. The problem is narrower: a monorepo is a bad pattern for a team where most of the work gets done by something that relearns the walls on every task.
Pulling the Slice Out
The fix I’ve settled on is structural. When a piece of functionality is clean enough to stand on its own, I pull it out into its own repository before it tangles into everything else. It gets its own tests, its own merge queue, its own release cadence. An agent working inside it can hold the whole thing in its head.
We already have a pattern for this, and it’s older than any of the AI tooling. Amazon has run this way since Jeff Bezos mandated that every team expose its data through service interfaces, with no direct linking and no reading another team’s data store. Steve Yegge described that mandate in 2011, and the detail everyone remembers is that non-compliance meant termination. Microservices came out of an organizational problem before they were a technical one: teams that couldn’t see inside each other needed real contracts. I’m solving that problem, except the team members who can’t see inside each other are models.
The first slice I pulled out was groups, the part of Pepper that handles shared context between people. Working on groups was much faster than working on Pepper itself, and the reason was almost embarrassingly simple. The test suite was only testing the group stuff. Nothing about calendars, nothing about the agent loop, nothing about the web client. A pull request in groups clears CI in about two minutes, against thirteen for the same work in the monorepo, and it has held at two minutes as the code grew. The old number survives in one place: a push to main, where the fuller job set builds the image and can take fifteen, after the merge instead of in front of it.
I don’t have a rule yet for when a slice is ready to leave the monorepo. Right now I go by feel: one clear job, one owner, and an API narrow enough to describe in a sentence. Pull it too early and you’ve built a service around something that still needs to change shape. Pull it too late and it’s already tangled into three other things. I’ve been wrong in both directions. And one slice out is not a track record. The first extraction is always the cleanest. Ask me again at six services, after the costs have compounded.
Anthropic ran into the same problem from the other direction and wrote up what agentic coding did to their CI. Their engineers ship eight times as much code per quarter as they used to, and Claude writes eighty percent of it. CI jobs grew twenty-five fold in six months on headcount that barely moved. Their answer is test impact analysis: keep the large codebase, and run only the tests a change could plausibly affect. If I had a team to maintain something like that, I’d build it. I don’t. Test selection is a system somebody has to own and keep correct, and at two people every system we own competes with the product. Splitting the repository buys most of the same speed by construction, and it fixes the context problem as well. At Anthropic’s size I would almost certainly choose the way they went.
There’s a principle underneath this. I want each of our Python codebases to look like every other open source Python codebase that exists. Not because I’m attached to convention for its own sake, but because a model has seen a million repositories shaped that way. It has seen almost none shaped like a sprawling internal monorepo with its own rules. A small, conventional repository is legible to an agent before it reads a single context file.
The Boundary Has to Be Real
Splitting only pays off if the interfaces are real. A boundary with a vague interface behind it is worse than no boundary. The coupling is still there and now you can’t see it. Google got this right early with protocol buffers: one schema everybody compiles against, changes that stay backward compatible, and no ambiguity about what a service accepts or returns. We’re doing a smaller version. Each service has a defined interface and the calls run over a fast internal Docker network. The interface is what we argue about in review, not the implementation behind it. For an agent working in one repository, that contract is the whole surface it has to respect.
So what catches the bugs that live between services, now that no single suite covers all of it? Three things, in descending order of respectability. The contract, which is why folding groups back into Pepper took a morning. A browser suite that runs overnight against the real application. And the fact that two of us live in the product all day, so anything broken between services turns up in our own use within hours. That last one is not a strategy. It’s what you get at two people with no customers, and it’s the first thing to break when either number changes.
Where this ends up, if I follow it through, is a web application in its own repository, the clients grouped with their browser tests, and a Pepper repository that is just the agent and its tools. That last part is the point. The thing that makes Pepper Pepper should be small enough that an agent can hold all of it at once.
The tradeoff shows up in cross-cutting work: features that used to be one pull request in the monorepo are now a change spread across several repositories. Three repositories means three pipelines and three cold starts, so total compute went up even as the wait in front of any one change came down. Most work happens inside a single repository, so I’m happy to pay it. My first answer was to check out every relevant repository into one parent directory and start an agent there, with all of them in view at once. That parent directory has since become a repository of its own.
One Tech Lead, Many Repositories
For anything that spans repositories, I start what I call my UTL agent, after the old Google title Uber Tech Lead. A UTL was senior and broad enough to operate across systems without owning any of them. Mine runs on Fable 5.1, the strongest model I have for this kind of judgment, though the role matters more than the model filling it.
The UTL lives in that parent directory, which is now a meta-repository. It checks out every other repository underneath it, carries the shared skills, and is growing an operations plugin that knows how to talk to production. The pipeline from the last post runs from there too, one pull request per repository per tick. Making that work meant a consistency pass across every repository: same CI, same branch protection, same conventions. If the plan is to keep splitting, creating a new repository has to be easy, or the friction becomes the reason you don’t.
The UTL almost never writes code. It’s instructed to delegate, and I’ve been strict about that, because the moment it starts editing files itself I’ve lost the thing I built it for. It’s my emissary to the agentic army. I talk to the UTL, the UTL talks to the subagents, and the subagents write the code.
What it has instead is a toolbox of specialized subagents covering the stages of our development cycle, and judgment about which to launch when. Opus for design. Sonnet, Luna, or Flash for implementation, depending on how much thinking the work needs. Opus again for responding to code review, because arguing with a reviewer about whether a finding is right takes more judgment than the code did. For work that spans repositories, the design pass produces a plan covering all of them. What changes where, in what order, and which pieces can move in parallel. Then the implementation agents go out, one per repository, each carrying a slice of that plan. What’s left is bookkeeping: tracking every pull request it spawned until each one lands, and knowing which are blocked on another repo’s change merging first.
What This Costs
None of this is free. Splitting a monorepo trades one kind of difficulty for another.
The first cost is in your own head. You have to decide which service a piece of work belongs in before starting it. A monorepo never charges you that tax. The second cost is reliability. Repositories that used to fail together as one deployable now fail independently. You have to know which parts of your system can take another part down with them, and build the ones that can’t afford it differently. We haven’t had that failure yet, so this cost is still theoretical for us. The third cost is coordination that never goes away. Shared libraries need update schedules, API contracts need versioning, and a change that used to be one pull request can turn into three, timed so none of them ships broken.
There’s a fourth cost. This is a two-person company that hasn’t launched yet, and repository architecture is not a feature. Somewhere in the middle of the split I asked Chris whether I was faffing around with infrastructure instead of building the product. His answer was that building on a bad foundation just eats you with low velocity, and the point of doing it now is that we never have to do it while customers are watching. He also pointed at something I hadn’t seen. When we hire, a bounded repository is the thing you can hand a new engineer whole. Here’s the interface, here’s everything it needs to do, go. That’s a much better first week than a tour of a monorepo’s cryptic corners.
I’ve paid all four, and I’d pay them again. The alternative, one repository that keeps absorbing everything, doesn’t remove that complexity. It hides it inside a single directory tree until an agent runs into it blind, which is a worse way to find out.
Where the Gate Sits Now
Code review used to be the thing everyone waited on. It was slow, and it was where a second set of eyes actually caught something, so everything else could afford to be slow too.
Agentic review took that bottleneck away almost entirely. A pull request gets opened and reviewed in the time it takes me to read the agent’s description and the parts of the diff I care about. We’re getting closer to letting the pipeline merge its own work, which would retire the rule I wrote about last time. What’s left waiting after that is the machinery underneath: the test suite, the merge queue, the deploy pipeline. Thirteen minutes doesn’t sound like a long time, until it’s the only thing standing between one agent finishing a task and the next one starting. Repeat that across every pull request a day produces, and thirteen minutes stops being a rounding error.
The other side of that machinery is where the split pays off. In a small repository, a feature can go from an idea to running in the product in about half an hour, and the only thing that half hour depends on is CI. In the monorepo, the same feature waited on tests for code it never touched.
That’s the argument for breaking the monorepo apart. The gating factor moved from human judgment to compute time, which is the one delay in this system that’s engineering-tractable. You can’t make a human review faster without asking less of them. You can make a test suite faster by shrinking what it has to know about, which is what a small repository does. I wrote in the first post about the tracker that decides what agents can touch. The repository boundary matters just as much, and it’s the one I spend most of my architecture time on now.
What’s Next
The next experiment follows from the way the UTL already works. If it can plan and delegate across repositories, it should be able to delegate the tech-lead role itself. The plan is to stand up a second, cheaper tech lead for one migration, run it to parity on its own repository, and have it escalate two things: decisions that need a human, and anything that touches production. If that works, Chris and I spend most of our engineering time on design documents rather than pull requests, and the design document becomes the real unit of work. Chris’s caveat, which I think is right, is that user experience is the part you can’t specify up front. That’s the honest limit on all of this, and probably a later post.