In my previous post I argued that the human code review has become the bottleneck in our software delivery process, and that most of what a review used to buy us no longer fits in a diff. But if we stop reading every generated line, when does a human ever look at this code, and what should they be doing when they do?

Somebody still has to understand that code. It was written fast, it passes a demo, and nobody on the team can explain it. Before anyone is willing to put it on production, it needs a review and often by two people. Letting others review code that AI wrote for you is not a good experience, as you may have noticed.

I want to make it a ceremony. Same slot every week, a fixed length, a defined output, like a retrospective. I call it a Grokathon. To grok, from Heinlein’s Stranger in a Strange Land, is to understand something so thoroughly that it becomes part of you. The word is sixty-five years old and predates the chatbot it now shares a name with. A hackathon is a race to produce code nobody understands yet. A Grokathon is the opposite ritual on the same code: nobody adds a feature, everybody has to be able to explain one.

You understand a codebase by cleaning it up

Ask anyone who has inherited a codebase how they got to know it. Nobody says they read it. They renamed something, moved a file, deleted a function to see what broke, and after a week of that they knew where everything was. You learn a system by putting your hands in it. Reading is what you do when you are not allowed to touch.

A generated codebase arrives all at once. It runs, the demo looks great, and nobody has ever been through it. Three helpers do the same thing, there is a config option nobody sets, there are defensive branches for conditions that cannot happen, and somewhere in there are the two functions that actually run in production. From the outside it all looks the same.

The ceremony is simple. Everybody takes a piece of it and cleans that piece up by hand. Five rounds, twelve minutes each, and every round is one ordinary refactoring that you already know how to do.

The ceremony

Ninety minutes a week, in the slot you were already spending on reviewing generated pull requests. Two to six people, at least in pairs. The subject is one batch of generated code that has already merged, not a pull request. Everybody clones the same repo, and each pair takes one file or module and works on its own branch.

Never alone, and not because you need the help. It is the rubber duck. Renaming a variable quietly is easy. Saying out loud “this one is called data and it holds the invoices we have not sent yet” is the moment you find out you were guessing. Hunt and Thomas had programmers explain their code to a rubber duck for exactly this reason, and a colleague is a duck that occasionally says “are you sure about that”.

So there is one procedural rule: you may not tidy silently. Say what you are about to do and why, before you do it. That single rule is the difference between four people cleaning up code and a team learning a system, and in a pair it mostly takes care of itself.

Two more rules and then the timer. You never take the code you prompted into existence yourself, because you are too kind to your own output and because the point of the session is that this code stops belonging to whoever generated it. And no scoreboard. The only number worth saying out loud is how many things you deleted without the tests noticing, and a high number is bad news for the team, which is exactly why it is the useful one.

Time What
0:00 Divide the work (10 min)
0:10 Delete (12 min)
0:22 Rename (12 min)
0:34 Normalize (12 min)
0:46 Align (12 min)
0:58 Test (12 min)
1:10 Walkthrough (20 min)

The walkthrough is two minutes per pair: what you deleted, what you renamed, what got past the tests, and what your piece actually does now that you have been through it. Then anything that showed up at more than one desk goes into the rules that produce the code, which is the standards file, the agent skill, the linter config or the prompt. That change is a normal pull request with a normal human review, because a rule that applies to the whole codebase has to be right.

This is where architectural guidelines should have been coming from all along. Not declared in a meeting by whoever had the strongest opinion, but found by hand, by people who just spent an hour with their fingers in the code. A guideline that comes out of a walkthrough arrives with three examples attached, and nobody has to be convinced that it matters.

It runs fine on a video call and a git remote, which is where most teams already are. One repo, one branch per pair, no pull requests during the session, and a dev container or a single docker compose run test so nobody spends the first round installing a toolchain.

Delete, rename, normalize, align, test

Five rounds, twelve minutes each, and the trick is that understanding is a side effect of all five. You are not trying to understand the code. You are trying to make it neat, and the understanding arrives on its own.

The examples below are not hypothetical. They come from a product we are building right now, where most of the code is generated, the review is ours and the standards live in ADRs. We have run four sessions so far, on Thursday, together, in the slot the code review would otherwise have taken. One caveat so you can judge the rest: the rounds ran looser than the timetable above. More on that below.

Delete. Anything nothing needs: the third helper that does what the first two already did, the option nobody sets, the branch for a condition that cannot happen, the abstraction with one implementation. Remove it, push it, and let CI tell you what you learned. Red means you just found out what it was for, which is worth more than reading it would have been. Green means one of two things: the code was dead and it stays out, or the tests are blind and you just found a hole. Write the hole down, you will need it in an hour.

Then do the same to the comments. Generated code narrates itself, line by line, in a tone of relentless helpfulness. Delete every comment that says what the code already says, and keep the ones that say why. There will be very few, because the generator did not have a why. What survives that cut is worth reading, which was not true of the file you started with.

