Your AI Agent Scheduler Needs a Clock-Skew Budget, Not Just Cron A developer detailed the need for an explicit clock-skew budget in AI agent schedulers, arguing that relying solely on cron expressions or wall-clock time can cause jobs to run twice, never run, or execute after authorization windows expire. The post recommends using monotonic clocks for elapsed-time decisions, storing both wall-clock and monotonic timestamps, and implementing a decision gate that halts dispatch when clock uncertainty exceeds a defined budget. It also outlines fault-injection tests and record schemas to ensure reliable scheduling across restarts and clock adjustments. A scheduler can be perfectly healthy and still run the wrong job at the wrong time. The failure is usually not the cron expression. It is the boundary between wall-clock time, monotonic elapsed time, leases, retries, and a process that may pause or restart. A reliable agent scheduler needs an explicit clock contract. Without one, a clock correction can make a job run twice, never run, or run after its authorization window has expired. Use wall-clock time for human meaning and durable records: Use a monotonic clock for elapsed-time decisions inside one process: Use a database or provider sequence for ordering across processes: A monotonic timestamp cannot be compared across hosts, and a wall-clock timestamp cannot safely measure a five-minute lease if NTP steps the clock backward. Store both kinds of evidence instead of pretending one timestamp answers every question. Here is a deliberately boring record shape: action: send digest run id: 01J... scheduled at: 2026-08-19T08:00:00Z not before: 2026-08-19T08:00:00Z expires at: 2026-08-19T08:05:00Z lease owner: worker-7 lease token: 1842 attempt: 1 state: READY The important part is not the field names. It is the decision rule: That last step matters after restarts. A clean restart is not proof that the previous effect did not happen. I covered the effect-side version of this problem in restart-safe agent deduplication https://dev.to/zira125/your-ai-agent-restarted-cleanly-why-did-it-run-the-job-twice-3p9d . A clock-skew budget is the maximum uncertainty you will tolerate between the clock used to schedule a run and the clock used to authorize dispatch. For example: Do not silently turn the budget into a larger retry window. If the observed offset exceeds the budget, stop dispatching new work or move runs to CLOCK UNCERTAIN. Existing in-flight work needs its own lease and effect policy. A simple gate can look like this: action = now wall < run.not before expired = now wall = run.expires at clock uncertain = abs host offset CLOCK SKEW BUDGET if clock uncertain: return CLOCK UNCERTAIN if action: return NOT READY if expired: return EXPIRED return DISPATCHABLE The ordering is intentional. A scheduler should not dispatch merely because a job is due if the host's clock is outside the authority's accepted uncertainty. Test at least these cases: | Fault | Unsafe symptom | Safer result | |---|---|---| | Clock jumps backward | due work appears early or leases live too long | use monotonic lease timers and hold new dispatch | | Clock jumps forward | future work runs immediately or expires | reject dispatch outside the freshness window | | NTP becomes unavailable | stale schedule decisions continue silently | enter CLOCK UNCERTAIN with an alert | | Worker pauses during a lease | two workers perform one effect | fencing token rejects the stale worker | | Scheduler restarts at a boundary | a run is lost or duplicated | reload durable state and reconcile by run ID | | DST or timezone conversion changes | local-time jobs shift unexpectedly | store UTC plus the original schedule zone | A useful failure-injection test does not just mock now . Pause a worker after it claims a lease, advance the authority clock, start a replacement worker, and then let the old worker attempt the effect. The expected result is a rejected stale token, not a second provider call. Human schedules may be expressed as “every weekday at 09:00 Europe/Berlin.” Convert that schedule to an unambiguous UTC occurrence before creating the run record. Persist the timezone and the resolved occurrence together. Do not let a browser session, email worker, or MCP tool reinterpret the local schedule. By the time work reaches an effect boundary, it should carry a concrete run ID, expiry, authority version, and idempotency key. This also makes audits possible. When a user asks why a run happened at 08:00 UTC, you can distinguish: Before running an always-on agent scheduler, verify: If you need a managed always-on runtime for an OpenClaw or browser-based agent, managed agent hosting on Ampere https://ampere.sh/?utm source=devto&utm medium=article&utm campaign=scheduler-clock-skew is one option to evaluate. Hosting can keep a process running, but it does not define the clock contract, lease semantics, effect idempotency, or recovery policy. Those still belong in the application. The practical lesson is simple: cron tells you when to try. A clock contract tells you whether the attempt is still authorized, current, and safe. If this kind of control-plane detail is useful, follow for practical agent reliability patterns rather than model demos.