# Routine schedules Ask a bot in ordinary language, for example: > Prepare a report at 9 am on the first of every month, in Asia/Kolkata. The bot turns calendar timing into a cron expression, and OpenMausBot validates it. The change applies at once; its card shows the rule, its timezone and the next three dates, with Edit, Pause and Undo. The scheduler wakes the bot at the matching time; no model runs in the background to check the date. A routine can also name **checks** (calendar, mail, other routines' runs): plain code that runs on each tick before any AI turn, so a quiet tick costs nothing. See [Proactive routines](proactive-routines.md). In **Automations → Schedule**, choose **Monthly**, **Yearly**, or **Custom cron (advanced)** in the existing routine editor. Monthly offers days 1–31 and **Last day**. The timezone defaults to your browser's zone for a new preset and can be changed. Saved cron routines retain their own zone even on another computer. Editing just a title or instructions preserves the exact schedule. ## Off, on, and done The switch on **Automations** pauses a schedule or a webhook and turns it back on; a schedule comes back with its next time. If the server refuses, the reason shows beside the switch. A one-time schedule whose time has passed reads **Done** and has no switch: turning it on would have nothing to do, so the server refuses it ("Its time has passed. Edit it to pick a new time."). Saving it with a new time turns it back on. Deleting a bot deletes its schedules and webhooks too, and their runs. A webhook's address stops working at once. Automations left behind by a bot deleted in an earlier version are removed the next time OpenMausBot starts, and never run meanwhile. ## Advanced cron Cron uses five fields: **minute hour day-of-month month day-of-week**. An explicit IANA timezone such as `America/New_York`, `Asia/Kolkata`, or `UTC` is required. Examples: | Timing | Expression | | --- | --- | | First of each month at 09:00 | `0 9 1 * *` | | Last day of each month at 09:00 | `0 9 L * *` | | Second Monday of each month at 09:00 | `0 9 * * MON#2` | | January 1 at 09:00 each year | `0 9 1 1 *` | | Weekdays at 09:00 and 17:00 | `0 9,17 * * MON-FRI` | | Every 15 minutes during weekday working hours | `*/15 9-16 * * MON-FRI` | The app uses [Croner](https://github.com/Hexagon/croner) to calculate dates, shared by the server and preview. It does not create a second timer service. Lists, ranges, steps, `L`, `W`, and `#` follow that library's calendar semantics. As with standard cron, restricting both day-of-month and day-of-week means **either** field may match; leave one as `*` when you intend just the other. Seconds, year fields, macros such as `@reboot`, shell commands, and arbitrary code are not accepted. The finest resolution is one minute. For elapsed-time repetition such as **every 90 minutes**, use the existing interval schedule. Cron fields describe calendar positions: `*/90` in the minutes field does not mean every 90 elapsed minutes. Holidays, external events, and conditions such as "after a payment arrives" are not calendar expressions; use an appropriate event/webhook workflow instead of a fake weekly schedule. ## Dates, clock changes and downtime - Dates that do not exist are skipped: the 31st skips shorter months and February 29 runs in leap years. **Last day** handles every month. - Daylight-saving gaps move the chosen wall-clock time forward through the gap. During a repeated hour, a matching clock time runs once, at its first occurrence. The preview uses the same calculation as execution. - Routine execution defaults to **Bot’s current setup**: its selected model and configured computer, including a self-hosted VPS. No Boat key is needed for that VPS. **Boat cloud computer** is a separate, explicit choice: the bot's own model works on its Boat. The agent tools call these `run_on: "maus"` and `run_on: "box"`; legacy stored `runOn: "cloud"` still means Boat and is not migrated to a different runner. - OpenMausBot must be running to dispatch routines, including cloud-targeted routines. There is no external always-on scheduling service: the schedule runs inside the app on your computer — it is not Grok's or anyone's cloud — so a sleeping computer or a quit app runs nothing, and the app cannot wake a sleeping Mac. The desktop app does the one thing it can: while plugged in, it keeps the computer from idle-sleeping for the hour before a due routine and while one runs (Automations → *Keep this computer awake for routines*, on by default; a closed lid still sleeps). For true 24/7, run OpenMausBot on a VPS — see [deploy-vps.md](deploy-vps.md). - Existing catch-up policy remains: up to 12 hours late, one missed occurrence can be dispatched; older work receives a missed-run receipt. The next date advances without replaying every missed minute. Queued/running/waiting work for the same cron routine does not accumulate overlapping copies (a run is waiting while teammates still owe it results on the job runtime, docs/runtime-contract.md §9). - A run that asks you for an approval or an answer waits for you. Its card and the Routines log say what it waits for ("Waiting for your approval to use COMPOSIO_SEARCH_TOOLS"), the approval notification names the routine, and the time it waits does not count toward its optional run limit, which bounds active work only. If the engine's own wait for an approval runs out with nobody's answer, the run stops with "Stopped: nobody answered the approval to use …" and the routine-failed notification. A run still waiting when its routine's next occurrence is due gives way to that occurrence. - An unattended run (scheduled, webhook or Run now) recovers from a passing failure by itself, the same on the desktop, a headless server and a Cloud home (`server/routine-recovery.ts`): - Its engine stalled, OpenMausBot restarted while it ran, the provider rate-limited it, or the provider failed for a moment: it starts once more in the same conversation after a short wait (15 s; a minute after a rate limit), told to check what its first start already did. A second failure fails it: "Failed after one automatic retry: …". - A run that a shutdown or crash cut off is picked up once when OpenMausBot starts again; the same run, never a copy. - Its bot or conversation was busy when it started: it waits there, checking every 30 s, for up to an hour. - A usage limit with another AI that can carry on, or a signed-out AI with a backup AI of the bot's own (or the app-wide one while Automatic recovery is on): the run starts again on that AI after a short wait (see "When an engine runs out" and "When an engine is signed out" in self-hosting.md), and the owner hears once that the bot is using it. - Signed out, out of credit, payment required, a usage limit nothing can carry on from, or an engine that needs setting up: no retry. The owner gets one notification per bot and cause saying what to do, and no more until one of that bot's runs completes. A usage limit that says when it resets (Claude Code does) puts the run back for just after the reset, if that is within 12 hours. - The log says what happened ("Retried after the engine stalled · Completed"), and each routine shows "N of M runs OK this week". - Existing one-time, daily/weekday and interval schedules are not migrated or reinterpreted. Team exports retain cron expressions and zones; imports remain paused until enabled. Older phone apps safely treat cron as a newer schedule; use chat or the desktop/web editor for it, rather than converting it to daily. Cron series are edited through the routine editor rather than calendar dragging, which could otherwise ambiguously change an entire expression. Dense calendar projections are bounded per day; Run logs remain the source of actual outcomes.