[Proposal] Scheduled-workflow precheck gate: skip runs with zero token/action cost
- Dominant language
- Rust
- Stars
- 32.7k
- Forks
- 4.3k
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 253
Description
## Summary
Add an optional **precheck gate** to `Schedule`-triggered workflows so a recurring workflow can cheaply decide *whether there is work* before executing its steps (which may call agents, webhooks, or post messages). When the gate says "no work," the run is recorded as skipped and no actions fire — no agent turn, no webhook, no channel noise.
This mirrors a pattern we recently upstreamed to two other agent runtimes and would make Buzz's scheduled agent workflows materially cheaper and quieter.
## Problem
`TriggerDef::Schedule` fires a workflow on a cron/interval. Today the only convergence control is per-step `if:` (evalexpr) conditions evaluated against trigger context + prior step outputs. For a *poller* workflow ("every 15m, check X, and if there's something new, have an agent summarize it"), each tick still spins up the step chain (and any agent/webhook action) even when there is nothing to do. On a busy relay with many scheduled agent workflows, that is a large, silent cost and a source of empty-run channel noise.
## Prior art (why we think this is the right shape)
We contributed the same idea to OpenClaw (`job.precheck`) and Hermes (`NO_WORK` wake gate) cron systems: a cheap gate runs *before* the expensive work; exit `0` / `WORK_NEEDED` → run, exit `2` / `NO_WORK` → skip. The key lessons we'd carry over:
- The gate lives **inside** the scheduler run path (not a sidecar) so there's one source of truth and no double-firing.
- One documented **contract** (exit code + a `NO_WORK` stdout convention) with tests.
- Skipped runs are **recorded distinctly** (not errors) for observability.
## Proposed shape (seeking maintainer direction before implementing)
A workflow-level, evalexpr-based gate that reuses the existing `evaluate_condition` machinery (no new execution surface, no shell):
```yaml
name: pr-triage
trigger:
on: schedule
cron: "*/15 * * * *"
precheck: "trigger_open_pr_count > 0" # evalexpr, same vars as step if:
steps:
- action: { send_message: { text: "…" } }
```
- If `precheck` evaluates false → the run is **skipped** (distinct status, metric), zero actions.
- Purely additive: workflows without `precheck` behave exactly as today.
- Reuses `evaluate_condition` (already bounded: 4 KB expr cap + 100 ms timeout), so it inherits the same safety envelope — **no shell/command execution added to the relay**.
### Deliberately out of scope
- Arbitrary shell/command prechecks (security surface in the relay — not proposing that).
- Any change to non-schedule triggers.
## Open questions for maintainers
1. Is a workflow-level `precheck` (vs. telling authors to gate step 1 with `if:`) worth the ergonomics + the distinct skipped-run status/metric?
2. Preferred skipped-run surfacing: a workflow-run status value, a metric only, or both?
3. Would you want the gate to have access to any state beyond current trigger context (e.g., last-run state) — or keep it stateless in v1?
Happy to implement behind a small, additive PR with tests + docs once the shape is agreed. I recently shipped #2289 (agent `error_class`) and #2296 (offline-mention notice) in this area and can follow the same minimal-diff approach.
Contributor guide
Research direction
Start by reviewing the TriggerDef::Schedule run path and the existing evaluate_condition machinery, including the current handling of step if: conditions. Before implementation, resolve the proposed contract for false prechecks, skipped-run status or metrics, and state access with maintainers; done should include agreed tests and documentation for schedule workflows without changing other triggers.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- backend
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Needs clarification
- Newbie friendliness
- 30/100