My first agent setup had one obvious win and one tension I had to work out how to handle. The win was speed. The agents got through a lot of work, fast. The tension was that a lot of work fast is exactly the thing that’s hard to review well. The changes were coming in quicker than my old habit of reading every diff carefully could keep up with. Speed felt like progress, but the two aren’t the same thing, and I could see where it led if I let review quality drop to match the pace: a codebase that drifts, one reasonable-looking change at a time.

Because the changes that rot a codebase don’t announce themselves. A slightly wrong abstraction here, a dependency that didn’t need to exist there, a test that checks the implementation instead of the behaviour. Each one is fine on its own. Together they’re a codebase I’d have to live in. Being the only developer raised the stakes rather than lowering them: there’s no second reviewer to catch what I miss, and no one but me accountable for what the code becomes. So rather than accept the drift, I did the thing I’d been doing the whole time anyway, which was to keep changing how I worked. Keep what held quality up, drop what didn’t, and adjust as the throughput climbed. The pipeline below is what that iteration converged on.

So the thing I actually needed wasn’t faster agents. It was work shaped so I could review it properly. And later, when I wanted the speed back by running agents in parallel, it had to be work that didn’t collide. The rest of this is how I got both.

Making changes small enough to review

The fix for the review problem is unglamorous: stop handing an agent a big, loosely defined task. Two skills sit in front of the coding agent and do that shaping before a line of code gets written.

The AI PM and the PRD skill

Two separate things sit at the front of the pipeline, doing different jobs.

The AI PM isn’t a skill, it’s a document. I keep a context.md that holds everything product-related: the thesis, the north-star metric, the user segments, the product pillars, every PRD and its current status, and the current priority order. I talk to it directly, and it’s written to behave like a product partner rather than a passive assistant, so it has opinions, it sequences, and it pushes back when something is off-strategy. “What should I build next” is a conversation with that file, not a guess.

It’s also where prioritisation lives. With a stack of drafted PRDs sitting there, it tells me what comes first and flags where they depend on each other. That matters more than it sounds, because the dependencies between PRDs are usually the same dependencies as between the modules they touch, which is what tells me what can run in parallel later and what has to be sequenced. Because it’s the source of truth, I keep it current as features ship and priorities move, rather than letting a stale list drive decisions.

The PRD skill is the separate piece. Once the AI PM and I have settled on what’s next, the PRD skill writes the actual PRD for that feature, usually over a bit of back and forth. Keeping “decide what we’re building” (the AI PM) apart from “spec it” (the PRD skill) and apart again from “build it” (the coding agent) is deliberate. Each step is a checkpoint, and this is where the quality of everything downstream gets set.

What the PRD is really doing is bounding the work, and that’s easiest to see in a real one. Here’s the spec for sharing a lift video, the biggest organic growth lever on my roadmap. It tiers the requirements so the must-haves are separated from the rest: a share button, a branded video with the lift overlaid, the system share sheet, and an image-card fallback when there’s no video are P0; Instagram Stories, a deep link back into the app, and overlay customisation are P1; templates and view-tracking are P2.

The more useful half is what it refuses to do. The spec is explicit that there’s no in-app feed, no share-analytics dashboard, no video editing beyond the overlay, and no sharing from anywhere but the lift detail screen. Every line in that out-of-scope list is something the coding agent now won’t drift off and build. And it leaves the genuinely undecided calls open rather than guessing at them: whether the overlay carries the lifter’s name, what the web fallback page should be, whether share-to-install attribution is worth the effort.

None of this lands in one shot. A PRD is the product of a long back-and-forth: the skill drafts, I push on scope and sequencing, it revises, and we go several rounds before it’s something I’d hand over. The conversation is where the thinking happens.

And it gets one more pass right before it’s built, which is the step that’s easy to skip and shouldn’t be. A PRD written a few weeks ago can be out of date by the time I execute it, because other features have shipped since. So before it goes to the execute skill I re-review it against the current app: what it assumes may have changed, it might overlap with something that landed in the meantime, the dependencies it names might have moved. A plan rots if you don’t reconcile it with what’s actually been built.

The execute skill

This is the piece that actually fixed the original problem. A PRD is still too big to hand straight to an agent. My execute skill takes the PRD and breaks it into an implementation doc: smaller tasks the agent can carry out one at a time. The agent follows that doc, not the PRD.

The size falls out of this on its own. Each task maps to a small commit. A feature ends up as several small PRs instead of one huge one. And small units are what make review possible again, because reviewing a tight, single-purpose diff is a different job from skimming a thousand-line PR and hoping. The agent’s mistakes are easier to catch when each change does one thing, and “looks fine” stops being a gamble.

The implementation doc isn’t fixed, though, and treating it as the final word would miss the point. Each phase can change in light of the ones before it. Once a commit lands and I’ve actually read it, what it taught me feeds back into the later phases, so the plan gets revised as the real code appears rather than being followed off a cliff. That’s deliberate. The point of all this was never to automate myself out of the loop. It’s the opposite: breaking the work into phases I review one at a time is what gives me insight into what’s going on and the confidence that I understand the code, instead of waving through a batch I never really read.

