This post is for you if:

  • You split big features into PRs, and design problems keep showing up after the early PRs are approved

  • You've tried building everything first and ended up with one giant PR nobody wanted to review

  • You run several coding agent sessions on one feature, and they drift apart on shared interfaces

  • Your agents rebuild everything on every change and flood shared CI

  • You want agents doing useful work overnight, not just while you watch

What it covers:

  • Building and validating the whole feature end to end before the first PR goes out

  • Splitting it into a stack of small PRs that each get a focused review

  • Coordinating about seven agent sessions with one coordinator, a versioned contract and a shared test environment

  • Keeping builds local and narrow, and never claiming "validated" from a stale build

  • Walking into the design review with evidence instead of a plan

If your features usually fit in one or two PRs, most of this is more process than you need.

You break a big feature into pull requests. PR 1 goes out, reviewers spend real time on it and approve it. A couple of weeks later you are working on PR 4 and something doesn't fit. Maybe the interface between two components doesn't hold up. Maybe a limit you promised can't be enforced where the design said it would be. Now you are changing a design that three people already signed off on.

I have been on both sides of this, as the author and as the reviewer. Nobody did anything wrong. We just asked people to review a design that nobody had run yet.

For a long time that was a fair trade-off. Building the whole feature before review meant weeks of work that might get thrown away, so small incremental PRs were the safe choice. With coding agents, that cost has moved. Writing code and running tests is cheap now. Finding a design problem in PR 4 is just as expensive as it always was.

The obvious fix is to build the whole thing first and open one giant PR. That solves one problem and creates another. The design has been run, but the PR touches every repo and nobody wants to review it. Reviewers put it off, or skim it and approve. It also makes them nervous, because it's hard to trust a change you can't really read.

So on my last feature I tried to get both. It touched several repos: a control-plane service, a Spark compute component, a public API and its spec, and the deployment wiring. Before the first PR went out for review, all of it was built, wired together and running end to end. But reviewers never saw one big diff. They got a stack of small PRs, each one a slice of something that already worked.

❝

Agent time is cheap. Reviewer time is not. Spend the first one to save the second.

What changed

Before agents, writing the code was the expensive part. You validated the design while it was being reviewed and accepted that some rework would show up late.

Now an agent can get a working first version up in hours and keep iterating on failing tests through the night. When the design changes, you regenerate against the new interface instead of rewriting by hand. What hasn't changed is how much attention a reviewer can give you. (I wrote about that side in Code Review in the Age of AI.)

Doing this with agents brings its own problems:

  • One agent session can't hold a feature that spans five repos. The context gets too big and earlier decisions quietly get lost.

  • When two agents build the two sides of an interface, they drift. Each makes a slightly different assumption, and both are "correct" on their own.

  • Agents don't mind rebuilding everything on every small change. Your shared CI and your teammates do.

  • An agent working overnight is only useful if it can reach an environment to test in.

  • It is very easy to write "validated" or "enforced" in a PR description when it isn't quite true.

How I set up the agents

I ended up with about seven agent sessions running in parallel. One was the coordinator. Each of the others owned one area (the service, the compute component, the API, the spec), and a couple only deployed and checked things in a dev environment.

The coding tools help more than they used to, though each does it differently. Claude Code and Codex can both spawn subagents that work in their own context and report back to the main session. Claude Code also has an experimental agent teams mode, where a lead session coordinates teammates that message each other. VS Code with Copilot lists all your agent sessions in one place. Whichever tool you use, the idea is the same: each child carries only the context for its own area, and the coordinator carries the decisions that cross areas. No single session has to hold all five repos.

A few rules made this work.

Only the coordinator changes shared things. The design doc, the interface contract and the overall status belong to the coordinator. Child sessions can propose a change, but the coordinator decides and tells whoever needs to know. Otherwise you get two sessions editing the same doc with two different ideas of what was agreed.

Only send a message if it changes something. Agents love sending each other status updates, and most are noise. My test: would the other session do anything differently after reading this? If not, don't send it.

Write the contract down and version it. This turned out to be one of the most useful habits on the project. The two sides of an interface don't negotiate in chat. They both work against a written contract with a revision number.

