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
onworkflow. 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}/pauseand.../resume— and listed/inspected viaGET /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;
Both kinds are decorators in Python too:
import cloaca
# poll trigger — return TriggerResult.fire(ctx) to fire, .skip() otherwise
# (a plain bool also works: True = fire with no context)
@cloaca.trigger(name="my_poll", on="my_workflow", poll_interval="30s")
def my_poll():
return cloaca.TriggerResult.skip()
# cron trigger — the function body is unused; `on` is required
@cloaca.trigger(on="my_workflow", cron="0 9 * * *", timezone="UTC")
def daily():
pass
Cron support on @cloaca.trigger was added in CLOACI-T-0688 — the reconciler
routes cron-bearing triggers to the cron scheduler. Python also has the full
runtime cron API (register_cron_workflow, …); see
Cron schedule. Note
cloacinactl package new only scaffolds cron packages for Rust — hand-author
the decorator in Python.
Not the same as reactor subscription fan-outTrigger-name fan-out (one trigger name, many subscribing workflows) is a different primitive from the DB-backed reactor → workflow subscription fan-out. The latter has a reactor publisher, a durablereactor_firingslog, and at-least-once delivery; this has none of those. See Trigger-name fan-out vs reactor subscription fan-out for the side-by-side.
- Cron schedule · Workflow
- Subscription fan-out — the other fan-out (reactor → workflow)