How we learned to rebuild these docs

Developer documentation decays in a particular way: the CLI gains flags, schemas change, init output shifts, and each page that quotes those surfaces goes stale on its own schedule. Hand-edits fix the page you touched and tell you nothing about its neighbors. This page describes the pipeline we built to solve that problem for the Hankweave manual itself – a system we call the forge, which locks a published runtime version, gathers receipted evidence about it, writes pages from those receipts, and checks the result deterministically before anything is published.

We did not begin with the program that produced the accepted edition. We began with a few briefs, then learned how much work it took to keep a correction when the next stage ran. What follows is the production story of those changes: the early serial hanks, the generated waves, and the fixed programs we now use to write, repair, check and illustrate the docs. The accepted 0.10.0 edition contains 70 pages; the website exists on a review branch, not as a deployed automatic GitHub workflow.

Why hand-editing leaves contradictions#

Why not just hand-edit the docs when a contract changes? We tried maintaining a tree in which the same mutable contracts appeared in several places, examples had never been executable, and prose predated its fixtures. The three August 2026 reviews independently recommended rebuilding that tree. We needed a way to carry a correction into the next edition, not just make today's paragraph look right.

The August 24 refresh shows the problem plainly: it touched 17 files and left 18 untouched, with those untouched pages already 4–6 months old. Even the refreshed pages shipped a v0.8.0 banner sample and the wrong --init task count. Updating one page did not tell us whether its neighbor still contradicted it.

We also learned not to ask prose review to stand in for execution. Model review missed dist-scaffolds-5, wizard-on-bare-invocation, and bun test-exits-0. We put deterministic checks first and at stage boundaries so we could inspect what actually ran.

That gave us another useful job for the forge: release auditing. When the shipped artifact and the source disagree, we can record the disagreement upstream instead of quietly choosing the more convenient story for the docs.

How the forge turns a version into pages#

A hank is a configured work program; a codon is one sealed task within it. A brief holds numbered facts and their receipts before we write prose. A rig runs deterministic setup before a codon, and a checkpoint preserves that codon's files. We kept those distinctions while changing the program around them.

The forge did not arrive at its current shape in one step. The table below is the development log for September 2026: each row is a working shape we actually ran, and each change followed a concrete failure – a retry storm, a discarded fix, a review that could not see what it needed. The versions in the table are versions of the forge's programs, not releases of the Hankweave runtime. Throughout this story we were documenting runtime 0.10.0.

Scroll to explore the table →
WhenProgram or working shapeWhat we changed
September 1, 2026C1, v0.1.0 through v0.1.4A serial nine-codon pilot proved three briefs before prose. Retries, removal of an exhaust extension and a larger packaging cap followed actual failed launches.
September 1C2 v0.2.0, then C3 v0.3.0C2 wrote and reviewed the pilot pages; C3 brought page-time errata back into briefs and added real fixture captures.
September 1–2Separate extraction and wave hanksThe v0.5.0 extraction hank separated source discovery from deterministic receipt checks. Wave shards let us work on page groups concurrently.
September 2–3Manifest-driven compiler and orchestratorWe generated stage-specific hanks and prompts, collected their artifacts, and reached the first complete 70-page set.
September 4docs-forge-v2: four fixed hanks10-ground, 20-briefs, 30-xpass and 40-pages took selected work through data inputs instead of generating a new program for each wave. A set went in and a revised set came out.
September 7–8Bounded repair and verification-only recovery45-repair-page repaired retained drafts; an authenticated completed author stage could finish review without writing the page again.
September 8Editorial and figure passesWe ran the separate 55-readability editor, attached fidelity-checked figures, then ran fresh 60-verify reviews against the integrated edition.

Two ideas survived every row of that log. First, facts live in briefs and prose is rendered from them. Second, a fact gap discovered during writing goes back upstream to the brief, never sideways into a prose patch. Our original C1/C2/B design is where those boundaries first became explicit: C1 vetted the skeleton – the page outline and writing order – and built briefs; C2 wrote pages and routed fact gaps upstream; B named the deterministic publication side. The figure below shows that v1 design, not the current v2 topology. The part to notice is the return arrow from repair to briefs; that is the rule we kept when everything else was reorganized.

