Skip to content

The nine stages

Each stage reads a file and writes a file. This page is what each one is responsible for, the decision that shaped it, and the thing that broke on a real paper.

1. Ingest

In: a PDF, EPUB, Markdown or text file. Out: doc.ir.json.

One adapter per format, and nothing downstream knows which one ran. New formats are new adapters, never new branches later on.

The PDF adapter is the workhorse and the one with scars. Two-column reading order is decided by whether a block spans the gutter — and "spans" had to include straddles, because a two-line title that crosses the gutter without being full width flipped whole pages into reading column two first, silently.

2. Clean

In and out: doc.ir.json.

Dehyphenation across line breaks (dictionary-checked, so "co-operative" survives), running heads and folios, paragraph fragments rejoined across column and page breaks, sentence segmentation, section structure.

The order of operations is load-bearing and documented in clean/pipeline.py. Line-number removal has to happen before dehyphenation, because a manuscript with line numbers produces recov- / 19 / ery and joining those gives you recov-19. That one corrupted 241 words before anyone noticed.

Document-level evidence before document-level surgery

Three behaviours here — stripping line numbers, stripping superscript citations, handling maths placeholders — inspect the whole document before acting on any block. A single digit welded to a word is far more likely to be a variable than a citation; a few dozen of them is a house style. Deciding per block would silently corrupt papers.

3. Triage

In and out: doc.ir.json, plus drop-report.md.

Every block gets keep, compress, transform or drop, with a reason and the rule that justified it. This is where the coherence principle is applied, and it is the largest single effect in the literature — removing helps more than adding.

The drop report exists because over-deletion is the failure that loses content invisibly. It is the file to skim when a programme feels thin.

4. Concepts

In: doc.ir.json. Out: registry.json.

What the document is about, and how hard each idea is. Two scores per concept:

Difficulty — abstractness, element interactivity, unfamiliarity, sentence density, span length. About the listener's working memory, not the subject's prestige.

Importance — position, reprise count, connectedness in the co-occurrence graph, author signalling, whether a figure supports it. About the document, not the reader.

Both are normalised within the document, then shifted by the listener profile. Their product is the elaboration budget: what earns a gloss, an image, three repetitions or nothing.

Absolute thresholds on relative scores select nothing

This has bitten twice. Both scores are normalised within the document, so a threshold like "importance above 0.5" only ever selects the paper's own subject — which is also the least abstract thing in it, so "abstract and important" selected zero concepts on every real paper. Thresholds here have to be positions in the document's own distribution, or ranks.

5. Verbalize

In and out: in memory, applied during planning.

Turning written text into speakable text, deterministically, because everything that touches a number, a name or a unit must stay in code that can be tested. The order is a pipeline and each step depends on the last: citations first (so [12] is deleted rather than read as "twelve"), then parentheses, then numbers, then symbols.

Exact by default. 0.0837 % becomes "zero point zero eight, three seven percent" — chunked, because a long digit run said as one stream is unhearable, but never shortened. The listener is a researcher and the numbers are the point.

6. Elaborate

In: registry.json. Out: registry.json, richer.

The only stage that calls a model, and it is off by default. Four tasks, spent in the order rule DIF-02 gives: gloss, then concrete anchor, then the "why", then the analogy. Explaining a term after showing its picture would be handing the listener an image of nothing.

Everything written here goes through the grounding gate before it is stored. Glosses and why-explanations carry the spans they were written from and every number in them must appear there. Anchors and analogies are ours — they are supposed to contain what the paper never said — so they get the numeric check only, and the planner introduces them as ours.

Every task has a documented degradation path, so one unreachable figure cannot kill a three-hundred-page book.

7. Plan

In: doc.ir.json + registry.json. Out: script.json.

Pure logic, no I/O, and the part with the real unit tests. The order of its steps is itself load-bearing: prompts must exist before prequestions can be drawn from them, the timeline must exist before spacing can be measured on it, and the duration budget must be enforced before the spacing repair pass because cutting beats moves everything after them.

Sections become segments of 45–90 seconds, split at beat boundaries and at the new-term budget. Each section closes on a recap and a question. Concepts come back at increasing intervals. The review block interleaves.

The exposure log is where SPC-01 becomes enforceable

"A minimum gap of three minutes between exposures" is unenforceable as written — a paper names its subject in most paragraphs. plan/exposure.py defines an exposure as a deliberate encounter, coalesces the ones a section's own structure forces together, and treats a close pair as an error only when the later one is a callback the scheduler chose.

8. Render

In: script.json. Out: the four artefacts.

Four views of one script, deliberately different. audio.md is only what gets said. study.md keeps everything the audio had to leave behind, with a page number on each claim. cards.json is the retrieval pool. manifest.json is the audit trail, one addressable chunk per beat so a re-render only re-synthesises what changed.

Pauses live in the manifest, not in the audio text: a break marker inside audio.md would be either unspeakable characters or words the engine reads aloud.

9. Lint

In: a build directory. Out: pass or fail.

Two halves. The text rules read audio.md and catch everything unspeakable — raw digits, brackets, citations, "e.g.", dangling references to figures. The script rules read the plan and catch everything unmemorable — a section that never asks you anything, a segment carrying five new terms, a repeat that is the same sentence twice, an anchor two concepts share.

Every script rule ships with a passing and a failing example, as named mutations of a real plan: "delete the answer beat", "give two concepts the same anchor". A rule with no failing example has never been shown to work.