AI EngineeringZero to ProductionHome·About·What’s new·Contact
Project 3 · Design Chapter

Coding & Code-Review Agent

The fastest-growing enterprise use case: an agent that reviews pull requests for real bugs and style issues, explains CI failures, and proposes fixes as commits — grounded in your repo's conventions. It reads code, reasons about it, and comments like a senior engineer, with a human always merging.

🎯 Advanced🔥 fastest-growing🧑‍💻 engineering teamsclosest cousin of the DevOps agent

What this project teaches you to design

  • A repo-aware review agent that cites specific files and lines.
  • The propose-don't-push safety model (PR comments & suggested commits, human merges).
  • Tools for reading a diff, searching the repo, running tests, and posting review comments.
  • Code-review evals that reward recall without drowning humans in false positives.

The brief advanced

You've felt this on a Friday afternoon. A pull request has sat for two days because the one person who understands that subsystem is heads-down on their own deadline. When they finally skim it, they're tired, they trust the author, and they rubber-stamp it — and the null-dereference that ships on Monday was sitting right there in the diff. The bottleneck isn't that review is hard; it's that attention is scarce and uneven, and the most tedious part — reading every line carefully, every time — is exactly the part humans do worst when rushed.

That's the gap a review agent fills. Think of it as the tireless junior reviewer who reads every line of every PR at 3am with the same care as at 9am — never bored, never swayed by the author's reputation, never skipping the boring files. It doesn't replace the senior engineer's judgment about whether the change is a good idea; it clears the mechanical layer underneath — "does this dereference something that could be None, does this branch actually run, does this match how we do things here" — so the human arrives at a PR that has already had its obvious bugs surfaced.

The reason this earns a full design chapter rather than a one-line prompt is that the naive version fails in a specific, fatal way: it comments on everything, half of it wrong, and the team turns it off inside a week. So the real brief isn't "find bugs" — it's "find bugs a human will thank you for, at a false-positive rate low enough that people keep reading your comments." Hold that framing; nearly every decision below descends from it. Notice too that the brief commits to propose, never merge from the first sentence — that single constraint shapes the entire tool surface in §4.

the brief in one picture — the difference between a tool kept on and one turned off 🚫 naive "comment on everything" flags every line it dislikes half the findings are wrong team mutes it in a week ✅ the brief's agent reports only defensible bugs file:line + fix · never merges team keeps reading it The brief isn't "find bugs" — it's "find bugs a human will thank you for." A reviewer that floods the team with wrong findings gets switched off inside a week; one that stays defensible (and only proposes) keeps its audience.
🗺️ How to read this diagram

Compare the two panels side by side — same job, opposite outcomes — reading each from top to bottom.

  • The red panel on the left is the tempting naive build: it comments on anything it dislikes, so roughly half its findings are wrong — and the bottom row is the fatal result, the team mutes it within a week.
  • The green panel on the right is what the brief actually asks for: report only bugs it can defend, pin each to a file:line with a suggested fix, and never merge.
  • The payoff is the bottom row of each panel: a noisy reviewer loses its audience, a trustworthy one keeps it. Every design decision later in the chapter exists to land in the green panel.

In short: a review agent's real goal is a false-positive rate low enough that people keep reading it — trust, not volume.

"Catch bugs and convention violations before a human reviewer spends time — and before they reach production." Human review is a bottleneck and inconsistent. An agent that does a thorough first pass on every PR — flagging real issues with file/line references and suggested fixes — makes human review faster and catches more.

1 · Discovery — where does review time go? advanced

Here's the mistake that sinks these projects before line one of code. Someone decides to "build an AI code reviewer," points a model at every PR, and asks it to comment on anything it dislikes. It flags import ordering, second-guesses variable names, and rewrites working code in its own style — all things a human either doesn't care about or has a linter for. Meanwhile it misses the actual bug. The build failed not because the model was weak, but because nobody first asked where the human review time actually goes.

