pinrail

Review the architecture first, then the diff

An agent changed 39 files across four parts of a system. I wanted to review the choices it made, not scroll through them in alphabetical order.

An agent finishes a change. It touched 39 files across a web app, an API, a new background job and the database, and the tests pass. Before it opens a pull request, it asks me to review it. Where do I start?

FIG 1A review of the example change in this post, from the map to the verdict. 81 seconds, narrated, with captions.

It is tempting to judge only the result: the tests pass, the feature works when I click through it, so I approve. I do not think that is enough.

I own the system. If the new export job holds a database connection for ten minutes and the pool runs out at night, I am the one who gets paged, and “the agent wrote it” is not an answer anyone accepts.

The agent’s structural choices compound. Whether exports run in the request or in a job, whether the file is served by the API or straight from the bucket, what state a failed attempt leaves behind: each of these is cheap to change in review and expensive to change once ten more changes build on it. An agent builds on whatever it finds.

Review is also how I keep a working model of my own code. If I approve changes I have not understood, within a few months I am maintaining a system I no longer know. I can ask an agent to explain it, but then I am trusting the same kind of account I chose not to check.

So I want to understand the change. The question is how to do that for every change an agent makes, several times a day.

The usual tool is the pull request diff. For this change, that is 39 files in alphabetical order, from CHANGELOG.md and config/config.exs down to web/src/routes.tsx.

FIG 2The same change as a diff, file by file in alphabetical order. Pinrail offers this view too, as its code review plugin, and for a smaller change it is still the one to use.

A diff is very good at what it was made for: showing exactly which lines changed. For a change that spans several parts of a system, it leaves most of the work to the reviewer.

  • It shows lines, not the shape of the change. The main decision here is that a job builds the file instead of the request. It is spread over a deleted controller, a new module, a worker and a config file, and no hunk states it.
  • The order is the alphabet’s. I read the worker before I have seen what enqueues it, and the migration last.
  • I rebuild the architecture in my head. That model is the most useful thing a review produces, and the tool throws it away. The next reviewer, or I next week, starts from nothing.
  • Interactions, order and lifecycle are not on any line. In this change, a failed export stays running between attempts, and no single line says so.
  • The interface is somewhere else. When the code changes what users see, the review should show what changed on screen, next to the code that changed it.
  • Comments are prose about a location. “The retry in the worker should reset the status” leaves the agent to work out which retry and which status.

None of this is new with agents. A colleague’s 39-file pull request has always been hard to review. What changed is how often it happens.

When a colleague explains a large change well, they do not start with files. They draw the parts that changed, with arrows for what calls what, walk through what happens when a user presses the button, and draw the states of anything with a status. Only then do we open the code, and by then I know what each file is for.

An agent is well placed to produce that walkthrough. It has just made the change, it knows which lines implement which part, and a structured description of it costs it little. The risk is a description that is wrong, or kinder to the change than the code is. So every card and every arrow must point to the lines behind it, the diff must stay one click away, and nothing the description leaves out may disappear.

The change map is that review, built as a plugin for Pinrail. The agent submits its map with pinrail submit and waits; I review it in a view made for the map, and my decision goes back to the agent as structured data. We deserve a better agent-human interaction explains why every kind of review in Pinrail gets a view of its own.

The example throughout is a made-up helpdesk app whose CSV export moves from the request into a background job: 39 files across a React front end, a Phoenix API, an Oban worker and S3.

The agent describes the parts the change touches, groups them into lanes, and connects them with the interactions between them. Each card’s header names the part’s kind and its status, new, changed or removed, and a bar under its name shows how many lines it adds and removes, so the large parts stand out before I open anything. Each interaction is labelled with what it does and how many files implement it. A label without a count has no code behind it.

FIG 3The map of the change. The request path runs from left to right, and the job sits below the API it is started from. Exports, with 171 new lines, is the largest part.

