Skip to content

feat(services): cron-based scheduling with natural language conversion#41

Merged
Mathews-Tom merged 3 commits into
mainfrom
feat/cron-scheduling
Mar 25, 2026
Merged

feat(services): cron-based scheduling with natural language conversion#41
Mathews-Tom merged 3 commits into
mainfrom
feat/cron-scheduling

Conversation

@Mathews-Tom

Copy link
Copy Markdown
Owner

Summary

Replace the rigid interval_days scheduling with cron expression support and a natural language schedule converter, inspired by SivaRamSV/paaw's JobCreator NL-to-cron pattern. Users can now configure loop schedules as cron expressions ("0 9 * * 1-5") or natural language ("every monday at 9am", "weekdays", "every 3 days"), with automatic fallback to the existing interval_days for backward compatibility.

Schedule Resolution Chain

schedule field (string)
  ├─ Non-empty + valid cron syntax → use directly
  ├─ Non-empty + NL pattern match  → convert via nl_to_cron()
  ├─ Non-empty + unrecognized      → log warning, fall back ↓
  └─ Empty string                  → interval_days_to_cron(interval_days)

Supported Natural Language Patterns

Input Cron Output
"daily" / "every day" 0 0 * * *
"weekly" / "every week" 0 0 * * 0
"monthly" 0 0 1 * *
"every monday" / "every fri" 0 0 * * 1 / 0 0 * * 5
"weekdays" 0 0 * * 1-5
"weekends" 0 0 * * 0,6
"every 3 days" 0 0 */3 * *
"every 4 hours" 0 */4 * * *
"daily at 9:30 AM" 30 9 * * *
"every monday at 3pm" 0 15 * * 1

Time extraction (at HH:MM AM/PM) combines with any day pattern.

Changes

New Files

  • src/vaultmind/services/cron.py (172 lines) — CronSchedule frozen dataclass with is_overdue() and validate() methods. interval_days_to_cron() for integer-to-cron conversion. nl_to_cron() regex-based NL parser supporting 10+ patterns with time extraction. Pure pattern matching, no LLM cost
  • tests/test_cron.py (205 lines) — 31 tests across 5 classes

Modified Files

  • pyproject.toml — Added croniter>=2.0,<3 dependency
  • uv.lock — Updated with croniter + pytz
  • src/vaultmind/services/scheduler.py — Added cron_expr: str = "" field to ScheduledJob. is_overdue() delegates to CronSchedule.is_overdue() when cron_expr is set, falls back to timedelta comparison otherwise. Added resolve_cron_expr(schedule, interval_days) module-level function implementing the resolution chain
  • src/vaultmind/config.py — Added insight_schedule, evolution_schedule, procedural_schedule fields to LoopsConfig (all default "" = derive from interval_days)
  • config/default.toml — Expanded [loops] section with schedule fields alongside existing interval_days
  • src/vaultmind/cli.py — Updated insight_loop, evolution_loop, and procedural_loop job creation to call resolve_cron_expr() and pass cron_expr to ScheduledJob

Backward Compatibility

  • All existing interval_days configs continue to work — schedule="" triggers automatic conversion
  • ScheduledJob interval field preserved; cron_expr is additive and takes priority only when non-empty
  • Existing tests pass unchanged — ScheduledJob(name=..., interval=..., execute=...) still works
  • ScheduledJob.legacy() factory unmodified

New Dependency

  • croniter>=2.0,<3 — production-ready cron expression evaluator, widely used, lightweight (only pulls pytz)

Test plan

  • 31 new tests in test_cron.py across 5 classes:
    • CronSchedule: validation, overdue checks with fixed/naive datetimes (5)
    • nl_to_cron: all NL patterns including time extraction, case insensitivity, unrecognized input (14)
    • interval_days_to_cron: day conversion, zero/negative edge cases (5)
    • resolve_cron_expr: explicit cron, NL conversion, empty/invalid fallback (4)
    • ScheduledJob cron integration: cron_expr priority, interval fallback, None last_run (3)
  • All existing scheduler tests pass unchanged
  • Full suite: 898/898 tests pass, 0 regressions
  • ruff check — clean
  • mypy --ignore-missing-imports — clean
  • Manual: verify insight_schedule = "every monday at 9am" triggers correctly in bot
  • Manual: confirm interval_days fallback produces identical behavior to pre-change

New module services/cron.py with CronSchedule dataclass (is_overdue,
validate), interval_days_to_cron() converter, and nl_to_cron() for
natural language schedule parsing (daily, weekly, monthly, weekdays,
day names, "every N days/hours", time extraction).

Add croniter>=2.0,<3 dependency for cron expression evaluation.
…allback

Add cron_expr field to ScheduledJob — when set, is_overdue() delegates
to CronSchedule instead of timedelta comparison. Add resolve_cron_expr()
that accepts explicit cron, natural language schedule, or falls back to
interval_days_to_cron().

LoopsConfig gains insight_schedule, evolution_schedule, procedural_schedule
fields (empty = derive from interval_days). CLI job creation wired through
resolve_cron_expr(). Old interval_days configs preserved for backward
compatibility.
31 tests across 5 classes: CronSchedule validation and overdue checks (5),
nl_to_cron natural language patterns (14), interval_days_to_cron conversion
(5), resolve_cron_expr fallback chain (4), and ScheduledJob cron_expr
integration with interval fallback (3).
@Mathews-Tom Mathews-Tom merged commit a6cad20 into main Mar 25, 2026
3 checks passed
@Mathews-Tom Mathews-Tom deleted the feat/cron-scheduling branch March 25, 2026 15:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant