Redeo Docs
DocsLADR / jsonMatches: The Structured Gate

Gates

jsonMatches: The Structured Gate

jsonMatches: The Structured Gate

jsonMatches is the most powerful gate condition. It extracts the first JSON block from the output, parses it, and validates against a declared JSON Schema. The same JSON Schema engine powers slateWrite.match — one implementation, two surfaces: verify-and-branch via if.jsonMatches, verify-and-persist via slateWrite.match.

JSON extraction rules

The runtime extracts JSON from the LLM output using a four-step fallback chain (shared with slateWrite.match):

  1. Strict JSON parse first. If the entire output is valid JSON, use it directly.
  2. Code fence lookup. If strict parse fails, look for a JSON code fence (json ... ) and extract its contents.
  3. Balanced brace match. If no code fence, look for the first balanced {...} or [...] block (handles prose-embedded JSON).
  4. No JSON found. The gate fails (returns false, no error).

This means the LLM can emit JSON in any of these forms and jsonMatches will find it:

text
# Raw JSON (strict parse succeeds)
{"verdict": "accept", "score": 4}

# Code-fenced JSON
```json
{"verdict": "accept", "score": 4}

Prose-embedded JSON (balanced brace match)

Based on my analysis, here is the verdict: {"verdict": "accept", "score": 4} The answer is acceptable.

text

The extraction is best-effort and handles the most common LLM output patterns. If the output contains no JSON at all, the gate fails silently.

Schema validation

The extracted JSON is validated against the declared JSON Schema. Common schema constraints:

  • required: [field1, field2] — listed fields must be present.
  • properties: { ... } — per-field type and value constraints.
  • enum: [a, b, c] — field must be one of the listed values.
  • minimum / maximum — numeric bounds.
  • minLength / maxLength — string length bounds.
  • additionalProperties: false — reject unknown fields (default behavior).
yaml
if:
  jsonMatches:
    type: object
    required: [verdict, score]
    properties:
      verdict: { type: string, enum: [accept] }
      score: { type: number, minimum: 4, maximum: 5 }
    additionalProperties: false
  then: continue
  else: { jump: { stepId: reflect } }

This gate accepts only outputs where the JSON has verdict: "accept" and score between 4 and 5, with no other fields. Anything else (missing fields, wrong enum value, extra fields) fails the gate.

Validation is strict by default. additionalProperties: false is the default — the schema rejects JSON objects with fields not listed in properties. This catches LLM hallucination of extra fields. Set additionalProperties: true explicitly if you want to allow extra fields.

jsonMatches vs slateWrite.match

Both if.jsonMatches and slateWrite.match use the same JSON Schema engine. The difference is what happens on a successful match:

Featureif.jsonMatchesslateWrite.match
PurposeVerify-and-branchVerify-and-persist
On matchFire then: action (continue, jump, write)Extract field, write to slate
On no matchFire else: actionSilent no-op (no error, no write)
Field extractionNo (gate is pass/fail)Yes (field: extracts one key before writing)
Write policyN/Aappend, overwrite, mergeByKey
yaml
# Verify-and-branch: gate decides next step
- id: verify
  if:
    jsonMatches: { properties: { score: { minimum: 4 } } }
    then: continue
    else: { jump: { stepId: refine } }

# Verify-and-persist: write validated data to memory
- id: extract
  slateWrite:
    to: { slate: Memory, folder: facts, file: core.md }
    field: fact
    on: append
    match:
      type: object
      required: [fact, confidence]
      properties:
        fact: { type: string, minLength: 1 }
        confidence: { type: number, minimum: 0.8 }

The same schema engine means you can use identical schemas in both places. A verify step can gate on jsonMatches and a subsequent extract step can persist the same validated shape via slateWrite.match.

Common patterns

Verify-and-persist. Use a write action under then: to persist verified outputs to a slate on gate pass.

yaml
- id: extract
  systemPrompt: 'Emit JSON {"fact": "...", "confidence": 0.0-1.0} if a durable fact is found.'
  if:
    jsonMatches:
      type: object
      required: [fact, confidence]
      properties:
        fact: { type: string, minLength: 1 }
        confidence: { type: number, minimum: 0.8 }
    then:
      write:
        to: { slate: "Memory", folder: facts, file: core.md }
        from: output
        on: append
    else: continue                              # no durable fact; move on

The fact is persisted only when the LLM emits structured JSON matching the schema with confidence >= 0.8. Prose-only outputs or low-confidence claims are silently dropped.

Nested object validation. JSON Schema supports nested objects for complex structured outputs.

yaml
if:
  jsonMatches:
    type: object
    required: [analysis, recommendation]
    properties:
      analysis:
        type: object
        required: [severity, root_cause]
        properties:
          severity: { type: string, enum: [critical, warning, info] }
          root_cause: { type: string, minLength: 10 }
      recommendation:
        type: string
        minLength: 20

This validates that the JSON has an analysis object with a severity enum and a root_cause string, plus a recommendation string of at least 20 characters.

Array validation. Validate arrays of objects.

yaml
if:
  jsonMatches:
    type: array
    items:
      type: object
      required: [id, name]
      properties:
        id: { type: string }
        name: { type: string }

This validates that the output is a JSON array of objects, each with id and name fields.