Topic 03

Small Steps and Checkpoints

Specification

The refund change is one task, and it should be several changes. Each one small enough to read in one sitting, each one ending green and committed. Then a wrong step costs one discard instead of an afternoon of untangling, and every diff you read answers one question.

This page defines the size this book calls reviewable, cuts the refund change into three steps of that size, and explains why the order of the steps matters as much as their size.

Reviewable Size, Defined

A change is reviewable when you can state its one intent in a sentence, read every changed line in one sitting without skimming, and name the tests that show it works. Cragmoor's team adds a rule of thumb: split anything over about 200 changed lines, or anything that changes the behaviour of more than one module. That number is a local team rule, not a law; another team's might be 100 or 400. What makes it useful is that it exists, so nobody has to argue about size one pull request at a time.

"One sitting" is the part of the definition that matters most, and it is about attention rather than length. Somewhere in a long read, every reader starts checking lines against a vague memory of the change's intent instead of the intent itself, and nobody notices the moment it happens. A change that cannot be read inside that span is read in two halves, and the bugs that live in how the halves fit together are the ones nobody sees.

Large mechanical changes, such as a rename across 300 files, are reviewed differently, by sample and by tests, and Chapter 11 covers them. The rule here is for changes whose every line carries meaning.

Signs a Change Is Too Big

The summary needs "and also". You catch yourself skimming. It touches a module the task did not name. You cannot say which test covers which part. Any one of these is the moment to split, because the bugs in a large diff sit in the part you skimmed, and Chapter 7 shows a rounding rule changing quietly inside exactly such a part.

Splitting the Refund Change

Step one adds tests for the current 24-hour rule an hour either side of the line, at 23 and 25 hours before a class, so the next step has something to stay green against. Step two replaces the two copies of 24 in refunds and booking with one shared value that both read, and changes no behaviour at all. Step three makes the value 12, moves the tests that encoded 24 with it, and adds as tests the three rows of the next topic's example table that sit at the new line. Each step is one pass of the loop from Chapter 2, and each ends in its own commit.

The order is not arbitrary. Step one's tests are written against code nobody has touched yet, so they pin what the system does today, not what anyone hopes it does. Step two then has a net: if the refactor changes any refund, step one's tests go red. Step three changes the rule on purpose, and the tests that move with it are the ones encoding the old rule, nothing else. Each step's diff is read against a commit that was green, which is what makes it readable at all.

The refund change as three commits
1. Pin today's ruletests only, about ten lines
→
2. One shared valueno behaviour change
→
3. 24 becomes 12the behaviour change
The refund change as three commits, newest first
$ git log --format=%s main..
Shorten the full-refund window to 12 hours
Use one refund window for refunds and the cancellation screen
Pin the 24-hour refund rule an hour either side of the line

Read the sizes: about ten lines, about fifteen, and for step three six changed lines in the rule, one short test for each of the table's three rows at the line, and the two places members read the window, the cancellation email and the schedule page, which the plan missed and the review in Chapter 6 finds. None of them takes more than a few minutes to read, and each has one sentence for its intent. The same work as one change would still be small, but its diff would mix new tests, a refactor and a rule change, and every line would need you to work out which of the three it belonged to.

Checkpoints

A checkpoint is a commit at a green state between steps. It is the point a discard returns to (Chapter 9), the base the next step's diff is read against, and the unit a later search through history examines when something breaks (Chapter 10). A step without a checkpoint merges into the next one, and from then on the two can no longer be reviewed or reverted apart.

The payoff comes when a step goes wrong, as step three does in Chapter 6. Because step two ended in a commit, throwing away step three's diff costs one command and loses nothing that was already right. Without that commit, the shared value from step two and the rule change from step three would sit in one working tree, and discarding the bad half would mean picking it out line by line.

Behaviour-Preserving First

