<!-- SPDX-License-Identifier: Apache-2.0 -->

# Scheduling

`config/cron.toml` and `config/heartbeat.toml` let an agent run turns on a schedule.

## Cron jobs

`config/cron.toml` defines a `[[jobs]]` array.

```toml
[[jobs]]
id = "daily-report"
prompt = "Write a status report and send it."
delivery = "announce"
execution = "isolated"
enabled = true
target = "last"

[jobs.schedule]
every = 86400
```

### Job fields

| Field        | Default      | Description                                                              |
| ------------ | ------------ | ------------------------------------------------------------------------ |
| `id`         | —            | Stable job identifier (required).                                        |
| `prompt`     | —            | Job prompt (required).                                                   |
| `delivery`   | `"announce"` | `announce`, `webhook`, or `none`; `webhook` requires isolated execution. |
| `execution`  | `"isolated"` | `isolated` or `main`.                                                    |
| `enabled`    | `true`       | Whether the job runs.                                                    |
| `target`     | `"last"`     | Target session for announce delivery.                                    |
| `provider`   | —            | Provider override.                                                       |
| `model`      | —            | Model override.                                                          |
| `webhookUrl` | —            | Absolute webhook URL; required for `webhook` delivery.                   |
| `schedule`   | —            | Exactly one of `every`, `cron`, or `at` (required).                      |

### Schedules

- `every` — interval in seconds, minimum 60.
- `cron` — a five-, six-, or seven-field cron expression.
- `at` — an ISO-8601 one-shot timestamp.

Five fields are `minute hour day-of-month month day-of-week`, with seconds fixed to zero. Six fields add leading `second`; seven fields also add trailing `year`. An omitted year matches every year. Fields support `*`, comma lists, ranges (`a-b`), and steps (`/`). Expressions are parsed during `dry-run` and startup configuration loading.

### Delivery and execution

`isolated` execution runs the prompt in a fresh internal session and delivers the captured response. `main` execution runs the prompt in the target session; `announce` sends responses there and `none` suppresses response delivery.

`announce` delivers output to the resolved target. `webhook` is supported only with `execution = "isolated"`: it POSTs `{ agentSlug, content, jobId }` to `webhookUrl` and treats a 2xx response as success. `none` runs without delivering.

Configurations combining `execution = "main"` with `delivery = "webhook"` are rejected. To retain webhook delivery, change `execution` to `"isolated"`; to retain the main session's history, change `delivery` to `"announce"` or `"none"`.

The `schedule` tool creates one-shot isolated jobs dynamically for ordinary sessions. In [remote sessions](../operations/remote-sessions.md), it creates caller-attested main-session jobs against an owned remote target. Replacing an unstarted remote reminder with the same ID and caller fences the old revision; a consumed remote one-shot cannot execute again.

## Heartbeats

`config/heartbeat.toml` controls periodic self-checks.

```toml
enabled = true
interval = 1800
target = "last"

[visibility]
showOk = false
showAlerts = true
useIndicator = true
```

| Field                     | Default  | Description                                |
| ------------------------- | -------- | ------------------------------------------ |
| `enabled`                 | `false`  | Whether heartbeats run.                    |
| `interval`                | `1800`   | Interval in seconds.                       |
| `target`                  | `"last"` | Target session.                            |
| `provider`                | —        | Provider override.                         |
| `model`                   | —        | Model override.                            |
| `activeHours`             | —        | Optional `start`/`end`/`timezone` window.  |
| `visibility.showOk`       | `false`  | Show successful heartbeat output.          |
| `visibility.showAlerts`   | `true`   | Show alert output.                         |
| `visibility.useIndicator` | `true`   | Accepted setting; currently has no effect. |

A heartbeat reads `tasks/HEARTBEAT.md`; if the file is absent or empty, the heartbeat is skipped. Responses equal to `HEARTBEAT_OK` after trimming are delivered only when `showOk` is enabled; other responses are delivered only when `showAlerts` is enabled. `useIndicator` is retained in configuration, but the runner does not currently emit channel indicators.

## Reload

Changes to `cron.toml` and `heartbeat.toml` are applied live while the runtime is running.