Before opening a file, I can see the shape: the report page calls a new endpoint, which queues a job, which streams rows from Reports into Storage, and the old CSV endpoint is gone. That red card is my first comment. In the diff, it is a deleted controller among 39 files. On the map, it is a public endpoint about to disappear, and I happen to know a nightly script still calls it.

FIG 4The review in the Pinrail app. Selecting a part shows the agent's summary of it, its files, its interactions and its comments.

Selecting a part or an interaction shows the agent’s summary, its files and line ranges, and the diagrams it appears in. The diff drawer under the map follows the selection, at the lines that implement it.

FIG 5With the drawer open, each selection opens its own lines: the Storage module, the Reports module, then the interaction between the worker and Reports.

I still read the code, but in the order of the architecture, and I know what each hunk is meant to do before I read it. When a hunk does not match the summary on its card, that mismatch is usually the comment.

A map is the agent’s account of the change, and an account can leave things out. The view counts the changed lines the map covers, 825 of 922 here, and marks the rest with an amber edge in the drawer. Choosing a file in the overview lights up whatever touches it.

FIG 6A file chosen in the overview. Reports is the only part that touches it, and 11 of its changed lines are on no part, which the amber edges mark.

Most uncovered lines are noise, such as documentation, fixtures or a config entry. The few that are not usually belong to a part the agent drew too narrowly, and that is where I look first. The agent’s skill runs the same check before it submits.

A comment goes on a part, an interaction, a line or a range of lines. Review is no longer confined to lines: I can comment on a part of the architecture, or on how two parts relate, as precisely as on a line of code. Badges on the cards, the tabs and the files show where the discussion is.

FIG 7A comment on a range of lines. The transaction that Repo.stream needs keeps a database connection checked out for the whole upload.

When a change touches the interface, the agent can attach screenshots of each screen before and after, or a recording when the change is about behaviour over time. I comment on them directly: on an area of a screenshot, or on a frame of the recording.

FIG 8The report page before and after pressing Export CSV. The comment is on the toast that the change adds.
FIG 9A recording of pressing Export CSV, paused when the toast appears, with a comment on the toast in that frame.

When the order of calls matters, the agent may add a sequence diagram, and every message links to the lines that send it.

FIG 10How the worker writes the file. The selected message is the retry, and its summary says what the diff only implies.

When the change adds a lifecycle, the agent may add a state diagram. Here it shows the bug from earlier at a glance: a failed attempt goes from running back to running, so the Exports page shows a spinner while Oban waits to retry.

FIG 11The lifecycle of an export, with a comment on the transition that a failed attempt takes.

A participant or a message that stands for a part or an interaction of the map is the same entity, with the same files, comments and reviewed mark, so nothing is reviewed twice.

FIG 12ExportWorker marked as reviewed in a sequence diagram is marked on the map too.

As I read, I mark each part as reviewed, and on a large change the header’s count is how I know what is left. The marks go back with the decision, so the agent knows what I examined and what I took on trust.

Then I approve or request changes. Approving with comments turns them into notes rather than requests.

Every comment comes back with its target: a part, an interaction, lines of a file, an area of a screenshot, a moment of a recording, or an entity of a diagram. With --json, the agent gets the decision as JSON, checked against the plugin’s schema. Without it, the agent reads Markdown, with each target named the way its own payload names it.

