Skip to content

Trigger

A Trigger starts one or more Workflows without a manual call. It is a named fan-out point: it has a firing rule and a primary workflow (on), and any number of workflows — across packages — can subscribe to its name. Two kinds of firing rule:

  • Poll — a function the scheduler runs on an interval; it decides whether to fire (and with what context).
  • Cron — fires on a cron schedule (expression + timezone).
  • A trigger is registered with the runner’s scheduler, which polls it on its interval and fires when the rule says so.
  • Firing is deduplicated (a context hash) so the same fire doesn’t double-run.
  • Fan-out (CLOACI-T-0777 / T-0778). When a trigger fires, every workflow subscribed to it runs, not just the primary on workflow. A workflow subscribes by naming the trigger in #[workflow(triggers = ["my_trigger"])], and subscribers may live in other packages; they’re resolved from registry workflow metadata. The primary workflow drives the audit record, return value, and error propagation; secondary subscribers are best-effort (a secondary failure is logged, never fails the primary). A plain cron schedule (no trigger name) still binds exactly one workflow. The scheduler’s auto-poll and a manual fire (POST /v1/tenants/{t}/triggers/{name}/fire, server mode) fan out the same way.
  • Operator controls (server mode): triggers can be paused and resumed over REST — POST /v1/tenants/{t}/triggers/{name}/pause and .../resume — and listed/inspected via GET /v1/tenants/{t}/triggers[/{name}]. (Reactors, by contrast, are paused/resumed over their WebSocket channel only.)

Both kinds are authored with #[trigger] and bind to a workflow via on:

// poll trigger
#[trigger(on = "my_workflow", poll_interval = "30s")]
async fn my_poll(/* ... */) -> TriggerResult { /* Fire(ctx) | Skip */ }
// cron trigger
#[trigger(on = "my_workflow", cron = "0 9 * * *", timezone = "UTC")]
struct DailyTrigger;