FIG. 1 How the forge turns a version into pages
Read the diagram as text
Output
version
  |
  | resolve (rig: fetch tag + tarball + schemas, cohere-check)
  v
 docs.lock
  |
  | hank C1: skeleton-vet → preservation → brief writers → fact-check
  v
 briefs
  |
  | hank C2 writers — Gate 1 (human reviews briefs) on this boundary
  v
 pages
  |
  | review + lint + panel
  v
 reviews
  |
  | findings + confusion dispositions
  v
 repair
  | \
  |  \ durability rule: fact gaps land in briefs, never prose
  |   `-----------------------------------------------> briefs
  |
  | re-render (forge render, idempotent) — Gate 2 (human reviews triage) on this boundary
  v
 md set
  |
  `-- hank B: publish — may not alter claims
      -> published site

Historical docs-forge-v1 C1/C2/B design, used in the September 1–4 campaign. The image was drawn during the September 8 figure pass; it still depicts the older design. “Published site” names the intended output, not a claim of public deployment.

Why the lock starts with a version#

Every edition starts with the version a reader can actually install. At the September 1 campaign opening, the resolver targeted the public, unscoped hankweave package, whose registry record then listed 19 versions with 0.10.0 latest. The private repository's restricted @southbridgeai/hankweave name was not the published artifact we needed to document.

We freeze the resolution in docs.lock.json: the version and resolution time, npm tarball hash and entry point, engine requirement, public tag and commit, schema hashes, captured surfaces and integrity findings. That gives a later reviewer a concrete starting point instead of whatever happens to be in our working tree. The lock for 0.10.0 appears below; two parts carry most of the weight. The publicRepo block pins the exact tag and commit the docs describe, and the surfaces block records deterministic counts – four init codons, 26 CLI flags, the --help hash – that later stages can re-measure and compare.

JSON
{
  "lockFormat": 1,
  "version": "0.10.0",
  "resolvedAt": "2026-08-31T17:00:07.795Z",
  "npm": {
    "name": "hankweave",
    "tarballSha256": "b0f13ad14db90f43e2091c83b2c70427368c945d8e676e0cbcc59ad050c675a8",
    "binEntry": {
      "hankweave": "dist/index.js"
    },
    "engines": {
      "node": ">=22.19.0"
    }
  },
  "publicRepo": {
    "url": "https://github.com/SouthBridgeAI/hankweave-runtime.git",
    "ref": "v0.10.0",
    "commit": "d0f0a86bcf4528f23ffa687b9c8964c15fe7cb88"
  },
  "schemas": {
    "sentinel.schema.json": "63b4609a21adf5866c259e60919e0ddfd359e452a55e798f3ab18ed3fad0a708",
    "hankweave.schema.json": "55bb4583b060d385da9ab51de643413e3e49ae592b3a88e2e83237ac667aa0f0",
    "hank.schema.json": "f82ec9348d5cacdb50a3d781f68a88637e11928ce43e435831d9f80223195d96"
  },
  "surfaces": {
    "helpSha256": "dca659a69b58fd08ff52cb56c5396f9b590f48c4ecd016b91640d3434280c442",
    "initCodons": [
      {
        "id": "analyze-haiku",
        "model": "haiku"
      },
      {
        "id": "analyze-gemini",
        "model": "pi/google/gemini-2.5-flash"
      },
      {
        "id": "analyze-pi",
        "model": "pi/anthropic/claude-haiku-4-5"
      },
      {
        "id": "analyze-gpt",
        "model": "pi/openai-codex/gpt-5.6-terra"
      }
    ],
    "initExit": 0,
    "cliFlagCount": 26
  },
  "integrityFindings": 1
}