The biggest thing we deleted was not code. Building v1 we held every feature up against the question of whether v1 needed it, and a cleanup sweeper for stale data lost that argument and moved to v2. A diff review never asks whether a feature should exist, because by the time somebody is reading the diff that question is long settled. This round asks it about everything you touch.

Rename. Name things after what they hold. Generated code is full of data, result, processedItems and handleData, names that describe the conversation the model was having rather than the value in the box. You cannot rename something correctly without knowing what it does, which makes this the cheapest way to find out that you do not.

Two of ours: a variable called calculated became calculatedScores, and a function called ensure became readOrStore. Both moves go against the advice you usually get. The first is redundant, because scores were the only thing around there that could be calculated. The second is longer and duller, and it trades a nice metaphor for a flat description of the two things the function does. Redundant and dull is where this round kept ending up, because the next reader has to work out what the thing is without asking anybody, and the next reader is often a machine.

Normalize. Normalize symmetries, in Kent Beck’s phrase: make things that do the same job look the same, and things that do different jobs look different. Three functions handle a missing record in three shapes, one returning null, one throwing, one handing back a tuple. Generated code has almost no symmetry, because every piece was written in a separate conversation without seeing the others. Doing this straight after the renaming helps, because consistent names are what make the inconsistent shapes visible.

In practice our asymmetries were less obvious than three return shapes. They were responsibilities: the same job sitting in a method of its own in one place and inlined in another, and an architectural pattern that the surrounding code follows and this one piece quietly skips.

Align. Every team has a shape it wants its code to have: which layer talks to the database, where validation happens, how errors travel, what a module looks like from the outside. Your piece does not have that shape, because the generator never read that document, or read it once and drifted. Move it back. This is also the round that tells you whether your guidelines are real, because if you cannot say what the rule is for the code in front of you, that is not a failure of yours, it is the finding, and it goes to the walkthrough.

Held against real code, a fair number of clauses in our ADRs turned out to be impractical, and we rewrote them. That felt wrong for about a minute and then it stopped, because a rule you cannot follow in the code as it exists is not a standard, it is a wish. Rewriting the clause is what makes the ADR usable again, and the ADR is what the next agent reads.

Test. Every hole the deleting exposed gets a test. Write the one that fails when you take that code out again. Not coverage, not a green wall, just the test that would have caught the thing that got past the suite an hour ago. Then take it out one more time and watch it go red, because a test you have never seen fail is a decoration.

We ended up writing our doubts down in TODOs as we read, and this round worked from that list rather than from coverage. If anybody was unsure whether a behaviour was right, that was the test to write. Doubt is easy to put into words while you are looking at the code and gone by the next day, so it has to be written down on the spot.

Yes, the tests come last, and normally I would argue the opposite. Here the missing tests are what we are trying to discover, so the first four rounds use the suite you have as an oracle and the last one repays what it could not tell you.

The rounds do not run in order

That is the part I had wrong. On paper the five rounds are a sequence and the timetable above still says so, because ninety minutes needs a structure and the order is a good default: renaming makes asymmetry visible, aligning tells you what is worth testing. In the code it does not behave. You are renaming something and you spot the branch that cannot fire. You are deleting a comment and you find the clause in the ADR that never made sense. The finding arrives when it arrives, and it is almost never for the round you are in.

What worked for us is a TODO in the code at that exact spot, one line, with the applicable round and what you doubted or still (may) want to fix. Then you go back to the round you are in. The next round picks the TODOs up, and whatever nobody picks up goes to the walkthrough, because a doubt that survived five passes is worth the whole table’s attention. It also handles the good idea you get two minutes into a twelve minute round, which otherwise either derails the round or is gone by the end of it.

So treat the rounds as five passes over the same code with a different question each time, rather than five jobs to finish in turn. The timer exists to make you move on before you disappear into one file.

Why this can replace the code review

Sam Newman lists four goals of a code review, and this session serves all four better than reading a diff does. Correctness is checked by the deleting and the testing, which try the test suite itself, the thing that will still be checking correctness next month. Shared learning is the entire session, out loud, in both directions, where a pull request comment teaches one author. Alignment comes from the guidelines the walk produces, which is the only mechanism here that scales, because it changes the generator instead of the output. And awareness follows from everyone having their hands in code they did not write.

The merge gate does not change. Tests green plus policy satisfied, decided by something deterministic, exactly as before. All that changes is what the people do.

That is the argument. What I can report is narrower. We have run four of these now and I cannot tell you they were faster, more thorough or better than the reviews they replaced. I did not measure any of that, and I do not believe the faster part anyway, because ninety minutes a week is ninety minutes a week.

