Posts
Functional Documentation Is an Agentic Guardrail
Functional documentation becomes more valuable, not less, when AI agents are writing and reviewing code. I heard an architect argue that, in the age of AI agents, functional documentation in source code was no longer …
In this article
Functional documentation becomes more valuable, not less, when AI agents are writing and reviewing code.
I heard an architect argue that, in the age of AI agents, functional documentation in source code was no longer necessary. The agent can read the code, after all. Why explain what a function does when the implementation is sitting right there?
My first reaction was that the opinion was somewhat absurd and potentially dangerous.
It seemed absurd because reading an implementation only tells the agent what the code currently does. It seemed dangerous because the argument removes an independent description of what the code is supposed to do. If the implementation is wrong, asking it to explain itself does not create a second opinion. It creates a confident explanation of the mistake.
Still, being offended by an engineering claim is not evidence. I wanted to challenge the assumptions with explicit checks and observe what the agent actually did.
The experiment
I gave an agentic coding tool a source file with no functional documentation. The tool copied the file into a temporary working area, made the requested changes, and opened a pull request.
While it was working, I introduced a tiny regression: I added a ! that inverted a condition.
The simplified version looked something like this:
function isEligibleForDiscount(customer: Customer): boolean {
return !customer.isActive && customer.yearsWithCompany >= 5;
}
The function name says it determines whether a customer is eligible. The implementation now requires the customer to be inactive. That may compile. It may even look reasonable during a quick scan. It is also the sort of one-character change that can quietly reverse a business rule.
The agentic code review did not mention it.
That failure already exposed two gaps. A useful unit test should have caught the changed behavior, and a careful reviewer could have questioned whether isEligibleForDiscount was doing what its name implied. Neither happened.
I work in enterprise environments, where unit tests often cover the happy path and politely decline to visit the rest of reality. That leaves us in the unhappy situation where every character changed in source code increases anxiety. A single ! is not a large change, but it can produce a very large incident. When the test suite cannot give us enough confidence, every additional signal of intent matters.
Then I repeated the experiment with functional documentation in the source file:
/**
* Determines whether an active customer qualifies for a loyalty discount.
*
* @param customer The customer whose discount eligibility is being evaluated.
* @returns `true` when the customer is active and has been with the company
* for at least five years; otherwise, `false`.
*/
function isEligibleForDiscount(customer: Customer): boolean {
return !customer.isActive && customer.yearsWithCompany >= 5;
}
This time, the agent flagged the mismatch during code review. The description said active. The implementation said not active. The contradiction was explicit enough that the agent could no longer glide past it.
Same class of change. Different result.
What the experiment does and does not prove
Here is the evidence I actually have:
| Run | Functional contract present | Injected regression | Review result |
|---|---|---|---|
| 1 | No | Boolean condition inverted with ! | Regression not flagged |
| 2 | Yes | Same class of Boolean inversion | Contract/implementation mismatch flagged |
That is useful evidence of a failure mode, but it is not a controlled benchmark. It is two observations from one workflow. It does not isolate every variable, establish a detection rate, or prove that every model will respond the same way. Agent output can be non-deterministic, and a different prompt, model version, repository state, or review configuration could change the result.
NOTE: In enterprise environments, many employees work across multiple languages. That adds another source of variability. Two people can describe the same requirement with different phrasing, idioms, translations, or domain vocabulary. As a native English speaker, I can sometimes recognize British or Indian English in a prompt by the use of query to mean a question rather than a database operation. It makes perfect sense in context, but it is also a small reminder that shared words do not always arrive with shared defaults. This does not make the model itself more non-deterministic, but it does make agent interactions harder to reproduce and increases the chance that intent will be interpreted differently. A functional contract gives the team a shared statement to review, refine, and keep with the code.
The published research later in this post provides the broader, independently verifiable evidence.
Code explains what it does, even when it is wrong
This is the weakness in the “the code is the documentation” argument: code is authoritative about what the machine will do, but it is not always authoritative about what the machine should do.
Those are very different questions.
If an agent sees only this:
return !customer.isActive && customer.yearsWithCompany >= 5;
it can explain the logic perfectly. It can trace the branches, infer types, and produce a confident summary of incorrect behavior. The implementation is internally consistent because it has no independent statement to disagree with.
Functional documentation creates that second statement.
Business intent
│
├── Function name
├── Functional contract
├── Types and constraints
├── Unit tests
└── Implementation
↓ disagreement
Human or agent review
The useful part is not repetition. It is independent agreement.
When all of those signals describe the same behavior, confidence goes up. When one disagrees, the reviewer has a reason to stop. Human and agent reviewers both benefit from evidence that makes disagreement visible.
Why agents make the distinction more important
An agent does not possess the team’s full history by default. It works from whatever context the tool gives it: selected files, repository instructions, nearby symbols, retrieved documents, test output, and perhaps a temporary or isolated copy of the worktree.
The exact mechanics vary by tool, so the temporary directory is not the important part. The boundary is.
People carry undocumented context between meetings, tickets, production incidents, and hallway conversations. That is fragile enough. An agent may begin every task without any of it. If a business rule exists only in someone’s memory, or in a chat thread the agent cannot see, it effectively does not exist inside the agent’s working context.
This produces a simple rule:
If a constraint matters to the behavior, encode it in an artifact that travels with the behavior.
Tests are one such artifact. Functional documentation is another. Types, schemas, examples, and architectural decision records can play the same role at different levels.
The more work we delegate, the less comfortable we should be with intent that lives only in (human) memory.
What the external evidence says
My experiment is not the only reason to think natural-language intent matters, although the available research does not test my exact pull-request scenario.
- GitHub documents that its cloud coding agent works in an ephemeral development environment, where it can explore the repository, change code, and execute tests and linters. That verifies the isolated-environment behavior for GitHub Copilot. It should not be generalized to every agentic tool, but it supports the broader point that an agent works inside the context and execution boundaries its tooling provides.
- The 2026 SWE-AGILE paper examines context constraints directly. Its authors describe the tradeoff between retaining too much reasoning history and discarding so much that the agent must repeatedly reconstruct its understanding. Their proposed context-management strategy improved results for 7B–8B models on SWE-bench Verified. This is evidence that context management affects software-agent performance, not evidence that functional comments alone solve the problem.
- A 2025 ICSE paper, Code Comment Inconsistency Detection and Rectification Using a Large Language Model, introduced C4RLLaMA. Across the datasets used by the authors, it outperformed the compared post hoc and just-in-time approaches at detecting code/comment inconsistencies and could also propose corrected comments. This is much closer to the mechanism in my experiment: the tool can compare two representations of behavior and surface a disagreement.
- A 2026 FSE paper tested that mechanism in a narrower but high-stakes domain. SmartComment combined an LLM with program analysis and found 203 valid code/comment inconsistencies in 1,000 real-world smart contracts, reporting 79.9% precision. Its program-analysis components improved F1 from 58.7% to 81.3%. That does not establish the same performance for ordinary enterprise code, but it does provide recent evidence that comments can act as redundant functional specifications for automated inconsistency detection.
- A 2026 study of LLMs as code reviewers provides the necessary warning label. The researchers found unstable tradeoffs between false acceptance and false rejection across models and prompts. They also found cases where a model rejected buggy code for the wrong reason. Agentic review is useful, but its verdict and explanation are not automatically trustworthy.
Put together, agent context, explicit behavioral descriptions, program analysis, and executable checks can all affect what an automated reviewer detects. None of them directly measures functional documentation in the same pull-request experiment I ran.
My experiment suggests that a functional contract provides another contextually useful comparison point, but it does not establish the size or reliability of that effect.
Documentation is not a substitute for tests
To be clear, the first experiment should still have failed a test.
it("should return true for an active customer with five years of history", () => {
const customer = { isActive: true, yearsWithCompany: 5 };
expect(isEligibleForDiscount(customer)).toBe(true);
});
Executable checks are stronger than prose for proving known examples. A CI pipeline can reject a broken test. It cannot reject a misleading comments unless another tool compares the comment with the implementation.
But tests do not make documentation obsolete either. A functional contract explains the rule those examples are meant to represent. You want both because they fail differently:
- Names provide a fast signal, but they are necessarily compressed.
- Documentation states intent, but it can become stale.
- Tests execute expectations, but they can be incomplete or assert the wrong thing.
- Types constrain possible states, but they rarely capture the whole business rule.
- Review compares the evidence, but only the evidence placed in front of it.
This is defense in depth for software intent. No single layer earns the right to be trusted unconditionally. That includes the test suite, and certainly a comment written during the previous human software development era of the codebase.
When functional documentation earns its keep
I am not arguing for a paragraph above every getter. Comments that translate syntax into English are noise:
// Increment the retry count by one.
retryCount++;
Nobody was saved by that sentence.
Functional documentation earns its keep when at least one of these is true:
- The business rule is not obvious from the code. Eligibility, authorization, billing, compliance, and state transitions are common examples.
- The function has important invariants. State what must always be true before or after it runs.
- A valid-looking implementation could still be behaviorally wrong. Boolean logic and boundary conditions are especially good at this trick.
- The cost of a regression is high. Money, access, privacy, safety, and destructive operations deserve redundant clarity.
- The function is a boundary used by other people or systems. Public APIs, shared libraries, jobs, and integration points need explicit contracts.
- The reason matters more than the mechanics. Explain why inactive accounts are excluded, not how
&&works.
You can turn that into a compact decision tree:
Could a reader infer the intended behavior from the signature alone?
├── Yes
│ └── Would a plausible implementation still hide a costly mistake?
│ ├── No → Prefer clear naming and tests.
│ └── Yes → Document the contract and test it.
└── No
└── Is the behavior externally visible or business-critical?
├── Yes → Document inputs, outputs, invariants, and edge cases.
└── No → Improve the name or design first; add a short "why" if needed.
That is the threshold I find useful: document the contract and intent, not a narrated copy of the implementation.
There is still a failure mode: stale documentation
The strongest objection to functional comments is legitimate. Documentation can lie.
The recent research provides receipts for that concern too. The 2026 SmartComment study found 203 valid code/comment inconsistencies in its sample of 1,000 smart contracts. That does not prove every inconsistency would become a defect, and smart contracts are not representative of every codebase. It does show that contradictory comments and implementations occur in real software and can be detected with useful, but imperfect, precision.
NOTE: AI tooling removes much of the mechanical effort from documentation maintenance. An agent can locate comments near changed behavior, compare them with implementation and tests, and propose updates in the same pull request. Research published in 2026 also shows that targeted AI-generated comments can improve human code comprehension in specific C and C++ tasks (ComCat). None of that removes the harder problem of deciding which behavior is intended when the code, tests, requirements, and comments disagree. AI can make the update cheaper. A person still has to verify that the updated contract is true.
If the behavior intentionally changes but the comment does not, the agent may flag the implementation even though the implementation is now correct. Worse, an agent might “fix” the code to agree with an obsolete description.
That does not make documentation worthless. It means documentation is part of the change.
When a pull request changes behavior, it should usually change the corresponding contract and tests in the same pull request. Review should ask whether all three still agree:
documentation == tests == implementation
Not literally, of course. Please do not build a framework named DocumentationEqualityFactory on my account. The point is that a behavioral change should leave consistent evidence across the artifacts a reviewer can inspect.
Agents can make this maintenance cheaper by locating nearby comments, examples, and tests that may need to change, provided we ask them to check and give them access to those artifacts.
My takeaway
My experiment was small. It was not a peer-reviewed benchmark, and I would not pretend one agent, one file, and one inverted condition proves a universal law about every coding tool.
It did demonstrate a useful failure pattern.
Without functional documentation, the agent had one fewer source of intent to compare against the implementation. With a clear description of the expected behavior and return value, the contradiction became visible and the review caught it.
So no, I am not convinced that agents make functional documentation unnecessary. I think the opposite is closer to the truth.
As agentic tools take on more implementation and review work, our codebases need to make intent easier to retrieve and contradictions easier to detect. Good functional documentation does both. It gives the agent another constraint, gives the reviewer another receipt, and gives the next developer a fighting chance.
The code tells us what it does.
The contract tells us whether it should.
-Rob