Doing the refactor, step two, before the behaviour change, step three, means each diff answers exactly one question. Step two's diff answers "did anything change?", and the answer must be no: every deadline and refund the tests expect stays as it was. Step three's diff answers "did the right thing change?", and it is a handful of lines plus the tests that say what "right" means. Mixing them gives a diff in which every line might be either kind, and that mixture is how a rounding rule or a boundary changes inside a refactor without anyone deciding it should.

Asking for Steps

Give the agent the steps, or tell it to stop after each: "do step one only, run the suite, stop." Left to finish the whole task, it produces one large diff, because nothing in its loop rewards stopping early: the loop from Chapter 1 ends when the model judges the task complete, and a task is not complete after step one. Stopping points are something you ask for, every time.

Agent session: asking for one step
you    Step two only. Replace the two copies of the
       24-hour window in refunds and booking with one
       value that both read. No behaviour change: every
       deadline and refund the tests expect stays the
       same. Run the full suite, show me the result,
       and stop. Do not start step three.

The message names the step, the files, the constraint that makes it checkable ("every deadline and refund the tests expect stays the same") and the stopping point. It does not say how to write the shared value; the plan already agreed that.

Between steps, commit, and consider starting the next step in a fresh session with a short note, as Chapter 4 recommends. Each session then holds one step's worth of context.

Commit vs a Tool Checkpoint

A tool checkpoint: as of 2026 several agents snapshot their own file edits and can rewind them. The snapshot belongs to one session's history, covers the files the agent edited, and does not undo what commands did, such as a migration applied or a package installed. Use it for a quick undo inside a step.

A commit sits in the repository's history, covers the whole tree, survives the session, and can be shared, reviewed and searched. Make every checkpoint between steps a commit.

Misconceptions
  • "One task should be one commit." One task is one branch (Chapter 9). Inside it, one commit per intent is what makes the history readable and searchable.
  • "Small steps mean more overhead." Each step costs a short instruction and a commit. One large diff costs a long read with a lower chance of catching the bug.
  • "If the tests pass, size doesn't matter." Tests check what they assert, and reading checks the rest. Kai's 1,400-line waitlist passed its tests and was never read (Chapter 2).
  • "The agent naturally works in small steps." It works until it judges the task done. Stopping points are something you ask for.
  • "The refactor and the behaviour change belong together because they are one task." Together they make a diff in which every line might be either, and you cannot tell which.
Why It Matters
  • Keep each step to one intent you can state in a sentence. The one-sentence test is quicker than counting lines.
  • Split anything you catch yourself skimming. Skimmed lines are unreviewed lines.
  • Make behaviour-preserving changes before behaviour changes, in separate commits. Each diff then answers one question.
  • Commit at every green step before asking for the next. The commit is the checkpoint a discard returns to.

Knowledge Check

A task is "make the refund window 12 hours, and while you're there, tidy the duplicated deadline code". How should it be split?

  • One step, because both parts touch the same two modules and the same tests
  • Change the rule first, then tidy the code, so the visible fix ships sooner
  • Tidy first with no behaviour change, commit, then change the rule and commit
  • Two branches, one per part, merged in whichever order review finishes first

A diff changes the refund boundary and also restructures the deadline code. What goes wrong when you review it?

  • Nothing, if the full suite passes on the final result of the combined diff
  • The diff is too large for git to display, so parts of it are silently hidden
  • Every line might be a restructure or a rule change, and you cannot tell which
  • The agent cannot run the tests for a diff that mixes two different intents

The agent ran a database migration during a step, and you rewind with the tool's checkpoint. What is the state afterwards?

  • The agent's file edits are undone, but the migration stays applied to the database
  • Both the files and the database are back to how they were before the step
  • Nothing is undone, because a checkpoint only records changes and cannot restore them
  • The rewind is refused until the migration has been reversed by hand

Which is the clearest sign that a diff in front of you is too big?

  • It is longer than 50 lines in total, counting every file the agent touched
  • It contains changes to tests as well as to the production code they cover
  • It was written by the agent in a single run without you stopping it
  • You notice you are skimming, and its summary needs "and also" to cover it

You got correct