Documentation / Conception / Guides pratiques / Schedule a workflow, and make it wait

Two nodes put time in your hands: the Planification (cron) trigger starts a workflow on a schedule, and the Attendre node pauses a running one. Both are durable — a pause is not a held connection or a sleeping process, it is state in the execution engine, so it survives restarts and deploys.

Schedule a workflow

Pick Planification (cron) as the trigger, then add rules. A rule is one of seven kinds:

Kind Fields Fires
Every N seconds interval (≥ 30) aligned on the epoch
Every N minutes interval aligned on the epoch
Every N hours interval, at minute */N hours, restarting at midnight
Every N days interval, at hour, at minute */N days, restarting on the 1st
Every N weeks interval, weekdays, at hour, at minute the chosen weekdays, one week in N
Every N months interval, day of month, at hour, at minute */N months; a month too short is skipped
Cron expression expression 5 fields, or 6 with seconds first

Several rules can coexist — the earliest one fires, and the trigger's rule output carries its index (0-based) so you can route on which one it was. The panel previews the next three occurrences as you type, computed with the very code the scheduler runs.

trigger:
  type: trigger.schedule
  config:
    timezone: Europe/Paris          # optional — defaults to the instance's
    rules:
      - { kind: days, interval: 1, atHour: 7, atMinute: 30 }
      - { kind: weeks, interval: 1, onWeekdays: [1, 4], atHour: 18 }

Weekdays follow the cron convention (0 = Sunday … 6 = Saturday); the editor shows them Monday-first with names, so you rarely type the numbers.

Times are local, and stay local across daylight-saving changes: a 09:00 rule fires at 09:00 before and after the transition, which means the real interval that day is 23 or 25 hours. The timezone comes from the rule, or failing that from the instance manifest (timezone, default Europe/Paris).

An empty rule list means "not configured yet" — the pause is unpublishing

A schedule fires as soon as its rules exist, so a new schedule trigger starts with rules empty and you add them last. But an empty list is not a pause: it is an unfinished schedule.

To pause a schedule that is already live, unpublish the workflow — the platform's single pause, whatever the trigger (see Manage workflow storage). An unpublished workflow is disarmed on the scheduler, so it stops firing, and republishing it (a rollback) re-arms the schedule as it was. Emptying rules instead would lose the rules you wrote.

Minimum frequency: 30 seconds

Anything faster is refused when you save. The reason is not load: the run index is audit history and is never purged, so an over-frequent schedule leaves permanent rows behind. Clinically meaningful periods run from minutes to months; for a one-off test, call a webhook instead.

Make a run wait

Add an Attendre node anywhere in the graph. It has one optional value input, re-emitted on value after the wait, so data flows through it. With nothing wired, use the implicit après/terminé pins to sequence: the node after the wait simply starts later.

For a duration

attente:
  type: flow.wait
  config: { mode: interval, amount: 24, unit: hours }

Units: seconds, minutes, hours, days.

Until a date

attente:
  type: flow.wait
  config: { mode: until }
  expose: [until]        # wired from a DateTime computed upstream

until can be fixed in the config or exposed as a port and wired — an appointment date read from the record, a deadline computed from an admission. A date already in the past does not wait; it is traced and the run continues.

Until an HTTP call

attente:
  type: flow.wait
  config:
    mode: webhook
    auth: header                                     # none | basic | header
    credential: cred-123                             # required unless auth is none
    limit: { kind: interval, amount: 7, unit: days }  # optional

The run pauses and the console shows its resume URL, also served on GET /api/runs/<runId> as waiting.resumeUrl:

curl -X POST http://localhost:4000/api/resume/<runId>/attente \
  -H 'content-type: application/json' \
  -d '{"decision":"go"}'

The body comes out on the resume port. Authentication is the same mechanism as an inbound webhook, using a credential from the Credentials module — the endpoint is public, so it is closed by default and only auth: none leaves it open.

limit bounds the wait ({kind: interval, amount, unit} or {kind: until, at}). When it expires, the node emits timedOut only — no value, no resume — so wiring an escalation branch off timedOut needs no condition: absence propagation mutes the normal branch on its own. Without a limit, the run waits indefinitely.

What a waiting run looks like

Its status is waiting, deliberately distinct from suspended, which means a human owes a decision. The waiting node wears a grey hourglass in the run graph, and the step-by-step names it, with its deadline and — in webhook mode — the resume URL. So the "à trancher" queue stays a list of things people must act on, and never fills up with countdowns.

Testing without waiting

Simulation never suspends. A wait is reported instead of endured: the report lists every wait the run would have taken (waits), so a 24-hour pause comes back instantly. For webhook mode, script the outcome — a payload to resume with, or a timeout to take the escalation branch.

Limits worth knowing

  • In a loop body, duration waits work but run in series: ten items with a one-hour wait is a ten-hour run. A webhook resume is refused there outright, since one URL cannot address a single iteration.
  • Renaming a waiting node while a run is paused on it makes its resume URL unreachable; the run then ends on its wait limit, or waits forever.
  • The step-by-step of a finished run is kept for the engine's retention window (24 h by default). A long wait is not affected — a paused run is still live — but the detail of a completed one eventually reduces to the index summary.

See also

75 documents11 sectionssource : /docs · généré au build