Build a Documentation Agent with GitHub Copilot
Documentation is the safest agent task there is. Nothing runs, nothing deploys, and every mistake is visible in a diff before it reaches anyone.
It also has the one failure mode that matters here, and it is severe in a different way: an agent will document behaviour that does not exist, fluently and plausibly, and documentation is trusted precisely because nobody re-derives it from the code.
Key takeaways
- Read, search and edit. No shell — documentation does not need to run anything.
- The central rule is one sentence: never document behaviour you have not read in the code.
- An honest gap beats an invented paragraph.
TODO: verifyis a good outcome. - Style rules are worth writing down because documentation is where inconsistency is most visible.
- Verify against the code, not against plausibility. A wrong default reads exactly like a right one.
Why this is the one to start with
If a team is nervous about agents, this is the place to begin.
No execution. There is no shell tool, so nothing runs at all.
Bounded edits. Markdown files, reviewed as a diff like any change.
Failures are legible. A wrong sentence is visible in a way that a subtly wrong code change is not.
The work is genuinely tedious. Documentation lags because writing it is unrewarding, not because it is difficult. That is exactly the profile of work worth delegating.
It builds calibration. A team that has reviewed a dozen documentation diffs has a much better sense of where these systems are confident and wrong, and that sense transfers to riskier agents.
The profile
---
name: docs
description: Writes and updates documentation for this project from the code
and from existing docs. Reads and edits markdown only. Use for READMEs,
reference pages, changelog entries and docstrings. Not for writing code
and not for inventing behaviour that is not implemented.
tools: ["read", "search", "edit"]
---
You write documentation for this project.Three tools. Read and search so it can find how things actually work; edit so it can write. No shell, because documentation does not need to run commands — and an agent that cannot run a command cannot report what a command printed, which removes an entire category of fabrication.
The rule that makes it safe
## The one rule
Never document behaviour you have not read in the code.
If you cannot find how something works, write `TODO: verify` and say so
in your summary. An honest gap is useful. An invented paragraph is worse
than nothing, because it will be trusted.
This applies to:
- Function signatures, parameter names and defaults.
- Return values and error conditions.
- Configuration keys and their defaults.
- Command-line flags.
- Example output. Never invent what a command prints.Every item on that list is something models fabricate confidently, and every one of them is fabricated in the most dangerous possible form: correct-looking.
A default of 30 where the code says 60. A parameter named timeout where the
code says timeout_seconds. A flag that would be sensible and does not exist.
None of these look wrong. All of them cost someone an afternoon.
Style rules
Documentation is where inconsistency is most visible, and style is cheap to specify.
## Style
- Second person, present tense. "You configure", not "the user should
configure".
- Say what it does before why it exists.
- One example per concept. Working examples only, drawn from real code
or real tests.
- No marketing language. No "simply", "just", "easy" or "powerful".
- British spelling.The banned-words line earns its place. Models reach for “simply” and “just” constantly, and both are quietly hostile: if the reader found it simple they would not be reading the documentation.
“Working examples only, drawn from real code or real tests” is the style rule that is really a correctness rule. An example copied from a passing test is an example that works.
Structure
## Structure
Reference pages: what it is, how to use it, parameters, examples,
limitations, related pages.
Every page states what the feature does *not* do.That last line is the most valuable structural rule available.
Documentation overwhelmingly describes capability. The questions people actually arrive with are frequently about limits — can it do X, does it handle Y, what happens with Z. A limitations section answers them once, and it is the section a generic writer never thinks to include.
What not to touch
## What not to touch
- Do not edit code. Docstrings in source files are the exception, and
only their text.
- Do not edit the changelog for releases already published.
- Do not reformat existing documentation you were not asked to change.The third rule prevents the most annoying documentation agent behaviour: a one-paragraph request returning a diff that rewraps four files. Noise like that makes the diff unreviewable, and an unreviewable diff gets approved without being read — which is precisely the state this agent must not create.
The second rule protects a record. Published changelog entries describe what shipped, correctly or otherwise, and rewriting them retroactively destroys the history they exist to preserve.
Using it
Write reference documentation for src/scheduler/ and put it in docs/reference/scheduler.md.
Read the code for every parameter and default — do not infer them from the names. Include a limitations section covering what the scheduler does not handle. Mark anything you could not verify with TODO: verify.
Compare docs/reference/ against the current code and list every place the documentation describes behaviour that no longer matches.
Report only. Do not fix anything yet — I want to see the list before anything changes.
That second request is the one people underuse. Finding stale documentation is tedious, mechanical and exactly what an agent is good at, and separating find from fix means you get a reviewable list rather than a large diff.
How fabrication actually happens
It is worth understanding the mechanism, because knowing it changes what you look for in review.
A model asked to document a function reads the code and produces a description. Where the code is clear, the description is accurate. Where the code is unclear — a value assembled from three places, a default set in a configuration loader elsewhere, an error raised by a library — the model still has to produce a sentence, and it produces the most likely one.
“Most likely” is doing something specific here. It means the sentence that would
typically be true of code shaped like this, across everything the model has ever
seen. Which is why the fabrications are always reasonable: a timeout default of
30 seconds, a parameter called retries, a flag spelled --verbose. These are
what such things usually are.
Three consequences follow.
Fabrications cluster around indirection. Values that are defined where they are used get documented correctly. Values assembled at runtime, injected, or inherited from a framework are where the invented specifics appear.
They are invisible without the code open. The whole point is that they are plausible. Reading the documentation critically will not find them; comparing it against the source will.
They increase with scope. A request to document one function keeps the code in view. A request to document an entire subsystem means most of it is being described from memory of what was read several steps ago.
Reviewing the output
Documentation review has a specific trap: it reads well, so it feels checked.
Verify every specific against the code. Parameter names, defaults, return types, error conditions, flag spellings. This is the entire review. Prose quality is secondary and self-evident; correctness is neither.
Check the examples run. An example that has never been executed is a claim. If it came from a test, that is evidence; if it did not, run it.
Look for confident vagueness. Sentences that describe a mechanism without
naming anything — “the scheduler handles retries appropriately” — usually mean the
agent could not determine the behaviour and produced a sentence anyway. Those are
the places TODO: verify should have appeared.
Check the TODO: verify markers were reported. They should be listed in the
summary. Ones left silently in the text are the ones that ship.
Testing it before you trust it
The same discipline as the review agent applies, adapted: give it something whose correct documentation you already know, and score the result.
Pick a module you know completely. One you wrote, ideally recently.
Ask it to document the module. No hints about the tricky parts.
Check every specific. Parameter names, defaults, return values, error conditions. Count what is wrong.
Look at where the errors are. If they cluster around the indirect values — the default injected from configuration, the error raised by a dependency — that is the mechanism described above and it is what to warn reviewers about.
Then check the honest-gap behaviour. Ask it to document something genuinely
undocumentable from the code alone: a value that comes from an environment
variable set in deployment, say. The correct output is TODO: verify, not a
confident sentence. If you get the confident sentence, the rule is not landing and
the profile needs sharpening.
What it does well
Reference documentation from code. Parameters, types, return values, error conditions. This is largely mechanical transcription, and mechanical work is where the agent is strongest and where a person is least willing to be careful.
Finding what has gone stale. Comparing a directory of documentation against the current code is genuinely tedious for a person, which is precisely why it never gets done and why documentation decays.
First drafts of explanations. A rough page you edit is a better starting point than an empty file, and the editing is quick.
Docstrings for existing functions. Reading an implementation and describing what it does is squarely inside what this is reliable at.
Changelog entries from commits. With review, since a commit message describes what changed in the code and a changelog entry describes what changed for the people using it, and those are frequently different sentences.
What it does badly
Explaining why. Design rationale is not in the code. An agent asked why something works this way will construct a plausible reason, and plausible reasons about decisions are the most confidently wrong output in this entire cluster.
Tutorials. A good tutorial is shaped by what confuses a newcomer, which requires having watched one get stuck. What comes back instead is a sequence of individually correct steps that teaches nothing, because it never anticipates the place where the reader will stop.
Judging what matters. It documents everything at the same weight. Deciding which three things a reader needs first, and which forty they will never care about, is editorial judgement and stays yours.
Anything about the future. Roadmaps, deprecation timelines, intentions. Not in the code, therefore invented.
Fitting it to existing documentation
An agent dropped into a repository with established documentation will produce pages that do not match it, and the mismatch is more jarring than the absence would have been.
Three additions to the profile fix most of it.
Name an exemplar. “Follow the structure and voice of
docs/reference/scheduler.md” gives the agent something concrete to imitate,
which works far better than describing the style in the abstract.
State the file layout. Where pages live, how they are named, what the frontmatter needs, how navigation is generated. A page in the wrong place with the wrong metadata is a page that does not appear.
Say what is generated. If part of your documentation is produced from code, say so and say it must not be hand-edited. Otherwise an agent will helpfully improve a file that is overwritten on the next build, and the improvement will vanish without explanation.
Keeping documentation current
The best use of this agent is not writing documentation once. It is keeping it from rotting.
Run the staleness check on a schedule. Monthly, against the reference documentation. The output is a list you triage rather than a diff you review, which makes it cheap enough to actually keep doing.
Document alongside the change. A pull request that changes behaviour is the moment the documentation is easiest to update, because the change is fresh and the diff is right there.
Prefer small, frequent updates. A large documentation rewrite is effectively unreviewable, and unreviewable diffs get approved without being read — which is how an invented default reaches a page nobody will ever check again.
Treat TODO: verify as a queue. They are honest markers of things nobody has
confirmed, which makes them a useful list — provided somebody works through it.
The honest accounting
Documentation agents are the easiest to oversell, so it is worth being plain about what changes.
Writing time drops substantially. Producing a first draft of a reference page is genuinely much faster.
Review time does not. Every specific still has to be checked against the code, and that check is the same work whoever wrote the draft. For a page dense with parameters and defaults, verification can take longer than writing would have.
The net gain is real but narrower than it looks. It is largest for pages that are mostly structure — reference material, changelogs, docstrings — and smallest for pages that are mostly judgement.
The largest gain is not writing at all. It is the staleness check. Nobody enjoys reading documentation against code looking for drift, so nobody does it, and documentation decays. An agent that produces that list monthly changes something no amount of good intentions has.
Common questions
Should it write the tutorials? Have it draft the reference material and write the tutorials yourself. Tutorials depend on knowing what confuses people, which is not in the code.
Can it update docstrings in source files? Yes, and it is a good use — reading an implementation and describing it accurately. Restrict it to the docstring text so a documentation task cannot change behaviour.
What about API documentation generated from code? If your documentation is generated, the agent’s job is improving the docstrings that feed it, not editing the output.
Can it write the changelog? From commits, with review. Commit messages describe changes to the code; a changelog describes changes to what users experience, and the translation needs judgement.
Can it document a codebase it has never seen? It can produce something. Whether it is right depends entirely on how much of the behaviour is legible from the code, and for an unfamiliar codebase the reviewer needs to know the code well enough to catch the confident errors — which somewhat defeats the purpose.
How large a request is reasonable? One module, one page. Requests spanning a whole subsystem produce more fabrication, for the reason described above, and a diff too large to review carefully.
Should it have MCP access? Rarely needed. If documentation depends on issues or discussions, a read-only GitHub server is reasonable — but check the output harder, since discussion text is a rich source of things that were proposed and never implemented.
One module at a time
Scope is the lever with the largest effect on documentation quality, and the temptation runs the wrong way.
A request to document an entire subsystem looks efficient. What happens is that the agent reads a great deal, then writes a great deal, and by the later pages it is describing code it read many steps earlier. That is exactly the condition under which the plausible-invented specific appears.
A request scoped to one module keeps the relevant code in view throughout. The output is shorter, the diff is reviewable, and the specifics are much more likely to be right.
The same applies to your review. Four small documentation diffs reviewed properly are worth more than one large one approved because checking it thoroughly would have taken an afternoon — and the large one is where the wrong default survives.
The limitations section
Of everything in the profile, one structural rule changes the output more than the rest: every page states what the feature does not do.
It is worth dwelling on because it is unusual, easy to skip, and disproportionately valuable.
Documentation is written by people who understand a thing, for people who do not. The writer knows the boundaries so well that they are invisible; the reader arrives with precisely those questions. Can it handle nested values? What happens if the input is empty? Does it work across time zones? Is there a size limit? Every one of those is answered somewhere in the code and nowhere on the page.
An agent is well placed to extract them, because limits are usually explicit in the implementation: the validation that rejects, the branch that raises, the parameter ignored under a condition, the hard-coded maximum. Asking for them turns implementation detail into the section readers need most.
Ask for it by name. “Include a limitations section covering what this does not handle” produces one. Left implicit, it does not appear.
Require it to cite the code. A limitation the agent inferred is a limitation that may not exist, and an invented limit is as damaging as an invented feature — it stops people using something that would have worked.
Keep the ones that surprise you. If the agent surfaces a limit you did not know about, that is the single most valuable line it will produce all week, and it is worth verifying carefully because it may equally be wrong.
Next
Agentic workflows closes the cluster: markdown automations that run in GitHub Actions, where a staleness check like the one above stops being something you remember to run and becomes something that runs on its own.
Sources
Every version-sensitive claim on this page was checked against first-party documentation. Only sources actually used are listed.
Your progress
Saved in this browser only. No account, no server, and nothing leaves your device. Clearing site data resets it.Sync across devices if you want it everywhere.
Saved in this browser and synced to your account, so it follows you between devices. Manage or delete it.
Was this lesson helpful?
We record which lesson you rated and whether it helped. Nothing identifies you — no account, no cookie, no session.