Markov provides three mechanisms for conditional execution: when, gate, and assert. This guide explains when to use each.
The when field on any step takes a Pongo2 boolean expression. If false, the step is skipped:
- name: deploy
type: shell_exec
when: "environment == 'production'"
params:
command: "./deploy.sh"
- name: notify
type: shell_exec
when: "needs_review and not auto_approved"
params:
command: "echo 'Flagged for review'"
Skipped steps are marked with status “skipped” in the state store — they don’t fail the workflow and don’t block downstream steps.
Expressions use Pongo2 (Jinja2-compatible) syntax:
when: "count > 5" # Numeric comparison
when: "status == 'ready'" # String comparison
when: "items | length > 0" # Filter usage
when: "enabled and score >= threshold" # Logical operators
when: "not error_flag" # Negation
when: "result.exit_code == 0" # Nested field access
A gate step evaluates named rules and sets facts based on which rules fire. Use gates when you need:
set_fact)rules:
- name: auto_approve
salience: 100
when: "severity == 'critical' and confidence > 0.95"
action: continue
set_fact:
auto_approved: true
approval_reason: "High confidence critical fix"
- name: flag_for_review
salience: 50
when: "severity == 'high' and not auto_approved"
action: continue
set_fact:
needs_review: true
workflows:
- name: main
steps:
- name: triage
type: gate
rules:
- auto_approve
- flag_for_review
- name: run_analysis
type: shell_exec
when: "not auto_approved"
params:
command: "echo 'Running manual analysis'"
- name: notify_reviewer
type: shell_exec
when: "needs_review"
params:
command: "echo 'Review needed'"
The gate evaluates all rules, fires matching ones in salience order, sets their facts, and the highest-salience fired rule’s action determines the gate outcome.
Use assert to validate preconditions and fail immediately if they aren’t met:
- name: check_preconditions
type: assert
that:
- "status == 'ready'"
- "score >= 4"
- "items | length > 0"
msg: "Preconditions not met"
The workflow fails on the first false expression. Use assert for conditions that should never be false at that point in execution — hard errors, not routing decisions.
Need to fail the workflow if a condition is false?
→ assert
Need to skip a single step?
→ when
Need to set variables based on conditions?
→ gate with set_fact
Need priority-ordered multi-condition evaluation?
→ gate with salience
Need to control looping/recursion?
→ gate controlling a recursive workflow
when is for simple skip/run decisions on individual stepsgate is for complex decision routing that affects multiple downstream stepsassert is for validating invariants that should never be violatedA common pattern uses a gate to set decision variables, then when conditions on downstream steps to act on those decisions:
rules:
- name: approve
salience: 100
when: "score >= 8"
action: continue
set_fact:
approved: true
- name: reject
salience: 50
when: "score < 4"
action: continue
set_fact:
rejected: true
workflows:
- name: main
steps:
- name: decision_gate
type: gate
rules: [approve, reject]
facts:
score: "{{ analysis.result.score }}"
- name: deploy
when: "approved"
type: shell_exec
params:
command: "./deploy.sh"
- name: notify_rejection
when: "rejected"
type: shell_exec
params:
command: "echo 'Rejected: score too low'"
- name: manual_review
when: "not approved and not rejected"
type: shell_exec
params:
command: "echo 'Score in review range'"