Tutorial: The Docs-Honesty Loop¶
What You’ll Build¶
Published documentation rots the moment the code it describes changes. The usual fix is discipline: remember to update the docs, hope a reviewer catches the ones you forgot. axiom-graph replaces that hope with a signal.
This tutorial walks the full loop end to end, the same loop that produced the site you are reading right now:
Author a consumer doc in DocJSON, linked through a dev-doc proxy to the capability it describes.
Change the code behind that capability.
Watch staleness flag the consumer doc
LINKED_STALEautomatically, even though it never linked to the code directly.Update the doc through the viz Doc Manager or the MCP doc tools, then re-verify.
Republish the corrected public site with
render-site.
The payoff is a documentation set that tells you when it has drifted instead of silently lying — because linking docs to code and detecting that those docs went stale are the two reads against one mesh.
This is the companion to the reporting-pipeline tutorial. That one shows an agent consuming the mesh to do work; this one shows the mesh keeping its own documentation honest.
The Proxy-Linking Architecture¶
Before the steps, the one idea that makes the whole loop work: consumer docs link through a dev-doc proxy, not at raw code.
It is tempting to point a user guide straight at the function it describes. Don’t. Function and symbol names change constantly; a consumer page wired to compute_staleness breaks the instant someone renames it, and the page itself rarely talks about that symbol by name anyway. Consumer docs describe capabilities, not symbols.
So the provenance chain has three layers, connected by documents edges in the mesh:
Code node <--documents-- Dev-doc section <--documents-- Consumer-doc section
(the symbol) (PRD / design / ADR) (this guide)
The dev-doc layer (a feature PRD, an interface spec, a design section, or an ADR) binds tightly to the code: it links to the actual functions and classes. The consumer layer binds only to that dev-doc section. The consumer page therefore rides the chain: it inherits a staleness signal whenever the code changes, but it is insulated from the code’s churn. Rename the function and the dev-doc’s link updates; the consumer page never notices, because it was never pointing at the symbol.
This is the difference between linking at code and linking through a stable description of code. Pick the dev-doc section that documents the capability, link to that, and your page stays stable under refactors while still hearing about real changes. The same architecture is described from the engine’s side in staleness and from the authoring side in DocJSON.
Step 2: Change the Code¶
Now play the part of the developer who refactors the underlying capability. Someone edits the staleness computation, the function your dev-doc section links to, and rebuilds the index:
axiom-graph build /path/to/project
build is discovery-only: it inserts new nodes and notices changed ones, but it does not overwrite existing staleness signals. It simply records that the code node’s content hash no longer matches the hash captured when the dev-doc was last verified.
Nothing about your consumer page changed. You did not touch its file. Yet, as the next step shows, it is about to be flagged, because the mesh knows your page is two documents hops downstream of the code that just moved. That is the whole point: drift is detected structurally, by following edges, not by anyone remembering to look.
Step 3: Transitive Staleness Flags the Doc¶
Run a check:
axiom-graph check /path/to/project
The code change ripples outward through the chain. The dev-doc section goes LINKED_STALE because the code it links to changed. And because your consumer page links to that dev-doc section, it goes LINKED_STALE too, transitively:
own: 1 CONTENT_UPDATED / 0 DESC_UPDATED / 0 RENAMED / 0 NOT_FOUND · link: 2 LINKED_STALE / 0 BROKEN_LINK · 47 VERIFIED
NODE OWN_STATUS LINK_STATUS
axiom_graph::...index.staleness::compute_staleness CONTENT_UPDATED VERIFIED
docs.features.staleness.design::architecture VERIFIED LINKED_STALE via ...::compute_staleness
docs.consumer.staleness::how-it-works VERIFIED LINKED_STALE via docs.features.staleness.design::architecture
Read the via breadcrumbs bottom to top: the consumer page is stale via the design spec, which is stale via the function that changed. You can trace the entire path back to the root cause without grepping.
Why the consumer page hears about it¶
Direct doc-to-code staleness is a single hop. Transitive propagation is what carries the signal across the doc-to-doc documents edge to the consumer layer, and it is opt-in and tag-gated. axiom-graph only propagates through documents that carry a tag listed in transitive_tags:
[axiom_graph.staleness]
transitive_tags = ["consumer"]
Because your page is tagged consumer, it participates. Developer specs that link to other specs do not pick up transitive signals unless their own tag is listed, so the noise stays where it belongs. The propagation loop walks doc-to-doc edges until the stale set stabilizes (usually one or two passes), with a visited-set guard so cycles can’t spin. See staleness for the full status model and configuration for the tag knobs, including frozen_tags for historical docs like ADRs that should not chase every edit.
This is the loop’s keystone: published, user-facing prose stays honest even though it never touches code, because staleness is a read on the same mesh the docs live in.
Step 4: Update and Re-Verify¶
A LINKED_STALE flag is an invitation to review, not a verdict that the prose is wrong. Sometimes the code change does not affect what your page says; sometimes it does. Follow the breadcrumb to find out.
Review the chain. Read the dev-doc section that the page links to (and, through it, the code that moved) to see whether your user-facing description is still accurate.
Edit if needed. Two paths, same mesh:
Viz Doc Manager. Open the viz dashboard, go to the Docs tab, and edit the section in the rich-text editor. Edits save straight back to the DocJSON file on disk, and the link picker lets you re-point the proxy link if the dev-doc section itself was renamed or restructured.
MCP doc tools. An agent connected over MCP calls
axiom_graph_update_sectionto patch the content, andaxiom_graph_add_link/axiom_graph_delete_linkto fix proxy links.
Re-verify, and mind the sticky rule. LINKED_STALE is sticky. It does not clear because you edited the prose, ran another check, or because someone upstream marked the code clean. The only thing that clears it is a fresh verification snapshot on the doc section itself:
axiom-graph mark-clean docs.consumer.staleness::how-it-works /path/to/project \
--reason "Reviewed after staleness refactor; user-facing behavior unchanged"
Note a sharp edge: the CLI mark-clean clears own-status drift (CONTENT_UPDATED / DESC_UPDATED), but to clear LINKED_STALE on a doc section, save it through axiom_graph_update_section, which auto-records a verification snapshot, or call mark_clean over MCP with the doc section’s node ID. Either way the snapshot captures the current hashes, so the next check promotes the section back to VERIFIED, and if the code drifts again later, the snapshot invalidates and the flag returns. That hash-anchored snapshot is the audit trail: a durable record of who reviewed which section against which version of the code.
While you are in here, also watch for BROKEN_LINK. Every build and check runs a consistency pass that flags any edge pointing at a node ID that no longer exists, for instance, if the dev-doc proxy section was deleted out from under you. Repair it with axiom_graph_delete_link plus a new link to the correct target. Unlike a rename (which the proxy architecture absorbs silently), a deletion is a structural break that always wants human eyes.
Step 5: Publish to the Site¶
With the section reviewed and back to VERIFIED, publish the corrected page to the static site.
Render¶
axiom-graph render-site /path/to/project
render-site walks the nav, renders each listed DocJSON section to clean Markdown (stripping the internal node-id links), prepends a provenance stamp, and writes the nested pages into userdocs/guide/ for a static site generator (Sphinx/MyST) to build into HTML. Because the output tree mirrors the source tree one-to-one, the relative links between pages resolve without any per-page configuration. Commit the generated userdocs/guide/** alongside your DocJSON. The nav file location and render defaults live under [axiom_graph.site] – see configuration.
The corrected page is now live, and the loop is closed: code changed, the mesh flagged the downstream consumer doc, you reviewed and re-verified it, and the republished site reflects reality.
This Site Is Built This Way¶
None of this is hypothetical. The documentation you are reading is itself a set of DocJSON nodes in the axiom-graph mesh. Each consumer page links through dev-doc proxies, ADRs, PRDs, and design sections, that bind to the code. Because consumer is a transitive tag, these pages inherit LINKED_STALE whenever the underlying code drifts. That flag is the maintainer’s cue to review; mark-clean re-verifies; render-site republishes the public mirror.
The full set of consumer guides, the recursive nav manifest, the render-site pipeline, and transitive LINKED_STALE are all shipping capabilities, not aspirations.
The takeaway is the loop itself. Documentation in axiom-graph is not a static artifact you periodically remember to update; it is a node in a typed mesh that tells you when it has drifted. The same edges that let an agent pull exactly the context it needs are the edges that carry a staleness signal from a changed function all the way out to the published page describing it. Honesty is not a process you impose on the docs; it is a query the docs answer about themselves.