Spec, plan, execute, verify, and the four places drift gets in
Drift is the gap between what the spec says and what the code does. It enters at four identifiable moments in the loop, each with a detector that costs under a minute.
Learnings from a No-Code Library: Keeping the Spec Driven Development Triangle in Sync
The loop you ran yesterday, drawn as a loop
Yesterday's plan, execute, verify ran as a line, and you stood at its end. Draw it as a ring, with the verified result feeding back into the spec, and the question changes from "did it work", which the test command answers, to "where can spec and code come apart unnoticed".
Böckeler's levels place today. Days 1 and 2 run spec-first, where the spec drives the work and may then go stale, not spec-anchored, where it is kept as the feature evolves. At spec-first nothing after the run compares the spec with the code, so the comparison has to happen inside the run, and that is where today works.
What drift actually is
Drift is not sabotage or a weak model. It is bookkeeping: the spec says four things, the code does three and a half, and no step in the run had to compare them. It shows up as a merged pull request that passes every test it ships with and still misses the ticket.
Drift point one: spec to plan
The plan quietly reinterprets a criterion, or covers three of four. The detector is Day 1's criterion-to-command grid, read against the plan before you approve it: every numbered criterion needs a row, and a row whose command would pass with the criterion unmet is a criterion the plan is not promising.
In Change B, the spec you run in Exercise 1, criterion 4 wants totals per currency, ordered by currency code ascending. A plan that tests keeping currencies apart and ignores the ordering has dropped half a criterion; the grid shows it in twenty seconds.
Drift point two: plan to diff
The agent changes a file the plan never named, for a reason that reads well in the transcript.
The detector is a file list: what differs from the shipped sandbox/, held against the
spec's bounds. A file outside them may be needed, but then the plan or the bounds were incomplete,
and you say which before merging. Exercise 1 gives you the command and has you run it.
Drift point three: diff to tests
Green means less when the tests moved in the same diff as the code. Two questions settle it: did any existing assertion change, and does each criterion have a test that fails without its implementation?
The second has a cheap physical version: remove the line that implements a behavior, run the
suite, and watch a named test go red. If nothing goes red, that behavior has no sensor. This probe
is the one thing in this lesson to run now. Try it in a throwaway copy, never in
sandbox/, which Exercise 1 copies. From the course root:
cp -R sandbox/swift day2-probe-work && cd day2-probe-work
Expect several tests to go red, not one; the one to find is
testThousandsAreGroupedWithCommas. On line 24 of
Sources/LedgerKit/TransactionFormatter.swift, change grouped(wholeUnits),
at the end of the var text = … line, to String(wholeUnits), then run
swift test. Unchanged, green is the line
Executed 34 tests, with 1 test skipped and 0 failures (0 unexpected) in … seconds.
It is not the last line: the Swift Testing summary prints after it and reads
Test run with 0 tests in 0 suites passed either way, so look a few lines up for the
line starting Executed.
swift test
cp -R sandbox/kotlin day2-probe-work && cd day2-probe-work
Expect several tests to go red, not one; the one to find is thousands are grouped with
commas in the integer part only. On line 33 of
src/main/kotlin/ledgerkit/TransactionFormatter.kt, change
val grouped = group(whole) to val grouped = whole, then run
./gradlew test. Unchanged, green is the last line, BUILD SUCCESSFUL in ….
./gradlew test
Then remove the copy, edit and all:
cd .. && rm -rf day2-probe-work
Of the red tests, read the one whose name says what broke, since a green line never names the criterion it checked. Exercise 2 runs a suite written against the spec over the tree Exercise 1 leaves; each red test names its criterion.
Drift point four: run to run
Tomorrow's session starts with none of today's conversation. Before your first message it
loads only the startup set, your CLAUDE.md files among them, and auto memory where
it is on (the course lanes turn it off, so each day starts from its own files alone), so a
decision that lives only in the transcript gets re-derived, differently. The detector is
structural: the decision lives in a file. A spec. A line in
CLAUDE.md, which Claude Code reads from ./CLAUDE.md or
./.claude/CLAUDE.md. A rule file at .claude/rules/<name>.md, whose
frontmatter paths: glob list scopes it to the files it concerns.
Guides and sensors
Böckeler's other split is the frame worth keeping. Guides feed instructions
forward: CLAUDE.md, rule files, the spec itself. Sensors report
back: tests, linters, the diff, a review pass. The first three detectors are sensors: each one
reports back on a run. The fourth is a guide, so it only works if the next session reads the file
and follows it, which is why the cheaper fix, when one exists, is a sensor.
Most drift complaints arrive as requests for more guidance when what is missing is a sensor:
a CLAUDE.md line spends context every session and depends on being
followed, while an assertion fails loudly. A useful team norm: when someone asks for a new rule,
ask first what would have gone red.
When drift does not matter
For a one-file spike you will read end to end and probably delete, the detectors cost more than the mistake they prevent: drift needs someone downstream to be surprised. Skip them on purpose and say so. A spike stops being one the moment a second person will read it, so start the detectors there.
Quick check
The tests are green, the diff is small, and criterion 3 was never implemented. Which detector should have caught it, and at which step?
The criterion-to-command grid, read against the plan before any code was written: criterion 3 had no row naming a command that would fail without it, so nothing in the run was ever obliged to deliver it. Point three's detector also fits, later: no test fails for want of criterion 3, which is why the suite is green. Either answer is right; the grid is the cheaper catch because it comes before any code.
Your agent changed a file the plan never mentioned. Why is that a signal rather than a tidy-up you can wave through?
Because the bounds are part of the contract you are holding the result against. A file outside them means the plan was incomplete or the bounds were wrong, and until you decide which, you cannot tell whether the extra change was necessary or invented.
Sources
Claude Code documentation accessed 2026-09-22 against version 2.1.280, and 2026-09-23 for
cc-core-44.
cc-core-08,cc-core-15— the project instruction-file paths, and.claude/rules/<name>.mdwith apaths:frontmatter list, from Manage Claude's memory.cc-core-36— what loads into the context window at session start, from Explore the context window.cc-core-44— auto memory is one folder per git repository, andautoMemoryEnabledturns it off for one project, from Manage Claude's memory.practitioners-04— the split between guides, which feed instructions forward, and sensors, which feed results back, from Birgitta Böckeler on harness engineering, SE Radio 730 (2026-07-22, accessed 2026-09-22).practitioners-05— the three maturity levels named spec-first, spec-anchored and spec-as-source, from Birgitta Böckeler, Understanding Spec-Driven-Development: Kiro, spec-kit, and Tessl, in her Exploring generative AI series (2025-10-15, accessed 2026-09-22).cc-core-32— the version this course was checked against, from the Claude Code changelog.- The Swift and Kotlin lines quoted above were recorded on this machine on 2026-09-23, in a
fresh
day2-probe-workmade by the setup block above, and the thousands-grouping pair was run red in a scratch copy of each lane the same day.