# Schedules and cron

Schedules queue **durable** work. The cron scheduler enqueues due schedules into `heartbeat_tasks`; a worker owns execution, leases, retries, and approval pauses. Both run in-process by default when you start the app — no separate daemon needed.

## Schedule formats

Schedules accept human strings, parsed into next-run timestamps:

- `daily 07:30`
- `every 5m`, `every 2h`
- `weekly`, `monthly`

Set them in **/schedules** (upsert, enable, pause, or process work manually).

## Backfill when overdue

If the app was off and a schedule missed several windows, the cron backfills up to a cap. The `catchup_policy` per schedule controls how:

| Policy | Behavior | Default for |
|--------|----------|-------------|
| `fire_all` | Queue every missed window | `daily`, `weekly`, `monthly` |
| `fire_once` | Queue only the most recent missed window | `every Nm/Nh` |
| `skip` | Advance silently, queue nothing | explicit opt-in |

An audit row `cron.schedule.backfilled` is written whenever more than one window was missed.

## Intervals

- Cron check: `CRON_DAEMON_INTERVAL_MS` (default 30s)
- Heartbeat worker: `HEARTBEAT_WORKER_INTERVAL_MS` (default 5s)
- Disable in-process daemons: `MADARX_DISABLE_INPROC_DAEMON=1`

## Related

- [Create an agent](/help/create-agent)
