0006. In-process cron scheduler, not Kubernetes CronJob
- Status: accepted
- Date: 2026-04-26
Context
The scheduling capability runs scans on a cron schedule and feeds each scan into the drift engine. There were three obvious ways to deliver this:
- An in-process scheduler running inside
wanderer serve, owning the cron lifecycle and invoking the same scanner pipeline the CLI and HTTP API use. - An external cron daemon (
cron,systemd-timer) that callswanderer scanon a schedule. - A Kubernetes
CronJobper target invoking a Wanderer container.
Option 3 is an attractive long-term answer at scale: parallelism for free, retries for free, observability through the cluster's existing machinery. Option 2 keeps Wanderer simple and shifts cadence to the host operator.
Decision
Wanderer ships option 1: an in-process scheduler driven by
github.com/robfig/cron/v3.
Schedules live in a YAML file pointed at by --schedules. The file
is validated at startup; a misconfigured cron expression fails the
process so the operator sees the problem before silently missed
runs accumulate. SIGHUP re-reads the file; the file is not watched
for changes.
The scheduler is owned by the serve command and shares its
lifecycle. It does not have its own binary, its own listener, or its
own configuration directory.
Consequences
- One process, one source of truth. The scheduler runs inside
the same
wanderer serveinstance that serves the HTTP API, holds the SQLite handle, and answers MCP requests. Operators have one thing to monitor. - No deployment ceremony. A small team running Wanderer on a
single host gets recurring scans without learning Kubernetes,
without writing a separate
systemdtemplate per target, and without per-target containerisation. - Limited horizontal scale. Two
wanderer serveinstances pointing at the same SQLite file would race; either we add leader election (premature) or operators run one instance per dataset. At MVP scales (≤ a few hundred targets, scans every few hours) this is not a concern. - Hot-reload is explicit, not magical. SIGHUP is the boring reload mechanism. A file watcher would feel modern but would also be the path most likely to silently miss a tick after a syntax error. SIGHUP forces the operator to acknowledge the change.
- Cron implementation is a real dependency.
robfig/cron/v3entersgo.mod. It is pure Go, BSD-3, widely deployed, and small. Per ADR-0003 this is a deliberate addition, justified by the ergonomics of having a cron parser we trust.
When to revisit: if Wanderer grows multi-tenant (per-org scheduling), or if a single instance saturates a host's resources, or if drift analysis becomes expensive enough that we want fan-out. At that point, expect a follow-up proposal that introduces either external orchestration (Kubernetes CronJob with Wanderer-as-worker) or explicit leader election. Until that demand is concrete, keep the in-process design.