The tool, and the contract
Throughline is a Git-native requirements tool: one small YAML file per item, a permanent UID, typed links, and a validator that fails the build on an orphan, a dangling link or an unaccepted item. tl-compose lets one graph borrow another's items by tag or by path. The first example's technical page sets out the working contract in full; it held here unchanged. The AI authors every item as proposed; a human ratifies; every commit names an item; every document is generated and gated; a claim about the world is a link to a source that was read.
Five graphs, and what they compose
Everything lives in one repository under one idd/ directory, one graph per subdirectory. Two shaping graphs record why the other graphs have the registers they have. The argument holds what the essay claims. The rendering holds the paragraphs and composes the argument by path, two of the company's own style graphs by tag, and three public writing axes from the catalogue by tag. The renderer is the small program that turns the rendering into a Word file, and has a graph of its own. Every link that crosses a graph boundary is stamped, so a change on either side of the seam is visible on the other.
Shaping, twice
Nothing was written into a working graph until a discovery pass had recorded why it would be shaped as it was. That pass is itself a small graph with five registers: the sources read, a one-paragraph reading of the domain, the needs the work has of its graph, one decision per register created, and one non-goal per register considered and rejected. It ran twice because the work turned out to be two things.
The argument's pass rejected three registers with reasons recorded: tests, because a claim is verified by evidence, not by a procedure; requirements, because nothing here is to be built; and paragraphs, because a paragraph's wording depends on voice, reader and channel while the claim does not. That last rejection is the design decision the whole example rests on: paragraphs belong one layer down, in a rendering that composes the argument.
The argument graph
Two schema rules did the work a reviewer would normally do. A coverage rule at error severity says an empirical claim with no supported_by link to evidence fails the check; that is what found three facts the four human drafts had carried, because a claim could not be typed empirical without a link and a link could not be made without reading. And objections are declared delivery roots, so an objection with no answers link into it is reported unserved; that turned the persuade axis's rule about acknowledging the strongest objection into structure, and gave the fifth draft one new paragraph and two new sentences.
Where the evidence supported less than the draft claimed, the claim was retyped rather than the evidence stretched: "each was feared at the time" became inferential and hangs on an assumption; "does not think, assess or choose" now rests on a stated definition of thinking.
The rendering graph
Those are the counts on the evening. The essay has since been through a rework: it stands at four sections and twenty-eight paragraphs, with a fifth register of thirteen version records, each naming a commit and the sha256 of the file at it. The measurements further down this page are of that later document.
The rendering composes six graphs. The argument, by path. The company's tone-of-voice and house-conventions graphs, private, pinned at a tag minted that evening for the edition of the style guide they encode. And three public, vendor-neutral axes from the catalogue: writing to persuade, writing for a general reader, and writing for the web.
Each paragraph carries three kinds of link. expresses to the claims it carries; cites to the evidence it names in its text; satisfies to the borrowed rules it applies. A coverage rule makes a paragraph that expresses nothing an error, and the title and teaser elements must express claims too, which is how the fourth draft's teaser was found to contradict the essay's first section. The house rules caught an italic sign-off, "a assessment" and an unexpanded abbreviation the same way: a satisfies link that could not honestly be made until the text changed.
Two knowing deviations were recorded as relates links on the rendering's intent with the reason, never as satisfies: the sentence-length rule, which the essay's twelve-word average does not meet by design, and the word budget, 900 in the intent against 1,169 rendered. The ratifier accepts or overturns them; the graph does not pretend.
The renderer
tl docs renders a document from item markers, and it renders each item as a block: a header with its UID and status, the text as a quotation, its links, its attributes. That is the audit view, and it is exactly what a reviewer wants. It is not an essay. Nor can the rendering graph embed a paragraph from the argument graph, since a document renders only the graph's own items. Both facts fed the design: paragraphs live in the rendering, and a small renderer of the project's own turns the generated document into the file a reader opens.
That renderer is forty lines of parsing and a Word writer. It strips the block chrome, renders section items as headings and elements by slot in the shape the author's working document already had, resolves each cites link to the URL on the evidence item so the published essay carries live citations, and writes the Word file in the house format with every zip timestamp fixed so that two runs give the same bytes. It has its own graph, with a test for every requirement in it. The fifth draft is the graph, rendered.
Two runs giving the same bytes is a weak claim, and the first version of this page made a weaker one: that regenerating after ratification produced a file whose hash had not changed. That could not have come out any other way. The renderer reads an item's text, title, order, form and slot; ratification writes ratified_by and ratified_fingerprint, and the renderer reads neither. Measuring it properly, on 17 September, meant proving that by removal rather than by argument: strip all 216 signature lines from the 108 item files that carry them, regenerate, and get the same document back. The claim worth making runs the other way. One character changed in one paragraph's prose moves the copy and exactly one of the twenty-seven parts of the Word file, and leaves the other twenty-six alone; the same holds for every one of the twenty-eight paragraphs, taken one at a time. Both halves are written down as assertions in a test, which goes red if either stops being true. Nothing runs it on a schedule yet: the repository has no CI.
Taking that measurement found five faults in the renderer that no gate could see. A paragraph set to rejected was still published, byte for byte, with the composed check, the document check and the render all green, because the only status the renderer filtered was deleted; it now refuses rejected, deferred and suspect, in both graphs and through the evidence a citation resolves to. Swapping two sections' order attributes changed nothing, because the order check filtered to paragraphs. Under a C locale with UTF-8 mode off the renderer stopped with a decode error and wrote nothing, which is the default state of a minimal container image. Six of the eight zip header fields were left to the platform rather than set. And the determinism test compared two renders to each other and to no recorded value, so a build whose only deflate is zlib-ng satisfied every assertion in it while producing a different file. Repacking the same twenty-seven parts on Fedora 42, whose zlib reports itself as 1.3.1.zlib-ng, gives 610,009 bytes rather than 602,437, twenty-five of the twenty-seven compressed differently, and every one of them decompressing to identical bytes; repacking them under zlib 1.3 reproduces the published file exactly. What is recorded and checked now is a manifest of the document, the sha256 of the copy and of all twenty-seven parts uncompressed. The hash of the Word file itself is a property of the deflate build that packed it, and is no longer published as though it travelled.
The challenge pass
Before hand-off, the challenge pass ran over all five graphs: the deterministic checks by script, the semantic ones by reading items held together. It found ten paragraphs that named evidence in their text with no edge, each of which got a cites link. It found one claim that restated two others, which was retired and its paragraphs pointed at the originals. And it found all 130 links into the composed sources unstamped, so none could detect a moved source; all were stamped, which is what makes an amended claim or a changed house rule mark its paragraphs suspect.
Reading the paragraphs against the rules they cited found two more things a script could not: one paragraph cited the active-voice rule and contained a passive, and every paragraph cited the sentence-length rule the essay does not meet. The first was reworded; the second became the recorded deviation above.
Ratification, and three tool releases
Ratifying 157 items exposed three defects in the toolchain, and each became a release before the evening ended.
- The ratification cockpit failed on every graph that composed a source. A private field it read from the composition library had been renamed a release earlier, and the cockpit's own tests had stubbed the seam rather than exercised it. The fix was one line; the real change was a test that composes a real source unstubbed, and a build step that runs the installed cockpit over the tool's own composed graph before it ships.
- Stamping 153 links took about fifteen minutes, because every command re-validated six sources over the network and each link was one process. Measured, not guessed: three seconds a call, of which 1.3 s was the network and 1.7 s was start-up. An issue was filed with the numbers and two asks, a revalidation window and batch mutations.
tl ratifytook one UID, and the hand-over command that passed two did not run. The author asked whether it should take several. It now does, in the core tool and the composition layer, each item shown and confirmed on its own.
The author then declined to press the button 157 times, delegated the signatures in writing, and the AI ran tl ratify on his instruction with the delegation recorded in every commit message. The ratified_by field names the human; the commit names the delegation. Whether that is a ratification or a formality is the question this example leaves open.
The gate
Three commands, on every commit: the composed rendering graph is sound with nothing unaccepted in it, its generated document matches the graph, and the essay renders with every claim expressed.
What the tooling taught
supported_by rule found three errors that four human drafts had carried, before any paragraph existed.tl docs is right for a reviewer and wrong for a reader. The gap is a small renderer with its own graph, not a hand edit of the output.relates link, never a satisfies. A rule the text knowingly breaks is recorded with a reason; the ratifier decides.--help before handing over a batch. The command that failed in the author's hands was the tool's limit, not his; the tool changed.