A validator is the small piece of logic that turns a node's raw output into a
transition key for the on: map. AI nodes use validators to parse a JSON
envelope from the model; script nodes use them to interpret the value the
script returns. This page covers the three validator kinds shared by
script and ai_judge, the two additional kinds consensus adds, the JSON
shape each expects, and the matching on: keys you must declare.
You will use validators on script, ai_judge, and consensus nodes.
ai_agent nodes do not have validators because they cannot branch.
How validators work#
When a node finishes, the executor produces a raw value. The validator maps
that value to one of the keys in the node's on: map:
boolean→'true'or'false'enum→ one of the named routesconfidence→'true'if score meets the threshold, else'false'
If the value cannot be parsed (for example, the model returned malformed
JSON), the AI executors retry up to maxRetries times before failing the
node. The retry prompt includes the previous response and the parser error
so the model can self-correct.
maxRetries is 0–5 for ai_judge and ai_agent (default 1) and 0–3 for consensus
(default 1).
boolean#
The simplest validator. Output is either true or false, and the on: map
must declare both keys.
AI nodes#
The prompt is automatically appended with this instruction:
Respond with a JSON object and nothing else — no prose before or after,
no markdown fences:
{
"reasoning": "<your step-by-step reasoning>",
"result": <true or false>
}The validator extracts the JSON, parses result, and maps true → 'true',
false → 'false'.
gate:
type: ai_judge
label: Should we deploy?
model: claude-sonnet-4-5
prompt: |
Given the build output below, is it safe to deploy?
{{ build.stdout }}
validator:
kind: boolean
maxRetries: 2
on:
'true': deploy
'false': abortScript nodes#
The validator checks whether the value the script returns is truthy:
- A truthy return value →
'true' - A falsy return value (
false,0,'',null,undefined) →'false'
Note that a script which throws, or a shell command that exits non-zero
when you did not use .nothrow(), is a hard node failure (status failed,
no transition), not a routed 'false'. To branch on whether a command
succeeded, run it with .nothrow() and return the check yourself:
test:
type: script
label: Run tests
script: |
import { $ } from 'bun';
const res = await $`npm test`.nothrow();
export default res.exitCode === 0;
validator:
kind: boolean
on:
'true': deploy
'false': abortBoth keys are required. If you only declare 'true', the loader rejects the
workflow.
enum#
Routes to one of a named set of routes. Use this when the answer is a small
fixed set (e.g. low | medium | high risk classification). Enum validators
are supported on ai_judge and script. Consensus does not support
enum. See Consensus for why.
The validator declares the allowed values under routes:. Your on: map
must declare a key for every route name (and only those names).
AI nodes#
The prompt is appended with:
Respond with a JSON object and nothing else — no prose before or after,
no markdown fences:
{
"reasoning": "<your step-by-step reasoning>",
"result": "<exactly one of: low, medium, high>"
}
The "result" value must be reproduced exactly (case-sensitive).If the model returns a result that is not one of the declared routes, the
attempt is treated as a validator failure and the executor retries.
classify:
type: ai_judge
label: Classify risk
model: claude-sonnet-4-5
prompt: |
Classify the risk level of this change.
Diff: {{ diff.stdout }}
validator:
kind: enum
routes: [low, medium, high]
maxRetries: 2
on:
low: auto-merge
medium: human-review
high: blockroutes cannot be empty: that would make it impossible for the workflow to
advance past the node, and the integrity check rejects it.
Script nodes#
For enum validators, the script must return the chosen route name. The
executor coerces the returned value with String(...) and matches it against
the declared on: keys:
- If it exactly matches one of the keys → that route is taken.
- If it does not match any key → the node fails.
Return the routing string. Don't print it:
classify:
type: script
label: Classify environment
script: |
export default process.env.ENV === 'production' ? 'high' : 'low';
validator:
kind: enum
routes: [low, high]
on:
low: stage
high: productionFor richer branching logic prefer a condition node downstream of a
boolean script node.
confidence#
A graded boolean. The model returns a numeric score; the validator routes to
'true' if the score meets a threshold, otherwise 'false'.
threshold is required and must be in the range 0–100.
AI nodes#
The prompt is appended with:
Respond with a JSON object and nothing else — no prose before or after,
no markdown fences:
{
"reasoning": "<your step-by-step reasoning>",
"score": <integer from 0 to 100>
}The validator parses score and routes:
score >= threshold→'true'score < threshold→'false'
The numeric score is also written to variables.<id>.score so downstream
nodes can reference it (see
Output chaining).
quality-check:
type: ai_judge
label: How good is this answer?
model: claude-sonnet-4-5
prompt: |
Score the quality of this answer from 0 to 100.
Answer: {{ draft.response }}
validator:
kind: confidence
threshold: 80
on:
'true': publish
'false': reviseBoth 'true' and 'false' keys are required, even when one branch loops
back to the same step.
Script nodes#
script nodes do support confidence: return a number (or an object with
a numeric score field) and it routes 'true' at or above the threshold,
'false' below. See the script node for details.
Consensus and validators#
Consensus has its own validator schema with four
kinds: boolean, confidence, answer, and most_consistent. enum is
not supported, because there's no well-defined per-agent approval rule for
an arbitrary route name.
boolean and confidence are gate modes. Each agent approves or rejects
independently (for confidence, an agent approves if its score meets the
threshold), and the node routes to approved once quorum agents approve,
otherwise rejected:
vote:
type: consensus
label: Multi-agent gate
model: claude-sonnet-4-5
prompt: Is the change safe to merge?
agentCount: 3
quorum: 2
validator:
kind: boolean
on:
approved: merge
rejected: blockanswer and most_consistent are consensus-only kinds with no ai_judge
or script equivalent. Each agent produces a free-form answer (answer
mode can restrict this to a fixed choices list), and the group votes by
plurality. These modes route to decided when the group converges on one
answer, or undecided otherwise, never approved/rejected:
diagnose:
type: consensus
label: What's the likely root cause?
model: claude-sonnet-4-5
prompt: What is the most likely root cause of this incident?
agentCount: 5
quorum: 3
validator:
kind: answer
on:
decided: report
undecided: escalateWhichever kind you use, on: must declare both keys for that mode: never
'true'/'false'. most_consistent (self-consistency, or USC) skips the
per-agent vote entirely: every agent answers freely, and a separate
aggregation pass picks the single most-consistent response.
Retries and self-correction#
When an AI node returns malformed JSON or a value that does not match the
validator (e.g. an enum result not in routes), the executor:
- Records the failure reason.
- Builds a retry prompt that includes the original prompt, the bad response, and the parser error.
- Sends the retry to the model.
- Repeats up to
maxRetriestimes.
If all retries fail, the node fails and the run halts at that node. You can inspect the per-attempt trace in the web UI to see what the model said.
Set maxRetries: 0 to disable retries entirely. The default of 1 is a
good starting point: most malformed-JSON cases recover on the second attempt.
Where to next#
- AI judge node — the most common home for a validator.
- Consensus node — quorum semantics and per-agent validator behaviour.
- Script node — return-value mapping in more detail.
- Output chaining — what the validator
writes back to
variables.