Redeo Docs
DocsLADR / Condition Keys Reference

Gates

Condition Keys Reference

Condition Keys Reference

Twelve condition keys are available as gate conditions: nine leaf predicates that test the output directly, and three compound combinators (and, or, not) that compose other conditions into a recursive tree. Each leaf is a single-key object; the key names the predicate, the value is its argument. Choose the most specific predicate that works for your output format.

The nine leaf condition keys

KeyArgumentWhat it checks
equalsstringExact string match against the trimmed output.
integerEqualsintegerOutput parsed as integer, compared equal. Tolerates trailing .0.
integerRange[a, b]Output parsed as integer, must fall within inclusive [a, b].
numberEqualsnumberOutput parsed as float, compared equal (epsilon tolerance).
numberRange[a, b]Output parsed as float, within inclusive range.
containsstringSubstring match. More forgiving than equals — handles prose around the marker.
in[a, b, c, ...]Output is one of the declared values. Handles LLM phrasing variance ("yes", "Y", "accept").
jsonMatchesJSON SchemaFirst JSON block extracted from output, validated against the schema. The structured gate.
matchesregex (RE2)Regex search against the output. Partial match — the pattern can appear anywhere in the string.
yaml
# String equality
if: { equals: "READY" }

# Integer comparison (parsing-tolerant)
if: { integerEquals: 5 }
if: { integerRange: [4, 5] }

# Floating-point comparison
if: { numberEquals: 0.95 }
if: { numberRange: [0.7, 1.0] }

# Substring (handles prose around the marker)
if: { contains: "VERDICT: ACCEPT" }

# Set membership (handles phrasing variance)
if: { in: ["yes", "Y", "accept", "ACCEPT"] }

# Structured (extract JSON, validate schema)
if:
  jsonMatches:
    type: object
    required: [verdict, score]
    properties:
      verdict: { type: string, enum: [accept] }
      score: { type: number, minimum: 4 }

# Regex (partial match, case-insensitive flag)
if: { matches: "(?i)\\bPASS\\b" }

Parsing behavior

The integer and number predicates trim whitespace, then attempt to parse the entire remaining string. They do not extract numbers from prose.

  • An output of "5" or "5.0" parses to the integer 5 (trailing .0 is tolerated).
  • An output of "Score: 5" or "The answer is 5." fails — the whole string is not a number.

Design system prompts to emit the number alone. If the LLM wraps the number in prose, use contains or jsonMatches instead.

Trailing .0 tolerance. LLMs sometimes emit 5.0 when asked for an integer. The integerEquals and integerRange predicates parse 5.0 as the integer 5. This is the only parsing tolerance beyond whitespace trimming.

Number epsilon. numberEquals uses an epsilon tolerance of 1e-9. This means 0.95 matches 0.9500000001 but not 0.9501. For practical purposes, numberEquals is exact for values produced by JSON serialization.

matches: Regex gates

matches applies an RE2 regular expression to the trimmed output. The match is a search (partial match), not a full-match — the pattern can appear anywhere in the string. This makes matches strictly more expressive than contains: any contains check is equivalent to a matches with the same literal string, but matches also handles character classes, anchors, quantifiers, alternation, and capture groups.

yaml
# Case-insensitive word match (equivalent to "in" with multiple casings)
if: { matches: "(?i)\\b(yes|y|accept)\\b" }

# Anchored: entire output must be a 4-digit year
if: { matches: "^\\d{4}$" }

# Extract a score embedded in prose: "Score: 42" or "score=42"
if: { matches: "score[:=]\\s*\\d+" }

# Reject outputs containing markdown code fences
if: { matches: "```", then: { jump: { stepId: rewrite } }, else: continue }

Validation at parse time. The regex is compiled when the config is loaded. An invalid pattern fails immediately with a clear error:

Step "verify": if.matches has invalid regex: error parsing regexp: ...

This means typos in the pattern are caught before any pipeline run, not at runtime when a node happens to produce output.

