Documentation It Writes, Checked
Ask the agent to document pricing and it writes clear, well-formatted prose about a module that applies the member discount to the order total. The module applies it to each class. Generated documentation describes the code the agent expected from the names and from what such code usually does. A confident wrong comment is worse than none, because the next reader, human or agent, believes it.
This is the last page of the chapter for a reason. Mapping, pinning and refactoring all produced knowledge about pricing that the team did not have before. Documentation is where that knowledge gets written down, and it is the one place where an agent's fluency can quietly undo the work: a polished paragraph that contradicts the tests, sitting next to the code, read by every future session.
Why It Describes Intent
Documentation is generated text like any other. It comes from the function's name, its parameters, the conventions around it, and the most common shape of code like it. A function called "pack price" that takes a size and a member flag most often multiplies a price by the size and applies a discount to the result, so that is what the agent writes. When the code does something unusual, such as rounding the discount for each class before adding them up, the documentation tends to describe the usual thing instead.
def pack_price(size, member):
"""Return the price of a class pack in cents.
Applies the 10% member discount to the pack total, so members pay
90% of size x the peak class price. Thread-safe; called only from booking.
"""
total = 0
for _ in range(size):
price = PEAK
if member:
price = int((price * MEMBER_RATE).quantize(Decimal("1"), rounding=ROUND_HALF_UP))
total += price
return total/**
* Returns the price of a class pack in cents.
*
* Applies the 10% member discount to the pack total, so members pay
* 90% of size x the peak class price. Thread-safe; called only from booking.
*/
export function packPrice(size: number, member: boolean): number {
let total = 0;
for (let i = 0; i < size; i++) {
let price = PEAK;
if (member) {
price = Math.floor((price * MEMBER_PERCENT + 50) / 100); // half-up, in cents
}
total += price;
}
return total;
}/**
* Returns the price of a class pack in cents.
*
* <p>Applies the 10% member discount to the pack total, so members pay
* 90% of size x the peak class price. Thread-safe; called only from booking.
*/
public static long packPrice(int size, boolean member) {
long total = 0;
for (int i = 0; i < size; i++) {
long price = PEAK;
if (member) {
price = BigDecimal.valueOf(price).multiply(MEMBER_RATE)
.setScale(0, RoundingMode.HALF_UP).longValueExact();
}
total += price;
}
return total;
}The comment and the code are a few lines apart and disagree by five cents. Nothing in the toolchain notices. The tests still pass, because comments are not executed. A reviewer skimming the diff sees a well-written docstring on a function that previously had none, and approves an improvement.
The Three Kinds of Wrong
The generated comment above contains all three kinds of wrong that generated documentation produces. The first is the description of intended behaviour: "applies the discount to the pack total" describes what pricing code usually does, not what this code does. The second is the restatement that adds nothing: "returns the price of a class pack in cents" tells the reader what the function's name and signature already did. It is harmless today and becomes wrong the day the code changes and the comment does not.
The third is the most dangerous: the confident claim about callers or guarantees that the agent never checked. "Thread-safe." "Called only from booking." Neither can be checked from the function itself. The first is a claim about concurrency that Chapter 10 showed is easy to get wrong. The second is false, as a search for callers shows: the admin report of pack sales calls this function too. A reader cannot verify these claims from the comment's own function, so they are believed or ignored, and either is a problem.
Checking It
The check is the same discipline as the map at the start of this chapter. Every factual sentence in generated documentation maps either to a test or to a line you can point at. The rounding claim maps to the characterization test, which says the opposite, so the sentence is wrong. "Thread-safe" maps to nothing, so it is deleted or verified. "Called only from booking" maps to a search, which disproves it. Sentences that map to neither a test nor a line are deleted. What remains is short, and true.
For pricing, the documentation worth having says what is surprising and points at the evidence. The member discount is rounded per class, which is why the 10-class pack is 16,160 cents, pinned by a named test and under review. The same 10% rule lives in refunds too, so a change to one must change both. Here is the checked version.
def pack_price(size, member):
"""Return the price of a class pack in cents.
The member discount is applied to each class and rounded per class,
so a member's 10-class pack is 16160, not 16155 (pinned by
test_member_pack_rounds_each_class__current_behaviour_under_review;
under review, see the pricing decision with Ines). The same 10% rule
is duplicated in refunds: change both.
"""
total = 0
for _ in range(size):
price = PEAK
if member:
price = int((price * MEMBER_RATE).quantize(Decimal("1"), rounding=ROUND_HALF_UP))
total += price
return total/**
* Returns the price of a class pack in cents.
*
* The member discount is applied to each class and rounded per class,
* so a member's 10-class pack is 16160, not 16155 (pinned by the test
* "member pack rounds each class (current behaviour, under review)";
* under review, see the pricing decision with Ines). The same 10% rule
* is duplicated in refunds: change both.
*/
export function packPrice(size: number, member: boolean): number {
let total = 0;
for (let i = 0; i < size; i++) {
let price = PEAK;
if (member) {
price = Math.floor((price * MEMBER_PERCENT + 50) / 100); // half-up, in cents
}
total += price;
}
return total;
}/**
* Returns the price of a class pack in cents.
*
* <p>The member discount is applied to each class and rounded per class,
* so a member's 10-class pack is 16160, not 16155 (pinned by
* PricingTest.memberPackRoundsEachClass_currentBehaviourUnderReview;
* under review, see the pricing decision with Ines). The same 10% rule
* is duplicated in Refunds: change both.
*/
public static long packPrice(int size, boolean member) {
long total = 0;
for (int i = 0; i < size; i++) {
long price = PEAK;
if (member) {
price = BigDecimal.valueOf(price).multiply(MEMBER_RATE)
.setScale(0, RoundingMode.HALF_UP).longValueExact();
}
total += price;
}
return total;
}The refactoring on the previous pages shows how fast restating comments decay. After nine named steps, a comment that described "the discount computed inside the loop" is wrong twice over: the discount is now its own function, and the loop is a sum. The checked docstring above survives the refactoring untouched, because it describes behaviour the characterization tests pin, not the shape of the code that happens to implement it. That is a useful test for any generated sentence: would it still be true after a refactoring that kept every test green? If not, it describes structure, and the code already does that.
Documentation Is Context
A comment is not only for people. The next agent session that opens pricing reads it as part of its context, and Chapter 4 explained what that means: text in the context is an instruction, whatever its author intended. Leave the generated sentence in place, and six months from now a session asked to "clean up pricing" sees a comment saying the discount applies to the total and code that applies it per class. It resolves the disagreement the way agents usually do, by making the code match the comment. Member pack prices change by five cents, the characterization test goes red, and the diff arrives with an explanation citing the documentation as the specification.
The previous page's discipline would catch that, if the test is still there and someone reads the red. But the comment is what started it. On legacy code, a wrong comment is a bug that has not happened yet, and Chapter 4's rule about instructions that rot applies to every comment in the repository, not only to the project instructions file.
What to Ask For
Ask for documentation of what is surprising, not of what is visible. The reader can see that a function loops over classes; they cannot see that the rounding inside the loop is a suspected bug, that the off-peak hour is read on the server's clock, that a copy of the rule lives in refunds, or why a strange line is there. Those are the sentences worth generating, and an agent will not produce them unless asked, because they do not follow from the names.
Then ask for evidence with every sentence. "Document pricing. For every factual sentence, quote the line or the test it is based on." The citations turn the draft into a checklist you can verify in minutes, and the agent's draft improves too: a sentence it cannot cite is a sentence it tends not to write. A pinned bug gets documented as a bug, with a pointer to its test, so that the next session treats it as a known issue under review rather than as a requirement, or as something to fix on the way past.
- "The agent read the code, so its documentation describes the code." It read the code and wrote what code like that usually does. Agreement is likely on ordinary code and unlikely on exactly the unusual lines that most need documenting.
- "More documentation is always better." A restating comment costs reading time, goes stale when the code changes, and can only ever disagree with the code, never add to it.
- "A wrong comment is harmless because the code is the truth." Agents read comments as context and act on them. The comment becomes the specification for the next change.
- "Docs can be fixed later." Later is when someone has already acted on them. At Cragmoor the wrong rounding sentence is exactly what a future refactoring would "correct" the code to match.
- Map every factual sentence in generated documentation to a test or a line, and delete the rest. An unchecked claim in a comment is a planted instruction for the next session.
- Ask for documentation of the surprising, not the obvious. The reader can already see the obvious.
- Ask the agent to quote the line or test behind each sentence it writes. The citation makes the claim checkable in seconds.
- Document a pinned bug as a bug, with a pointer to its test, so the next session neither treats it as a requirement nor fixes it unreviewed.
Knowledge Check
Where is generated documentation most likely to disagree with legacy code?
- On functions with long, descriptive names, since the agent has more words to misread
- On ordinary code that follows common conventions, since the agent skims what looks familiar
- On the unusual lines, such as per-class rounding, where the code departs from the common shape
- On the oldest files, since the agent's training data does not include code from three years ago
Which of these generated comment sentences is the most dangerous?
- "Returns the price of a class pack in cents."
- "Loops over the classes in the pack and adds up their prices."
- "Thread-safe; called only from the booking module."
- "Applies the member discount when the member flag is set."
How does a wrong comment turn into a behaviour change in a later session?
- The test runner reads every docstring and fails any test that contradicts the behaviour it documents
- A later session reads the comment as context and makes the code match it, citing it as the specification
- The type checker uses doc comments to infer return values, so it rejects the original code outright
- The comment is copied into the instructions file automatically, where it then overrides the tests
You want useful documentation for pricing from the agent. What should you ask for?
- A complete description of every function, so that no reader needs to open the code again
- Documentation that matches the code's names exactly, so that the names and the comments agree
- A short summary at the top of the module, since per-function comments always go out of date
- What is surprising, such as the pinned rounding bug and the duplicate rule, with the line or test behind each sentence
You got correct