Here is what a capable model produces when you ask it for a client suitability record. This is not a strawman — it is the good version, from a careful prompt:
SUITABILITY RECORD — Marged Ellis
Portfolio: Ellis Family Income
Your portfolio is currently valued at approximately £486,000, held across
a balanced mix of global equities (around 58%), sterling corporate bonds
(roughly 30%), and property funds (about 12%). This allocation remains
well suited to your Balanced risk profile and your objective of drawing a
steady income from 2028.
Ongoing charges of 0.62% and an advice fee of 0.45% apply.
That is fluent, correctly formatted, and plausible in every particular. It is also unusable, and the reason is not a mistake you can point at.
Nothing in it is wrong. That is the problem
Read it again with one question in mind: which of those numbers came from a record, and which came from the model?
You cannot tell. Neither can the client. Neither can the compliance officer who reads it in eighteen months. Every figure is stated in the same confident register, and the register is the only evidence on offer.
Now the specifics:
- “approximately £486,000” — the extract says £486,210.44. Something rounded it, and nothing recorded that it did.
- “about 12%” — the property funds line is 11.9%. Close. Also: this portfolio’s property holding was never sourced at all in our fixture, so a model producing this sentence has produced a figure for a position it was never given.
- “remains well suited to your Balanced risk profile” — this is a suitability determination. A sentence a regulator reads as a professional judgment, generated by next-token prediction.
- “Ongoing charges of 0.62%” — correct, as it happens. Indistinguishable from the ones that are not.
The document is not false. It is unfounded, which is worse, because false documents get caught and unfounded ones get filed.
The missing piece
Every figure needs to carry the record it came from, and a figure with no record must be incapable of appearing — not discouraged, not flagged in review, incapable.
That rules out the obvious architecture. If a model writes the document text, then provenance is at best an annotation the model also writes, and a model that can write a footnote can write a wrong one. The fix is not a better prompt or a checking pass. It is to take the pen away.
What you will build
An agent that assembles a suitability record and cannot write a word of it.
The conversation edits structured state — a declared set of fields, each one pointed at a source record. The document is a pure function of that state, recomputed from scratch every time anyone asks for it. Delete a field and it disappears from the document. Point a field at a record that does not exist and it renders blank, and the blank is reported.
By the end you will have run this, and understood why it is the load-bearing test:
REFUSED: 1 figure(s) no longer match the record they cite. The document was
not rendered.
total_value: document says '486210.44', source says '911000.0'
cited as: Custodian position extract · VAL-2026-08-29-PF4402 · total_value_gbp
The custodian restated the valuation after those figures were read into state. Every figure in the document was still footnoted; one footnote had quietly stopped being true. The renderer produced nothing at all.
The six steps
| Step | What it teaches |
|---|---|
| Artifact as outcome | The deliverable is a file, not a reply, and that changes the design |
| State, not prose | Fields live in declared memory; the model negotiates, tools write |
| Grounded fields | Every figure traces to a record. Unsourced renders blank, never plausible |
| Derived rendering | Template plus state equals document, recomputed on the far side |
| Revision as diff | An edit changes state and re-renders; history is append-only |
| Stated limits | What it refuses, in the same words the code uses |
Before you start
Clone the companion repository.
Chapters 1 through 5 need no credentials, no network, and no model — the proof
runs under bare python3. Only the last chapter talks to the agent.
cd tutorials/rasa-document-artifact-tutorial
make prove
If that exits zero, you have everything you need.
