Skip to content
Expedify
8 min

A typed verdict, not a paragraph

Make the verdict machine-readable so the decision is taken by a node — and meet the boolean that fails open on the approve branch.

The reviewer has an opinion. Something now has to route on it, and a gate that searches for the word “approved” somewhere in a paragraph is not a gate — it is a substring match with consequences.

So the verdict comes back as JSON, is parsed by a node, and is read as a field. Three nodes, in this order:

Verdict → parse → route

The reviewer returns JSON; a transform parses it; a condition compares one field. The decision is made by the third one.

Loop BodyCompleteNoYes

Scroll for all 12 steps →

This is the verdict from a real run, unedited:

the reviewer's output
{
  "decision": "approve",
  "score": 5,
  "account_name": "Nimbus Retail",
  "violations": [],
  "critique": "The proposed resolution correctly denies a cash refund as per policy,
               offers appropriate non-cash remedies, escalates the repeat incident,
               notifies the account manager, and includes all affected accounts."
}
Four of these five fields are consumed by a node. Only `critique` is read by a human — or by the proposer, on the next attempt.

The point is not that the model returns typed JSON. It is that the decision is taken by a node. The model produces a value; a comparison — not a paraphrase, not an inference — decides what happens next. Every hard rule in this course is a condition node, and a proposal that reaches the gate has already had its judgement converted into something that cannot be argued with.

Three ways this breaks, all of them quiet

The first is the dangerous one: it fails open on exactly one branch, and the branch it fails on is approval.

asking for a boolean and comparing it to “true”

What happens
the template boundary hands the condition Python’s True and False — capitalised — so the approve path silently never matches while the reject path appears to work
The fix
have the model emit a lowercase string and compare that

writing the response format as a plain string

What happens
it passes validation as valid and runnable, then fails at run time with “expected an object, but got a string”
The fix
it is an object with a type inside it

using words instead of symbols in the condition

What happens
the operator must be ==, not equals
The fix
the validator catches this one and names the allowed set — run it

The fields worth having, beyond the decision

  • A score. Not for the gate — for the audit row and for you, reading twenty runs later and asking which ones were marginal.
  • The violations, as a list. So an escalation can say what was wrong, and so you can count which rule fires most often. Do not interpolate that list into a message body — see lesson 17.
  • A critique. The only field written for a reader, and the one the correction loop feeds back.

A note on providers. Object-mode responses are enforced by some providers and merely encouraged by others; where they are not enforced, a model can still return prose and your parse step will fail. That is the correct place for it to fail — loudly, before the gate — but build knowing that the guarantee is not universal.

Related lessons