# Schedules



A **schedule** fires an agent on a recurring cron schedule, seeding each run with
a kickoff message. Use it for recurring work — a daily report, a nightly sweep, a
weekday digest.

## Create a schedule [#create-a-schedule]

<Tabs items="['Console', 'API']">
  <Tab value="Console">
    Open an agent's **Schedules** and add one: pick the cron expression, the
    timezone, and the kickoff message each run should start with. Schedules can be
    enabled and disabled without deleting them.
  </Tab>

  <Tab value="API">
    `POST /v1/schedules`. `cron` and `timezone` are always required. Then either
    name an `agent` and a `kickoff`, or name a `deployment` that carries both.

    ```bash
    curl https://agents.clusterbase.dev/v1/schedules \
      -H "Authorization: Bearer $CLUSTER_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{
        "agent": "agt_3f9c2a",
        "cron": "0 9 * * 1-5",
        "timezone": "America/New_York",
        "kickoff": [{ "type": "text", "text": "Publish today'\''s engineering digest." }]
      }'
    ```

    ```json
    {
      "id": "sched_a1b2c3",
      "agent_id": "agt_3f9c2a",
      "cron": "0 9 * * 1-5",
      "tz": "America/New_York",
      "kickoff": [{ "type": "text", "text": "Publish today's engineering digest." }],
      "enabled": true,
      "next_fire_at": "2026-06-17T13:00:00Z",
      "last_fired_at": null
    }
    ```
  </Tab>
</Tabs>

| Field        | Notes                                                                                                           |
| ------------ | --------------------------------------------------------------------------------------------------------------- |
| `agent`      | The agent id to run. Must be one you own. Omit when `deployment` is set.                                        |
| `cron`       | A standard 5-field cron expression, e.g. `0 9 * * 1-5` (09:00, Mon–Fri).                                        |
| `timezone`   | An IANA timezone, e.g. `America/New_York`. Cron is evaluated in this zone.                                      |
| `kickoff`    | Text content blocks seeded as the `user.message` for each fired run. Omit when `deployment` is set.             |
| `deployment` | Optional. A [deployment](/docs/agents/api-reference#deployments) id to run instead of a bare agent — see below. |

The response reports `next_fire_at` (the next UTC instant the schedule will fire)
and `last_fired_at`.

### Run through a deployment [#run-through-a-deployment]

Set `deployment` instead of `agent` + `kickoff` and the schedule becomes just a
clock: the deployment supplies the agent, the kickoff, the VM environment, and
the credential vaults for every fired run. This is how a recurring run gets a VM
and vault credentials. Supplying `agent` or `kickoff` alongside `deployment` is
ambiguous and returns `400`. A deployment on a saved (`existing`) environment is
refused at fire time — there's no caller around to provision against, so
scheduled runs use fresh, ephemeral VMs.

## How firing works [#how-firing-works]

A background dispatcher polls for due schedules and fires each as a new session
on the schedule's agent, appending the kickoff message to start the turn. Firing
is **at-most-once**: the next fire time advances and commits before the run
starts, so a crash mid-fire skips that occurrence rather than running it twice.

If a schedule's agent has been archived, the dispatcher disables the schedule
(`enabled` flips to `false`) instead of firing it — archiving an agent stops it
from starting new sessions, so a bound schedule can no longer run.

## Manage a schedule [#manage-a-schedule]

Enable or disable, or change the cron/timezone/kickoff, with `PATCH`:

```bash
curl -X PATCH https://agents.clusterbase.dev/v1/schedules/sched_a1b2c3 \
  -H "Authorization: Bearer $CLUSTER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "enabled": false }'
```

Delete it with `DELETE /v1/schedules/sched_a1b2c3`.

To watch what a fired run did, open its session and read or stream its
[events](/docs/agents/sessions-and-events).
