Skip to main content

Guards

Guards evaluate conditions and decide whether an action should run for each record, acting as quality checkpoints in your workflow.

Syntax

- name: my_action
guard:
condition: "expression"
on_false: "skip" | "filter"
FieldTypeDefaultDescription
conditionstringRequiredExpression evaluated against upstream data
on_falsestringfilterAction when condition is false
passthrough_on_errorbooleantruePass record through if evaluation fails

on_false Options

ValueDescription
skipAction skipped, record continues to downstream actions
filterRecord removed from workflow entirely

Condition Expressions

Comparison Operators

guard:
condition: "score > 85"
condition: "status == 'approved'"
condition: "facts != []"
OperatorDescription
==, !=Equality
>, >=, <, <=Comparison
and, or, notLogical

Advanced Operators

OperatorExample
INstatus IN ["active", "pending"]
NOT INcategory NOT IN ["spam"]
CONTAINStags CONTAINS "important"
LIKEname LIKE "prod_*"
BETWEENscore BETWEEN 50 AND 100
IS NULLdescription IS NULL

Boolean Values

Boolean keywords are case-insensitive, matching SQL convention:

guard:
condition: 'passes_filter == true' # valid
condition: 'passes_filter == True' # valid
condition: 'passes_filter == TRUE' # valid

Prefer explicit comparison over a bare field reference for boolean fields. A bare reference evaluates using Python truthiness — this fails silently when the upstream action stores "false" as a string (which is truthy) rather than a Python bool:

# Fragile — string "false" is truthy, so the guard never filters
condition: 'passes_filter'

# Explicit — correct regardless of whether the value is a bool or a string
condition: 'passes_filter == true'

Built-in Functions

guard:
condition: 'len(items) > 0'
condition: 'max(scores) >= 85'

Supported: len(), str(), int(), float(), abs(), min(), max()

Examples

Filter Empty Results

- name: canonicalize_facts
dependencies: fact_extractor
guard:
condition: 'candidate_facts_list != []'
on_false: "filter"

Skip Optional Processing

- name: enhance_summary
guard:
condition: 'needs_enhancement == true'
on_false: "skip"

Quality Gate

- name: generate_final_output
guard:
condition: 'quality_score >= 85'
on_false: "filter"

Context Access

Guards can access:

SourceSyntax
Direct fieldcandidate_facts_list
Specific actionextract_facts.count
Context scope observednum_similar_facts
- name: validate
context_scope:
observe:
- group_by_similarity.num_similar_facts
guard:
condition: 'num_similar_facts != 1'
on_false: "skip"

Downstream Behavior

How guard results affect downstream actions in a multi-action workflow:

on_falseOutput recordDownstream actions
skipOriginal content preserved, metadata.reason: "guard_skip"Process normally — each action evaluates its own guard independently
filterRecord excluded from outputNever sees it — record is removed from the pipeline

Skipped records flow downstream

When Action A skips a record (on_false: skip), Action B still receives it and can process it with its own LLM call. Each action's guard is independent:

actions:
- name: extract_facts
guard:
condition: 'status == "active"'
on_false: "skip" # Inactive records pass through with original content

- name: generate_summary
dependencies: extract_facts
# Receives ALL records from extract_facts, including skipped ones
# Can define its own guard or process everything

Upstream failures are short-circuited

When an upstream action fails for some records (e.g., batch API errors), those records are marked with _unprocessed: true and automatically skipped by all downstream actions — no context loading, prompt rendering, or LLM calls are wasted. These records are preserved in the output for lineage traceability.

Error Handling

Guard evaluation errors are classified into three categories, each with different handling:

CategoryExamplepassthrough_on_error respected?Behavior
SemanticUnquoted string (status == approved)No — always uses on_falseCondition itself is broken; cached after first occurrence
DataMissing field, type mismatchYesField absent for this specific record
TimeoutEvaluation exceeded time limitYesTransient failure

Semantic errors bypass passthrough_on_error because the condition is fundamentally broken — passing records through would give wrong results for every record, not just one. These errors are logged once (circuit breaker), not per-record.

Data and timeout errors respect passthrough_on_error (default: true). Set to false to apply the configured on_false behavior instead:

guard:
condition: 'passes_filter == true'
on_false: filter
passthrough_on_error: false # filter the record if evaluation fails
tip

When a guard silently lets records through unexpectedly, check target/errors.json for G002 events — these indicate evaluation failures that were swallowed by passthrough_on_error: true.

Common Mistakes

Unquoted String Literals

String values on the right-hand side of comparisons must be quoted. Unquoted strings are treated as field references and produce a preflight validation error:

# WRONG — "approved" is interpreted as a field name
guard:
condition: 'hitl_status == approved'

# CORRECT — quote string literals
guard:
condition: 'hitl_status == "approved"'

Limitations

  • No external calls - Guards can't make API requests
  • Limited functions - Only built-in functions available
  • File granularity pre-filter - With File granularity, guards run as a per-record pre-filter before the action receives the array
  • Single expression - Complex logic should use tool actions

Guards with File Granularity

When a guard is configured on a File-granularity action (tool or HITL), the guard evaluates per-record as a pre-filter before the action receives the data array.

- name: deduplicate_active
kind: tool
granularity: file
impl: deduplicate
guard:
condition: 'status == "active"'
on_false: filter # Only active records sent to dedup tool

Behavior

on_falsePassing recordsFailing records
filterSent to actionRemoved from pipeline
skipSent to actionPreserved in output with original content

The action only sees records that pass the guard. This is useful for:

See Also