Branch with guards and switch
There is no if node in the DSL. To make a run take different paths depending
on data, wire one of two gateway node types — flow.guard stops a branch
outright when a condition doesn't hold, flow.switch picks exactly one of
several named paths — and lean on absence propagation for the "else" you'd
otherwise have to draw by hand: a node with a required input that never
arrives is skipped, not errored. This recipe wires all three into one
branching workflow. For the model behind them, see
Branching and absence.
Prerequisites
- A workflow file to edit — see Your first workflow if you don't have one yet.
- Comfort with port-to-port wiring; see Wire ports if
from/toconnections are new to you.
Stop a branch with flow.guard
Use a guard when the rest of a branch should run only if a condition on some
data holds. This is the real gate from
content/clinical-demo/workflows/bio-result.workflow.yaml:
Add the node with its condition, a JEXL expression over
criteria:nodes: guard_creatinine: type: flow.guard config: condition: criteria.code.code == "creatinine"Wire the tested data to
.criteria:connections: - from: trigger.result to: guard_creatinine.criteriaWire
.passto whatever should run only when the condition is true:connections: - from: guard_creatinine.pass to: compute_dfg.resultWhen the condition is false, the guard emits nothing at all —
.passnever fires, socompute_dfg(and everything wired downstream of it) is skipped for that run. There is no second branch to author.If downstream needs a value other than
criteriaitself, wire it to the optional.valueinput —.passthen carries.valueinstead of.criteria. The same file does this for its second guard:nodes: guard_alerts: type: flow.guard config: condition: criteria.count > 0 connections: - from: drug_safety.report to: guard_alerts.criteria # tested - from: guard_alerts.pass to: notify_alert.report # forwarded on passHere
guard_alerts.criteriais tested forcount > 0, and because nothing is wired to.value,.passforwardscriteriaitself (the report) tonotify_alert.
flow.guard's type/valueType config fields (the types of criteria and
value) are marked as inferred hints in the catalog
(libs/shared/src/engine/catalog-metadata.ts) — the visual editor fills them
in as soon as you wire .criteria/.value and shows them read-only. When
hand-editing YAML, you don't need to set them at all; only condition is
yours to write.
Route to one of several cases with flow.switch
Use a switch when there are more than two outcomes, or you want them named
instead of just pass/no-pass. This is the real gate from
content/clinical-demo/workflows/analyse-posos.workflow.yaml:
Add the node. Set
config.typeto the object type flowing through.value, andconfig.casesto an ordered list of{ name, condition }, eachconditiona JEXL expression overvalue:nodes: check_alerts: type: flow.switch config: type: Integer cases: - name: critical condition: value > 0Wire the routed data to
.value:connections: - from: interactions.count to: check_alerts.valueWire each case's output — one port per
cases[].name— to its own downstream node:connections: - from: check_alerts.critical to: make_alert_obs.afterA switch also always exposes a
.defaultoutput, emitted when no case matches. Leaving it unwired is fine — as in this file, which has nothing to do when there's no critical count.
The switch tests cases in order and routes to the first whose condition is
true; put the more specific conditions first. Exactly one output fires per
run (a case, or default); everything wired to the other output ports is
skipped — the same absence propagation the guard relies on. Case names become
port names, so keep them to identifiers (letters, digits, _, - — the
editor sanitizes typed names to this set).
Like the guard, flow.switch's config.type is an inferred hint, filled in
by the editor once .value is wired; set it by hand only when authoring YAML
directly.
Let absence be the else you don't write
A required input a run never receives — because the node upstream chose not
to emit it — leaves the node skipped, not errored. This is the same
mechanism behind an unmatched guard or switch case, and it composes with any
compute node for free. From external-plugins/demo/behaviors/clinical.ts:
export const detectConditionFromEgfr: BehaviorFor<"clinical.condition.detect-from-egfr"> = () => ({
run: ({ inputs }) => {
const proposal = suspectedConditionFromEgfr(inputs.egfr);
return proposal ? { proposal } : {}; // no proposal → branch stopped
},
});
detect_condition only emits proposal when eGFR is low enough to suspect a
condition. Wire straight through, with nothing extra:
connections:
- from: detect_condition.proposal
to: confirm_condition.proposal
- from: confirm_condition.condition
to: record_condition.condition
When no proposal is emitted, confirm_condition and record_condition are
both skipped for that run — there is no else branch to author.
Absence only takes over at runtime. A required input still needs a
connection (or a fixed value) at authoring time: leaving guard_alerts.criteria
or check_alerts.value unwired is a static validation error —
Nœud « … » : entrée requise « … » non connectée.
(libs/engine-core/src/engine/validate.ts) — not something absence
propagation will paper over. Wire it, and let the run decide whether data
actually shows up.
Verify both paths run
- Validate the workflow to confirm every required input is wired and every connected type matches.
- Simulate the workflow with inputs on each side of
your guard's condition, and one per switch case, then check the node
statuses: the branch you expect should read
done, the othersskipped.
Related
- Branching and absence — the model behind guard, switch, and absence propagation.
- Wire ports — port-to-port connections, split-pins, and fixed values, the mechanics this recipe builds on.
- Sequence with after and done — chain or join branches by control flow alone, once a guard or switch has already cut a path.
- Loop with map — the third flow primitive, for iterating over a list instead of branching.
- JEXL — the expression language for
condition. - Workflow DSL and
Core primitives — the full schema for
flow.guard,flow.switch, and every other primitive. - Validate a workflow and Simulate a workflow — check a branching workflow before running it for real.
- Glossary — guard, switch, absence propagation.