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 weekdays

Supports 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: 5s

Triggers 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 vars

Triggers 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: 5m

Evaluates 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: 10m

Git changes

trigger:
  git:
    branch: main
    poll_interval: 30s

Polls 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: 5s

Overlap 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.