Triggers
A trigger is the answer to one question: what makes this workflow run? rupu workflows can fire by hand, on a schedule, or in reaction to an SCM or issue-tracker event.
How workflows fire
Every workflow declares a trigger with a top-level trigger.on field in its .rupu/workflows/<name>.yaml. There are three modes; manual is the default when trigger is omitted.
| trigger.on | When it fires | Where it lives |
|---|---|---|
manual | rupu workflow run <name> | every install |
cron | system cron / launchd invokes rupu cron tick | every install |
event | matching events appear via rupu cron tick (polled) or rupu webhook serve (live) | polled + webhook tiers |
The full schema, with cross-field validation rules:
trigger: on: manual | cron | event # default: manual cron: "0 4 * * *" # required when on: cron (5-field UTC) event: github.issue.opened # required when on: event filter: "{{ event.repo.full_name == 'foo/bar' }}" # optional, only when on: event
cron: is only allowed when on: cron; event: and filter: only when on: event. A missing on: defaults to manual.Cron
Cron-scheduled workflows run on a clock. rupu has no scheduling daemon of its own — you install a single system-cron or launchd entry that calls rupu cron tick once a minute, and each tick walks every cron-triggered workflow and fires those whose schedule matched between the persisted last_fired timestamp and now. It is idempotent at 1-minute granularity, so an overrunning tick never double-fires.
Install one entry in your crontab / launchd plist:
* * * * * /usr/local/bin/rupu cron tick
Then give the workflow a schedule (5-field UTC expression):
# .rupu/workflows/nightly-audit.yaml name: nightly-audit trigger: on: cron cron: "0 3 * * *" # every day at 03:00 UTC steps: - id: scan agent: security-reviewer actions: [] prompt: | Audit the repo for newly-added secrets in the last 24h.
Use rupu cron tick --dry-run to verify a crontab line without firing anything. rupu cron list is read-only — it prints every cron-triggered workflow plus its next firing time, handy before you add the cron entry.
Events
Event-triggered workflows react to activity in your SCM and issue trackers. rupu offers two delivery tiers that share the same workflow YAML and the same event vocabulary:
- Polled — the CLI-native path.
rupu cron tickasks each configured connector for new events since the last tick. No server required. - Webhook —
rupu webhook serve, a long-lived HMAC-validated HTTP receiver for sub-second latency and broader event coverage.
Polled sources
Configure the sources you want polled in ~/.rupu/config.toml (global) or <project>/.rupu/config.toml (project shadows global). The list is empty by default — rupu polls nothing until you ask it to.
[triggers] poll_sources = [ "github:Section9Labs/rupu", { source = "gitlab:my-org/my-repo", poll_interval = "15m" }, ] # Optional: cap events processed per source per tick. Default 50. max_events_per_tick = 50
Each entry is either a bare "vendor:owner/repo" string (eligible every event tick) or an inline table with a source-local poll_interval cadence override (30s, 5m, 2h, 1d). The source model spans repos and trackers: github:owner/repo, gitlab:group/project, linear:<team-id>, and jira:<site>/<project> (or jira:<project> when [scm.jira].base_url is set).
Webhook serve
For sub-second latency or events the polled tier doesn't deliver, run rupu webhook serve under your own supervisor. Secrets come from environment variables only — never config files, never the keychain.
RUPU_GITHUB_WEBHOOK_SECRET=<your-webhook-secret> \
rupu webhook serve --addr 0.0.0.0:8080
The receiver validates the vendor signature (X-Hub-Signature-256 for GitHub, X-Gitlab-Token for GitLab, Linear-Signature + timestamp freshness for Linear, X-Hub-Signature for Jira Cloud), maps the raw delivery onto the rupu event id, and fires matching workflows with {{event.*}} populated. Bind to 127.0.0.1 and front it with a TLS-terminating reverse proxy — rupu does not terminate TLS itself.
The event vocabulary
Each connector lifts events from the vendor's events API and maps them onto canonical rupu ids. You match against these in trigger.event:, and glob wildcards (*) work — github.issue.* matches every GitHub issue event, "*.pr.merged" matches cross-vendor, and "*" wakes on anything. A few common ids:
| Event id | Meaning |
|---|---|
github.issue.opened | a GitHub issue was opened |
github.issue.labeled | a label was added to a GitHub issue |
github.pr.opened | a GitHub pull request was opened |
github.pr.review_requested | review requested on a GitHub PR |
github.push | commits pushed to a GitHub repo |
gitlab.mr.merged | a GitLab merge request was merged |
linear.issue.updated | a Linear issue changed (state/priority/…) |
issue.queue_entered | semantic alias: issue moved into a queue-like state |
Beyond canonical vendor ids, rupu derives a broader semantic alias vocabulary (e.g. issue.queue_entered, pr.review_activity) from the same deliveries. Aliases are matchable alongside canonical ids, but one delivery still fires a workflow at most once. A complete event matrix and the templating metadata exposed as {{event.*}} live in docs/triggers.md.
A worked event workflow:
# .rupu/workflows/triage-incoming-issues.yaml name: triage-incoming-issues trigger: on: event event: github.issue.opened filter: "{{ event.repo.full_name == 'Section9Labs/rupu' }}" steps: - id: comment_back agent: issue-commenter actions: [] prompt: | Post a triage summary on issue {{ event.repo.full_name }}#{{ event.payload.issue.number }}.
Inspecting & operating
The cron subcommand is your window into both scheduled and polled-event triggers — there is no separate events daemon to manage.
rupu cron events— read-only; prints each event-triggered workflow with its matched event id, configured sources, and the persisted cursor.rupu cron tick --skip-events— run the cron tier only, leaving polled events untouched.rupu cron tick --only-events— run the polled-event tier only.
Splitting the two flags lets each tier run at its own cadence:
* * * * * rupu cron tick --skip-events # cron only, every minute */5 * * * * rupu cron tick --only-events # events every 5 minutes
Per-source cadence is controlled by the poll_interval override on a poll_sources entry: it decides whether a source is due to be polled on a given rupu cron tick --only-events, without changing workflow-matching semantics. On a source's first poll rupu emits zero events and sets the cursor to "now," so adding a source never triggers a stampede over its history.
From trigger to autonomy
Triggers are the wake-up mechanism for autonomy. An autoflow is a workflow that runs unattended; its wake_on: matches the very same event vocabulary described here, so triggers are precisely how autoflows come to life. The events themselves originate from the SCM and issue-tracker connectors covered under Integrations — wire up a connector there, list it under poll_sources or point a webhook at it, and your workflows start reacting to the world.