Triggers
DAGs can be triggered automatically via the trigger: block. Multiple triggers can coexist – any matching trigger starts a run. Run daggle serve to activate triggers.
Cron schedule
trigger:
schedule: "30 6 * * MON-FRI" # 6:30 AM weekdaysSupports standard 5-field cron expressions plus shorthands: @every 5m, @hourly, @daily, @weekly.
Firing precision
The scheduler checks for due cron entries on every poll tick — by default every 30 seconds. A run fires within [scheduled_time, scheduled_time + poll_interval]. For most batch workflows this is fine: a 30 6 * * MON-FRI cron lands between 06:30:00 and 06:30:30.
If you need finer precision — e.g. an @every 1s schedule that must actually fire every second — lower scheduler.poll_interval in config.yaml to match the cadence you need:
scheduler:
poll_interval: "500ms"See Global configuration for the full scheduler block.
File watcher
trigger:
watch:
path: /data/incoming/
pattern: "*.csv"
debounce: 5sTriggers when files matching the pattern are created or modified. Debounce prevents firing on partial writes.
Webhook
trigger:
webhook:
secret: "${env:WEBHOOK_SECRET}"Triggers on HTTP POST to /webhook/{dag-name}. The scheduler starts an HTTP server when webhook triggers are detected.
Validate requests with HMAC-SHA256 via the X-Daggle-Signature header (format: sha256=<hex>).
Example: GitHub webhook
curl -X POST http://localhost:8787/webhook/deploy-on-push \
-H "X-Daggle-Signature: sha256=$(echo -n '{}' | openssl dgst -sha256 -hmac 'your-secret' | awk '{print $2}')" \
-H "Content-Type: application/json" \
-d '{}'DAG completion
trigger:
on_dag:
name: daily-etl
status: completed # completed (default), failed, or any
pass_outputs: true # pass upstream outputs as env varsTriggers when another DAG completes or fails. Enables multi-DAG workflows without sub-DAG composition.
Condition polling
trigger:
condition:
command: 'test -f /data/ready.flag'
poll_interval: 5mEvaluates a shell command or R expression on an interval. Triggers when it succeeds (exit code 0).
trigger:
condition:
r_expr: 'DBI::dbGetQuery(con, "SELECT COUNT(*) FROM new_data")[[1]] > 0'
poll_interval: 10mGit changes
trigger:
git:
branch: main
poll_interval: 30sPolls the local git repository for new commits. Triggers when the commit hash changes.
Combined triggers
Triggers are additive – any matching trigger starts a run:
trigger:
schedule: "0 6 * * *"
watch:
path: /data/incoming/
pattern: "*.csv"
debounce: 5sOverlap policy
By default, triggers are skipped if the DAG is already running. Set overlap: cancel to kill the old run:
trigger:
schedule: "@every 5m"
overlap: cancel # kill old run, start fresh| Policy | Behavior |
|---|---|
skip (default) |
Ignore trigger while DAG is running |
cancel |
Cancel the running DAG, start a new run |
Catchup policy
If daggle is offline when a scheduled tick should fire, the tick is normally lost — the cron library only fires future ticks after registration. Opt in with catchup to have daggle reconcile missed ticks on startup:
trigger:
schedule: "0 9 * * *"
catchup: once # default is off| Mode | Behavior |
|---|---|
off (default) |
Missed ticks are dropped; only future ticks fire |
once |
If one or more ticks were missed, fire one catch-up run on startup, then resume the normal schedule |
all |
Fire one run for every missed tick (Airflow-style backfill), serially. Capped at scheduler.max_catchup_runs (default 100) so a long outage cannot trigger an unbounded run storm — when truncated, a warning is logged |
Catchup is anchored to the end timestamp of the most recent terminal run (completed or failed). For DAGs with no run history, catchup is skipped — there is nothing to anchor against.
Catchup fires only once per DAG per scheduler process: a cold start triggers it, but a hot reload (e.g. editing the DAG file while daggle is running) does not. Caught-up runs appear in logs with source=catchup so they are distinguishable from normal cron firings.
Catchup currently applies to YAML-declared trigger.schedule only. Schedules created via the REST API (runtime schedules) do not honour catchup in v1.