Documentation / Conception / Guides pratiques / Branch with guards and switch

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/to connections 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:

  1. Add the node with its condition, a JEXL expression over criteria:

    nodes:
      guard_creatinine:
        type: flow.guard
        config:
          condition: criteria.code.code == "creatinine"
    
  2. Wire the tested data to .criteria:

    connections:
      - from: trigger.result
        to: guard_creatinine.criteria
    
  3. Wire .pass to whatever should run only when the condition is true:

    connections:
      - from: guard_creatinine.pass
        to: compute_dfg.result
    

    When the condition is false, the guard emits nothing at all — .pass never fires, so compute_dfg (and everything wired downstream of it) is skipped for that run. There is no second branch to author.

  4. If downstream needs a value other than criteria itself, wire it to the optional .value input — .pass then carries .value instead 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 pass
    

    Here guard_alerts.criteria is tested for count > 0, and because nothing is wired to .value, .pass forwards criteria itself (the report) to notify_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:

  1. Add the node. Set config.type to the object type flowing through .value, and config.cases to an ordered list of { name, condition }, each condition a JEXL expression over value:

    nodes:
      check_alerts:
        type: flow.switch
        config:
          type: Integer
          cases:
            - name: critical
              condition: value > 0
    
  2. Wire the routed data to .value:

    connections:
      - from: interactions.count
        to: check_alerts.value
    
  3. Wire each case's output — one port per cases[].name — to its own downstream node:

    connections:
      - from: check_alerts.critical
        to: make_alert_obs.after
    

    A switch also always exposes a .default output, 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 others skipped.
75 documents6 sectionssource : /docs · généré au build