Stage one compresses every row into short factual statements. It does no judgment, no inference, no ranking. It will write "£42,300 paid to a vendor across 11 invoices." It will not write "this vendor is a key dependency," because that is an interpretation, and interpretation belongs to a later, named stage that owns it.
Keeping that stage stupid turned out to be load-bearing in a way we did not anticipate. We run it on a small model with thinking switched off. Turning thinking on made it worse: the model started improving the text, and improved text came back truncated. The dumb stage has to stay dumb.
Everything downstream reads that one shared information layer, built from every row rather than the interesting ones, so coverage is a property of the pipeline rather than something an agent decides.
Descriptive synthesis then runs in buckets grouped by topic and record type, and splits across two agents with one responsibility each. The generator proposes candidates from this run's new information plus a topic index of what is already known, so it hunts new angles instead of restating old ones, and it is judged on generation quality alone. The consolidator folds those candidates into the living set by emitting a transaction of lifecycle operations, add, update, merge, drop, demote, keep, applied deterministically. It never rewrites the whole set, and anything it does not touch carries forward byte-identical.
All of it lands in typed tables. An early design used a generic key-value blob, which is the natural thing to reach for and precisely the thing that cannot support a verifier, a denominator, or a per-consumer query. One consequence is worth naming: prose stops being where truth lives. Any guidance an agent eventually reads is generated from the stores and regenerable from them, and our last stage curates exactly that and files it as proposals.
Grounding
Then we added the piece that mattered most. Every claim has to cite the records it rests on. A cheap deterministic pre-check fails anything with no valid citation before a model sees it, and then a verifier from a different model family re-rates each surviving claim against only its cited sources. The verifier's number becomes the confidence. We built a field to hold the generator's own confidence in its output and then deleted it, because we could not name a question it answered honestly.
In testing, the verifier and the citation gate changed the outcome without changing a word of the prose: nothing about the writing got better, the claims simply got attached to their evidence.
The second thing citations bought us was not designed for. Every rung cites the rung below, so a claim walks back to the statements it cites and those walk back to raw rows. Nothing the system believes is a dead end.
Where we stopped asking the model
Once you start looking, the design turns out to be a list of places we took discretion away.
The extractor does not judge. The aggregator has no model in it at all: it walks raw records, computes the exact n/N, and that computed ratio is the confidence. The synthesizer may propose any pattern over any field, value, or population, but it has to express it as a typed predicate, and each shape has exactly one deterministic evaluator over raw rows, which turns scoring into a finite dispatch instead of text-to-SQL chaos.
| Shape |
Meaning |
Example |
CONSTANT |
field of a population is always one value |
invoices always issue from one entity |
BOUNDED |
numeric field stays inside a band derived from the population |
anomaly baselines |
PERIODIC |
recurring amount on a cadence |
rent of about £5,200 on the 1st, 11 of 12 months |
The proposing model is bounded to a published catalog of fields per record type, so open-ended can never mean inventing a column. And for BOUNDED, the band is computed from the population's own mean and standard deviation rather than chosen by the model, because scoring a model against its own guess is circular.
Open-ended in what gets proposed. Closed-form in how it gets checked.
The reversals
The interesting part of the build was watching production data correct us.