# Lorenz Convergence Check — instructions for AI agents

The check's headline is "How your product work could move EBITDA". It is free and needs no account. It returns an overall figure from 0 to 100 with a word (Diverging, Drifting, Aligning, Converging, lowest first), a figure for each of 7 dimensions, how much of the check the shared material could show (evidence coverage), how an engagement would start, with a short list of threads, and a link to a hosted report the person can share. This guide describes engine 8.0.0 (`report.engine_version`), the built-in model.

Base URL: https://lorenz.converge.studio
A person can do the same thing by hand at https://lorenz.converge.studio/assess

## What the check grades

7 dimensions, each answered by one recognition question (10 points) and 3 scored questions (30 points each); the weight is each dimension's share of the overall figure:
1. Economic Contribution (weight 18): Assess the value created or protected after costs, and when that value becomes available.
2. Strategic Coherence (weight 18): Assess whether the allocation and sequence of product effort advance the company's chosen direction.
3. Customer & Market Alignment (weight 18): Assess whether the work addresses consequential needs in the customers and markets the business intends to serve.
4. Delivery Credibility (weight 14): Assess whether the organization can deliver a usable result within the required time and capacity.
5. Operational Sustainability (weight 9): Assess whether the organization can sustain the ongoing responsibilities created by its product work.
6. Risk & Resilience (weight 9): Assess whether material downside is contained and the organization can recover or change direction.
7. Outcome Learning & Adaptation (weight 14): Assess whether the organization can determine whether work helped and use the findings to change subsequent decisions.

The check reads every question from the shared material first. Anything the material does not show earns nothing: the figure is never graded on a curve, and a sparse upload scores low. When less than 40% of the check could be judged, the figure still shows and the band word reads "Not enough to tell". The check never asks what the files already answer.

The path: Share → Check → Priorities → Clarify → Report. About two minutes. You get a report preview link after the read-back that fills in as you answer, and the final link at the end; the check is not finished until you have the final link.

