Article the sixth
Three regulated decisions
The pattern in all three is the same, and it is worth saying before the details. A model is good at the routine middle of a caseload. Policy is good at the edges. A person must own whatever is consequential or contested. A chart lets you write down which is which, once, in a form that is checked and versioned, and then lets the model work inside it. This article shows the pattern three times. The first is a program you can run. The other two are charts that validate with vt validate and are shown, not run, and the difference is stated where it matters.
These are illustrative designs, not descriptions of any institution's practice, and not legal advice. What a lender, insurer or provider must do depends on jurisdiction and facts.
1. Lending: the ceiling the model cannot talk past
A lender lets a model underwrite consumer loans. Policy has three rules. A debt-to-income ratio over 50% is declined without asking anyone. Approval is available only up to a ratio of 43% and an amount of 50 000. And a model that tries to do something it was not offered goes to a person.
<state id="screening">
<transition cond="ctx.dti > 50" target="declined"/>
<transition target="underwriting"/>
</state>
<state id="underwriting" vt:decide="underwriter" vt:candidates="approve decline refer">
<transition event="approve" cond="ctx.dti <= 43 && ctx.amount <= 50000" target="approved"/>
<transition event="decline" target="declined"/>
<transition event="refer" target="officer_review"/>
<transition event="error.decide.invalid" target="officer_review"/>
</state>
The first rule is a plain eventless transition in the screening state: no decider is consulted, so there is nothing to be talked out of. The second is the guard on approve. A guard that is false removes the event from the offer, and so the model never sees it. The third is error.decide.invalid, which the core raises when an answer was not offered.
The full example runs three applications. Its output is quoted and read in Explainable by construction. What matters here is the design choice: the compliance-relevant rule is in the chart, not in the prompt. A prompt can be argued with, injected into or forgotten after a model upgrade. A guard is evaluated by the core every time, and the Lean model proves that whatever a decider returns, the transition taken was one that was enabled.
A lender would add reason codes to the decline event, so that an adverse-action notice can name specific reasons from the record. The chart can require the payload; the choice of lawful reasons is the lender's.
2. Healthcare: deny exists in exactly one state
A payer receives prior-authorisation requests. A model may approve routine requests, or refer them. Only a clinician may deny. That last rule is the interesting one, because it is not a guard or a prompt: it is a fact about the shape of the chart.
<state id="triage" vt:decide="triage-model" vt:candidates="approve refer"
vt:fallback="clinician/on-call" vt:deadline="4h">
<transition event="approve" cond="ctx.routine" target="approved"/>
<transition event="refer" target="clinician_review"/>
<transition event="error.decide.invalid" target="clinician_review"/>
</state>
<!-- The only state in which `deny` exists, and its decider is a person. -->
<state id="clinician_review" vt:decide="clinician/on-call" vt:candidates="approve deny request_info"
vt:fallback="clinician/medical-director" vt:deadline="72h">
<transition event="approve" target="approved"/>
<transition event="deny" target="denied"/>
<transition event="request_info" target="awaiting_info"/>
</state>
Search the chart for deny. It appears once, in clinician_review, whose decider is a person. The triage model's candidates are approve and refer, so there is no sequence of model answers, prompts or tool calls that reaches denied without a clinician's journal entry on the way. A reviewer can establish this by reading, and a tool can establish it by querying the chart as RDF, since every chart is also a graph.
Two timing details are worth noticing. vt:deadline="4h" with a fallback hands an unanswered triage to the on-call clinician, as a durable timer: it survives a restart, and its firing is a journaled event. The clinician state has its own 72h deadline that escalates to the medical director. Rules that say a decision must be reached within a period become states and timers, and so they are visible in the diagram and in the record.
This chart is shown, not run. It validates, which checks that it is well-formed and conforms to the shapes. No example program drives it, and the semantics of the deadlines is described in the agents documentation, not re-proved here.
3. Insurance: the model pays, the adjuster refuses
An insurer lets a model fast-track small, clean claims. Anything else is flagged for an adjuster. Refusing to pay is a consequential act, and the chart reserves it for a person.
<state id="screening" vt:decide="claims-model" vt:candidates="fast_track flag"
vt:maxDecisions="3">
<transition event="fast_track" cond="ctx.amount <= 5000 && ctx.flags == 0" target="paid"/>
<transition event="flag" target="adjuster_review"/>
<transition event="error.decide.invalid" target="adjuster_review"/>
<transition event="vt.agent.budget-exhausted" target="adjuster_review"/>
</state>
<!-- Refusing payment is an adjuster's decision; the reason is journaled with it. -->
<state id="adjuster_review" vt:decide="adjuster" vt:candidates="pay refuse investigate">
<transition event="pay" target="paid"/>
<transition event="refuse" target="refused"/>
<transition event="investigate" target="investigation"/>
</state>
<state id="investigation" vt:decide="adjuster" vt:candidates="pay refuse">
<transition event="pay" target="paid"/>
<transition event="refuse" target="refused"/>
</state>
The same structure again: refuse exists only in states decided by adjuster. The model's fast_track is enabled only for an amount of 5 000 or less with no fraud flags. vt:maxDecisions="3" bounds the number of times the model can be asked in this state, and when the budget is spent the chart sends the claim to a person, so a model that vacillates cannot run for ever or be re-asked until it says yes.
What the three have in common
- The rule is structural. "Only a person denies" is not a policy document a model might read. It is the absence of a transition.
- The model is useful where it is allowed to be. It handles the routine cases, which is most of them, and the chart gives it nothing it could misuse at the edges.
- People are in the loop by design, not by exception. A person's decision is a journal entry of the same kind as a model's, with the same record of what was offered.
- Changing the rule is changing a version. A new threshold is a new chart with a new hash, replayable against last year's cases before it goes live.
What you still have to do
Write the right rules. Review them for fairness and legality. Validate your model. Secure access to the journals, and decide what personal data may go in them: today, keep it out of events and refer to it, because a journal is append-only and the redaction scheme is only specified, not built. Vertep makes the rules explicit, enforced and recorded. It does not make them correct.