Triggering workflows: watchers, cron, and polling daemons
State: open/active · Genre: analysis
Use case: triggered-recurring-runs RFC mapping: new Source: cross-repo survey of
sksizer/* (2026-07-05), companion to the workflow-constellation RFC (PR #682).
The need
Section titled “The need”Workflows do not only run on invocation — they are woken by file watchers with debounce, cron schedules, launchd/systemd timers, and hand-rolled polling daemons, each of which independently builds cursor state, rate limiting, PID/pause files, and idempotent-restart plumbing around the actual workflow. UC1-UC10 describe what runs; nothing in the taxonomy describes when and what wakes it.
Evidence
Section titled “Evidence”DarkFactory
Section titled “DarkFactory”rework_watch.py: a 529-line hand-rolled daemon — poll loop over open PRs, JSON cursor of last-seen comment IDs, per-PR rate limiting (3/hour with timestamp pruning), PID files, pause files, fork/setsid daemonization, stale-PID cleanup — all wrapped around dispatching one workflow.
Where: python/darkfactory/cli/rework_watch.py
Pain: The daemon’s state machine and rate caps are pure trigger infrastructure around one conceptual loop.
markdown-contract
Section titled “markdown-contract”Per-vault reactive pipeline: recursive fs.watch with 300ms debounce and relevance filtering triggering re-validation pushed over SSE, with timer lifecycle and idempotent watcher restart hand-managed per vault.
Where: apps/web/src/daemon/watcher.ts · apps/web/src/daemon/runs.ts
Pain: Debounced event-triggered re-execution with per-vault re-entrancy is all hand plumbing.
A deterministic ‘orchestrate watch’ pending-work gate plus a never-exiting shell poll wrapper (stdout presence as the only event signal) implementing level-triggered wake beside a slow /loop cadence.
Where: plugin/lib/services/orchestrator/ops/watch.ts ·
plugin/skills/orchestrate/watch_loop.sh
Pain: Event-wake semantics improvised from a polling script because workflows have no trigger surface.
polish
Section titled “polish”The product roadmap specifies scheduled/recurring workflow runs with a scheduler UI as a core feature of its planned YAML workflow engine.
Where: docs/ui/03-scheduler.md · docs/phases/01-polish-phase.md
Pain: Scheduling is planned as greenfield work atop an engine that also doesn’t exist yet.
agentpants
Section titled “agentpants”6-hourly GitHub Actions cron chaining build → index → curl a deploy hook for the skill-directory indexer.
Where: .github/workflows/index-skills.yml
Pain: Trigger and pipeline welded together in CI YAML; no resume of a failed run.
launchd plists scheduling the Obsidian sync pipeline, with the interval switched by battery/AC power state; a periodic while-loop variant lives in the same script family.
Where: bin/mac/obsidian_interval.sh ·
bin/mac/templates/com.sizerllc.obsidiansync.plist.template · sync.sh
Pain: Scheduling logic scattered across plist templates and shell loops.
Flowline fit
Section titled “Flowline fit”No current module covers this — a genuine taxonomy gap. Flowline need not ship a daemon, but a minimal trigger contract (evented/scheduled invocation + idempotent re-entry + cursor/rate-limit state riding on @flow/durable) would absorb the cursor-file, debounce-timer, and PID-file plumbing every trigger currently rebuilds.
Proposed API
Section titled “Proposed API”Builds on: 12 Introduces: extension: flowline/trigger — watch(), cron(), poll()
What wakes a workflow, as library surface instead of ad-hoc daemons with hand-rolled debounce, PID files, and cursor state:
import { watch, cron, poll, fileCursor } from "flowline/trigger";
watch("docs/**/*.md", { debounce: 2_000 }, (paths) => run(rebuild, { seed: Changed.of(paths) }));
cron("*/15 * * * *", () => runDurable(orchestrateTick, { runId: `tick-${isoNow()}` }));
poll({ every: 60_000, cursor: fileCursor(".sdlc/pr-cursors") }, (cursor) => run(checkPrs, { seed: Cursor.of(cursor) }));Entirely an extension — the RFC’s taxonomy describes what runs, never what wakes it. Debounce, cursor persistence, overlap suppression (skip a tick if the previous one is still running), and pause/PID plumbing are the recurring hand-rolled parts.