An event carries no workflow name. When one arrives — a prescription recorded, a lab result received — the platform has to decide which workflow runs. With a single workflow per event type there is nothing to decide. The moment a second one listens to the same event, you need to say what tells them apart.
The rule, in one paragraph
Among the workflows whose trigger declares that event type, the platform keeps
the routable ones (status: active, or no status — see
Organisation metadata), evaluates their
selectors in file order, and runs the first one whose selector is true. A
workflow with no selector is the catch-all of its event: it runs when no
selector matched. One event still starts exactly one run.
Writing a selector
A selector is a jexl expression on the event payload, exactly the expression language of the Garde node — see Branch with guards and switch. It sits on the trigger:
trigger:
type: trigger.medication-prescribed
selector: patient.clairance < 30
Two workflows can then split one event honestly:
# renal-dosing.workflow.yaml
trigger:
type: trigger.medication-prescribed
selector: patient.clairance < 30
# interaction-analysis.workflow.yaml — no selector: the catch-all
trigger:
type: trigger.medication-prescribed
A prescription for a patient with a clearance under 30 goes to the renal workflow; every other prescription falls through to the interaction analysis.
A selector that fails to evaluate excludes its candidate — it never wins, and
it never fails the ingestion. A missing field, a typo in a path: the candidate
is skipped, the incident is logged, and the event continues to the next
candidate. A clinical event is not lost because an expression was wrong. The
editor and POST /api/workflows/save still refuse a selector that does not
compile, so the mistake is normally caught before it ships.
Two catch-alls on one event is refused
If two routable workflows declare the same event type and neither has a selector, nothing distinguishes them — file order would decide silently, and one of the two would never run. That is refused:
- when you save from the console, with a
400naming both files. Nothing is written; - at startup, if such a pair reaches the instance another way — a
git pull, a file edited by hand. The proxy refuses to boot rather than run half of what the files describe.
Two ways out, both stated in the error: give one of them a selector, or unpublish one.
Note what is not a conflict: two distinct selectors (that is the whole point), or a selector plus one catch-all (the selector is tried first, the catch-all takes the rest).
Events that never compete
Some events address one precise workflow, so several workflows on the same event type are normal and nothing is ever shadowed:
- webhooks — the URL carries the workflow's own UUID;
- sub-workflow calls — the parent's
workflow.executenode names its child; - schedules — each tick is emitted by the scheduler for the file that owns that schedule.
These need no selector, and the console shows them without a ranking.
Reading the routing table
/routing in the console lists every event type with its candidates
in decision order, each one's selector (or « attrape-tout »), and its fate:
routed, shadowed, or out of routing (unpublished, or never published). It is the screen that answers "why did that
workflow run?" — and it names conflicts along with the gesture to settle them.
Pausing a workflow
Not by emptying anything: unpublish it. It is the platform's single pause, whatever the trigger — an event-triggered workflow leaves routing, and a scheduled one is disarmed on the scheduler. Its published versions are kept, so a rollback republishes it as it was. See Manage workflow storage.
See also
- Organisation metadata —
statusand the lifecycle it governs - Branch with guards and switch — the same expression language, inside a workflow
- Trigger with a webhook and Schedule a workflow — the triggers that address one workflow directly