What I can tell you is that they were more fun, and after four of them I have stopped treating that as a side benefit. The reason is ownership. A code review asks somebody to approve, which means putting your name under code you did not write. Defending a diff that a machine wrote is not fun. This session asks nobody to claim anything up front. You take a piece, you clean it up, you say out loud what it does, and by the end it belongs to the team instead of to whoever prompted it. Fiddling with the code together to make it nicer and understand it better is why people took the job.

Tidy First? was a question, and the answer changed

None of these moves are mine. Kent Beck names them in Tidy First?. Delete redundant comments and normalize symmetries are in his list almost word for word, and explaining variables is the renaming round.

But the title of that book is a question, and the answer is the interesting part. Beck does not tell you to tidy, he tells you when it pays. His case rests on two things. It is social: a person wrote this code and a person will read it next, so structure is a courtesy between colleagues. And it is economic: tidying costs your hours now and pays them back on the next change, so you tidy first when that change gets cheaper by more than the tidying costs. That is why the book also has chapters called Tidy Later and Tidy Never.

Neither is true anymore. Nobody wrote this code, so there is no author to be courteous to and no colleague who was going to read it. And nobody paid for it, so nobody will pay for the mechanical rewrite either, because that is precisely the work the machine now does for nothing.

His answer was “it depends”, and the things it depended on are gone. That does not make tidying less important. It makes the case for it completely different, and a lot shorter:

  1. It is the only way in. No author to ask, no commit message that explains a decision, no colleague who remembers the trade-off. Reading does not work at this volume, and asking the model gives you a plausible answer rather than a true one. Renaming a variable correctly is a test of understanding you cannot pass by accident.
  2. The code is the prompt. Structure used to be for the humans who would read the code later. Today it is the context the next agent reads before it writes. Tidy code generates better code and untidy code generates worse code, on its own output, every day. Beck’s economics had no path from the structure back into the thing that produces structure. Now there is one, and it compounds in both directions.
  3. Asymmetry marks the seams. He normalized symmetries because symmetric code is easier to read. In generated code, two things that do the same job in two different shapes were written in two different conversations, neither of which saw the other. That is where the misunderstandings live, so normalizing symmetries stopped being a matter of taste and became a search technique.
  4. Nothing slows coupling down anymore. Constantine’s equivalence still holds, and Beck could take for granted that coupling accumulates slowly because writing code is slow. A thousand coupled lines is a minute of work now, and removing them costs exactly what it always did.
  5. It is how you take ownership of code you did not write. You cannot be responsible for something you have never touched, and somebody has to be. That one is not an economic argument at all, which is rather the point.

What still holds is the discipline. Structure and behavior never go in the same commit, which is why a Grokathon branch is structural from start to finish and safe to merge the same day. Batches stay small. And Tidy Never is still a real answer, which is why you do not run this on a prototype you are about to throw away.

Tidying is deciding, not editing

If applying a tidying is free now, the obvious move is to hand all five rounds to the agent and have a clean module in ninety seconds. It would do a decent job. And you would have paid for the session and received nothing, because the output of a Grokathon was never the diff.

Which means it is worth saying what tidying actually is. We have been sloppy about that for as long as it did not matter.

Every piece of work produces two things: the artifact, and the change in the person who made it. We only ever pay for the first. The second is a side effect nobody measures, and that was fine, because for most work it is a bonus. Automation is the trade where you keep the artifact and give up the side effect, and for most work that trade is obviously worth taking.

Tidying is the exception, because for tidying the side effect is the product. Which gives you a test you can apply to any task, not just this one. Throw away the diff and see what is left. If nothing is left, delegate it. If what is left is a person who knows something they did not know this morning, that is tidying, and handing it over buys you nothing at all.

Put it the other way around. The code does not need tidying. You do. Sending the agent to tidy for you is sending somebody else to the gym and asking them how it went.

That is not an argument against using the agent during the session. It is an argument about which half of the work you give it, because every one of the five rounds splits into a decision and an edit.

  • It can rename every occurrence in the repository. It cannot choose the name, and choosing the name is the entire exercise.
  • It can delete the comments that repeat the code. It cannot tell you which one was the only place a decision ever got written down.
  • It can make three functions symmetric. It cannot notice that the asymmetry was trying to tell you something.
  • It can apply your architectural guidelines. It cannot tell you that a guideline is wrong, which is the main thing the align round is for.
  • It can write the test. It cannot tell you which behavior was worth pinning down in the first place.

So the rule for the ninety minutes is not “no agents”. It is that you may let it type and you may not let it decide. “Rename data to pendingInvoices everywhere in this file” is fine, and honestly a better use of everyone’s time than doing it by hand. “Clean up this file”, “suggest better names” and “make this consistent” are not requests for help. They are the session, and if you ask for them you have skipped it.

That is the whole ceremony, and it is the reason it survives a tool that can do everything else. Understanding is not something you can schedule directly, but deleting and renaming and testing are, and they reliably produce the other. You are still responsible for what you ship, and now there is an hour and a half in the week where you earn the right to say so.