The recognise reply, the dry grade's reply and the stored grade's reply each carry `say` and `progress`. Relay `say` to the person word for word before anything else. Before each ask, name the step from `progress` as "Step N of 5 · Name", with the name from `progress.title` (the dry reply's reads "Step 4 of 5 · Clarify Your Approach"). When a step starts, say its purpose once, after its step line, as llms.txt lists them. In Clarify, number the questions as "Question 2 of N", where N is the dry reply's `question_count`: the one count, the same number the page shows and `progress.questions_left` carries. It does not change as you ask. The read-back before the questions is not a question. When the dry reply's `financial_question` is true, the last of the N is the optional financial question, counted in N like the others.

Each step's purpose, said once when the step starts, right after its step line: "Step 1 of 5 · Share Your Files — gathering your files and showing you what will be sent." "Step 2 of 5 · Check Data Integrity — making sure your files were read correctly." "Step 3 of 5 · Confirm Priorities — confirming the goals and business priorities your work is measured against." "Step 4 of 5 · Clarify Your Approach — a few questions your files could not answer." "Step 5 of 5 · Get Your Report — your result and what to do next."

Work WITH the person. They confirm how their files were read and the priorities list, correct the read-back, and answer the questions. Never invent an answer, and never change their data.

## 1. Gather the six inputs from whatever tools you reach — never invent, ask the person

Your first message to the person is this paragraph, word for word: "Here is what is about to happen. I will gather your backlog, roadmap and strategy documents into a few files, show them to you, and send them to the Lorenz Convergence Check. It reads how well your product work lines up with your goals and what closing the gaps could be worth. You answer a few short questions and get a report you can share. It takes about two minutes, and you can come back with more information and run it again as often as you like." Then tell the person the path: Share → Check → Priorities → Clarify → Report. About two minutes. You get a report preview link after the read-back that fills in as you answer, and the final link at the end; the check is not finished until you have the final link. Then gather what covers ONE planning period. Up to 8 files, 256 KB in all, 500 rows. Each file is plain text: CSV, Markdown, JSON or plain text. Spreadsheets (XLSX) and PDFs cannot be read: export them as CSV, or copy the text. The six inputs:
Required: Backlog (with descriptions and whatever you track); Plan (current roadmap, with supporting details); Strategy (mission, goals, priorities — and anything else that says where you're going).
Helpful for a full review: Business cases (or financial targets); Customer evidence (research, requests, usage); Capacity (people, points or a budget); Support and incidents (tickets and outages); Risks (known risks and promises); Outcome reports (results of past work).

In detail, the six inputs:
- business direction (the mission and the priorities or goals, each with a target if there is one)
- the current plan (what is committed for this period)
- the underlying work, including maintenance and ongoing work (the backlog or tracker export, one row per backlog item)
- capacity and constraints (budget or people per team, dates fixed from outside)
- the reasons behind the work (business cases or financial targets, customer evidence, promises made to customers, risks)
- how the work is run and checked (support and incident records, outcome reports on past work, how plans change)

In these instructions and to the person, a backlog item is one row of the work list: a story, an epic, a feature or whatever the team plans in.

This is what you get: https://lorenz.converge.studio/assess/example (an example report for an invented team).

Nothing needs a separate document. If something is not written down (a budget practice, a strategy), do not invent it or hunt for it: tell the person, and they answer the check's questions instead. A tracker export is best with every column you can get: owner, status, theme or epic, rank, size, the goal each backlog item serves, how firm it is, dates fixed from outside, dependencies, cost or value. A CSV template: https://lorenz.converge.studio/assess/template.csv

When backlog items name the goal, promise or risk they serve, send those goals, promises and risks as tables too (a small CSV each, one row per goal, promise or risk, with its name in a column): a name that appears only in prose matches nothing, and the link is read as missing.

If the person has no business case or financial figures written down (expected value of the main work, targets, costs, the size of the customer base), you may help them build one from what they know before sending: ask, work the arithmetic with them, and write the result as a short plain-text file. Say which figures the person supplied and which you derived from them, and send only what the person has confirmed. Never invent a figure: a figure nobody gave stays out, and the check scores what is not shown as not shown. The person's own financial figures (annual revenue, annual EBITDA, EBITDA margin, or the size bands) never go in a file or anywhere else you send: a derived business case may state costs, benefits and timing, but not those figures. They are the last question, counted in `question_count`, and are handed to the report after `#` (see step 4).

A strategy document, OKR page or leadership brief shared as prose (Markdown or text) is read for the business priorities, with any targets, and the mission, and as notes for the check's questions. At most 120 pieces of each document are read for priorities.

## 2. Show me each file and the privacy note, and wait for the OK

Before sending anything, show the person each file, what is in it and what you left out. In the same message, under the heading "Data sensitivity", say in bold, word for word: "Lorenz's Convergence Check inspects your process and approach, not your domain strategy. Before anything is sent, I can replace proper names, sensitive terms and specific metrics with neutral stand-ins. Tell me what to mask." Then, under the heading "Privacy", tell them: "The files you share are not stored. Only the neutral input read from them, the column mapping you confirm and the report are kept, for 30 days unless you leave an email, and none of it is ever used to train a model. To read them, the files are sent to TypeSafe, whose model answers the assessment's questions about them. TypeSafe is the only outside service that reads your files, and Lorenz does not keep the files themselves. The report quotes the answers you give and any passage you point at, up to 500 characters each. A fingerprint of what the model was asked, and its answers — not the text itself — is kept for 30 days so that checking the same files again reads the same answers." Then say what comes next, word for word: "Next is Step 2 of 5 · Check Data Integrity. The check reads your files and tells us how it understood each one, so you can correct it. To do that, I need to send the files to Lorenz." Then, as you ask for their OK to send, say: "I will make three web requests to Lorenz. Your app may ask you to allow each one." Wait for their OK.

When the person asks you to mask something, replace it the same way in every file: one stand-in per name or term ("Customer A", "Product 1"), used wherever the original appears, because a goal, promise or risk named on a backlog item links only when the two names match. Replace a metric with a stand-in number of the same kind; never delete it, because a target that is missing is scored as not shown. Leave column headers and the structure of each file as they are. Then show the person the list of what you replaced, each stand-in next to the original, and the masked files, and wait for their OK again. Keep that list to yourself: it is never sent, and you use it to read the report back to the person in their own words.

Say what comes next before the other two requests as well, each in the message that ends the step before it: before the dry grade (step 4), "Next is Step 4 of 5 · Clarify Your Approach. The check now grades what your files show, which can take up to a minute. Then I read back what it found and ask the few questions your files could not answer."; before the stored grade (step 5), "Next is Step 5 of 5 · Get Your Report. I am sending your answers to store the report, which can take up to a minute." Never send a request the person has not just been told about.

Send all three requests (step 3, then steps 4 and 5) the same way: write each request body to a file and use one command of the same shape every time, so that one "always allow" covers the rest. For example:

curl -sS -X POST <address> -H 'Content-Type: application/json' --data-binary @<file>

Never ask the person to turn on a bypass or a wider permission mode. If you cannot send (the person declines, or you have no way to make a web request), save the prepared files to a folder, tell the person where, and point them to https://lorenz.converge.studio/assess?via=manual ("Share text or files") to upload them there. Do not retry in a loop.

## 3. Read the columns and confirm the readings and the priorities list

POST https://lorenz.converge.studio/api/assess/recognise
Content-Type: application/json

{"files": [{"name": "backlog.csv", "text": "<the file's full text>"}]}

The reply:

{"files": [{"name": "backlog.csv", "role": "backlog", "columns": [{"source": "Story Points", "mapped_to": "size", "confidence": "high"}]}], "kinds": ["backlog"], "priorities": {"proposed": [{"id": "p-1", "title": "…", "order": 1, "target": "…", "span": {"file": "strategy.md", "index": 4, "text": "…"}}], "unsure": [], "mission": {"text": "…", "confidence": 0.8}, "from": ["strategy.md"]}, "engine": "8.0.0", "model": {…}, "model_pin": {"token": "…", "expires_at": "…"}, "retention": "..."}

Keep `engine` and `model_pin` exactly as they came: every grade request in steps 4 and 5 sends both back unchanged, so the whole check runs under one model version.

Call this once per set of files. Check is a message of its own. Open it with one line per file, as "<file>": read as <role in plain words> (the role words: `backlog` "your backlog", `roadmap` "your plan", `strategy` "strategy notes", `budget` "capacity and budget", `promises` "promises made"). Give no row counts of your own: `say` carries the count. Show the person only the columns the reader was unsure of: a column whose `confidence` is `low`; a column that came back as `ignore` with the reader's guess under `suggested` (it was read below 0.6); and, when no column of a work list was read as its name, the most certain name guess, which comes back already read as `title` with `confidence` `low`. Show each with its file and what it was read as (or the guess), in the words below, not the field name, and ask the person to confirm or correct it. Then say, word for word, "The other N columns read clearly. Say 'show all' to see them." (N is the number of the other columns). When no column was unsure, say only "All N columns read clearly. Say 'show all' to see how each one was read." (N is the number of columns). When the person says "show all", show a table for each file: its role, then every column, what it was read as and ONE confidence reading: when the column carries `certainty` (from 0 to 1), show that number to two places and not the word; show the `confidence` word only when there is no number (below 0.6 the reader was unsure). To correct, edit `mapped_to` (or `role`) in the `files` array and set that column's `confidence` to `exact`. When the person takes a `suggested` guess, set `mapped_to` to it with `confidence` `exact`. Keep every column; set one you cannot use to `ignore`. The edited `files` array is the `mapping` you send next.

End the Check message by asking "Is that how your files should be read?" and wait for the person's answer. Do not show the priorities in the same message: the person answers Check first, and Priorities starts in your next message.

A file's `role` is one of: `backlog` (the work list, one row per backlog item), `roadmap` (work placed in time or tiers), `strategy` (priorities, goals or risks), `budget` (money or capacity per team) or `promises` (commitments made to customers or others). A prose file (a strategy page, meeting notes, a business case, a decision record) is sent as `strategy` with every column set to `ignore`: its lines are never counted as backlog items or goals, and it is read as notes. The check proposes this itself when a table in the same files already lists the work; otherwise set it yourself.

In the Priorities message, show `priorities.proposed` (title, target, and the words it came from), `priorities.unsure` (pieces the reader was not sure of; the person decides) and `priorities.mission`. The person edits, reorders, removes or types their own, up to 7. Each confirmed priority may carry `from`: the proposed priority's `span.file`, for one found in a document (leave it out for one the person typed). `priorities.skipped: "no_model"` means the document could not be read this time: ask the person to type their priorities. Read the proposed list against the document before you show it: when a title is only a number or a fragment, or a heading in the document is missing from the list, say so plainly and offer the document's own headings as the list. Offering the document's words is not inventing a priority.

Priorities is a new message, after the person has answered Check: say "Step 3 of 5 · Confirm Priorities" and its purpose. The numbered sections of these instructions (gather, show, read, grade, present) are for you, not the person's step numbers: the person's step is always "Step N of 5". If priorities were found but the person has not confirmed them, show the ordered list and ask them to confirm or correct it. If they already confirmed the list while you read the columns, do not ask again: say "Step 3 of 5 · Confirm Priorities — completed from the priorities you already confirmed." and continue. Finding or uploading a document is not confirming it: never say the step is complete without the person's confirmation. If none could be read, ask the person to type their priorities, or say that they chose to continue without them. Never go from Step 2 to Step 4 without this, and never invent a priority. In the same message as the priorities, ask "What is the planning unit that you are working in?" with the answers numbered: 1 Stories, 2 Epics, 3 Releases, 4 Initiatives, 5 Features, then 6 Something else (the person says the unit in their own words). No file says the planning unit, so ask it every time, unless the person has already told you. Send the answer as `"corrections": {"level": "<the answer's word, or the person's own words, at most 64 characters>"}` with the dry grade AND the stored grade. A person who skips it is not asked again.

What each field means:
- id — Item key (ID)
- title — Name
- description — Description
- created — Created date
- last_updated — Last updated date
- funding_line — Team / funding line
- value — Business value (a value number, not a composite rank)
- priority_label — Priority label (High, Must…)
- urgency — Urgency / time value
- risk_reduction — Risk reduction / opportunity enablement
- cost_low — Cost, low
- cost_expected — Cost, expected
- cost_high — Cost, high
- size — Size estimate
- size_unit — Size unit
- acceptance — Acceptance criteria written?
- requester — Requested by
- customer_specific — For one customer? (core or custom)
- perceived_value — Perceived value (Kano…)
- alignment — Mission fit mark
- commitment_level — How firm (commit, stretch…)
- planned_for — Planned for (quarter, date)
- excluded — Left out of the plan?
- included — In the plan?
- candidate — Up for this cycle?
- exclusion_reason — Why left out
- rank — Rank
- goal — Goal it serves
- commitment — Customer promise it serves
- risk — Risk it addresses
- depends_on — Depends on (other items)
- work_type — Kind of work (Kano, FURPS, type)
- status — Workflow status (To Do, Done…)
- theme — Theme / epic / parent group
- fixed_date — Date fixed from outside (contract, law, event)
- composite_score — Ranking score (WSJF, RICE, ICE…)
- parent — Parent item
- flexibility — Fixed or flexible
- furps — FURPS category
- measure_current — Goal: current value
- measure_target — Goal: target
- mission_ref — Goal: part of the mission served
- outcome_recorded — Goal: outcome recorded?
- owner — Owner / assignee
- owner_type — Promise: owner type
- firmness — Promise: how firm
- probability — Risk: probability
- impact — Risk: impact
- mitigation — Risk: what is being done about it
- budget — Budget (money)
- capacity — Capacity
- capacity_unit — Capacity unit
- tolerance — Tolerance (+/-)
- period — Budget period (quarter, year)
- target_share — Target share of the mix (%)
- ignore — not used.

On a goal, risk or promise row, the column holding its own name is `title`, not `goal`, `risk` or `commitment` (those name what a backlog item serves).

## 4. Read back what was found and ask me the questions one at a time

POST https://lorenz.converge.studio/api/assess/grade
{"files": [<the same files>], "mapping": [<the confirmed files array>], "priorities": [<the confirmed list>], "mission": "<the confirmed mission>", "read_for_priorities": [<the recognise reply's priorities.from>], "engine": "8.0.0", "model_pin": <the recognise reply's model_pin>, "corrections": {"level": "<the planning unit the person gave in Priorities>"}, "dry": true}

Leave `corrections` out when the person gave no planning unit. This grade stores nothing final. The report preview, graded from what the files answered, is stored as a draft at `draft_url`, under `slug`. The model reads the files first, so allow up to a minute. The reply carries `say`, `progress`, `engine`, `model`, `model_pin`, `mode`, `read_back`, `closing_questions`, `question_count`, `financial_question`, `draft_url`, `slug`, `not_asked`, `submission`, `recognised`, `unknown_targets`, `inexact_links`, `ignored_columns`, `dropped_rows`, `row_counts`, `warnings`. `submission`, `recognised` (how many of each kind were read), `row_counts`, `dropped_rows` (rows set aside, and why), `ignored_columns`, `unknown_targets` (names on backlog items that match nothing shared), `inexact_links` and `warnings` are the receipt. `unknown_targets` lists the names written on backlog items — a goal, promise or risk they serve, or other work they depend on — that match nothing shared (that link is read as missing); a promise or risk written only in prose cannot be matched, so a table of them (one row each) is what links it, and a missing piece of work is a row the work list leaves out.

Read the receipt BEFORE the read-back, and stop for a blocker only: nothing was read as a backlog item (`recognised.items` is 0), or more than half the rows of a work list were set aside (add up every `dropped_rows` entry for that file with `entity` `item` — rows are set aside for more than one reason, one entry per reason — and compare the sum with the rows the file holds). For a blocker, tell the person what happened, in a message of its own, and offer the fix: change the files or the mapping with them and go back to step 3 (read the columns again, then a new dry grade, which makes a new draft link). Do not read the rest of the receipt to the person (names that match nothing, rows set aside, columns not used): the `wrap_up` of the stored reply says what to add next time. A fix is never offered in the same message as a question, and never once the questions have started.

First the read-back. Say: "This is what we believe from what we found. Correct anything that is wrong." Then show the lines from `read_back` that the person may correct, those with `correctable: true` (their `id` is one of `period`, `level`, `priorities`, `committed`, `capacity`), in ONE message, each line's `text` as written, and end the message by asking, word for word: "Is anything in this list wrong?" Wait for the person's answer before anything else: the preview link is not in this message. Leave out the `level` line when it has no `value`: the planning unit was asked in Priorities, and it is not asked again here. Do not read the other lines: they are counts, carried in the reply but not shown. Each correctable line carries the `value` that was read. Nothing in the read-back is one of the N questions. A correction is the person's answer and replaces what was read for that fact only. Collect corrections for step 5's `corrections`: `period` (text), `level` (the planning unit, as text of at most 64 characters), `priorities` (1 to 7 titles, top first; they replace those read), `committed` (how many backlog items, from the top of the ranked order, are committed) and `capacity` {"total": <number>, "unit": "<text>"}. Send only lines the person changed, and the planning unit from Priorities.

After the person has answered the read-back, open your next message with the report preview link from `draft_url`, as “Your report preview: <link>”, and say it fills in as they answer. It is a halfway point, not the result: ask Question 1 in that same message, never present the preview as the finished check, and never ask whether the preview is right.

Then the questions. `closing_questions` holds at most one question per dimension, at most 7; `question_count` is how many questions you ask in all: these, plus the financial question when `financial_question` is true. Ask them one at a time, in the order given, as "Question 2 of N" with N = `question_count`, in plain words; the person answers in their own words and you match their answer to a choice with them. Each question has an `id`, its `dimension_name`, a `kind` and a `prompt`:
- `kind: "choice"`: read the `prompt`, then every choice's `text` in the order given, numbered 1, 2, 3…, then "Not sure" as the last number, and say they can answer with the number. A number is the choice it names; an answer in their own words is matched to a choice with them. Never reorder the choices, never say which is better, and never mention a level or a score. A choice with `example_counts: true` counts in full only with a short example in the person's own words; without one it counts for less. Do not say that on the list. When the person picks such a choice and gave no words beyond the number, ask once, as part of the same question: "Can you give a short example, in a sentence? This answer counts in full only with one." Send what they say as `example`; when they gave words with their answer, those words are the example. When their words describe a different choice from the number they gave, read both choices back and ask which one holds.
"Not sure" or a skipped question sends nothing, never a no or a zero. An answer changes only its own question. When an answer contradicts one the person already gave (the capacity answer says nothing is set aside for support, and a later answer says a share of capacity is), name both and ask which holds before you go on; never pick one for them.

When `question_count` is 0, say that the files answered everything the check would ask. `not_asked` names the dimensions nothing shared speaks to ({"dimension", "name"}): they are not asked about and show as not shown. `mode` (`backlog_only`, `no_figures` or `figures_found`) is for you only: never name it to the person.

When the dry reply's `financial_question` is true, the last question is the optional financial question, counted in `question_count` like the others: ask it after the closing questions, as "Question N of N · Your Financial Figures" (N = `question_count`). It is not in `closing_questions`, and its answer has no key in any request body. When `financial_question` is false (`mode` is `backlog_only`), do not ask it: the report has no dollar section. Say first, in these words: "The last question is about your company's annual revenue and its EBITDA or EBITDA margin. It is optional, and it is used only for the dollar figures on your report." Then: "Only your agent will know these numbers. They are not sent to Lorenz or to the model that reads your files." Then ask "Which figures do you have?" with the three answers numbered: 1 "Exact figures", 2 "Select from example ranges", 3 "No figures on hand". Ask only the chosen answer's parts, as parts of this one question, and ask them together in ONE message, not one part per message. For 1: the annual revenue, the annual EBITDA (a loss is negative) OR the EBITDA margin (%), whichever they have, and whether the revenue is ARR; when they know neither the EBITDA nor the margin, read the five margin bands numbered 1–5 and take a number. For 2: read the seven revenue bands numbered 1–7 and the five margin bands numbered 1–5, take a number for each, and ask whether the revenue is ARR. 3 skips it. Offer any annual revenue, EBITDA or EBITDA margin found in the shared files for the person to confirm or correct. Every part is optional, a skip stays blank, and no question comes after this one. The revenue bands: 1 Up to $1M (`up_to_1m`), 2 $1M – $5M (`1m_5m`), 3 $5M – $10M (`5m_10m`), 4 $10M – $25M (`10m_25m`), 5 $25M – $50M (`25m_50m`), 6 $50M – $100M (`50m_100m`), 7 Over $100M (`over_100m`). The margin bands: 1 Heavy loss, −40% or worse (`loss_over_40`); 2 Loss-making, −40% to −10% (`loss_10_40`); 3 Break-even, −10% to +10% (`break_even`); 4 Profitable, +10% to +25% (`profitable`); 5 Highly profitable, +25% or better (`highly_profitable`). Read the person the band labels only, never the ids: the id in backticks is what the link carries. Withholding them changes no score: the overall figure, the dimensions and the band word are the same with or without them; only the report's dollar figures need them. People may keep them back for security reasons, and that is fine. Financial figures are never sent: a body holding one is refused (400 `revenue_not_accepted`). A band is not a figure, but it travels the same way and never goes in a body either. When the person gave a usable basis (annual revenue with an EBITDA, an EBITDA margin or a margin band, or a revenue band with a margin band), send `"figures_on_hand": true` with the stored grade (a yes, never a number). Give the figures or bands to the report only when the stored grade's `report.framework.mode` is not `backlog_only` (otherwise the report has no dollar section: give the plain link), and only after `#` in the report link, as plain numbers (a loss negative, margins as percentages) or band ids, with only the chosen answer's parts: `<report_url>#financial_v=3&basis=figures&annual_revenue=10000000&ebitda=2000000`, `#financial_v=3&basis=figures&annual_revenue=10000000&ebitda_margin=20`, `#financial_v=3&basis=figures&annual_revenue=10000000&ebitda_margin_band=profitable`, or `#financial_v=3&basis=band&revenue_band=10m_25m&ebitda_margin_band=loss_10_40`, adding `&use_arr=1` when the revenue is ARR. A browser never sends the part after `#` to a server; the report fills the figures in and removes them from the address bar. Give that link as THE link to the report, never after a plain one: a person who opens the plain link first sees no figures. If the person chose "No figures on hand" or gave none, give the plain link and tell them the report has a place where they can enter their figures or their company's size privately.

## 5. Grade and store the report and give the link

POST https://lorenz.converge.studio/api/assess/grade
{"files": [...], "mapping": [...], "priorities": [{"id": "p-1", "title": "…", "order": 1, "target": "…"}], "mission": "…", "read_for_priorities": [...], "engine": "8.0.0", "model_pin": <the same model_pin>, "corrections": {"level": "<the planning unit from Priorities>", "capacity": {"total": 120, "unit": "points"}}, "closing": [{"question": "<question id>", "choice": "<choice id>", "example": "<the person's own words>"}, {"question": "<question id>", "choice": "<choice id>"}], "draft_slug": "<the dry reply's slug>", "dry": false}

`draft_slug`: send the dry reply's `slug` with the stored grade, so the final report keeps the same link as the draft. Without it the final report gets a new link.

`closing`: one entry per question the person answered — `question` is the question's `id`, and exactly one `choice` (a choice's `id`, never its text or a level); `example` is optional, at most 500 characters, and only the person's own words. Leave out a question the person skipped or was not sure of. Answer only the questions the dry reply asked. At most one answer per question, one per dimension and seven in all; more than that is refused (400 `answer_not_asked`). `corrections`: the planning unit from Priorities, and only the read-back lines the person changed. `figures_on_hand`: see the financial question in step 4.

`priorities`: at most 7, each with an `id` and a `title` of at most 200 characters, an optional `order` and a `target` of at most 200 characters. `mission`: at most 1000 characters. Send the recognise reply's `priorities.from` as `read_for_priorities`; the report lists them. Add "email": "<address>" only if the person asks to keep the report longer than 30 days. The reply carries `say`, `progress`, `done`, `report_url`, `wrap_up`, `slug`, `report`, `expires_at`, `engine`, `model_pin`: {"say": "…", "progress": {…}, "done": true, "report_url": "https://lorenz.converge.studio/assess/<slug>", "wrap_up": "…", "slug": "…", "report": {…}, "expires_at": "…", "engine": "8.0.0", "model_pin": {…}}. `report_url` is the report's link; give it to the person, with the financial figures after `#` as step 4 says. It can be read again with GET https://lorenz.converge.studio/api/assess/<slug>.

## 6. Present the result

The stored grade's reply carries `done: true`, `report_url`, `say` and `wrap_up`: relay the sentence, then give the person the link right after it. Give ONE link: `report_url` with the person's financial figures or bands after `#` when they gave them (step 4 says how), else the plain `report_url`. The check is finished only then; until the person has the link, it is not. Do not refresh or reopen the intake page.

Then relay the stored reply's `wrap_up` word for word, keeping its line breaks and its list. It is the whole closing message: the result, how the EBITDA figure works, what to add for a fuller read and where to run the check again. Add nothing to it: no dimension list, no coverage figure, no summary of the receipt. The report shows the rest. The one thing you may add after it: offer to talk it through with the Lorenz team; the report page has a "Discuss your results" button that sends them a request from the page (it does not book a meeting).

## Limits and errors

- 30 files read per connection per hour (step 3 counts each file; each grade in steps 4 and 5 counts one). More answers 429 with `code: "rate_limited"` and `retry_after_seconds`. Read the columns once per set of files, not once per attempt.
- 20 stored reports per connection per hour, the drafts from step 4 and the final reports from step 5 counted together. More answers 429 with `code: "rate_limited"` and `retry_after_seconds`. Do not retry in a loop.
- 400 `invalid_body`: the request is malformed; the `error` says where. 400 `revenue_not_accepted`: the body held a financial figure, and financial figures are never sent; take it out.
- 409 `engine_mismatch` or `model_pin_required` (a grade without the `engine` and `model_pin` the recognise reply issued), and `model_pin_expired`, `model_pin_invalid` or `model_version_missing`: the check cannot continue under the model it started with. Start again from step 3.
- 413 or 422 with `code` too_many_files, too_large, too_many_items, unparseable or mapping_mismatch: the `error` says what to change.
- 503 `unavailable` or `model_unavailable`: the service is off; try again later.
- 429 `daily_limit` (reading the columns in step 3, or either grade): "The free assessment has reached today's limit. Try again tomorrow." The free check has a daily spending limit for the model that reads the files, shared by everyone. Tell the person and stop; try again the next day (UTC). This is not `rate_limited`: waiting a few minutes does not help.
- 503 `scoring_unavailable` (either grade; when the model does not answer while reading the columns, step 3 reads them from the headers instead and never sends it), with `Retry-After: 60`: "The scoring model did not answer, so no report was made. Try again in a minute." Nothing was stored. Wait a minute and send the same request once more; do not retry in a loop.
- 500 `internal`: something failed on our side. Try once more; if it fails again, stop.
- Both grades read the files with the model first: allow up to a minute before treating one as failed.
