The short version
- Reference docs drift because the code and the page change in different places, at different times. More care does not fix a structural gap.
- Build the reference from the source, and check every pull request for docs it would make wrong.
- Generated updates arrive as change requests. Nothing is published until a person approves it.
- Coding agents read the same reviewed docs over MCP, with the permissions of the person who connected them.
Every engineering team that publishes an API knows the moment: a customer quotes the docs back at you, and the docs are wrong. Nobody lied. The code moved, and the page did not move with it.
This paper is for the people who own that problem: engineering leaders, platform teams and the engineers who end up answering the support ticket. It explains why the gap opens, gives you a way to size it for your own team, and describes how Gravity closes it without taking the final word away from your people.
Why reference docs drift
A fact in your reference has two homes. One is the code that makes it true: a handler, a schema, a constant. The other is the sentence that states it. The first changes in a pull request, reviewed by engineers, merged and deployed by the pipeline. The second changes when somebody remembers.
Drift is not a discipline problem. It is what happens when one fact lives in two places that change on different schedules.
Three things make it worse as a team grows. Changes get smaller and more frequent, so each one feels too minor to document. The person who knows the page is rarely the person changing the code. And nothing fails when the page is wrong: the build is green, the tests pass, and the first signal is a reader.
The cost lands far from its cause. It shows up as integration bugs on the customer’s side, support tickets, and engineers pulled away to explain behaviour the docs should have explained. It also erodes trust: once readers find one wrong fact, they stop believing the rest and go and read the code, or ask.
Set these to your own team. The model only does the arithmetic.
Merged changes that alter something the reference states.
Share of those where someone also edits the page.
Share of wrong facts someone spots and corrects later.
Wrong facts after 12 months
29
Where it levels off
≈ 40
Merged without a flag, with the check
0
- Kept by hand
- Checked in the pull request that changes it
Try it with your own team. Two things stand out. The level where it settles depends far more on how much is documented in the same change than on how hard people work to fix pages later. And the only way to reach zero is to never let the change and the page come apart.
Docs as a build output
You would never hand-edit a compiled binary to match a change in the source. The reference deserves the same treatment: it should be built from the code, every time the code changes, by the same pipeline.
Gravity reads the OpenAPI documents in your repository and writes the reference from them: every operation, with its parameters, request bodies, responses and types. Every block it writes remembers the operation it came from. When a later change touches that operation, Gravity knows exactly which blocks to refresh, and which ones to leave alone.
A pull request that…
api/openapi.yaml
- required: [amount]
+ required: [amount, reason]
Docs site, by binding
3 of 6 blocks reached
API › Create a refund
Request body
openapi.yaml · POST /v1/refunds
Updated, marked breakingAPI › Create a refund
“When to refund in part”
Written by a person
Never touchedAPI › Get a refund
Response fields
openapi.yaml · GET /v1/refunds/{id}
Not reachedAPI › List refunds
Query parameters
openapi.yaml · GET /v1/refunds
Not reachedGuides › Refunds
“Refunds within 60 days”
Claim about src/refunds/**
Checked against the codeChangelog › Unreleased
Entries
Commits and the OpenAPI diff
Entry drafted
A newly required field is a breaking change, so it is called out in the change request and the changelog.
The reference is not the only output. The same run can draft guides that explain the change, add entries to the changelog from your commits and release tags, and record durable facts in Nucleus, Gravity’s memory. Each of these is a pass. You choose which ones a repository runs, and each can be tried locally with gravity preview before anything is written.
Drift as a check in CI
If the docs are a build output, a pull request that makes them wrong should be visible in the pull request. Gravity runs as one step in the CI you already have. On a pull request it checks; on a push to the default branch it proposes the updates; on a release it writes the changelog.
.github/workflows/gravity.yml
name: Gravity
on:
push:
branches: [main]
pull_request:
release:
types: [published]
workflow_dispatch:
permissions:
contents: read
pull-requests: write
jobs:
gravity:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: Grupo-Impulso-Digital/gravity-cli/ci/github@v1
with:
token: ${{ secrets.GRAVITY_REPO_TOKEN || secrets.GRAVITY_TOKEN }}
gravity init --ci github writes this file and can set the secret for you.
The check looks for four kinds of finding. Drift: the reference no longer matches the code. Claims: a guide states something the changed code contradicts. Coverage: an operation has no page. Verbatim: a document you locked to the repository disagrees with the code.
Every finding is reported on the pull request. Which ones block the merge is your decision, written in the repository’s .gravity.yaml so it is reviewed like any other change to your pipeline.
.gravity.yaml
passes:
- name: pr-check
kind: check
triggers: [pr]
options:
failOn: [drift, claims, verbatim]The same thing for one run: gravity check --fail-on drift,claims,verbatim
Pull request #1287 · Checks
Gravity · 2 findings block this merge
Exits with 1 · 3 findings reported in the pull request
- Drift · blocks
POST /v1/refunds now requires reason
API › Create a refund still lists it as optional.
- Claims · blocks
Guides › Refunds says “within 60 days”
src/refunds/policy.ts allows 30 days.
- Coverage · noted
GET /v1/refunds/{id}/events has no page
Added in this pull request.
The check also lists what each pass would write. Once the change merges, the push to the default branch proposes those updates for review.
A good way in is to start with the check reporting only, read what it finds on a few real pull requests, and then make drift blocking. If your guides are detailed, claims are a natural next step.
People still decide
Generating the reference does not mean publishing it unread. Everything a pass writes arrives as a change request in the same queue as edits written by hand. Reviewers see the difference block by block, can ask for changes, and approve. Only then does it go live.
The words around the facts stay yours. Gravity tracks who owns each block, and the rule is simple: a pass may refresh what it generated, never what a person wrote.
The push: reason becomes required
API › Refunds
Create a refund
Gives money back on a charge, in full or in part.
POST /v1/refunds
- amountinteger · required
- chargestring · required
- reasonstring · optional
Refund in part when the customer keeps some of the order, and explain it to them in your own words.
The published page, before the change merges.
LiveSome teams later let a well-proven pass publish without review, for example the changelog of an internal service. That is a choice per pass, and it only takes effect once someone with write access to the target space has allowed it.
Agents read the same source
Your engineers are no longer the only readers. Coding agents write a growing share of integration code, and they are only as accurate as what they read. If they read a stale wiki or guess from old code, they reproduce the drift at machine speed.
Gravity serves the same reviewed docs, search and memory to agents over MCP. An agent works as the person who connected it, so it reads exactly what that person may read: published pages, and internal spaces only where they have access.
Your agent’s MCP settings
{
"mcpServers": {
"gravity": { "url": "https://docs.acme.com/api/mcp" }
}
}The first call opens a sign-in in the browser. You approve the agent once; you can see and revoke that grant at any time, and every call it makes is recorded.
Add a refund button to the order page. Use our API.
POST /v1/refunds needs amount, charge and reason. I’ll add a reason picker to the dialog and send it with the request.
Open a call to see what it returned.
A plan to adopt it
Adoption does not need a migration project. It starts with one repository and one engineer, and widens one stage at a time, each stage useful on its own. The plan below is the order we recommend; tick steps as you go, or copy it into your own tracker.
0 of 15 done
One repository
0 of 4See what Gravity would write, before anything is published.
In the pipeline
0 of 4Every pull request is checked, every merge proposes the update.
The whole product
0 of 4Every repository behind the product feeds the same docs.
Across the organisation
0 of 3Review stays with the people who own each space.
Questions teams ask
The CLI runs where your code already is: on an engineer’s machine or as a step in your CI. It reads the repository, works out what changed since its last run, and sends Gravity what it needs to write the docs.
The reference itself is built from your OpenAPI documents, without AI. Passes that write prose, such as guides and the changelog, send the relevant changes through Gravity’s AI gateway.
Further reading
How the reference is generated, with a page you can explore: Generation. How review works for generated and written changes alike: Review and publish. Cited answers for readers and memory for agents: AI and search.