RE2 syntax. The engine uses Go's regexp package, which implements RE2. This means:

  • Supported: ., *, +, ?, {n,m}, [abc], [^abc], ^, $, \\b, \\d, \\w, \\s, |, (...), (?i) / (?s) / (?m) flags.
  • Not supported: backreferences (`\1`), lookaround (`(?<=...)`), possessive quantifiers. These are intentionally excluded for linear-time guarantees.

When to use matches vs contains. Use contains when you need a simple literal substring — it's clearer and faster. Use matches when the output has structure that a literal can't capture: variable casing, optional whitespace, multiple alternative spellings, or positional constraints (anchored to start/end).

NeedUse
Literal substringcontains
Case-insensitive substringmatches: "(?i)keyword"
Match at start/end onlymatches: "^prefix" or matches: "suffix$"
Multiple alternative patterns`matches: "cat
Format validation (ID, date, code)matches: "^\\d{4}-\\d{2}$"

Choosing the right condition

Use the most specific predicate that works.

  • equals is brittle — one whitespace difference fails. Use only when you control the output format exactly.
  • contains is more forgiving — it handles prose around a marker. Good for "output contains VERDICT: ACCEPT" patterns.
  • matches is the generalization of contains — any pattern contains can match, matches can too, plus case-insensitivity, anchors, alternation, and character classes.
  • integerRange / numberRange are best for scored outputs (1-5 ratings, 0.0-1.0 confidence).
  • jsonMatches is best for structured outputs. It extracts JSON from the output and validates against a schema. The most powerful leaf condition.
  • in is best when the LLM might phrase the same answer multiple ways ("yes", "Y", "accept").
Output formatRecommended condition
Bare token ("READY", "done")equals or in
Integer score ("4", "5.0")integerEquals or integerRange
Float score ("0.85")numberEquals or numberRange
Prose with marker ("VERDICT: ACCEPT after analysis")contains
Case-insensitive or pattern-based markermatches
Structured format (date, ID, code)matches with anchors
JSON object ({"score": 4, "verdict": "accept"})jsonMatches
Multiple possible phrasingsin or matches with alternation
Need to combine multiple checksand / or / not (see below)

If your system prompt produces structured JSON, always prefer jsonMatches over string parsing. It handles extraction (code fences, embedded JSON), validation (schema constraints), and is immune to prose formatting variance.

Compound conditions: and / or / not

Three structural combinators compose leaf conditions (or other compounds) into a recursive tree. This makes the gate expressive enough for any boolean logic without changing the output format or adding new leaf predicates.

KeyArgumentSemantics
and[cond, cond, ...]All sub-conditions must pass. Short-circuits on first failure.
or[cond, cond, ...]Any sub-condition must pass. Short-circuits on first match.
not{condNegates the sub-condition. Passes when the inner condition fails.

Each sub-condition is itself a full condition object — leaf or compound. Nesting is unlimited.

yaml
# Accept only if output contains "READY" AND has a digit somewhere
if:
  and:
    - { contains: "READY" }
    - { matches: "\\d" }

# Accept if any common affirmative phrasing appears (case-insensitive)
if:
  or:
    - { matches: "(?i)\\byes\\b" }
    - { matches: "(?i)\\baccept\\b" }
    - { matches: "(?i)\\bapprove\\b" }

# Accept anything that does NOT contain "ERROR"
if:
  not: { contains: "ERROR" }

Nesting. Compound conditions compose freely. A realistic gate might combine a positive check with a negative exclusion:

yaml
# Accept high-confidence JSON that is NOT a duplicate
if:
  and:
    - jsonMatches:
        type: object
        required: [fact, confidence]
        properties:
          confidence: { type: number, minimum: 0.8 }
    - not:
        contains: "DUPLICATE"
  then: continue
  else: abort

This gate passes only when the output is valid JSON with confidence >= 0.8 and the text does not contain the literal "DUPLICATE" anywhere. Either condition alone is insufficient.

Short-circuit evaluation. and stops at the first failing sub-condition; or stops at the first passing one. The reason string in the gate result identifies which sub-condition caused the outcome. This means expensive conditions (like jsonMatches) should be placed last in an and chain — if an earlier contains fails, the JSON extraction never runs.

Empty arrays. An empty and: [] vacuously passes (all zero conditions hold). An empty or: [] vacuously fails (no condition can pass). This is standard boolean logic. However, the validator rejects both as configuration errors — if.and has empty array (must have at least 1 condition) — to prevent accidental no-op gates.

Validation. All sub-conditions are recursively validated at config load time. A regex inside a not is compile-checked; a jsonMatches inside an and is JSON-parsed; an inverted integerRange inside an or is caught. The error messages include the step ID for easy debugging.

Worked examples

Single-threshold accept/reject.

yaml
- id: verify
  systemPrompt: 'Score 1-5. Output the integer alone.'
  if:
    integerRange: [4, 5]
    then: continue
    else: { jump: { stepId: refine } }

Confidence-conditional early exit. Use jsonMatches to extract a confidence field and skip ahead when it's high.

yaml
- id: classify
  systemPrompt: 'Output JSON {"confidence": "high"|"medium"|"low"}.'
  if:
    jsonMatches:
      type: object
      properties:
        confidence: { type: string, enum: [high] }
    then: { jump: { stepId: synthesize } }
    else: continue                              # fall through to gather more

Multi-verifier majority vote. Run multiple verifiers; the gate naturally prunes dissenters.

yaml
- id: verify
  nodes: 5
  systemPrompt: 'Score 1-5. Output JSON {"score": N, "verdict": "accept"|"reject"}.'
  if:
    jsonMatches:
      type: object
      properties:
        verdict: { type: string, enum: [accept] }
        score: { type: number, minimum: 4 }
    then: continue                              # only accepting verifiers survive
    else: abort

Downstream multi_ingest: { nodeRef: accumulate } reads all surviving (accepting) verifiers. Majority is implicit in the count of survivors.

Phrasing-tolerant acceptance. Use in to handle LLM phrasing variance.

yaml
- id: classify
  systemPrompt: 'Output "yes" or "no".'
  if:
    in: ["yes", "Y", "YES", "accept", "ACCEPT", "true", "True"]
    then: continue
    else: abort

The gate accepts any reasonable affirmative phrasing without needing jsonMatches.

Regex format validation. Use matches when the output must conform to a specific pattern — a date, an ID, a code — and you don't want the overhead of jsonMatches.

yaml
- id: extract_date
  systemPrompt: 'Extract the publication date. Output YYYY-MM-DD only.'
  if:
    matches: "^\\d{4}-\\d{2}-\\d{2}$"
    then: continue
    else: { jump: { stepId: retry } }            # malformed date; try again

The anchored pattern (^ ... $) rejects outputs with extra prose. Without anchors, matches would accept "The date is 2024-01-15 and ..." — the pattern appears as a substring. Use anchors when you need the entire output to match.

Compound gate: accept-with-exclusion. Combine and + not to accept structured output that passes a quality bar but excludes known-bad patterns.

yaml
- id: generate_and_filter
  nodes: 5
  systemPrompt: 'Generate a concise answer. Output JSON {"answer": "...", "confidence": 0.0-1.0}.'
  if:
    and:
      - jsonMatches:
          type: object
          required: [answer, confidence]
          properties:
            answer: { type: string, minLength: 10 }
            confidence: { type: number, minimum: 0.7 }
      - not:
          matches: "(?i)(I cannot|I'm sorry|as an AI)"
    then: continue                                # high-quality, non-refusal answers survive
    else: abort

This is a common production pattern: the jsonMatches check ensures structural correctness and confidence; the not check filters out LLM refusals that happen to be wrapped in valid JSON. The and short-circuits — if the JSON fails validation, the refusal regex is never evaluated.

Compound gate: multi-format acceptance. Use or when the LLM might emit one of several valid output formats.

yaml
- id: verify
  systemPrompt: 'Output a verdict: either a bare word (accept/reject) or JSON {"verdict": "accept"|"reject", "score": N}.'
  if:
    or:
      - { equals: "accept" }
      - jsonMatches:
          type: object
          properties:
            verdict: { type: string, enum: [accept] }
    then: continue
    else: { jump: { stepId: refine } }

The or accepts either a bare "accept" token or a JSON object with verdict: "accept". This handles LLMs that sometimes wrap their answer in JSON and sometimes emit a bare word — without forcing the prompt to constrain the format.