Validators

How node outputs become transition keys.

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 routes
  • confidence'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': abort

Script 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': abort

Both 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: block

routes 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: production

For 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': revise

Both '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: block

answer 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: escalate

Whichever 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:

  1. Records the failure reason.
  2. Builds a retry prompt that includes the original prompt, the bad response, and the parser error.
  3. Sends the retry to the model.
  4. Repeats up to maxRetries times.

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#

View this page as Markdown

Predictable workflows from unpredictable AI