A real example: both sides of an interface were checking the same version number, with a limit tied to it. We decided the compute side should own that version pin. That moved the contract from Rev 1 to Rev 2 and made the service-side check and its limit dead code. Dead checks like that hang around in docs and PR descriptions for months, so I asked the coordinator to clean it up properly:

Prompt (to coordinator): "Compute now owns the version pin. Amend the contract
and tell the sessions that are affected."

AI Agent Actions:
1. Moves the contract from Rev 1 to Rev 2 and adds a change log entry
2. Marks the old check and its limit as retired
3. Updates the design doc to match
4. Messages only the service and compute sessions (API and spec aren't affected)
5. Each of them removes the dead check in its own PR

The 4S approach

Looking back, what I did falls into four steps. I call it 4S: Split, Scope, Soak, Show.

  • Split: break the feature into a stack of PRs that can each be reviewed on their own

  • Scope: keep builds local and narrow, so you aren't flooding CI

  • Soak: run the whole feature end to end, again and again, before anyone reviews it

  • Show: present the design with evidence instead of promises

The first three go round in a loop. Show happens once, when the loop is green.

Split: stack the PRs

This is the step that keeps "validate everything first" from turning into one giant PR. Validating the whole feature at once doesn't mean asking anyone to review it all at once.

A stacked PR is a chain of pull requests where each one is based on the one below it instead of on main. Each layer is small enough to review by itself, but you build and test the whole stack together.

I didn't use any special stacking tool on this project. I found out later that GitHub now has stacked PRs built in, and it looks like it would simplify this further: each PR shows where it sits in the stack, and GitHub can rebase and retarget the layers for you.

I sliced along contract boundaries, starting with the layer everything else depends on. The stack looked roughly like this:

Layer

What's in it

1

Contracts, models and validation

2

Core service logic

3

Compute engine changes

4

Public API

5

Deployment and configuration

6

End-to-end tests and docs

What I learned:

  • Every layer should build and pass its tests on its own. A reviewer should be able to approve layer 2 without reading layer 5.

  • Put a stack map in every PR description: which layer this is, what sits above and below it, and a link to the design doc.

  • I had an agent rebase the whole stack onto main twice a day. Rebasing every day is boring. Rebasing after a week is a project.

Scope: build less, push less

Seven agent sessions working around the clock can easily overwhelm a shared CI system. That's unfair to everyone else on the team, and slow for you too. So I kept the inner loop local and narrow, and only went to CI on purpose.

What agents do by default

What I asked them to do instead

Rebuild the full solution on every edit

Build only what changed, and run only the related tests

Push after every small fix

Batch commits and push a meaningful chunk of work

Trust a quick compile

Run the full local validation, including packaging, before pushing

"It passed yesterday"

Validate again on a build that has the latest main in it

The last row matters more than it looks. It's easy to deploy a build made before someone else's change landed on main, and then report it as validated. Now there is a simple gate before any deploy:

git fetch origin
git merge-base --is-ancestor origin/main <build-commit> && echo "fresh" || echo "stale: rebase and rebuild"

One more thing that helped: the Spark jar was 187 MB, and most of that was libraries the Spark runtime already provides. Marking those as provided brought it down to 43 MB. Every build, upload and deploy after that was faster, and we did a lot of them.

Soak: let it run overnight

This is where cheap agent time pays off. Before any person reviews anything, the agents run the full feature end to end, fix what breaks, and run it again. (I covered the general shape of these loops in Stop Prompting Your Agent. Design the Loop.)

The overnight loop looked roughly like this:

Prompt (scheduled): "Rebase the stack, build, deploy to the dev environment,
run the end-to-end scenarios, and fix failures in the layer they belong to."

AI Agent Actions:
1. Rebases each layer onto the latest main
2. Builds and tests each layer
3. Checks that the build has the latest main and no other session is using
   the environment, then deploys
4. Runs the end-to-end scenarios
5. If something fails, finds the right layer, fixes it there, and runs again
6. Posts a short status, but only if something changed

A few guardrails kept this from going sideways:

  • Nothing deploys from a stale build. The freshness check runs every time.

  • Before I finalized a plan or a PR, a second agent gave it one rubber-duck review. One good pass catches the real problems; more than that and you're mostly paying for opinions.

  • A small personal automation posted status to my notes chat. It stays quiet when nothing has changed, and posts at most every 30 minutes when something has.

  • I caught myself calling things blocked when they were just waiting on a build. That made the status sound a lot worse than it was, so now I say "in progress" instead.

The environment problem

This was the hardest part. An agent running at 2 AM can only validate against an environment it can reach. We used a shared pre-production environment. It has real infrastructure, but other people use it too, so it's slower and you have to be careful.

It was also shared between my own agent sessions. If two sessions deploy at the same time, they overwrite each other's builds and neither run tells you anything. So the sessions checked with each other before deploying, and only one run used the environment at a time. It was slower, but every result meant something.

What I really want is a local environment that runs the whole platform. Someone on my team is building exactly that. Anything that depends on real infrastructure (identity, networking, managed services) will still need a real environment, so write your end-to-end tests to run locally as much as possible and keep the infrastructure-dependent part small.

Settle the scary questions with evidence

Soak is also a good time to close questions that would otherwise turn into long meeting debates.

The compute job needed a short-lived credential. The question was where to put it so the job could always get it, but it would never show up in logs or event history. Instead of arguing about it, I had an agent run a short spike with the approach we picked, then search the job's event logs for the token. It found zero occurrences. That number went into the design doc, and the approach was approved on that basis.

Show: walk in with evidence

By the time I published PR 1 and set up the design review, I wasn't presenting a plan. I was presenting something that already ran. This is what I brought:

What I brought

The question it answers

Design doc, Part I: requirements, design, gaps and challenges, no code

What are we building and why?

Design doc, Part II: implementation details

How does it actually work?

Contract history (Rev 1 → Rev 2)

What changed, and who agreed to it?

Limits register (L1 to L10)

What are the hard limits, and are they really enforced?

End-to-end run results

Does it work?

Log search results for the credential

Is it safe?

Known gaps, each with a plan

What isn't done yet?

Part I is for most of the room. Most people in a design review don't need class names. They need to know what problem we're solving, how, and what's still open. I kept Part I free of code and updated the doc every time a decision changed. The agents read the doc too, so an out-of-date doc leads straight to wrong code.

The limits register keeps you honest. Every hard limit (counts, payload sizes, timeouts) got a number, L1 to L10, listed in the design doc and in every PR that touches it, marked enforced, pending or retired. Once I caught myself marking a limit as enforced when the code didn't check it. Now my rule is simple: if I can't point to the line of code that enforces it, it's pending.

The night before a review, it's also worth asking an agent to play a skeptical senior reviewer: list the hardest questions and point to the evidence for each. The ones it can't answer go in the doc as known gaps, before the meeting instead of during it.

❝

I didn't feel confident in that review because I had practiced. I felt confident because I had already watched it run.

What I would do differently

  • Keep the stack shallow. Around six layers was fine; ten would have turned every rebase into a negotiation.

  • I'd lock the contract earlier. A lot of the rework came from contract changes, and a few more days on the contract before parallel work would have saved a lot of overnight loops.

  • Push harder for a local environment. Waiting on the shared one was one of the bigger slowdowns.

  • Warn reviewers before the stack lands. A pile of PRs showing up out of nowhere is a lot to take in, even when all of it has been validated. Share the stack map and the design doc first.

Try it on your next feature

You don't need seven agents to start. Pick your next feature that needs more than one PR and try a few of these:

  • Have one session own the design doc, the contract and the status, and give each child session only its own context

  • Version the interface contract before parallel work starts

  • Split the work into stacked PRs along contract boundaries, each one building on its own

  • Keep builds local and targeted, and never claim validation from a build without the latest main

  • Get the whole thing passing end to end before PR 1 goes out

Agents didn't make design reviews any less important for me. They moved the surprises to before the review instead of after it, without handing reviewers one giant PR to get there. For a big feature, that alone was worth the setup.

References

-Sanjai Ganesh