A reply is consumed once, by the person who asked. A document is kept, forwarded, filed, and read by people who were not in the conversation and cannot ask a follow-up question.
That difference is not about formatting. It changes what the agent must guarantee.
Three properties a reply does not need
It outlives its context. The reader in eighteen months has the document and nothing else — not the transcript, not the caller’s tone, not the moment where the adviser said “roughly, for now”. Anything the document does not carry explicitly is gone.
It is read as a claim by the firm. A chatbot saying something wrong is a bad chatbot. A suitability record saying something wrong is the firm saying it, in writing, on its own letterhead.
It has to be reproducible. “What did we send the client in August?” must have exactly one answer, and producing it again must not produce something slightly different.
None of the three is satisfied by generating text. All three are satisfied by deriving a document from state that was stored.
The shape this forces
Stop thinking of the document as the output of the conversation. Think of it as a view over state that the conversation edited.
conversation ──edits──▶ STATE ──renders──▶ document
▲
│
source records
Everything in this tutorial follows from the direction of those arrows. The conversation touches state. State is built from source records. The document is computed from state, and nothing writes to it directly — there is no arrow into the document except through the renderer.
Run it:
make render
You will get a Markdown file. Look at the section near the bottom:
## Provenance
Every value above, and the record it came from.
| Field | Value | Source record |
| --- | --- | --- |
| client_name | Marged Ellis | Custodian position extract · CL-77301 · legal_name |
| total_value | £486,210.44 | Custodian position extract · VAL-2026-08-29-PF4402 · total_value_gbp |
| objective | Draw a steady income from the portfolio from 2028, … | Client fact-find questionnaire · FF-77301-OBJ · value |
That table is not documentation of the document. It is the same data the body was rendered from, printed a second way. It cannot disagree with the body, because both come from one place.
Why Markdown and not PDF
The canonical artifact is deterministic Markdown, and PDF — if you need it — belongs downstream of it.
The reason is that the guarantees in this tutorial are checked by diffing. Byte-identical re-renders, one-field changes showing up as one-field diffs: all of it needs a diff surface a human and a test can both read. PDF byte-diffs are noise, and a PDF that differs in its embedded timestamp fails an idempotency check for no reason anyone cares about.
Get the text artifact right and correct. Then render it to PDF, and let the PDF step be a formatting concern rather than a correctness one.
The completeness section
Scroll further down the rendered document:
## Completeness
3 declared field(s) have no source and render blank:
- `property_value`
- `property_weight`
- `include_property_breakdown`
A document that states what is missing from it is doing something a generated document structurally cannot. A model writing prose has no concept of a field it was not given — the absence produces no output, so nothing marks it. Here the field list is declared up front, so a field with no value is a visible, countable gap.
That is the first payoff of deriving rather than writing, and the next chapter is about where those fields are declared.
