To schedule an AI agent with cron, you write a five-field expression that says when the run should fire, attach it to the agent, and set the time zone explicitly. A cron schedule turns a one-off agent into a recurring one: a daily sales digest at 9am, a weekly report every Monday, a nightly cleanup at midnight. The clock triggers the run; you never press a button. That is the whole appeal, and also where the sharp edges live.
This guide walks through the practical parts: how cron syntax actually reads, how to pick a sensible cadence, how to avoid the time zone and overlap traps that bite almost everyone, how to make a re-run safe, and how to know when a job silently failed to fire. It assumes you already know how to stand up a basic agent, covered in how to set up your first AI agent.
Why schedule agents in the first place
You schedule an agent when the work is tied to time, not to a person sitting down to start it. A daily digest, a weekly KPI summary, an end-of-month invoice sweep: these should happen on their own, on time, whether or not anyone remembers. The value of a recurring agent is that it removes the human trigger entirely, so the work never slips because someone was on holiday or simply forgot.
The classic candidates are reports and digests. A morning briefing that pulls yesterday's numbers, a Monday roll-up of last week's pipeline, a nightly data refresh. Anything you would otherwise put on a recurring calendar reminder is a candidate for a scheduled agent, and the writing side of that is covered in how to write a prompt for a recurring agent. Recurring reporting specifically gets its own treatment in AI agent weekly KPI reports.
What does cron syntax actually mean?
Cron expresses a schedule in five space-separated fields, read left to right: minute, hour, day of month, month, and day of week. Each field takes a number, a wildcard, a list, or a range. A wildcard means "every". So the position of a value tells cron what it controls, and the value tells cron when to match. Once you can read the five slots, every expression becomes legible.
Here is the field order and a few worked examples. These are factual cron expressions, not statistics, so they hold on any standard cron system.
┌───────────── minute (0 - 59)
│ ┌─────────── hour (0 - 23)
│ │ ┌───────── day of month (1 - 31)
│ │ │ ┌─────── month (1 - 12)
│ │ │ │ ┌───── day of week (0 - 6, Sunday = 0)
│ │ │ │ │
* * * * *
0 9 * * 1 → 9:00 every Monday
0 9 * * * → 9:00 every day
*/15 * * * * → every 15 minutes
0 0 1 * * → midnight on the 1st of every month
30 17 * * 5 → 17:30 every Friday
Read 0 9 * * 1 field by field: minute 0, hour 9, any day of month, any month, day of week 1. That resolves to 9:00 every Monday. The two wildcards in the middle are doing the work of saying "regardless of the date, just match the weekday". Most expressions you write will lean on wildcards for the fields you do not care about.
The two slots people misread
Two fields trip people up. First, day of week is zero-indexed from Sunday on most systems, so 1 is Monday and 5 is Friday; always confirm your platform's convention. Second, if you set both day of month and day of week to specific values, many cron implementations run when either matches, not both. That surprises people who expect "the 1st and only if it is a Monday". When in doubt, keep one of those two fields a wildcard.
How do you choose the right cadence?
Pick the longest interval that still delivers the value on time. If a report is read once a week, running it daily wastes runs and clutters inboxes; if data goes stale within an hour, a daily refresh is too slow. The right cadence matches how often the underlying data changes and how often a human actually acts on the output. Start conservative and tighten only if you have a reason.
In our experience building recurring reference agents, the most common mistake is scheduling too frequently out of caution, then drowning the recipient in near-identical outputs they stop reading. A digest that arrives every hour gets ignored; the same digest once each morning gets opened. Frequency is not a proxy for usefulness. Match the run rhythm to the decision rhythm, and where a run produces several steps, the design in how to build a multi-step agent workflow keeps each scheduled run coherent.
Why are time zones the hardest part?
Cron runs in whatever time zone the scheduler is set to, and that default is frequently UTC, not your local time. So an expression you read as "9am" may fire at 9am UTC, which could be the middle of the night for your users. The fix is to set the time zone explicitly for the schedule, rather than trusting the host's default, so the wall-clock time means what you think it means.
Daylight saving time is the second trap, and it is sharper than it looks. When clocks spring forward, a job scheduled in the skipped hour may not run at all that day; when clocks fall back, a job in the repeated hour can run twice. A run pinned to 2:30am is exactly the kind that gets silently skipped or doubled. Keep time-sensitive jobs out of the DST transition window, set an explicit zone, and test around the spring and autumn changes rather than assuming they will behave.
A simple rule that avoids most pain
Store and reason about schedules in UTC, then convert to the user's zone only for display. If a run truly must hit a local wall-clock time, set the schedule's zone to that location and accept that DST will shift the UTC moment twice a year. The mistake to avoid is mixing the two mental models, where half your reasoning is in UTC and half in local time. Pick one as the source of truth.
How do you prevent overlapping runs?
Overlap happens when a run takes longer than the gap to the next trigger, so the scheduler starts a second run while the first is still working. Two copies of the same agent then race over the same data, which can double outputs or corrupt shared state. The fix is a lock: before a run starts, it checks whether another run is active, and if so it skips or waits instead of piling on.
The skip-if-running pattern is usually the safest default for agents. When the trigger fires and a previous run is still going, the new run exits quietly and lets the next scheduled slot try again. That is better than queueing runs that stack up faster than they drain, which only deepens the backlog. The contrarian point worth stating plainly: more frequent scheduling does not mean more throughput if your runs cannot finish inside the interval, it just guarantees overlap. Size your interval to comfortably exceed your slowest expected run, and the lock becomes a rarely-used safety net rather than a constant crutch.
Why must a re-run be safe to repeat?
Idempotency means running the same job twice produces the same result as running it once, with no duplicate side effects. This matters because schedulers occasionally fire twice, and because you will sometimes re-run a missed job by hand. If a run is not idempotent, that second execution sends a second email, posts a second message, or charges a second time, turning a harmless retry into a visible mistake.
The practical way to get there is to make each run check what it already did before acting. Tag outputs with the run's date or a unique key, and before sending or writing, confirm that key has not already been handled. A digest for 9 June should detect that 9 June was already sent and stop. Designing runs to be safe to repeat is what lets you retry freely after a failure, which is the foundation of every recovery story below, and it pairs with the visibility practices in how to monitor agent activity.
How do you catch missed or failed jobs?
A scheduled job has a failure mode plain error handling misses entirely: it never runs at all, and silence raises no alarm. A crashed run throws an error you can catch; a run that never fired produces nothing, no log, no exception, no signal. So monitoring scheduled agents means watching for absence, not just for errors. You need to know when an expected run did not happen.
The standard technique is a heartbeat or dead-man's-switch: each successful run reports in, and a separate watcher alerts you if no check-in arrives within the expected window. A 9am daily job that has not reported by 9:15 triggers an alert. This flips the logic from "tell me when something breaks" to "tell me when nothing happened", which is exactly the gap that catches teams off guard. Pair that with alerts on actual run failures so you cover both the run that errored and the run that vanished, and route both into the activity view described in how to monitor agent activity.
Log enough to debug after the fact
When a scheduled run does fail, you will not be watching it live, so the logs are all you have. Record the start time, the inputs, the outcome, and any error for every run. A schedule that updates over time also needs a clear record of what changed and when, which is the change-management discipline covered in AI agent update cycles. Good logs turn a 2am failure into a five-minute morning fix instead of a mystery.
When should you skip cron and use events?
Cron is right when work is tied to the clock; it is wrong when work is tied to something happening. If an agent should react the instant a form is submitted, a file lands, or a payment clears, polling on a schedule is both slower and wasteful, because most runs find nothing to do. An event trigger starts the agent at the moment of the event, with no clock involved and no empty runs.
In practice many agents use both. Cron handles the steady cadence, the morning digest and the weekly report, while events handle the react-now work, the new signup or the inbound webhook. The decision is simple: ask whether the trigger is "at this time" or "when this happens". Clock answers go to cron; happening answers go to events. Mixing them deliberately, rather than forcing everything onto a schedule, gives you both timely reactions and reliable routines.
Frequently asked questions
What does the cron expression 0 9 * * 1 mean?
It runs the job at 9:00 in the morning every Monday. The five fields read minute, hour, day of month, month, and day of week. Here minute is 0, hour is 9, the day of month and month are wildcards, and day of week is 1, which is Monday on most cron systems.
How do I stop two scheduled agent runs from overlapping?
Use a lock so a new run skips or waits if the previous one is still going. Before starting, the agent checks for an active run; if one exists, it exits quietly. This protects you when a job runs longer than the gap between scheduled triggers, which is the most common cause of overlap.
What time zone does cron use for agent schedules?
Cron uses whatever time zone the scheduler is configured for, which is often UTC by default. Always set the time zone explicitly so 9am means 9am where your users are. Daylight saving shifts can move or skip a run, so confirm the zone and test around clock changes.
Why does idempotency matter for scheduled agents?
Schedulers sometimes fire twice or you re-run a missed job by hand. If a run is idempotent, doing it again produces the same result without duplicate emails, double charges, or repeated posts. Designing each run to be safe to repeat removes the fear of retrying and makes recovery from failures simple.
When should I use event triggers instead of cron?
Use cron for work tied to the clock, like a 9am digest or a Friday report. Use event triggers when work should start the moment something happens, such as a new form submission or a file upload. Many agents use both: cron for routine cadence, events for react-now tasks.
Three takeaways before you close this tab
- Read the five fields. Minute, hour, day of month, month, day of week, with wildcards for the parts you do not care about.
- Pin the zone and guard the run. Set the time zone explicitly, lock against overlap, and make every run safe to repeat.
- Monitor for silence. Alert on the run that never fired, not just the run that errored.
On Gravity, recurring agents handle the cron, locking, and monitoring for you; you describe the outcome and the cadence, and the run shows up on time. The thinking above is what makes that reliable underneath.