Skip to content

[Schedules 3/8] Scheduler engine — cron evaluator goroutine #31

@sre-helmcode

Description

@sre-helmcode

Implementation Order: 3 of 8

Depends on: #29 (models), #30 (CRUD API)
Feature: Schedules — Recurring automated tasks for AI agent teams

Summary

Implement the scheduler engine: a background goroutine that evaluates cron expressions every minute and triggers due schedules. This is the core timing mechanism.

Architecture

API Server starts
  └── scheduler.Start(db, triggerFunc)
        └── goroutine: tick every 60s
              ├── SELECT * FROM schedules WHERE enabled=true AND next_run_at <= now()
              ├── For each due schedule:
              │     ├── go triggerFunc(schedule)  ← parallel, non-blocking
              │     └── UPDATE next_run_at to next occurrence
              └── Sleep until next tick

Key Requirements

Non-blocking

  • CRITICAL: The scheduler MUST NOT block the API server. It runs in its own goroutine.
  • Each schedule execution runs in a separate goroutine — 10 schedules can run in parallel without issues.
  • The scheduler ticker continues checking every minute regardless of running executions.

Parallel Executions

  • If a schedule triggers while a previous execution is still running, a new parallel execution starts (no skip, no queue).
  • Each execution is fully independent with its own ScheduleRun record.

Cron Library

  • Use a Go cron parsing library (e.g., github.com/robfig/cron/v3 or similar) to:
    • Parse and validate cron expressions
    • Calculate next_run_at given a cron expression + timezone

Timezone Support

  • All cron evaluations must respect the schedule's configured timezone.
  • Store next_run_at in UTC internally, but evaluate using the schedule's timezone.

Package Structure

internal/scheduler/
├── scheduler.go      // Start(), Stop(), tick loop
├── cron.go           // ParseCron(), NextRun() helpers
└── scheduler_test.go // Unit tests

Graceful Shutdown

  • scheduler.Stop() must cleanly stop the ticker
  • Wire into the API server's shutdown handler (already exists for NATS, Docker, etc.)

Acceptance Criteria

  • New internal/scheduler/ package
  • Start(db, executeFn) launches background goroutine
  • Ticks every 60 seconds, queries for due schedules
  • Each due schedule triggers executeFn in a new goroutine
  • Updates next_run_at after triggering
  • Handles timezone correctly
  • Stop() for graceful shutdown
  • Wired into cmd/api/main.go startup and shutdown
  • Unit tests for cron parsing and next-run calculation
  • Integration test: create schedule due in past → verify it triggers on next tick

Metadata

Metadata

Assignees

Labels

featureNew feature or request

Type

No type

Projects

Status

Todo

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions