# Schedules

> Scheduled prompts and scripts: cron expressions, intervals and one-off times, at most one run per slot, script schedules that skip the model, and where results go.


Schedules run an agent on a timer. They need `habakkuk serve` running: it polls the schedules every 15 seconds.

## Creating a schedule

Ask in a chat ("every weekday at 8, summarise my inbox"), and the agent creates the schedule with the `schedule_create` tool. Results go to the chat the schedule was created from.

Or manage them from the terminal:

```bash
habakkuk schedules                                  # list
habakkuk schedules add -agent habakkuk -name standup -when "0 9 * * 1-5" -prompt "Post today's board" -to telegram:123456789
habakkuk schedules pause|resume|rm|run ID
```

`-when` takes:

| Form | Example |
|---|---|
| A cron expression | `"0 9 * * 1-5"` (9:00 on weekdays) |
| An interval | `"@every 30m"` |
| One time | `"at 2026-12-24 18:00"`, or with a zone: `"at 2026-12-24T23:00:00Z"` |

Times are in the config's `timezone` (default the server's). `schedules add` prints the first run with its zone, and the UTC time when that zone is another one:

```
sch_4f1c scheduled; first run Mon 2026-11-09 17:57 America/Toronto (22:57 UTC)
The time was read in America/Toronto, the config timezone. For another zone, give an offset: at 2026-11-09T17:57:00Z is UTC.
```

`-to` takes a `CHANNEL:CHAT[:THREAD]` target, as `habakkuk send` does. `habakkuk schedules run ID` queues an extra run now.

`habakkuk schedules` shows each schedule's last result: `started` while a run is going, then `done`, `failed: <reason>`, or a script's own status. A one-off that has run shows as `ran once`.

## How runs work

- **At most once per slot.** For each due slot the scheduler enqueues a job whose ID is the schedule plus the slot time, so a slot runs at most once even with several processes polling or a crash in between.
- **No late runs.** A slot more than an hour late (Habakkuk was down) is skipped, not run late.
- **Fresh sessions.** Each run is a fresh session. What it sends to a chat is still shown to the agent that answers that chat on your next message there, so you can ask about it. See [Sessions](https://www.habakkuk.ai/docs/agents/sessions.md#messages-from-elsewhere-in-the-same-chat).
- **Unattended.** Anything that needs approval is denied unless the agent has an approval reviewer (`approvals: reviewer` or `reviewer_then_human`; see [Permissions and approvals](https://www.habakkuk.ai/docs/agents/approvals.md#approvals)).
- **Budgets.** A scheduled task whose agent is out of budget is skipped without starting, and its chat gets one notice per limit and period. See [Spending limits](https://www.habakkuk.ai/docs/models/usage-and-limits.md#spending-limits).

## Script schedules

Script schedules run a command instead of a prompt. Use them for frequent jobs (sync a ledger, check an inbox, send reminders) where a model call per run would be slow and costly.

```bash
habakkuk schedules add -agent habakkuk -name inbox -when "@every 10m" \
  -run ./check-inbox.sh -on-output deliver -timeout 2m -to telegram:123456789
```

- **Who can add one.** Only the owner, through the CLI. `schedule_create` has no command field and refuses one, so an agent cannot turn scheduling into unattended shell access.
- **Running.** The command runs with `/bin/sh -c` (PowerShell on Windows) in the workdir (`-workdir`, default the agent's workspace). It gets the environment plus `$HABAKKUK_HOME/.env` and `HABAKKUK_AGENT`, `HABAKKUK_SCHEDULE_ID` and `HABAKKUK_SCHEDULE_NAME`. A timeout (default 120s) kills everything it started.
- **Output.** Empty stdout, or a line that is exactly `[SILENT]`, means nothing to say. Otherwise `-on-output` decides:

| `-on-output` | What happens |
|---|---|
| `deliver` | stdout is sent to the chat |
| `agent` | a turn starts with the schedule's prompt and the output appended |
| `silent` | nothing is sent |

- **Failures.** A non-zero exit or a timeout is recorded and not retried. The third failure in a row sends one notice to the chat, and the first success after it says the script works again.
- **No overlap.** A slot that comes due while the previous run is still going is skipped.

> [!NOTE]
> Script schedules cannot yet be declared in `config.yaml`, and there is no command to edit one in place: remove it and add it again.

[Architecture: schedules](https://www.habakkuk.ai/docs/reference/architecture.md#schedules) has the details.

---

Source: https://www.habakkuk.ai/docs/agents/schedules
