Skip to main content
The Flow Scheduler is a stateless tick endpoint. Each call sweeps every IR-native pipeline, computes which are due to fire NOW based on their ir.schedule, and dispatches each one via the existing pipeline run route. Schedules saved by the Schedule tab finally fire.

Endpoint

GET /api/v1/cron/flow-scheduler-tick
Authorization: Bearer $CRON_SECRET
(Or pass x-cron-secret: $CRON_SECRET header.) Auth: Fails closed when CRON_SECRET env is unset (matches the contract of /api/v1/cron/approvals-expire and other cron routes). Response:
{
  "tickAt": "2026-04-27T20:00:00.000Z",
  "totalPipelines": 47,
  "scheduled": 12,
  "due": 3,
  "dispatched": 3,
  "failed": 0,
  "results": [
    { "pipelineId": "...", "tenantId": "...", "fired": true, "status": 201 }
  ],
  "durationMs": 412
}

Per-kind decision logic

Implemented as the pure selectDuePipelines(inputs, now) helper at lib/flow/schedule/due-pipelines.ts.
Kind”Due” condition
croncron-parser yields any occurrence in (lastRunAt, now] (most recent wins)
intervalnow - lastRunAt >= minutes * 60_000
rruleRRule.between(lastRunAt, now) returns ≥ 1 occurrence
lastRunAt = null (pipeline never ran): treated as 24h lookback to avoid stampeding old, never-run pipelines that were created days/weeks ago. Bad schedules (malformed cron, malformed rrule) silently skip the pipeline — parsePipelineIR would have rejected the IR save earlier anyway.

How to fire it (every-minute external trigger)

The scheduler tick is not self-firing — it’s a stateless endpoint expecting an external orchestrator. Options:
SetupApproach
Self-hosted (k8s)A CronJob resource hitting /api/v1/cron/flow-scheduler-tick every minute
Self-hosted (Linux box)* * * * * curl -fsSL -H "x-cron-secret: $CRON_SECRET" https://your-app/api/v1/cron/flow-scheduler-tick
AWSEventBridge Schedule → API destination — playground does NOT enable this by default (project rule: no new AWS resources without authorization)
VercelA Vercel Cron Job (vercel.json crons array) hitting the endpoint

Idempotency

  • Calling the endpoint multiple times in one minute does not double-fire a pipeline — once a run is dispatched, lastRunAt advances to the fire time, and the next tick’s (lastRunAt, now] window excludes the same occurrence.
  • Per-tick fanout is O(n) over all IR-native pipelines + 1 IR fetch each. Phase 6.6 hardening adds an index + a “schedule_only” projection.

Honest residuals (carried into Phase 6.6)

  • Per-tenant scheduler dashboard showing miss / lag / failed dispatches
  • trigger.file_arrival.deadline.{windowMinutes, onMiss} enforcement — currently only pure schedule kinds fire; deadline windows on file_arrival triggers are not yet checked
  • Lock-free guarantee under concurrent ticks — current impl assumes the external orchestrator doesn’t double-fire the endpoint; an advisory PG lock around the sweep would close the gap