For surface evidence, we run the published artifact with bunx hankweave@<version> --help and --init in a temporary directory. We keep help.txt and the init fixture from that binary. The flag inventory, cli-flags.json, also uses parser source, so we do not describe the whole inventory as an execution capture.

Comparing those two sources is what produces the lock's integrityFindings count. When artifact and source disagree, we record the disagreement in the release-integrity report rather than picking the more convenient story. At 0.10.0, the report has one P2: five parser-real flags are absent from --help. Their inventory belongs to the CLI reference.

Output
# Release-integrity report — hankweave@0.10.0

Resolved 2026-08-31T17:00:07.795Z. Artifact-truth rule: docs describe the published artifact; skews below are RELEASE bugs, not doc disputes.

- **[P2] flags parsed but absent from --help** — --replay, --max-cost, --max-time, --without-proxy, --ignore-data-mismatch

This check changed what we could honestly write. The private repository's stale checked-in dist suggested five init codons; the published 0.10.0 artifact scaffolds four, matching source. We document the four we observed and keep the canonical init tree in first-run.

When the version changes, lock-diffs enumerate the pages that need edits. Upgrade guidance belongs on the upgrading page; this page does not promise or reproduce a compatibility matrix.

Which receipts can carry a claim#

Before writing a claim, we ask what kind of evidence it needs. Behavior means running the published artifact in a fixture. Contract means the published schemas or shipped binary's output. Rationale comes from public source at the locked tag. Private design history can add color, but it cannot establish runtime behavior.

Scroll to explore the table →
Truth planeSourceLoad-bearing?
Behavior ("running X does Y")published npm artifact, executed in fixturesrequired
Contract (schemas, flags, events, exit codes)published schemas + shipped binary's own outputrequired
Rationale (why it works this way)public repo source at tagrequired
Color (design history, PR discussions)private repo when availablenever

We do not ship a runtime claim supported only by private material. If the public inputs cannot establish it, we need an upstream publication request, not a local workaround.

We make that decision in the brief, while correction is still cheap. Each fact is a bullet with a typed receipt: artifact, contract, source, old-doc, review or external. Those labels describe the evidence form, not permission to use it for any claim. A review can explain our decision to rebuild; it cannot prove how a runtime flag behaves.

A claim such as “model X works” needs a small, budgeted live-smoke check with a dated annotation. That check is a dated behavior receipt for this narrow claim; it does not make private source load-bearing. A fixed version does not freeze model catalogs: the retired-Gemini-preview issue showed that the catalog can drift without a code change. The check must be rerun on its schedule.

How we split the work across fixed hanks#

The early human gates helped us stop at an inspectable point. Gate 1 covered three briefs, the vet report and manifests; Gate 2 covered finished pages and triage. In the C1/C2/B design, we could judge the fact layer before reading the whole prose set. C2 combined fresh-context writers – continuationMode: "fresh" starts a new session – review, confusion reports, repair and triage; B could change presentation but not claims.

We did run the publication side. On September 3–4, the v1 publisher wrote 70 MDX pages, redirects, llms-full.txt with its blocklist preamble and the parquet to docs-overhaul-0.10.0. The branch built 74 static pages. Those are different counts: authored documentation pages and site-build pages.

The weakness of v1 was that the work program itself was generated for each batch, so the machinery changed while it ran. In v2 we inverted that: the hanks stay fixed, and each launch receives the work it should do as data. The first four programs separated grounding, brief revision, cross-page checking and page writing; later we added narrower programs for cases where a completed draft deserved a smaller next step. The figure below shows the resulting topology.

Output
v2 core and recovery programs (September 2026)

locked inputs + previous set
          |
      10-ground
          |
      20-briefs --------> 25-verify-brief
          |               (brief assertion when selected)
      40-pages
          |
      30-xpass ------> supported corrections
          ^                  |
          |             45-repair-page
          +------- corrected pages and briefs

retained, unchanged authored candidate
          |
   verification-only finish
          |
   fresh review + assertion

