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):
- Strict JSON parse first. If the entire output is valid JSON, use it directly.
- Code fence lookup. If strict parse fails, look for a JSON code fence (
json ...) and extract its contents. - Balanced brace match. If no code fence, look for the first balanced
{...}or[...]block (handles prose-embedded JSON). - 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:
# 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.
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).
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:
| Feature | if.jsonMatches | slateWrite.match |
|---|---|---|
| Purpose | Verify-and-branch | Verify-and-persist |
| On match | Fire then: action (continue, jump, write) | Extract field, write to slate |
| On no match | Fire else: action | Silent no-op (no error, no write) |
| Field extraction | No (gate is pass/fail) | Yes (field: extracts one key before writing) |
| Write policy | N/A | append, overwrite, mergeByKey |
# 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.
- 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 onThe 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.
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: 20This 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.
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.