{"slug": "one-reviewer-per-layer-splitting-code-review-by-the-way-code-is-read", "title": "One reviewer per layer: splitting code review by the way code is read", "summary": "Addy Osmani highlighted how the Claude Code team restructured code review after merged changes in its monorepo rose from about 2,500 a day in May to more than 30,000 a day in September, with merged pull requests on the Claude Code repositories more than doubling between June and September while the share that had to be reverted fell by more than 40%. The team routes each change through six sequential layers — verification before the pull request, AI review on the pull request, a blocking security review, CI, risk-scaled human approval, and staged rollout with a rollback mechanism — rather than relying on a single reviewer. Osmani argues the AI review gate asks too much of one agent by having it read code at every level of abstraction at once, and proposes splitting review by what a reader must understand: intent, form, local meaning, wiring, and beyond.", "body_md": "Addy Osmani [shared an article](https://x.com/addyosmani/status/2114604912520921136) on how the Claude Code team reviews code now that Claude writes most of it. The numbers explain why they had to change. In their monorepo, merged changes went from about 2,500 a day in May to more than 30,000 a day in September. Nobody reads that line by line. On the Claude Code repositories, merged pull requests more than doubled between June and September, and the share that had to be reverted fell by more than 40%.\n\nThey did not stop reviewing. They layered it. A change passes six layers of checks, and a rollback mechanism sits behind the last one. Each layer catches some bugs and misses others. Few bugs get through all six.\n\nI like this system a lot. One thing about it made me want to write.\n\n## Their layers are stages\n\nThe six layers are points in time. Verification happens before the pull request, AI review on the pull request, then a blocking security review, then CI, then human approval scaled by risk, then merge, staged rollout, and revert. A change walks through them in order, like gates on a road.\n\nInside the AI review gate, one reviewer is asked to check that the change addresses the issue, that it fits into the larger system, and that it does not break other parts of the codebase. Several agents hunt in parallel and vote on what they find. But each of them is asked to read the code the whole way, from the characters on the line to the system around them.\n\nThat is a lot to ask of one reader. A human engineer does not hold product intent, service boundaries, null-check edge cases, and syntax rules in mind at the same moment. We zoom in and out. We read one function closely, then step back to see who calls it, then step back again to see who uses it. A single agent with everything in one context does not get to zoom. Every level of abstraction sits in the same window and competes for the same attention. Writing and reading code is not one discipline. It is several stacked on top of each other, and each one needs a different mental model.\n\n## Code has layers of meaning\n\nTake the oldest complaint about programming. Change one semicolon, one dot, one space, and the program breaks. Code demands exact form. That looks like the obvious first layer.\n\nIt is, but it splits in two. If the changed character stops the code from parsing or compiling, a compiler finds it in milliseconds, every time, for free. No agent should spend a token there. The Claude Code team says the same thing in its own words: when the reviewer keeps flagging an issue, fix it at the source with a lint rule.\n\nThe dangerous change is the one that still compiles. `<` instead of `<=`. `=` instead of `==`. `a.b` instead of `a?.b`. A missing `await`. The form is valid. The meaning moved. That bug does not live in the layer of form at all. It lives one layer up.\n\nSo the layers are not about where a character sits. They are about what you have to understand to judge it.\n\n## Six layers\n\n**Layer 0: Intent.** Should this change exist, and does it solve the problem it claims to solve? The Claude Code team keeps this for humans: deciding whether to build something at all. An agent can still check the narrower question, whether the diff matches its own description.\n\n**Layer 1: Form.** Syntax, types, formatting, naming, project conventions. Almost all of it belongs to deterministic tools: the compiler, the type checker, the linter, the formatter. An agent is useful only for conventions that no tool can express.\n\n**Layer 2: Local meaning.** What this piece of code does on its own. Does the function do what its name and contract promise? Edge cases, empty input, null, off-by-one, error paths, and what the function costs: a blocking call, a query inside a loop. The reader of this layer needs the function and its contract, and nothing else. It should never see the routing table or the product roadmap. Unit tests are its evidence.\n\n**Layer 3: Wiring.** How the piece fits into the system. Who calls it, what it calls, the shape of the data that crosses the boundary, API compatibility, database migrations, configuration. A function can be correct on its own and still break the assumption of every caller. This reader needs the boundary, not the body.\n\n**Layer 4: Runtime.** What happens when the system runs over time. Concurrency, shared state, retries, timeouts, performance, failure modes, feature flags, deployment order. Layer 3 asks whether the pieces connect. Layer 4 asks how they behave once they are moving. Those are two different mental models, so they get two different readers.\n\n**Layer 5: Use.** How people actually use the system, and what this change does to them. Workflows, defaults, the user experience, and the documentation and configuration that are now wrong. The Claude Code team's REVIEW.md guidance names two failures from this layer: the docs should not lie, and the config should not misroute.\n\nSome concerns do not fit on one layer. Security is the clearest case. An injection bug can sit in the form of a string, a broken permission check in local meaning, a missing check at a trust boundary in the wiring. Security is not a layer. It cuts across all of them. I come back to that below, because it turns the layers into something more useful than a list.\n\n## A tree, not a line\n\nIf each layer gets its own agent, the agents form a tree.\n\nAt the leaves, one layer-2 agent reads one function or one file. It knows nothing about the rest of the system, and that is the point. Above them, one layer-3 agent reads each module boundary. It never sees the function bodies. It sees what the leaves report: a short contract of what each piece does, what it accepts, and what it returns. At the root, one agent holds the system and the way it is used.\n\nInformation travels up as contracts, not raw code. That keeps every agent's context small. The Claude Code team already found that a subagent with fresh context reviews better than the agent that wrote the code. A tree takes that one step further. Every reader gets fresh context, and a narrow one.\n\nThis is an old idea. In 1972, David Parnas published [\"On the Criteria To Be Used in Decomposing Systems into Modules\"](https://dl.acm.org/doi/10.1145/361598.361623). His rule was information hiding. Each module hides a design decision that is likely to change, and the rest of the system sees only its interface. The point was that no person should have to hold every detail of a system in their head to change one part of it.\n\nWe follow that rule in our code, and then break it in our prompts. We put the diff, the surrounding files, and every level of abstraction into one context window, and hope the model sorts them out. A tree of agents is information hiding for the reviewer. The wiring agent does not care how a function sorts its list, only what its contract promises. The function agent is not distracted by the routing table. Each reader is defined as much by what it is not shown as by what it is.\n\n## Down the tree and back up\n\nThe tree works in both directions.\n\nTop-down, one change fans out. A change to the system becomes changes at several module boundaries, and each boundary becomes changes to several functions. The root starts from use and pushes questions down: this endpoint now returns null in one case, so which callers break, and does any of them crash?\n\nBottom-up, local facts travel back up into system judgment, without spilling into sibling nodes. Suppose a layer-2 leaf notices that a helper now makes a synchronous network call, or runs a query inside a loop. Locally, the function still returns the right value. The leaf does not need to know whether the cost matters. It writes the fact into the contract it passes up. The layer-4 runtime reader knows the function sits on a hot path. The layer-5 use reader knows that path is the checkout page, and that it will now hang. Each layer answers only the part its own mental model can see.\n\nThe same path carries problems a leaf cannot solve. If a function cannot keep its contract without breaking an invariant, the leaf should not improvise a workaround. That is how a local patch quietly breaks the architecture. It reports the conflict upward, to the layer that owns the decision, and in the end to the people who own the intent.\n\nThat two-way pass is what catches a change that is correct at every layer and still wrong for the user.\n\n## Layers times lenses\n\nSecurity is not the only concern that cuts across the layers. History does too: why the code looks the way it does, and which decisions it carries. So does reversibility: how you would undo the change if it goes wrong. Call these lenses. A layer says what you are reading. A lens says what you are reading it for.\n\nThe six layers above were already one lens. I was reading every layer for correctness without naming it. Put the layers in rows and the lenses in columns, and you get a matrix. Intent stays out of it, because it stays with humans.\n\n| Layer | Correctness | Security | History | Reversibility | \n|---|---|---|---|---|\n| **Form** | Does it parse, type-check, and follow conventions? | Any secrets, unsafe string building, banned APIs? | Does it bring back a pattern the codebase dropped? | Is the diff small and isolated enough to revert cleanly? | \n| **Local meaning** | Does the function do what its name promises? | Does it validate input and fail safely? | Does it undo a deliberate earlier fix? | Does it change stored data in a way that cannot be undone? | \n| **Wiring** | Do the callers' assumptions still hold? | Is every trust boundary checked? | Does it break a recorded architecture decision? | Can old and new versions run side by side? | \n| **Runtime** | Races, retries, timeouts? | Resource exhaustion, rate limits, secrets in logs? | Has this kind of change caused an incident before? | Can it be switched off without a deploy? | \n| **Use** | Do workflows still work, and are the docs still true? | Can a user now see or do something they should not? | Do users depend on the old behavior? | After a rollback, can users recover their data? | \n\nEach cell is its own mental model. For an agent, each cell is also its own query and its own context. And the two come from different directions.\n\nThe row decides what to load. A local-meaning reader loads one function and its contract. A wiring reader loads signatures and their callers. A runtime reader loads configuration, flags, and deployment order. A use reader loads docs, workflows, and telemetry.\n\nThe column decides what to ask. The security lens brings a threat model. The history lens brings `git blame`, past pull requests, and the decision log. The reversibility lens brings migrations and the rollout setup.\n\nSo nobody has to build twenty agents. Build five context retrievers, one per row, and four lens prompts, one per column. Every cell is a combination of the two. Nine parts give you twenty narrow readers. A new lens, performance or accessibility, adds a whole column for the price of one prompt.\n\nThe history column is the weak one. `git blame` says who changed a line and when. It does not say why. A cell that asks whether a change breaks a recorded architecture decision only works if the decision was recorded somewhere an agent can query. Most teams keep that reason in a chat thread or in one person's head. The matrix makes the gap visible: it is an empty input, not a weak model.\n\n## Where it can break\n\nThe seams are the risk. Bugs like to live between layers, where each reader assumes the other one checked. The contract a leaf passes upward is the most important artifact in the whole tree. If it is vague, the layer above reviews a summary instead of the code.\n\nThe matrix adds a second kind of seam. A change can look correct in the wiring row and be dangerous only under the security lens. When two cells report on the same line from different angles, that overlap is often where the real bug is. The coordinator should treat it as a signal, not as a duplicate to throw away.\n\nMore agents also means more noise. The Claude Code team saw one pull request cycle more than fifty times overnight between an agent author and an agent reviewer. When they checked what the reviewer kept flagging, about 59% of it was code that had been there since the first pass. They now ask the author to rethink the approach after three cycles and limit later reviews to the new diff. A tree of reviewers needs the same discipline, applied harder: one coordinator that removes duplicate findings, a vote on what is real, and a cap on rounds.\n\nCost is the third limit. A one-line copy change does not need twenty readers. In practice the matrix is sparse. Almost the whole form row belongs to compilers, linters, and secret scanners, not to agents. Their risk classifier already decides how much human review a change gets. The same classifier can decide which cells run. A docs fix gets one or two. A change to billing or the auto-updater gets the full grid.\n\n## How I would test it\n\nThe Claude Code team keeps a collection of pull requests that caused incidents, and scores its reviewer against that collection every day. That collection is the right test for this idea too.\n\nLabel each incident with the cell where the bug actually lived: its layer and its lens. If most of them sit in the wiring and runtime rows, then a single reviewer that reads everything is spread thin exactly where the damage happens. If a whole column stays empty, either that lens is already covered, or nobody is looking through it. The labels also say which cell to build first.\n\nThe Claude Code team layered the pipeline. I would also layer the reader.", "url": "https://wpnews.pro/news/one-reviewer-per-layer-splitting-code-review-by-the-way-code-is-read", "canonical_source": "https://djordjepuzic.com/blog/posts/2026-10-27-one-reviewer-per-layer/", "published_at": "2026-10-27 14:33:35+00:00", "updated_at": "2026-10-27 14:41:38.569385+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools", "artificial-intelligence"], "entities": ["Addy Osmani", "Claude Code", "Anthropic"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/one-reviewer-per-layer-splitting-code-review-by-the-way-code-is-read", "markdown": "https://wpnews.pro/news/one-reviewer-per-layer-splitting-code-review-by-the-way-code-is-read.md", "text": "https://wpnews.pro/news/one-reviewer-per-layer-splitting-code-review-by-the-way-code-is-read.txt", "jsonld": "https://wpnews.pro/news/one-reviewer-per-layer-splitting-code-review-by-the-way-code-is-read.jsonld"}}