The arrows describe handoffs, not one JSON hank containing the other hanks. 40-pages runs its own write, errata, review, cold-read and repair codons in sequence. 30-xpass checks the whole collection after page work, because page-time errata can change briefs. When that pass finds a supported correction, we repair the affected page and check the collection again. 25-verify-brief is a separate assertion program for a selected brief, not a required detour on every page.

The smaller recovery programs earn their place when a full rewrite would be waste. In the September 7 environment-variable repair, the draft had omitted a Bedrock qualifier. 45-repair-page changed one table row, then ran an independent whole-page review and final assertion. Its receipt records 664 seconds and $0.1572 tracked cost; the brief and trace were unchanged. That is one measured repair, not a price promise for the next one.

Sometimes the author has finished and only the reviewer has stopped. On September 8, the fixture guide and state-file reference each completed a verification-only finish without another author codon or a change to the reviewed subject. We require the completed author journal and unchanged candidate before taking that path. A rejected report can tell us what to repair; it never approves the repaired bytes.

Checkpointed intermediates make this work inspectable. The early programs also used a cost watchdog and a laziness observer. We had a reason for the latter: a Gemini packet rewrite once ended “success” after 15k output tokens of thinking and no file. We added incremental writing and self-verification rather than treating a successful session as an output receipt.

Why fixes must land upstream#

When we find a fact gap while writing or reviewing, we repair the brief or skeleton before re-rendering. Otherwise the next assembly can erase the correction. C2's repair codon routed nine fact gaps back to the brief layer instead of inventing answers in prose; the next wave could use those repairs.

We had already seen the opposite happen in opusdoc v1.7.0: the next mechanical assembly discarded polish-codon fixes. That experience is why we care about where a correction lives, not only whether today's page reads correctly.

Where deterministic checks hold#

Some reference material is truth scattered across dozens of source files – event names, error classifiers, exit codes. For that material we give models and deterministic code different jobs, in a pattern we call the extraction sandwich. Extraction codons find the pieces and write a receipted JSONL intermediate – one JSON record per line, each record pointing back at its source lines. Rigs then check receipt windows and coverage, and render byte-stable output. This is separate from capturing --help and --init; its purpose is to let us compare generated catalogs across editions without a prose rewrite obscuring the change. The validation report below is the gate between extraction and rendering.

Output
# Extraction validation — PASS

events rows: 36 · errors rows: 20 · event candidates: 36 · error candidates: 63

- all receipt and coverage checks passed; tables rendered

Read the counts as two different stories. The event side reached 36/36 exact coverage: every candidate became a receipted row. The error side filtered 63 candidates down to 20 rows, which is the sandwich doing its job – deterministic checks rejecting what extraction over-collected. The proof covers 36 runtime event rows and 20 error classifiers in the generated reference catalogs, each with a line receipt. The rig’s 23 initial failures were adjudicated as validator defects, and the combined receipt and coverage checks passed. The rendered output is embedded by reference/events and reference/errors-and-exit-codes, which own those catalogs.

The generated-wave era taught the same separation at a larger scale. The first gen-wave1.py emitted 15 brief prompts and three shard hanks; compile.py later handled stages including briefs, xpass, pages, errata and journeys. The cross-brief ownership pass found nine issues across 20 briefs, including two P0 registry restatements, and replaced repeated claims with links to their owning pages. Models judged the ownership issues; deterministic receipt and publication checks guarded the resulting artifacts.

We designed publication to fail closed on stale pages, skipped required checks, invalid examples, unresolved impact work or missing artifacts. That is the acceptance boundary we want before replacing a good edition; it is not evidence that an unattended publisher has been deployed.

What agents read after publication#

We also prepare a clean markdown copy for automated readers. The publication design gives each page a stable URL and starts /llms-full.txt with the version pin, a blessed install command from docs.lock, model-adapter routing guidance and the full falsehood blocklist. The accepted branch includes this agent-facing output; public deployment remains separate.

