Problem
The routine scheduler uses setInterval(tick, 60_000) — a plain JS timer inside the Node.js process. This works for single-instance dev but has problems in production:
- No persistence — if the server restarts, missed ticks are lost
- No distributed locking — running 2+ instances fires routines twice
- ±60s precision — checks every minute, not at exact cron time
- No catch-up — if the server was down during a scheduled tick, it's silently skipped
Current implementation
packages/@boringos/core/src/scheduler.ts — createRoutineScheduler()
→ setInterval(tick, 60_000)
→ tick() queries active routines, matches cron, fires
Proposed solution
Use BullMQ repeatable jobs when Redis is available:
- When
app.queue(createBullMQQueue({ redis })) is configured, the scheduler should register routines as BullMQ repeatable jobs instead of using setInterval
- BullMQ handles: cron precision, persistence across restarts, distributed locking (only one instance processes each job), automatic catch-up for missed jobs
- Fall back to current setInterval when no Redis is configured (dev mode)
Architecture
No Redis (default):
setInterval(60s) → tick() → match cron → fire (current behavior, unchanged)
With Redis:
For each active routine → BullMQ.add(routineId, {}, { repeat: { cron: expression } })
BullMQ processes → fires routine (workflow or agent wake)
On routine CRUD → update/remove BullMQ repeatable job
Impact
packages/@boringos/core/src/scheduler.ts
packages/@boringos/pipeline (may need repeatable job support)
packages/@boringos/core/src/boringos.ts (pass queue adapter to scheduler)
Files
scheduler.ts:16 — createRoutineScheduler()
scheduler.ts:49 — interval = setInterval(() => tick().catch(() => {}), 60_000)
Problem
The routine scheduler uses
setInterval(tick, 60_000)— a plain JS timer inside the Node.js process. This works for single-instance dev but has problems in production:Current implementation
Proposed solution
Use BullMQ repeatable jobs when Redis is available:
app.queue(createBullMQQueue({ redis }))is configured, the scheduler should register routines as BullMQ repeatable jobs instead of using setIntervalArchitecture
Impact
packages/@boringos/core/src/scheduler.tspackages/@boringos/pipeline(may need repeatable job support)packages/@boringos/core/src/boringos.ts(pass queue adapter to scheduler)Files
scheduler.ts:16—createRoutineScheduler()scheduler.ts:49—interval = setInterval(() => tick().catch(() => {}), 60_000)