{
"verdict": "request_changes",
"comments": [
{
"target": { "node": "csv-endpoint" },
"body": "Keep this endpoint for one more release, with a Deprecation header. The finance team's nightly script still calls it."
},
{
"target": { "file": "lib/helpdesk/workers/export_worker.ex", "side": "new", "start_line": 40, "line": 49 },
"body": "This holds a database connection for the whole upload, up to ten minutes. Read the rows in keyset pages instead."
},
{
"target": {
"screen": { "node": "reports-page", "index": 0, "image": "after" },
"region": { "x": 0.622, "y": 0.773, "width": 0.365, "height": 0.205 }
},
"body": "Link to this export, not to the whole list."
},
{
"target": { "diagram": "lifecycle", "item": "t3" },
"body": "Set it back to queued between attempts, or the page shows a spinner for the whole backoff."
}
],
"reviewed": ["exports-context"]
}
**Changes requested**: Move report exports to a background job
## Comments
1. **Part** *GET /reports/:id/csv*
Keep this endpoint for one more release, with a Deprecation header. The finance team's nightly script still calls it.
2. `lib/helpdesk/workers/export_worker.ex:40–49`
This holds a database connection for the whole upload, up to ten minutes. Read the rows in keyset pages instead.
3. **Screenshot** *After pressing Export CSV* of *Report page*, after, the area at 62% across and 77% down, 37% × 21% of the image
Link to this export, not to the whole list.
4. **Transition** *upload fails, attempts left / retry* in the state diagram *An export's status*
Set it back to queued between attempts, or the page shows a spinner for the whole backoff.
Reviewed: Exports

The agent does not have to guess what “the retry” or “the toast” means. It applies the comments and submits the next round with --revises.

One line, in whatever file the agent already follows:

AGENTS.md
When a change is complex, submit it for review with the `change-map` Pinrail plugin.
Otherwise, use the `code-review` plugin.

The rest ships with the plugin, as a skill Pinrail installs for the agent: how to find the parts and interactions of a change, pick the line ranges, capture the interface, decide when a diagram helps, check what the map leaves out, and act on my decision. Pinrail validates the payload before it reaches my inbox, so a malformed map goes back to the agent, not to me.

Drawing a change is not a new idea, and several tools now do it for agents’ work:

  • Archify has the agent write a typed JSON map of a system and renders it as an HTML diagram, with nodes linked to files and line ranges and a before-and-after comparison of two snapshots. It documents a system and how it changed, rather than collecting a decision on it.
  • Graphify builds a knowledge graph of a codebase for agents to query, and uses it to rank a queue of pull requests and flag the ones that touch the same parts. It helps the agent and the queue rather than the reading of one change.
  • PR Lens draws each pull request as architecture and data-flow diagrams, posted as a comment on the pull request and updated on every push.
  • Review bots such as CodeRabbit and Greptile also build a graph of the codebase, but they use it to write their own comments, which is a different job.

In these tools the diagram is a picture of the change. In the change map it is where the review happens. Every comment, reviewed mark and verdict is attached to a part, an interaction, a line, an area of a screenshot or an entity of a diagram, and goes back to the agent as structured data that it applies in the next round. The map is also checked against the diff, so every changed line it leaves out is marked.

The main difference is the order in which I review. I look at the shape of the change first, decide which parts deserve a close reading, and read their code in the order the system runs it. In the example, each of the three structural problems shows on the map or in a diagram before I read most of the diff: the removed endpoint, the long transaction and the stale status. The diff is where I confirm them. My comments are shorter too, because each one is attached to what it is about.

  • The map is still the agent’s account. The view marks every changed line the map leaves out, but it cannot check that a summary describes its code correctly. Reading the code of the parts that matter is still my job.
  • Writing a map is extra work for the agent. For a three-file fix, it costs more than it saves. Tell the agent to use a diff for such changes, as in the instruction above: the right view for the job.
  • A new round does not show the last one. A revision does not yet show my earlier comments beside the parts they were on, or what changed in the map between rounds.
  • The layout is automatic. A change with many crossing interactions can still produce a busy map. Lanes that follow the direction of the calls help.

The change map is one of the official plugins that come with Pinrail. Install it in Settings › Plugins, and press Send a sample in its details to open a smaller change than this one.

FIG 13The change map in Settings › Plugins. Its skill is off until you turn it on, and Send a sample opens a review to try it.

The plugin is open source, in the Pinrail repository: a manifest, two JSON schemas, a Markdown template and a React view. If your reviews need a different view, for a migration, an infrastructure plan or anything else, an agent can build one with you. Build the review view your work needs follows one from the first request to a real review, and Writing a plugin covers every part in detail.