The preamble carries the blocklist because an agent that ingests old docs can relearn a phantom surface such as --autostart, hankweave-results/, or hankweave run. The blocklist’s corrections are owned by the agent preamble; this page does not copy them. The agent build chooses one path per topic rather than serializing toggle variants. Stripe’s 69.7KB flattened file is the cautionary example: it repeats eight language/framework install sections, making the agent-facing text bloated and self-contradictory-looking.

Each page carries a version banner rendered from the lock. The banner is the human-facing expression of the version pinned in docs.lock.

How the pipeline audits a release#

Using the runtime this heavily gave us documentation evidence we would not have found by reading alone. The staged findings file contains 22 verified findings against 0.10.0, numbered 1–14 and 18–25. In that dated review record, P0 means a reader-breaking defect, P1 a trust defect and P2 a coherence defect, not a runtime status. Zai-routed codons reported $0 cost, so dollar budgets did not bind on that route; another finding recorded lazy time-budget enforcement rather than an interrupt. We also verified matching public mirror tags for v0.8.0, v0.9.0 and v0.10.0 because the resolver depends on them.

We drafted, polished and reproduced the issue reports before posting 13 of them upstream as SouthBridgeAI/hankweave #223–#235. Reproduction materially changed three reports. That was work worth keeping: the reports and the docs could share the corrected evidence.

The pipeline’s own review codon found a three-way disagreement about --idle-timeout: help says “0-255, default: 0,” the parser rejects 0, and the schema allows 0. It became finding 12 and was drafted as issue 8 in the pipeline’s issue set, separate from the repository reports numbered #223–#235.

The staged findings file has no numbered entries 15–17. Finding 15’s substance was superseded in-file by entry 19, “correction of finding 15”; findings 16–17 remain log-only and are not presented as entries here. The numbering gap is therefore not three additional findings omitted from the total.

How we made room for readers and figures#

A checked page can still be tiring to read, and a correct page can still lack the figure that would make its structure visible. The last stage of the September campaign therefore ran two parallel refinement tracks after correctness work: prose editing and figure development. Both produced candidates, not approvals, and both fed a final whole-set verification before assembly.

On September 8 we made editing its own bounded program: 55-readability gives one Sol codon a page, brief, trace, work orders, peer snapshot and source evidence, with the Vue writing guide leading its instructions. That distinction from release approval mattered immediately. The first design-page edit misread maxAttempts: 3 as three total starts. A source check caught the error and a second editing run corrected it. Seven completed Sol runs edited six distinct pages for $9.9437 tracked cost. After the associated source-based corrections and small integration edits, ten public bodies differed from the pre-editorial edition and sixty were unchanged.

We developed image figures alongside that work in a separate docs-figures workspace. Each figure starts with the page and its text twin, then a brief naming the exact labels and edges. The pipeline routes small diagrams to image generation and dense diagrams to Mermaid, applies a paper-background rig, and checks fidelity before attachment. Retained-session edits are new candidates and go through the same checks. The final selection retained 14 raster figures and seven Mermaid figures, with illustrations on three walkthrough pages. The map below shows how the two tracks merge back into the suite: edited pages and vetted figures are integrated together, then the integrated page and its brief go through a fresh 60-verify before assembly and acceptance.

Output
reviewed pages
    |
    +--> 55-readability --> edited candidate
    |
    +--> 50-diagrams --> checked Mermaid twins
                            |
                  separate docs-figures work
                  brief -> route -> candidate
                            |
                   paper rig -> fidelity gates
                            |
                     selected figures
    |
integrate selected edits + figures
    |
fresh 60-verify on integrated page + brief
    |
assemble -> accept -> publication branch

These were separate passes, not an automatic editorial/figure branch inside every default release. The image workflow was still awaiting integration as a fixed figure hank.

The retained image near the start of this case study is a good example of why captions need dates. Its Gemini gate passed every required label and edge; the final Opus inspection found soft spacing and style defects, but no hard or fidelity defect. It was an accepted rendering of the old C1/C2/B design, not a drawing of today's fixed hanks.

