Write for agents & GEO
Writing docs agents and answer engines actually use comes down to two things: write the non-obvious (not a restatement of your types), and structure it so a model can find the answer, quote it, and trust it. This page covers both — and follows its own rules.
Both halves are backed by the repo's evals: docs earn their keep by stopping agents from confidently guessing wrong, and that only works if the right fact is present and structured so the model actually reaches it. (The hard part isn't generating llms.txt or the markdown mirrors — the pipeline does that — it's the content itself.)
What to write: the non-obvious
Info
Document the non-obvious. Error conditions, defaults, ordering constraints, and surprising behavior pay off. Restating what your CLI --help, .d.ts types, or README already expose adds ~0.
Why restatements add nothing
An agent reading your library already has its compiled code, type signatures, and CLI help text in context. A doc page that paraphrases a function signature gives the agent a second copy of something it can already see — no new information, just more tokens to read.
The repo's evals measured this directly. Per-fixture lift, pooled across five models, concentrated entirely on behavioral gotchas; conventional restatements were flat:
| Fixture | What it tests | Lift |
|---|---|---|
nav-unknown-group | A page declares a group the config doesn't know → the build fails | +28 |
search-when-embeddings | When the default BM25 index is enough vs. when to add embeddings | +28 |
explain-cli-flag, mounted-changelog-urls, bundle-rationale, custom-generate-script | Facts already in --help, the .d.ts types, or the README | −2 to +6 |
The mechanism is that docs stop agents from confidently guessing wrong. Without the nav-unknown-group doc, agents don't say "I don't know" — they assert the wrong answer. With it, the confident-wrong rate drops to near zero. The conventional fixtures don't move because the agent could already recover those facts from the package itself.
The litmus test
Before you write a page or paragraph, ask:
Could the agent recover this from the type signature, the CLI help, or by reading the code?
- Yes → skip it, or compress it to a one-line pointer. The package already self-documents it.
- No → write it. This is where the docs earn their keep.
What counts as non-obvious
What fails, and the exact failure mode. "An undeclared group makes the build fail" is invisible in the types — the field is just a string.
The default behavior and the rule for choosing otherwise. "Search uses a static BM25 index by default; add embeddings only when…" — a decision boundary no signature encodes.
What must run before what. "Run generate before your app build," "register remarkInclude before the default plugin stack" — order that types can't enforce.
Anything that violates a reasonable default expectation — silent fallbacks, values that look interchangeable but aren't, side effects that aren't obvious from the call site.
Write it like a gotcha
Lead with the surprising fact, not the API. Compare:
Skip — restates the type:
The
groupfield on a page is an optional string that associates the page with a navigation group.
Write — documents the behavior:
If a page declares a
groupthat isn't defined in your config, the build fails — leadtype won't silently drop it or invent the group. Declare every group indocs.config.tsfirst.
The second version is the one an agent can't reconstruct from the type. It's also the one that prevents a confident wrong answer.
Why it's also cheaper
Documenting the non-obvious doesn't just raise correctness — it makes agent runs cheaper. When the answer is one short doc instead of a fan-out of grep/read/list calls probing your package, agents read fewer tokens and make fewer tool calls. The evals show every model spent 32–54% fewer tokens with docs present, including frontier models that barely needed them for correctness.
How to structure for answer engines (GEO)
Generative Engine Optimization (GEO) is SEO for answer engines: ChatGPT, Claude, Perplexity, and AI Overviews don't rank a page — they retrieve a passage, quote it, and attribute it. The unit that gets cited is a section, not a page. Structure each one so it survives being pulled out on its own.
The same eval mechanism applies: a correct fact the model can't locate, or quotes out of context, still produces a confident wrong answer. Structure is what closes that gap.
Lead with the answer
Put the answer in the first sentence of the section, then explain. Answer engines excerpt the top of a matched passage; if it opens with preamble ("Before we discuss rate limits, it's worth understanding…"), the citation is useless and the model keeps guessing. Cut the runway.
Phrase headings as the question a user would ask
Models match a user's query against heading text. A heading that mirrors the question wins:
- Write:
### How do I rotate an API key? - Skip:
### Key management
Question-form H2/H3s turn your outline into a set of retrievable answers.
Make every section self-contained
A model quotes one section without its neighbors, so each must stand alone:
- Define (or link) a term the first time it appears in the section — don't rely on a definition three headings up.
- Use the specific noun, not a pronoun: write "the API key", not "it" or "this value". Once a passage is excerpted, the antecedent is gone.
- Don't open a section with "As mentioned above" — there is no "above" in a citation.
Be specific, not vague
Replace adjectives with values. "Flexible and powerful" tells a model nothing it can quote; "returns a 429 once you exceed 100 requests per minute" is exactly what it surfaces. Exact status codes, limits, ranges, defaults, and version numbers are what get retrieved and trusted — and they double as the non-obvious facts from the first half.
Use one term per concept
Pick one name and keep it. Alternating between "API key", "access token", and "credential" for the same thing fragments retrieval (each phrasing matches different queries) and signals inconsistency a model reads as lower trust.
Keep the heading hierarchy sequential
Go H2 → H3 → H4 without skipping a level. The heading tree is the topic map an engine parses to understand how your concepts nest; a jump from H2 to H4 breaks that map. (This page follows its own rule.)
Label every code block
Tag the language on every fenced code block — a ts-labeled fence, not a bare one. The label tells an engine which stack the snippet answers, so it surfaces your TypeScript example for a TypeScript question instead of guessing.
Describe images in alt text
Answer engines can't see your diagrams. Put the information the image conveys into its alt text — "Pipeline: MDX → convert → llms.txt + search index + markdown mirror", not "diagram". If the alt text is empty, that content simply doesn't exist for an agent.
Let leadtype check the mechanical ones
Info
leadtype lint flags the structural signals — geo:heading-skip, geo:code-language, and geo:image-alt — and leadtype score rolls them into a 0–100 agent-readiness score mapped to the ora rubric. The editorial tactics above (lead with the answer, question-form headings, specificity) are yours to apply — a linter can't judge them.
Test it
GEO has a tight feedback loop: ask your product's real questions in ChatGPT, Claude, and Perplexity, and check whether your docs are cited, accurate, and recommending the right approach. When they're not, it's usually a structure problem — the fact is there but buried, or quoted without its context.