A financial-adviser assistant needs to assemble a client document with figures such as portfolio values and fees. The people checking that document need to find the source of each figure, not just read a plausible explanation.
You will build a fictional Thornbury Wealth workflow in which the conversation edits stored fields and the document is rendered from their source records. The illustrative document below shows the problem: its figures and conclusions arrive without references.
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.
Before using this sample document, a reviewer needs to know where its figures and conclusions came from.
Find the source of each figure
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. The sample states a judgement without identifying the source decision behind it.
- “Ongoing charges of 0.62%” — correct, as it happens. Indistinguishable from the ones that are not.
The problem is that the document gives the reviewer no way to distinguish a sourced figure from an unsupported one.
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.