# Writing documentation for agents

A documentation package is read by a model, not a person browsing a website. That changes
what belongs in it. `docspack doctor` enforces most of this, so the rules below are what the
warnings mean.

## Cut filler, not prose

The instinct to compress everything to terse notes is wrong, and the effect is measurable.
Stripping every prose sentence from a 309-chunk package — keeping only headings, code and
lists — produces this:

- corpus on disk: 62,366 tokens becomes 43,807
- what a query returns: 14% fewer tokens
- top result correct: 17 of 20 queries instead of 20 of 20

The index matches on the words someone types. Those words live in sentences. A query for
"basic authentication" found the basic-auth chunk in full text, and a generic middleware
page once the prose was gone. Losing 15% of retrieval accuracy to save 14% of a budget that
is already capped is a bad trade.

What is free to remove is filler: narration and marketing that carries neither a fact nor a
word anyone would search for. `doctor` flags these:

```
In this guide we will explore…      nobody searches for it
Let's dive in!                      nobody searches for it
As you can see, …                   nobody searches for it
A blazing-fast, world-class router  nobody searches for it
```

Delete them. Keep "Keys are scoped per environment, so a test key fails against production"
— every word of that is a fact, and half of them are search terms.

## One claim per sentence

`doctor` warns at 40 words. A long sentence usually contains two facts and a conjunction,
and both facts retrieve better apart. Splitting also gives the ranker more distinct chunks
to work with.

## Show, do not describe

`doctor` counts chunks with no code, no inline identifier, no list and no table. A chunk
that only describes is hard to act on and hard to find, because API names are the terms
users search for.

```md
Bad:   The client must be configured with credentials before use.
Good:  Call `client.setApiKey(key)` before any other request.
```

Names in `inline code` are extracted as entities and indexed, which is how a query for
`setApiKey` finds a chunk that never spells the word out in prose.

## One heading, one question

docspack splits chunks at `##`. Write headings that match how someone would ask:
"Verifying a webhook signature" retrieves better than "Advanced usage". A section that
answers five questions becomes one chunk that half-answers all of them.

`doctor` warns above roughly 1,500 tokens, because a chunk that large crowds out everything
else in a response, and below 30, because a chunk that small answers nothing.

## How the ranking treats what you write

Two facts about bm25, the ranking, decide more than any advice on this page.

Tags outweigh prose — the weights are `(content 1.0, tags 3.0)` — so the words in a heading
and in a `<!-- docspack: tags=… -->` directive are the strongest lever you have. Function
words are filtered out of heading-derived tags for exactly that reason: a section titled
"How to use it" otherwise outscored a 605-chunk corpus on every question beginning "how do I".

**And bm25 normalizes for length: a short chunk outranks a long one on the same match.** A
thorough page therefore loses to a stub that says less about the same thing. This pulls
against the response budget, which prefers few large chunks over many small ones, and there
is no setting that is right for every corpus — so measure it:

```bash
npx docspack eval ./eval/queries.json
```

`eval` answers a set of questions from the package and reports hit rate, top-1 rate and mean
answer size. Sweeping `maxChunkTokens` against 40 questions on one real corpus showed 400
tokens retrieving best (35/40 in the top 3) and 900 answering best (27/40 top-1, at twice the
tokens per answer). The number to pick depends on your corpus; the tradeoff is always there.

## Do not ship the same thing twice

`doctor` reports two chunks that normalize to the same text. Duplicates compete for the same
query and waste the response budget on repetition. This happens most often when a package is
built from a site that publishes both a full document and per-page versions of it.

## What build removes for you

<!-- docspack: tags=strip, remove, markdown, front matter, chrome, badges -->

`docspack build` strips front matter, badges, and site chrome such as "Edit this page" or
"Was this page helpful?" out of your Markdown. Front matter is not simply deleted: `title`
becomes the document title and `tags` or `keywords` are indexed alongside the prose.

Table alignment padding is collapsed as well, which is lossless for a reader and for a
renderer but can be three quarters of a generated reference page's measured tokens.

## Checking your work

```bash
npx docspack doctor            # what is wrong
npx docspack doctor --strict   # structural warnings as failures — what prepublishOnly runs
npx docspack doctor --pedantic # the same, plus prose style
npx docspack preview "how do I authenticate"
npx docspack eval ./eval/queries.json --min-hit-rate 90
```

`preview` is the honest check. It answers through the same ranking and token budget an agent
gets, so a chunk that reads well but retrieves badly shows up immediately.

`eval` is `preview` over a whole question set, with a number at the end. It is the only check
that can fail on retrieval: every chunk in a package can be well-formed, well-tagged and the
right size while the package still answers the wrong question, and `doctor` cannot see that.
With `--min-hit-rate` it belongs in CI beside `doctor --strict`.

Every command's own flags are on its help page — `docspack eval --help`, `docspack doctor
--help` — which is where to look rather than at the global one.