Final review also caught a wrong anchor-rig description in concepts/rigs. We corrected its page and brief, then reviewed all 70 pages again because the shared brief context had changed – a late fix anywhere invalidates approvals that depended on the old context. The September 8 record reports 70/70 final passes and 9/9 acceptance checks. We published the 70-page, 21-figure edition to docs-0.10.0-final and served that branch locally for review. It was not pushed or deployed.

How to run the forge at a version#

If you want to reproduce the method, start with the docs-maintenance example, then distinguish the historical forge commands below from Hankweave's own CLI. The v1 forge had its own repository with start_here.md, log.md, hank/, runner/, read-only-at-runtime data/, gitignored runs/, results/ and scratch/. V2 keeps its fixed programs under hanks/.

In the September 2 v1 record, forge ground --version V --prev P combined resolution, snapshotting, fixture recapture, extraction and impact. That entry explicitly says fixture/extraction plumbing had not yet been exercised end to end. V2 later ran grounding for 0.10.0, but that does not prove this older command completed a new-version recapture. We keep that distinction rather than turning a design into a completed-run claim.

The historical forge.py run staged data swap-atomically, compiled and validated each hank, committed under the clean-tree law, launched provider-capped shards, collected artifacts into results/<tag>/, and saved resumable state.json; --recover rebuilt state from run directories. The September 2 log records Baseten 2 and zai 1 after four Baseten shards hit 429 within two minutes. Those are dated operational settings, not current runtime defaults.

forge render assembles out/<version>/docs/<section>/<page>.md deterministically. The newest canonical page wins, banners and runtime come from the lock, and embeds are re-derived. The render is repeatable: repeated runs of the 39-page set produced the identical digest a26292998ab6d33a.

forge impact --from A --to B is the change-impact gate. On real 0.9.0→0.10.0 caches it classified 41 MUST, 22 REGEN, and 6 CARRY, with evidence from the help hash, flag sets, init fixture, schema hashes, and 21 changed server files.

During a headless run, we inspect agentRoot/ files and .hankweave/events/events.jsonl rather than waiting for console chatter. The state file is <exec>/.hankweave/state.json.

Failure has several recorded shapes. A completed codon count does not by itself mean the run exited successfully: a budget cap trip exits 1 mid-chain, and package-gate tripped its $0.50 cap at $0.5218 even though its 9/9 codons had completed. A zai five-hour-window quota returned 429 code 1308 and failed a run mid-flight as non-retriable. Resume-after-kill hit a rollback-manifest bug at 0.10.0; the recovery instruction was to salvage sealed artifacts from agentRoot/ instead.

The model policy is history, not a runtime guarantee. In the 2026-09 run, the run.sh launcher refused any hank naming sonnet; its building ladder was glm, Kimi-K3, and deepseek-flash, with page writers on gpt-5.6-luna. These names describe that run, not a runtime model catalog.

The costs belong to their particular runs. At wave-three completion on September 2, v1 had tracked $128.87 of its then-$205 envelope ($25 pilot plus $180 scale); Zai spend was unmetered. The first complete v1 set later recorded $218.19. V2's September 4 generation recorded $24.61 across 87 runs, including retries. Those runs started with different amounts of existing work and used different review programs, so they are not a controlled speed or cost comparison.

The later hardening campaign shows what still hurts. Its September 7 duration audit measured a median page chain of 2,506 seconds, about 42 minutes, with eleven serial codons. Three full reviews occupied about 52% of the median critical path. The audit also reproduced avoidable re-authoring on a carry-only plan and broad review invalidation after a peer brief changed. We have proved narrower repair and review finishes; we have not proved that every unchanged page or new version avoids repeated work.

We can rebuild projections from an accepted set without model calls. An unattended GitHub workflow that selects only necessary work, preserves valid approvals, verifies a new version and deploys it remains future work. The next useful proof is that complete path, not another promise that a successful local edition has already automated it.