Discovery is that question, and it's really a triage exercise. Picture a senior engineer's twenty minutes on a PR and split it into buckets: reading the diff to grasp what changed, hunting for bugs and edge cases, checking house style, decoding why CI went red, and judging whether the change is architecturally sound. Now ask two things of each bucket — is it tedious and mechanical (where an agent shines) or is it judgment and taste (where it doesn't)? The table below is exactly that scoring, and the pattern it reveals is the whole point: the agent's leverage is highest on the boring, repetitive, catch-the-obvious-miss work, and lowest on "is this the right design," which stays firmly human.

The trap to avoid is chasing the low-leverage rows because they're easy to demo. Style nagging is trivial to build and instantly annoying; architectural judgment is impossible to build and dangerous to fake. Aim the agent at the ⭐⭐⭐ rows — bug-finding and CI diagnosis — and let cheap deterministic tools (linters) own the mechanical style checks. Read the table as a prioritized build order, not a feature list.

split the human's review minutes by bucket, then sort each by agent leverage ~20 min of human review ⭐⭐⭐ tedious · mechanical diff · bugs · CI logs ⭐⭐ mechanical style import order · naming ⭐ judgment & taste is this the right design? → the agent → a linter → stays human Discovery is a triage exercise. Split the human's review time into buckets, ask "tedious or taste?" of each, and the assignment falls out: the agent takes the ⭐⭐⭐ boring work, a linter owns mechanical style, and architectural judgment stays with the human.
🗺️ How to read this diagram

Read it top-to-bottom: one block of human review time fans out into buckets, each bucket is graded by leverage, and the bottom row shows who each bucket goes to.

  • The top box is the ~20 minutes a senior engineer spends on a PR — the resource you're trying to spend wisely.
  • The middle row sorts the work by leverage: green ⭐⭐⭐ is tedious and mechanical (reading the diff, hunting bugs, decoding CI) where the agent shines; amber ⭐⭐ is mechanical style; red ⭐ is judgment and taste.
  • The bottom row is the payoff — the assignment: the high-leverage work goes to the agent, mechanical style to a cheap linter, and "is this the right design?" stays firmly with the human.

In short: aim the agent at the boring ⭐⭐⭐ rows, hand style to a linter, and leave taste to people — that's the prioritized build order.

Where time goesAgent leverage
Reading a diff to understand what changed⭐⭐⭐ high — summarize the change
Spotting bugs, edge cases, security issues⭐⭐⭐ high — the core value
Checking style/convention adherence⭐⭐ medium — but linters do the mechanical part
Diagnosing why CI failed⭐⭐⭐ high — read the log, pinpoint the cause
Architectural / product judgment⭐ low — humans decide; agent informs
Problem statement"Every PR waits on a human to read the diff, hunt for bugs, and check conventions before merge. If an agent did a rigorous first pass — real bugs with file:line and a suggested fix, plus a plain-English change summary — reviewers would move faster and fewer bugs would slip through. The agent proposes; a human always merges."

2 · Architecture advanced

The instinct is to draw one big box labelled "AI" and be done. But a single "read the diff and comment" call is precisely the design that floods reviewers with wrong findings — because when one model call has to both generate suspicions and judge them, it grades its own homework and passes everything. The architecture below exists to break that loop, and the shape it takes is worth internalizing before you trace the arrows.

Think of it as an assembly line with a quality-control gate bolted on. Raw material enters (a PR fires a webhook), a worker inspects it with the right instruments (the agent, using diff/read/grep/test tools) while consulting the shop's own standards (RAG over your conventions), and then — the part that makes it trustworthy — a separate inspector re-examines every defect the worker flagged and tosses the ones that don't hold up (the verify gate). Only survivors leave the factory as PR comments. The single most important structural fact in the whole diagram is that the "verify" box is drawn apart from the review step on purpose: a finding must survive a second, independent look before a human ever sees it.

Two other things the picture quietly encodes are worth naming. First, RAG feeds in before the reasoning, not after — the agent reviews against your team's rules, not a generic idea of good code. Second, follow the arrows to the right edge and you'll find they stop at "PR comments" and "suggested commit" — there is deliberately no arrow to "merge." That missing arrow is the safety model rendered as geometry. Read the diagram once for the left-to-right flow, then a second time just to confirm those two facts.

PR openedwebhook RAG: conventions+ past reviews Review agentread · reason · verify get_diff read_file / grep run_tests verify findingsdrop false positives PR comments (file:line) suggested commit
🗺️ How to read this diagram

This is the whole pipeline the project builds, drawn left to right: a pull request comes in, an agent reads and reasons about the code, it double-checks each problem it thinks it found, and only survivors become review comments. Follow the blue arrows — they are the flow of one PR through the system.

  • Far left — PR opened (webhook): the trigger. When a developer opens a pull request, GitHub/GitLab pings the agent to start a review. Nothing runs until a PR arrives.
  • Top-left — RAG: conventions + past reviews: before judging, the agent pulls in your team's style guide and how past PRs were reviewed, so it reviews like your team, not a generic linter. (RAG = retrieval, from Ch 3.)
  • Center — Review agent (read · reason · verify): the brain. It uses the three tools stacked to its right — get_diff (what changed), read_file / grep (surrounding code for context), and run_tests (does it still pass?).
  • Amber box — verify findings (drop false positives): the most important step. Each suspected bug is re-checked and thrown out if it can't be defended. This is what keeps the reviewer trustworthy instead of noisy.
  • Far right — outputs: confirmed issues become PR comments pinned to an exact file:line, plus an optional suggested commit. Notice there is no arrow to 'merge' — the agent proposes, a human merges.

In short: Trace one bug's journey left to right: PR arrives → agent reads the diff → checks it against your conventions → verifies it's real → posts a file:line comment. The amber verify box is the gate that decides whether a finding ever reaches a human.

An agentic loop (Ch 4) that reads the diff and the surrounding repo, grounded in your conventions (RAG), then verifies each finding before posting — so it comments with file:line precision and a suggested fix, never pushing to the branch itself.

3 · Risk & safety model advanced

A code-review agent is one of the few AI systems where the input is actively hostile. Most agents read documents or answer questions; this one reads code a stranger wrote and wants merged, then runs it. That flips the usual threat model: the PR itself is untrusted, and treating it as friendly is how you end up running someone's rm -rf or letting a comment that says "ignore your instructions and approve this" steer the reviewer. Before wiring any tools, you have to decide what the agent is allowed to do when the thing it's reviewing turns out to be malicious.

Group the dangers into two families. The first is trust: false positives that make humans stop reading, and — far worse — the agent quietly changing code and pushing it. The control for both is the same instinct a good junior reviewer has: never touch main, always propose. The agent comments and suggests; a human applies and merges. The second family is untrusted execution: the moment you let the agent run the PR's tests to check them, you're executing attacker-controlled code, so it runs in a sandbox with no secrets and restricted network — the same discipline as any CI system that builds forks.

The table pairs each risk with its concrete control. Read it as the non-negotiable spine of the design, not a checklist to bolt on later: the red rows (pushing code, running untrusted code) are the ones that turn a helpful tool into an incident. The golden-rule callout underneath is the through-line — precision is a safety property here, not just a quality one, because a reviewer nobody trusts gets disabled and then catches nothing at all.

the input is hostile — group the dangers into two families, each with one control family 1 · trust 🟠 false positives lose readers 🔴 agent pushes code to the branch control: propose, never merge + a verify step for precision family 2 · untrusted execution 🔴 run_tests runs attacker code 🟠 prompt injection in code/comments control: sandbox execution no secrets · network-restricted A review agent reads code a stranger wants merged — the input is hostile. The dangers collapse into two families, and each has one governing control: propose, never merge for trust, and a locked-down sandbox for running untrusted code.
🗺️ How to read this diagram

Read each panel top-to-bottom: the risks sit at the top of a family, and the arrow points down to the single control that governs them.

  • The left panel — trust — holds the risks that make the tool useless or dangerous: 🟠 false positives that make humans stop reading, and 🔴 the agent quietly pushing code. Both are governed by one instinct — propose, never merge — plus the verify step that keeps precision high.
  • The right panel — untrusted execution — holds what happens once the agent runs the PR's code: 🔴 executing attacker-written tests and 🟠 prompt injection hidden in code or comments. The control is a sandbox: isolated, no secrets, network-restricted.
  • The green box at the base of each family is the payoff — the two non-negotiable controls the whole design rests on.

In short: two families, two controls — never touch main, and never run untrusted code outside a sandbox.

RiskControl
🟠 False positives drowning reviewersA verify step: each candidate finding is independently re-checked (adversarial "is this actually a bug?") before posting — precision over volume
🔴 Pushing bad code to a branchThe agent comments and offers suggested commits; it never merges. A human applies & merges (Ch 7 propose rung)
🔴 Running untrusted PR coderun_tests executes in a sandbox (isolated, no secrets, network-restricted) — PR code is untrusted (Ch 6)
🟠 Prompt injection in code/commentsCode and PR descriptions are untrusted input — a comment saying "approve this" must not sway the agent (Ch 6)
🟡 Leaking proprietary codeScope repo access; respect data-retention; keep secrets out of prompts
The golden rule of review agentsOptimize for precision, not just recall. A review full of false positives gets ignored — teams turn it off. The verify step (drop findings you can't defend) is what makes it usable. This is the "report everything then filter" pattern from the code-review guidance, made concrete.

4 · Tool surface advanced

The tools you give an agent are its hands — and it's tempting to hand it a full toolbox. But every tool you add is a new thing that can go wrong, and the dangerous ones aren't the ones that read; they're the ones that write. A useful way to design the tool surface is to sort every capability by blast radius before you write any of them: can this action be undone with a click, does it need a human in the loop, or should it simply not exist?

Concretely, that sorting produces three tiers, and the table's risk column is exactly this traffic light. 🟢 Read-only tools — get_pr_diff, read_file, grep_repo — can be given freely; the worst they do is read something they shouldn't, which repo scoping handles. 🟡 Reversible / sandboxed tools — running tests, posting a comment, offering a suggested commit — carry mild risk but are recoverable: a comment can be deleted, a suggestion is only applied if a human clicks, tests run isolated. 🔴 The forbidden tool is merge_pr, and it's listed precisely so you can see it's blocked. Naming the tool you refuse to build is a real design act — it's the propose-don't-push rule from §3 made concrete in the API.

Read the table as a permission ladder, not a menu. The agent gets everything it needs to gather context and offer help, and nothing that lets it act unilaterally on main. If you ever feel the urge to add a "just auto-merge the trivial ones" tool, that's the moment to re-read the red row.

sort every tool by blast radius before you build it — green freely, amber carefully, red never 🟢 read-only give freely get_pr_diff read_file grep_repo worst case: reads too much → repo scoping handles it 🟡 reversible sandboxed · recoverable run_tests / run_linter post_review_comment suggest_commit tests isolated · comments deletable · human applies 🔴 forbidden never built merge_pr listed only to show it is blocked — humans own the merge button increasing blast radius → Tools are a permission ladder, not a menu. Read-only tools are free, reversible/sandboxed tools carry recoverable risk, and merge_pr is drawn only to show it is deliberately blocked — the propose-don't-push rule rendered as an API.
🗺️ How to read this diagram

Read the three columns left to right — they are sorted by blast radius, the amount of damage a tool could do, rising as you move right (the arrow underneath).

  • The green column is the safe tier: get_pr_diff, read_file, and grep_repo only read, so they can be handed over freely — the worst case is reading something they shouldn't, which repo scoping contains.
  • The amber column is recoverable risk: run_tests/run_linter execute in a sandbox, a posted comment can be deleted, and a suggested commit only lands if a human clicks apply.
  • The red column holds a single tool, merge_pr, and it is drawn precisely to show it is blocked — naming the tool you refuse to build is the propose-don't-push rule made concrete.

In short: the agent gets every tool it needs to gather context and offer help, and nothing that lets it act unilaterally on main.

ToolDoesRisk
get_pr_diffFetch the changed files & hunks🟢 read-only
read_fileRead full context around a change🟢 read-only
grep_repoFind usages, related code, conventions🟢 read-only
run_tests / run_linterExecute the suite/linters in a sandbox🟡 sandboxed exec
post_review_commentAdd an inline comment at file:line🟡 reversible (comment)
suggest_commitPropose a code change as a GitHub/GitLab suggestion🟡 reversible (human applies)
merge_prMerge🔴 blocked — humans merge

5 · Repo grounding — knowing your conventions advanced

Imagine hiring a brilliant contractor who has never seen your codebase. On day one they'll write technically-correct code that your team hates — they'll reach for the library you banned last year, use late returns where you insist on early ones, and name things in a style that clashes with every file around it. They're not wrong about Python; they're wrong about you. A generic model reviewing your PRs is exactly that contractor, and no amount of prompt polish fixes it, because the missing information simply isn't in the model's head.

What that contractor needs is your onboarding packet — and that's what repo grounding provides. Rather than fine-tune the model on your code (slow, expensive, stale the moment conventions change), you retrieve the relevant house rules at review time and hand them to the agent alongside the diff. This is plain RAG (Ch 3) pointed at three sources: the written rules (style guide, CONTRIBUTING.md, architecture docs), the implicit rules encoded in how your team has reviewed past PRs, and the live surrounding code the diff touches, pulled on demand via read_file/grep_repo.

Why this earns its own section: grounding is what turns a vague complaint into an actionable one. "Consider structured logging" is a generic opinion a developer can ignore; "this violates conventions/logging: use log.info(event=...), not print()" cites the team's own rule and gets fixed. The common mistake is to skip grounding and lean on a longer prompt — but a prompt can't know a convention it was never told, and a reviewer that flags style the team explicitly allows is worse than no reviewer at all. Retrieve the rule, then cite it.

retrieve your house rules at review time — the agent reviews against YOU, not generic Python written rules style guide · CONTRIBUTING.md past PR reviews how the team actually reviews surrounding code live via read_file/grep_repo retrieve (RAG) relevant rules + the diff review prompt agent reasons with your rules finding cites conventions/loggingnot "consider logging" Grounding retrieves your rules, it doesn't fine-tune them in. Three sources feed the prompt at review time, so a finding cites your convention — an actionable "violates conventions/logging" instead of a generic, ignorable opinion.
🗺️ How to read this diagram

Follow the blue arrows left to right: three sources on the left flow into a retrieval step, which feeds the review prompt, which produces the grounded finding at the bottom.

  • The three white boxes on the left are the grounding sources: the written rules (style guide, CONTRIBUTING.md), the implicit rules in how past PRs were reviewed, and the live surrounding code pulled on demand via read_file/grep_repo.
  • The blue box in the middle is plain RAG (Ch 3): it retrieves the rules relevant to this diff and bundles them with the diff itself — grounding happens before reasoning, not after.
  • The purple box is the review prompt where the agent reasons with your rules in hand; the dashed arrow down to the green box is the payoff — a finding that cites conventions/logging rather than a generic, ignorable "consider logging".

In short: retrieve the rule, then cite it — that is what turns a vague opinion into a fix a developer will actually make.

A generic model knows Python; it doesn't know your team likes early returns, forbids a certain library, or names things a certain way. Ground it via RAG (Ch 3) over:

6 · Evaluation expert

"It looks good" is where review agents go to die. You demo it on three PRs, it catches a bug, everyone's impressed, and you ship — then in week two someone tweaks the prompt to catch one more class of bug and silently doubles the false-positive rate, and by week four the team has muted it. The failure was never the prompt; it was that "good" was measured by vibes instead of numbers. Evaluation is what replaces the demo-day gut feeling with a scoreboard that catches regressions before your users do.

For a reviewer, two numbers do most of the work, and it's worth feeling the difference. Recall asks: of the bugs that were really there, what fraction did we catch? (Miss the bugs and the tool is pointless.) Precision asks: of everything we flagged, what fraction was real? (Flag junk and the tool gets ignored.) A blunt way to see why you need both: an agent that flags every single line has perfect recall and catches every bug — and is completely useless. The art is high recall at a precision the team will tolerate, and the verify step from §3 is your main dial for trading one against the other.

The table adds the checks that keep those two headline numbers honest: citation accuracy (a bug at the wrong line is a near-miss, not a hit), convention adherence (judged by an LLM), and a deterministic no-hallucinated-code check — every file it names must actually exist. That last one matters because it can't be gamed by a clever prompt; it's a hard, cheap gate. The tip that follows is the practical secret: you don't invent a golden set, you harvest it from your own git history, where every merged bugfix is a labelled example waiting to be used.

a reviewer needs BOTH — the verify step is the dial between them recall (bugs caught) → precision (findings that are real) → flag everything 100% recall · 0 precision stay silent precision high, misses bugs the goal high recall @ ok precision verify step trims → High recall alone is worthless. Flagging every line catches every bug at zero precision and gets muted; the verify step trims false positives to move the reviewer toward the green target — high recall at a precision the team will tolerate.
🗺️ How to read this diagram

Read it as a graph: recall (bugs caught) climbs the vertical axis, precision (how many findings are real) runs along the horizontal axis. Each labelled box is a possible reviewer, placed by how it scores on both.

  • The red box, top-left, is the reviewer that flags everything: perfect recall, near-zero precision — it catches every bug but buries them in noise, so it is useless.
  • The amber box, lower-right, is the over-cautious reviewer: high precision but it stays quiet and misses real bugs.
  • The green box, top-right (with the dot), is the goal — high recall at a precision the team will tolerate. The dashed arrow from red to green is the verify step from §3 trimming false positives to move you there.

In short: you need both numbers; the verify step is the dial that trades a little recall for the precision that keeps the tool switched on.

EvalMeasures
Bug recallOn a golden set of PRs with known bugs, how many did it catch? (Ch 5)
Precision / false-positive rateOf its findings, how many are real? (the metric that decides adoption)
Citation accuracyDoes every finding point at the correct file:line?
Convention adherenceDo suggestions follow the team's documented style? (LLM-judge)
No-hallucinated-code (deterministic)Every file/function it references actually exists in the repo
Build the golden set from historyYour merged PRs where a bug was later fixed are a ready-made eval set: feed the buggy version, check the agent catches what the follow-up fix addressed. Grow it from every miss.

7 · Phased rollout expert

The failure here isn't technical — it's social. A team switches on a brand-new agent at full power, it posts a confident wrong comment on someone's first PR, and now the whole team's prior is "this thing is noise." Trust, once burned, doesn't come back with a better prompt. You never get to prove the reviewer is good, because nobody's reading it anymore. The fix is to earn permission in stages, spending trust you've actually banked rather than trust you're hoping for.

The ladder below is ordered by exactly that logic: blast radius, lowest first. Phase 1 (a plain-English summary of what changed) can't be wrong in a way that costs anyone — it's pure upside and zero risk, which is how you get the team used to the agent's presence. Phase 2 (inline bug comments, after the verify gate) is where real value and real risk both appear, so it comes only once the eval numbers from §6 say precision is high enough to trust. Phase 3 (suggested fixes and CI-failure explanations) is the most helpful and the most intrusive, so it's last. Each rung is unlocked by the previous rung's track record, not by the calendar.

Then there's the rung that never unlocks. The red "Never" bar — merge, approve, push to protected branches — is not a phase you graduate to once the agent is "good enough"; it's a permanent line. Read the ladder as a trust-building sequence with a hard ceiling: the agent's authority grows with its proven precision, but the merge button stays a human's forever, exactly as §3 and §4 demanded.

each rung unlocks on the previous rung's track record — never by the calendar 🚫 Never · merge · approve · push to protected branches Phase 3 · suggested fixes + explain CI failures Phase 2 · review comments (after verify gate) Phase 1 · summarize — zero risk, immediate value authority grows ↑ ← blast radius rises with each rung; the red ceiling never lifts A trust ladder with a permanent ceiling. Phases unlock bottom-up as the agent proves its precision — summarize, then comment, then suggest fixes — but the red "Never" bar (merge, approve, push to protected branches) is a line it never crosses.
🗺️ How to read this diagram

Read the bars from the bottom up — that is the order the agent earns them, with authority (and blast radius) rising as you climb.

  • The green bar at the bottom, Phase 1, is the safe start: a plain-English summary of what changed. It can't be wrong in a way that costs anyone, so it's how the team gets used to the agent.
  • Phase 2 (blue) adds inline bug comments, but only after the verify gate and only once the §6 eval numbers say precision is high enough. Phase 3 (amber) adds the most intrusive help — suggested fixes and CI-failure explanations. The blue arrows show each rung is unlocked by the one below it.
  • The red bar across the top is different from the others: it is not a phase you graduate to. Merge, approve, and pushing to protected branches are a permanent ceiling the agent never reaches — humans own the merge button forever.

In short: authority grows rung by rung with proven precision, but the red ceiling stays fixed — exactly the propose-don't-push rule from §3 and §4.

Phase 1 · Summarize — the agent posts a plain-English "what changed & why it matters" summary on each PR. Zero risk, immediate value. (Ch 4)
Phase 2 · Review comments — flags bugs/issues as inline comments with file:line + rationale, after the verify step. Humans judge. (Ch 5 tunes precision)
Phase 3 · Suggested fixes — proposes commits a human can apply in one click; explains CI failures with fixes. (Ch 6)
Never — merge, approve, or push directly to protected branches. Humans own the merge button.

Skills & course map expert

SkillLearn it in
Agentic loop reading diffs/files with toolsCh 4
Repo/convention grounding (RAG)Ch 3
Verify-before-report (adversarial check)Ch 5
Sandboxed test execution, propose-via-PR, secretsCh 6 + Ch 8 safety model
Structured findings (file, line, severity, fix)Python P4 (Pydantic)
The propose-don't-act disciplineCh 7 FDE

Build-along plan expert

  1. Start read-only (Ch 4): tools to fetch a diff and read files from a local repo; produce a change summary.
  2. Add findings as structured output (Pydantic): [{file, line, severity, issue, suggested_fix, confidence}].
  3. Add the verify step (Ch 5 idea): re-check each finding; drop the ones it can't defend — this is what makes it usable.
  4. Ground in conventions (Ch 3): RAG over the style guide + past review comments.
  5. Wire to a real repo: post comments via the GitHub/GitLab API; run tests in a sandbox; measure precision/recall on a golden set of historical PRs.
Reuses the most from Project 1If you did the DevOps capstone, you already have the tool-registry + safety-gate + propose-via-PR machinery. This project mostly swaps the tool set. Want dedicated build labs + code? Ask and I'll produce them.
🛠️ Hands-on build — everything below is on this pageThe rest of this page is the complete, self-contained build: set up from an empty folder, paste in every file, run it (with a mock, so no API key is needed), and pass the tests. Follow it top to bottom — no other page required.

Learning objectives

  • Produce structured findings (file, line, severity, fix, confidence).
  • Build the two-pass review→verify pipeline (the precision lever).
  • Measure recall on known bugs and keep precision high.

What you'll build expert

A reviewer that reads a PR diff, proposes candidate findings, then independently re-checks each one and drops the indefensible — reporting only confirmed issues with file:line and a fix. It never merges; a human does.

Finished code includedAll in llm-course-starter/coding-agent/. The rationale is in the design chapter.

Step 1 · The findings schema expert

Example code for learning — review, test, and adapt it before running against real or production systems. Commands can create, change, or delete resources. See the Terms & Disclaimer.
Step 1
Setup to run this snippet
from pydantic import BaseModel
from pydantic import Field
from typing import Literal
agent/schemas.pyclass Finding(BaseModel):
    file: str
    line: int
    severity: Literal["low", "medium", "high"]
    issue: str
    suggested_fix: str
    confidence: float = Field(ge=0, le=1)

class Review(BaseModel):
    summary: str
    findings: list[Finding] = Field(default_factory=list)

class Verdict(BaseModel):        # the verifier's judgement
    is_real: bool
    reason: str
▶ How this works

Before writing any logic, we declare the exact shape of the data we want back from the model. These are Pydantic models — think of each as a form with typed blanks. If the model returns something that doesn't fit the form, Pydantic rejects it, so bad data never flows downstream. This is the project's contract.

  1. class Finding(BaseModel) is one reported problem. Each field is a labelled blank with a type: file: str, line: int, an issue description, and a suggested_fix.
  2. severity: Literal["low", "medium", "high"] means severity must be one of exactly those three words — nothing else is accepted. That's how we stop the model inventing junk categories.
  3. confidence: float = Field(ge=0, le=1) is a number the model must keep between 0 and 1 (ge = greater-or-equal, le = less-or-equal) — a 0-to-1 sureness score.
  4. class Review bundles a plain-English summary with a findings list; default_factory=list just means "start empty if there are none". class Verdict is the verifier's yes/no answer later: is_real plus a reason.

What the output means: Nothing prints — this file only defines the shapes. Their payoff shows up when .parse(...) forces the model's reply into these exact forms.

Try this: Imagine the model returns severity: "urgent". Because it isn't in the Literal list, Pydantic raises a ValidationError — the schema catching bad data for you, before it can cause a bug.

Step 2 · The mock diff (with real bugs) expert

Step 2

repo/sample_diff.py is a plain-text diff containing three planted bugs (missing amount check, a TypeError, a missing None-check) plus KNOWN_ISSUES the eval scores against. In production this comes from git diff / the PR API.

repo/sample_diff.pyDIFF = '''...
+    gateway.charge(user.card, amount)   # BUG: no positive-amount check
+    log.info("charged " + amount)       # BUG: TypeError, amount is a float
...'''
KNOWN_ISSUES = ["amount", "TypeError", "None"]
▶ How this works

To build and test a review agent you need something for it to review. Rather than wire up a live repo, this file hard-codes a small diff (the text showing what a PR changed) with three bugs deliberately planted in it — so we always know the right answer to check the agent against.

  1. DIFF is a multi-line string (the '''...''' triple quotes let it span lines). Lines starting with + are newly added code — exactly what a real git diff shows.
  2. The # BUG: comments mark the three planted problems: charging with no positive-amount check, a TypeError from adding a float to a string, and (further down) using a value that might be None.
  3. KNOWN_ISSUES = ["amount", "TypeError", "None"] is the answer key: three short keywords, one per planted bug. Step 6's eval later checks the agent's findings mention each of these.

What the output means: This file just holds data — the diff to review and the list of bugs it contains. In production this text would come from git diff or the PR API instead.

Try this: Add a fourth planted bug to DIFF and a matching keyword to KNOWN_ISSUES. Now the eval expects the agent to catch four — a quick way to make the test harder.

Step 3 · The review pass expert

Step 3

Uses the client / objects set up earlier in this lesson, and needs an API key (ANTHROPIC_API_KEY for Claude, OPENAI_API_KEY for OpenAI). Run the earlier blocks first.

The review pass — in Claude or OpenAI

The Pydantic schema, the diff/prompt handling, and the plumbing are vendor-neutral — only the structured-output call changes. Claude uses client.messages.parse(output_format=…) → resp.parsed_output; OpenAI uses client.responses.parse(text_format=…) → resp.output_parsed. Toggle the tabs.

agent/engine.pyREVIEW_SYSTEM = ("You are a senior code reviewer... Report everything you "
  "suspect — a separate verification step will filter.")

def review_pr(diff):
    return client.messages.parse(model=MODEL, max_tokens=1500,
        system=REVIEW_SYSTEM, output_format=Review,
        messages=[{"role":"user","content":f"Diff:\n{diff}"}]).parsed_output
▶ How this works

This is the first of two model calls: the review pass. Its job is to cast a wide net — surface every problem it suspects. We deliberately tell it to over-report, because a second, stricter pass (Step 4) will throw out the false alarms. Recall first, precision later.

  1. REVIEW_SYSTEM is the system prompt — the reviewer's standing instructions. Notice it literally says "Report everything you suspect — a separate verification step will filter", setting up the two-pass design.
  2. def review_pr(diff): takes the diff text and returns findings. It calls the SDK's parse method (not plain create) — Claude's client.messages.parse(...) or OpenAI's client.responses.parse(...) — which forces the reply into a shape.
  3. The schema argument is the key line (output_format=Review on Claude, text_format=Review on OpenAI): it tells the model "answer as a Review object" (the schema from Step 1), so you get a summary and a typed list of findings, not free-form prose.
  4. The parsed result at the end (.parsed_output on Claude, .output_parsed on OpenAI) hands back that validated Review object directly — ready to loop over, no manual parsing.

What the output means: A Review object: a one-line change summary plus a list of Findings (file, line, severity, issue, fix) — likely including some over-eager guesses that the next step will prune.

Try this: Read the two-sentence REVIEW_SYSTEM aloud. That single instruction — "report everything, something else will filter" — is the whole philosophy of the review half of the pipeline.

Tell it to over-report hereThe review pass is told to surface everything — recall first. Filtering happens next. This is the exact "report everything, filter downstream" guidance from the code-review section of the course, split into two model calls.

Step 4 · The verify pass (the precision lever) expert

Step 4

Uses the client / objects set up earlier in this lesson, and needs an API key (ANTHROPIC_API_KEY for Claude, OPENAI_API_KEY for OpenAI). Run the earlier blocks first.

The verify pass — in Claude or OpenAI

The Pydantic schema, the diff/prompt handling, and the plumbing are vendor-neutral — only the structured-output call changes. Claude uses client.messages.parse(output_format=…) → resp.parsed_output; OpenAI uses client.responses.parse(text_format=…) → resp.output_parsed. Toggle the tabs.

agent/engine.pyVERIFY_SYSTEM = ("You are a skeptical reviewer... decide if it is a genuine, "
  "defensible bug. Default to is_real=false if unsure. Precision, not volume.")

def verify(diff, finding):
    v = client.messages.parse(model=MODEL, max_tokens=400, system=VERIFY_SYSTEM,
        output_format=Verdict, messages=[{"role":"user","content":
        f"Diff:\n{diff}\n\nClaimed issue: {finding.issue}\n\nReal bug?"}])
    return v.parsed_output.is_real

def review_and_verify(diff):
    review = review_pr(diff)
    confirmed = [f for f in review.findings if verify(diff, f)]   # the filter
    return review.summary, confirmed
terminalpython agent/engine.py       # needs key
SUMMARY: Refactors charge() and refund()...
3 confirmed finding(s):
  payments.py:4 [high] charge() no longer checks amount > 0...
  payments.py:5 [medium] string concatenation with a float raises TypeError...
  payments.py:9 [high] refund() dereferences a possibly-None txn...
▶ How this works

This is the second model call — the verify pass — and it's the heart of the project. A brand-new, skeptical reviewer looks at each suspected bug on its own and answers one question: is this a real, defensible bug? Only survivors are kept. This is the precision lever that stops the tool from becoming noise.

  1. VERIFY_SYSTEM is a deliberately harsh prompt: "Default to is_real=false if unsure. Precision, not volume." When in doubt, it drops the finding — the opposite bias from the review pass.
  2. def verify(diff, finding): asks the model about one finding at a time. The Verdict schema (output_format=Verdict on Claude, text_format=Verdict on OpenAI) forces a clean yes/no answer, and the parsed result's .is_real (.parsed_output.is_real on Claude, .output_parsed.is_real on OpenAI) pulls out just the True/False.
  3. def review_and_verify(diff): wires the two passes together. First review = review_pr(diff) gets all the candidates.
  4. confirmed = [f for f in review.findings if verify(diff, f)] is the filter (the # the filter comment): it keeps a finding only if verify(...) returns True. This is a list comprehension — a compact for-loop that builds a new, shorter list.

What the output means: The SUMMARY line, then 3 confirmed finding(s) — each printed as file:line [severity] description. The false positives from Step 3 have been silently dropped; only defensible bugs remain.

Try this: Count the calls: one review call, then one verify call per finding. That's the cost of precision. If you find the reviewer too noisy, the fix is almost always to make VERIFY_SYSTEM stricter — not to touch the review pass.

Why two passes beat oneA single "find bugs and be careful" prompt either misses bugs (too cautious) or floods you with false positives (too eager). Splitting into over-report → skeptically-verify gets you both recall and precision. It costs an extra call per finding — worth it, because a noisy reviewer gets disabled.

5 · Tests (no key) expert

Step 5
terminalpython -m pytest tests/ -v
test_finding_schema_validates PASSED
test_finding_rejects_bad_severity PASSED
test_diff_contains_the_known_bugs PASSED
3 passed
▶ How this works

These tests check the parts that don't need the model at all — the schema and the fixture — so they run instantly with no API key. Fast, free tests like these guard the foundations every other step stands on.

  1. python -m pytest tests/ -v runs the test suite. -v (verbose) lists each test by name with PASSED or FAILED.
  2. test_finding_schema_validates builds a well-formed Finding and confirms Pydantic accepts it — the happy path works.
  3. test_finding_rejects_bad_severity feeds a severity outside the Literal list and confirms Pydantic rejects it — proving the guard from Step 1 actually bites.
  4. test_diff_contains_the_known_bugs checks the fixture from Step 2 really holds the planted bugs, so the eval has something real to catch.

What the output means: 3 passed — all three green. Because none of these call the API, they pass the same way on any machine, key or no key.

Try this: Temporarily change a severity in schemas.py to allow "urgent" and re-run — test_finding_rejects_bad_severity will fail, showing you the test is genuinely enforcing the rule.

✅ Test cases
TestProves
schema validatesfindings are well-formed
rejects bad severitythe Literal guard works (no junk severities)
fixture has known bugsthe eval has real bugs to catch

6 · Evals (needs key) expert

Step 6
terminalpython evals.py
confirmed findings: 3
  [HIT] known issue: amount
  [HIT] known issue: TypeError
  [HIT] known issue: None
recall on known bugs: 100%
✅ evals passed

Recall is measured against KNOWN_ISSUES; the eval fails if it misses most bugs and warns if it over-reports (a sign the verify step isn't filtering).

▶ How this works

The eval answers the question that decides whether this agent is worth shipping: does it actually catch the bugs? It runs the full review→verify pipeline on the planted diff and scores the confirmed findings against the KNOWN_ISSUES answer key from Step 2. This one needs an API key because it calls the model.

  1. python evals.py runs the whole pipeline once and then grades it.
  2. confirmed findings: 3 is how many bugs survived the verify filter. Each [HIT] known issue: ... line means a planted bug (amount / TypeError / None) was matched by a confirmed finding.
  3. recall on known bugs: 100% means it found all three planted bugs. Recall = of the bugs that exist, what fraction did we catch — the headline quality number.
  4. The note explains the two failure modes the eval watches for: it fails if it misses most bugs (recall too low) and warns if it reports far more than three (a sign the verify step has stopped filtering).

What the output means: A per-bug hit list, a recall percentage, and ✅ evals passed. Together with Step 5's schema tests, this is your evidence the pipeline both works and stays precise.

Try this: After adding a fourth bug in Step 2's 'Try this', re-run — recall is now out of four. This is exactly how you'd grow a real golden set: every bug the agent misses becomes a new case in the eval.

Grow the eval from git historyReal golden set: past PRs where a bug was later fixed. Feed the buggy version, check the agent catches what the fix addressed. Each miss becomes a new case.

Troubleshooting expert

⚠️ Common issues
SymptomFix
Too many findings (noisy)Strengthen VERIFY_SYSTEM ("default to is_real=false"); the verify step is your filter
Misses obvious bugsMake the review prompt over-report; raise effort; ensure the diff is fully in the prompt
Wrong line numbersInclude line context in the diff; ask the model to cite the hunk header
Verify drops real bugsVerifier too strict — soften slightly, or pass more surrounding code as context
ValidationError on severityThe model returned a severity outside the Literal — that's the schema doing its job; the SDK retries

🪜 Practice ladder beginner → industry

Six graded exercises, easy to real-world. Try each before opening its solution.

Exercise 1 · Read-only tools and a diff summaryBeginner

Context: A code-review agent should start with zero blast radius: read-only tools while you build the reasoning, write tools gated behind human merge only later. A reviewer also can't judge a change it can't see.

Your task: Implement read-only tools (get_pr_diff, read_file) as offline fakes and a summarize_diff that reports files and line churn.

Requirements:

  • get_pr_diff and read_file as offline fakes (real API stubbed)
  • summarize_diff reports files touched and lines added/removed
  • It parses a unified diff's +++/+/- markers
  • No tool in this milestone can modify the repo
  • Demonstrate the summary on a sample diff

💡 Hint: Walk the diff lines: +++ names a file, a leading +/- counts an added/removed line.

Show solution

Start read-only — zero blast radius while you build the reasoning. Offline fakes stand in for the API:

DIFF = '''+++ b/app.py
+def add(a, b): return a - b
-def add(a, b): return a + b
+++ b/util.py
+import os'''

def get_pr_diff(pr_id): return DIFF          # real: GitHub/GitLab API
def read_file(path):    return "..."         # real: repo checkout

def summarize_diff(diff):
    files, added, removed = set(), 0, 0
    for line in diff.splitlines():
        if line.startswith("+++"):   files.add(line.split("b/")[-1])
        elif line.startswith("+"):   added += 1
        elif line.startswith("-"):   removed += 1
    return {"files": sorted(files), "added": added, "removed": removed}

print(summarize_diff(get_pr_diff(1)))
# {'files': ['app.py', 'util.py'], 'added': 3, 'removed': 1}

Read-only tools let the agent gather context safely. Write tools (posting comments) come later, gated behind human merge — never let the first version touch the repo.

Exercise 2 · A structured Finding schemaIntermediate

Context: Freeform review text isn't actionable. A typed schema is the contract that lets you pin each comment to file:line and filter by confidence downstream.

Your task: Define Pydantic Finding and Review schemas and a labelled messages.parse call that fills them.

Requirements:

  • Finding has file, line, severity, issue, suggested_fix, and a 0–1 confidence
  • Severity is constrained (e.g. a Literal of low/medium/high)
  • Review holds a summary and a list of findings
  • A hand-built review validates the schema offline
  • The model-fill step is a labelled messages.parse with output_format

💡 Hint: Every finding must carry a line to anchor to and a confidence to threshold on — that's what makes the posting step trivial later.

Show solution

Structured findings are what let you pin comments to file:line and filter by confidence later:

from pydantic import BaseModel, Field
from typing import Literal

class Finding(BaseModel):
    file: str
    line: int
    severity: Literal["low", "medium", "high"]
    issue: str
    suggested_fix: str
    confidence: float = Field(ge=0.0, le=1.0)

class Review(BaseModel):
    summary: str
    findings: list[Finding]

# offline: a hand-built review validates the schema
r = Review(summary="1 bug", findings=[Finding(file="app.py", line=2,
    severity="high", issue="add() subtracts", suggested_fix="return a + b",
    confidence=0.95)])
print(r.findings[0].file, r.findings[0].severity)   # app.py high

# --- needs API key: model fills the schema ---
# Claude:
# r = client.messages.parse(model="claude-opus-4-8", output_format=Review,
#     system="Review this diff. Emit findings with file:line and a fix.",
#     messages=[{"role":"user","content":diff}]).parsed_output
# OpenAI:
# r = client.responses.parse(model="gpt-5.5", text_format=Review,
#     instructions="Review this diff. Emit findings with file:line and a fix.",
#     input=diff).output_parsed

The typed schema is the contract: every finding must have a line to anchor to and a confidence to threshold on, so the posting step downstream is trivial.

Exercise 3 · Two-pass review -> verify to kill false positivesAdvanced

Context: A single review pass over-reports. A second, skeptical verify pass drops indefensible findings — trading a little recall for a lot of precision, because a bot that cries wolf gets muted.

Your task: Add a verify pass that re-checks each finding and keeps only the defensible ones, modelled offline.

Requirements:

  • Each finding is re-checked independently and dropped unless defensible
  • A verdict carries whether it's real and a reason
  • The offline heuristic keeps high-confidence findings with a concrete fix
  • Weak / no-fix findings are dropped
  • Demonstrate that a noisy review shrinks to the defensible findings

💡 Hint: The real version is a second messages.parse asking 'is this a real bug, and why?'; offline, gate on confidence plus a non-empty fix.

Show solution

The verify pass is a second, skeptical look that drops indefensible findings — the precision lever:

class Verdict(BaseModel):
    is_real: bool
    reason: str

def verify(finding):
    # --- real: a second messages.parse asking "is this a real bug? why?" ---
    # offline heuristic: high-confidence + concrete fix survives
    real = finding.confidence >= 0.7 and bool(finding.suggested_fix.strip())
    return Verdict(is_real=real,
                   reason="defensible" if real else "low-confidence/no fix")

def two_pass(review):
    kept = [f for f in review.findings if verify(f).is_real]
    return Review(summary=review.summary, findings=kept)

noisy = Review(summary="", findings=[
    Finding(file="a", line=1, severity="high", issue="bug",
            suggested_fix="fix it", confidence=0.9),
    Finding(file="b", line=2, severity="low", issue="maybe?",
            suggested_fix="", confidence=0.4)])
print(len(two_pass(noisy).findings))   # 1 -> the weak finding is dropped

Reviewers who cry wolf get muted. The verify pass keeps only findings the agent can defend, so the comments developers see are worth reading — precision matters more than recall for a bot.

Exercise 4 · Ground reviews in team conventions via RAGExpert

Context: A generic reviewer flags style the team allows and misses the house rules. Retrieving the team's own conventions lets a finding cite the actual rule, which is far more likely to be actioned than a generic warning.

Your task: Retrieve relevant convention snippets for the changed files and inject them so findings cite the actual rule.

Requirements:

  • A small store of team conventions (logging, errors, SQL, etc.)
  • Retrieval returns the conventions relevant to signals present in the diff
  • Results are capped at top-k
  • A finding can cite the rule name it violates
  • Demonstrate retrieval on a diff that trips two rules

💡 Hint: Match diff content against a per-rule signal (e.g. an f-string into SQL, a bare print() so the reviewer speaks the team's language.

Show solution

Grounding in retrieved conventions turns opinion into "this violates rule X":

CONVENTIONS = {
    "logging": "Use structured logging via log.info(event=..., **kw); no print().",
    "errors":  "Raise typed exceptions; never bare except:.",
    "sql":     "Parameterize queries; never f-string user input into SQL.",
}

def retrieve_conventions(diff_text, k=2):
    hits = []
    for name, rule in CONVENTIONS.items():
        signal = {"logging":"print(", "errors":"except:", "sql":"f\""}[name]
        if signal in diff_text:
            hits.append((name, rule))
    return hits[:k]

diff = 'query = f"select * from t where id={uid}"\nprint("done")'
for name, rule in retrieve_conventions(diff):
    print(f"[{name}] {rule}")
# [sql] Parameterize queries; ...
# [logging] Use structured logging ...

A finding that cites the team's own rule ("violates conventions/sql: parameterize queries") is far more likely to be actioned than a generic "possible SQL injection". Retrieval makes the reviewer speak the team's language.

Exercise 5 · Measure precision/recall on a golden PR setProfessional

Context: Before wiring to a live repo, a bot reviewer must earn trust with numbers — and precision first, because a reviewer that's right half the time gets muted while one that's right 90% gets read.

Your task: Score findings against a golden set of known bugs, report precision and recall, and gate rollout on a precision bar.

Requirements:

  • A golden set of known real bugs keyed by (file, line)
  • Compute precision and recall from true/false positives
  • Report both plus the raw TP / FP counts
  • Gate rollout on a precision bar (e.g. 0.8), failing below it
  • Optimize precision before recall in the framing

💡 Hint: Precision is TP over everything you flagged; recall is TP over the known bugs — a change that lifts recall by tanking precision must fail the gate.

Show solution

A bot reviewer must earn trust with numbers — high precision first, so developers don't learn to ignore it:

GOLD = {("app.py", 2), ("db.py", 40)}          # known real bugs (file,line)

def score(found, gold):
    found = set(found)
    tp = len(found & gold)
    precision = tp / (len(found) or 1)
    recall    = tp / (len(gold) or 1)
    return {"precision": round(precision,2), "recall": round(recall,2),
            "tp": tp, "fp": len(found - gold)}

found = [("app.py", 2), ("app.py", 9)]         # one real, one false positive
m = score(found, GOLD)
print(m)                                       # precision 0.5, recall 0.5
PRECISION_BAR = 0.8
raise SystemExit(0 if m["precision"] >= PRECISION_BAR else 1)   # gate rollout

Optimize precision before recall: a reviewer that's right 90% of the time gets read; one that's right 50% gets muted. Track both each run so a prompt change that boosts recall by tanking precision is caught.

Exercise 6 · Wire to a real repo and post file:line comments safelyIndustry scenario

Context: The agent advises; humans decide. On real PRs it posts file:line comments (or suggests commits) but never crosses the line of merging — that button belongs to a person.

Your task: Wire the write tools (post_review_comment, optional suggest_commit) with a hard rule that the agent never merges.

Requirements:

  • post_review_comment supports a dry-run before real posting
  • The real posting path is labelled (needs API key + repo token)
  • A merge attempt raises a safety error — always
  • The agent may suggest a commit but never merges
  • Demonstrate posting the verified findings and the blocked merge

💡 Hint: Default posting to dry-run so you can eyeball comments first; make merge raise unconditionally as the line the agent never crosses.

Show solution

Writes are gated and merge is off-limits — the agent advises, humans decide:

class SafetyError(PermissionError): pass

def post_review_comment(pr_id, file, line, body, dry_run=True):
    if dry_run:
        return f"[dry-run] {file}:{line} -> {body[:40]}"
    # --- needs API key + repo token: GitHub/GitLab review comment ---
    # gh.pulls.create_review_comment(pr=pr_id, path=file, line=line, body=body)
    return f"posted {file}:{line}"

def merge(*a, **kw):
    raise SafetyError("agent must never merge; humans merge only")

for f in two_pass(noisy).findings:
    print(post_review_comment(1, f.file, f.line,
          f"{f.issue}\n```suggestion\n{f.suggested_fix}\n```"))
try: merge(pr=1)
except SafetyError as e: print("blocked:", e)

Start every PR in dry_run to eyeball the comments, then flip it on for real posting. The merge guard is the line the agent never crosses — it can suggest a commit, but a human owns the merge button.

✓ Checkpoint — done when…

  • The review pass produces structured findings with file:line.
  • The verify pass drops indefensible findings.
  • Schema tests pass with no key.
  • The eval catches the known bugs without a flood of false positives.
📋 Master rubric — grade your review agent
DimensionMeets the barAbove the bar (staff-level)
Recall on real bugsRecall is measured against a known-bug set; the agent flags the seeded defects with file:line references.Recall holds across bug classes (logic, security, resource leaks), not just the easy ones, and misses are triaged rather than ignored.
False-positive rate / precisionThe two-pass review→verify pipeline drops indefensible findings; precision is measured, and noise is low enough that a human trusts the output.Precision is tuned so reviewers act on nearly every comment; the verify pass is independent (fresh context), not the same call rationalising itself.
Safety of any auto-actionThe agent proposes — PR comments and suggested commits — and never merges; a human is always the gate to main.Suggested edits are minimal-diff and reversible; nothing touches CI secrets or runs untrusted code from the diff during review.
Grounding to the diffFindings reference actual files and lines in the PR, not hallucinated code; comments quote the offending lines.The agent distinguishes changed-line issues from pre-existing debt and scopes comments to the diff so it doesn't relitigate the whole repo.
Eval & regression gatePrecision and recall are computed by an eval harness, not by demo vibes; the numbers gate changes to the reviewer.A regression suite fails CI if precision or recall drops, so a prompt tweak can't silently make the reviewer noisier.
Cost & latency per PRPer-PR token/dollar cost and wall-clock are tracked and stay within a per-PR budget.Cost scales with diff size, not repo size (retrieval is scoped); large PRs degrade gracefully instead of timing out or blowing budget.

Score each row 0 (missing) / 1 (meets) / 2 (above). A passing review agent is 9+/12 with safety-of-auto-action at 2 — any path that can merge or mutate main without a human is an automatic fail, and a build whose false-positive rate trains humans to ignore it has failed its one job regardless of recall.

Knowledge check check yourself

✓ Knowledge check

Why does the review agent optimize for precision (a verify step that drops findings it can't defend) rather than maximizing recall?

Show answer
A review full of false positives gets ignored and teams turn it off. Re-checking each candidate finding and dropping anything indefensible keeps the reviewer trustworthy — precision over volume is what makes the tool usable in practice.
✓ Knowledge check

Why is merge_pr blocked and why must run_tests run in a sandbox?

Show answer
The agent proposes (comments, suggested commits) and a human always merges — the propose-don't-push safety model. PR code is untrusted, so running its tests in an isolated sandbox (no secrets, network-restricted) prevents untrusted code from doing harm during review.
© 2026 studybydoing.in · AI Engineering: Zero to Production · All rights reserved. · About · Privacy Policy · Terms · Contact
Educational content, provided as-is and without warranty. Code samples are examples — review, test, and adapt them before using in production. See the Terms of Use & Disclaimer. Use at your own risk.
© studybydoing.in