To make that concrete, here’s how one shipped feature, a private friends leaderboard, broke down. Seven phases, each compiling on its own and landing as a single PR:

  1. Database: the schema and the supporting RPCs.
  2. Domain: swap the old scope filter for a leaderboard mode and rename a field, with the existing board still working.
  3. Domain: the table types and repository protocol, pure contracts with no implementation yet.
  4. Data: the Supabase repository and mappers, with the package’s unit tests.
  5. Presentation: the view models, with their tests.
  6. UI: the table picker, the create and join sheets, the settings screen.
  7. Flow and integration: routing, the factory, and wiring the real repository into the app.

The order isn’t arbitrary. Each phase only depends on the ones above it, so the build never goes red between PRs. And the phases that add real logic carry their tests in the same PR, so a change and the proof it works land together rather than tests being something I promise to get back to.

So the flow is: an idea, decided and sequenced against the AI PM, then a PRD from the PRD skill, then an implementation doc from the execute skill, then small tasks, small commits, and usually a handful of PRs.

The second problem: parallel agents collide

Once changes were small and reviewable, I wanted the speed back, this time without giving up the review discipline. Running several agents at once is the obvious way to get it, and the naive version of that falls apart quickly for mundane reasons:

  • Two agents edit the same file and produce changes that conflict.
  • One agent assumes a shape for code another agent hasn’t finished writing.
  • Scope leaks. An agent refactors something outside its task, and now two PRs touch the same code.

None of these are prompting problems. Two processes writing to the same place with no shared contract will clash however good the prompt is. It’s a structural problem, so it needs a structural answer.

Lanes are packages

Each unit of parallel work is a lane, and a lane is nearly always a package. A package exposes a public interface and hides its implementation; other modules depend on the interface, not the internals. That’s the same boundary I’d been drawing to stop human teams clashing, and it does the same job for agents.

Two things have to hold for an agent to work without colliding. It needs filesystem isolation, so its edits don’t physically overlap with another agent’s, and that’s what the worktree gives you. It also needs logical isolation, so its assumptions don’t overlap, and that’s the package boundary.

Worktrees on their own aren’t enough. You can give two agents separate working directories and still have them both reach into the same shared module and produce changes that fight when you merge. The package boundary is what makes the isolation real rather than cosmetic. Setting up the worktree is the easy half; the module design is the half that actually does the work.

Not everything sits cleanly inside one package, so there’s also an integration lane. That’s where the cross-cutting work and the wiring between modules happens, run deliberately and usually not at the same time as the lanes it depends on.

Running the lanes

At this point the parallelism is safe to use. Each lane is a package in its own worktree, working through its own implementation doc. Because the boundaries are real, the agents don’t collide, and the integration lane handles the seams once the lanes land.

There’s an iOS-specific wrinkle here that you won’t hit in most languages. Xcode doesn’t cope well with the same project being opened from more than one place at once, so creating a worktree also clones the project rather than just pointing at the shared one. Each lane gets its own copy that Xcode can open on its own, which keeps the parallel work from tripping over Xcode itself. Once the worktree is deleted, so is the cloned project.

I don’t hand-roll a new lane. A scaffold skill creates the new SPM package for me, laid out like every other package: same structure, same boundaries, same conventions. That consistency is part of what keeps the isolation holding, because every lane looks like every other lane, and an agent dropped into one already knows where things go.

A real example of this running three ways at once. I was iterating on the leaderboard to add a deep link into the app, which needed some different data behind it. The integration lane handled the deep-link wiring in the main app, while I took over the leaderboard lane myself to make the UI changes. At the same time, a separate lane was adding the ability to view other users’ profiles, in its own module, untouched by either of the other two. Three pieces of work in flight, three boundaries, no collisions, and one of the three workers was me rather than an agent, which the model handles just as happily.

Does the planning layer earn its place?

The fair challenge: the PRD skill and the execute skill add steps. Do they produce better code than prompting a capable agent directly, or do they just make me feel organised?

My honest answer is yes, and the clearest place I see it is the UI. When UI work rode along inside a much larger PR, design details slipped through, not because I didn’t care but because the PR was too big to review at that level. A spacing that was slightly off, a colour that wasn’t quite the token, a component that didn’t match the design I had in mind. Now the UI lands as its own PR with a single purpose, so I review it as UI, against the design I expected, and I spend more time getting it right because that’s the only thing in front of me. The screens come out cleaner and closer to what I intended, and that’s a direct result of each PR doing one thing.

The reason I think it works is that the decomposition forces scope to be settled before the agent starts, which is when scope is cheap to change. Prompting directly pushes that decision into the diff, where it’s expensive, and the diff was the thing I couldn’t review properly in the first place.

Where it breaks

It has clear failure modes:

  • The integration lane is a shared chokepoint. Here only one feature needed to touch it, which was fine, but push several through it at once and it gets painful fast. As a one-person team I can hold the whole picture and see where the conflicts will sit; on a multi-person team, several people contending for the same lane would be a real headache rather than a manageable one.
  • The AI PM over-specs. Left alone it invents scope and gold-plates, so editing the PRD isn’t optional.
  • I don’t hand everything to an agent. The UI lanes I tend to take myself, because the design call is mine and it’s the place I’m least willing to let “looks fine” through.

The takeaway

The whole thing comes down to one trade I didn’t expect to be making: I gave up raw speed and got it back as something better. Small changes made the work reviewable, clean boundaries made it parallel-safe, and both of those are the same discipline wearing different clothes. Agents didn’t make architecture matter less, they raised the premium on it. The cleaner the boundaries, the more agents I can run before it turns to mud, and the smaller the changes, the more of that work I can actually trust.