Scheduling
Run an agent on a cron expression. Schedules are their own resource, so one agent can have many, each with its own input and timezone. Manage them at /v1/schedules. Scheduled runs appear in the dashboard like any other run.
Cron expressions
Section titled “Cron expressions”Five fields: minute, hour, day of month, month, day of week. Each field accepts *, lists, ranges and */N steps:
# ┌─── minute (0-59)# │ ┌─── hour (0-23)# │ │ ┌─── day of month (1-31)# │ │ │ ┌─── month (1-12)# │ │ │ │ ┌─── day of week (0-7, Sun=0 or 7)# │ │ │ │ │# * * * * *
0 9 * * 1-5 # every weekday at 9:00 AM (in the schedule timezone)*/15 * * * * # every 15 minutes0 0 1 * * # first day of every month at midnightSchedule API
Section titled “Schedule API”Create a schedule
Section titled “Create a schedule”POST /v1/schedulesContent-Type: application/json
{ "agentName": "research-agent", "cronExpr": "0 9 * * 1-5", "timezone": "America/New_York", "inputTemplate": { "mode": "daily-digest" }, "deliveryEmail": "me@example.com", // optional: email the result "enabled": true}
Response: 201 Created{ "id": "sched-uuid", "agentName": "research-agent", "cronExpr": "0 9 * * 1-5", "inputTemplate": { "mode": "daily-digest" }, "deliveryEmail": "me@example.com", "enabled": true, "nextFireAt": "2026-07-28T13:00:00Z"}List schedules
Section titled “List schedules”GET /v1/schedules
Response: 200 OK — bare array[ { "id": "sched-uuid", "agentName": "...", "cronExpr": "...", "enabled": true, ... }]Update a schedule
Section titled “Update a schedule”PUT /v1/schedules/{id}Content-Type: application/json
{ "enabled": false } // pause. Also accepts cronExpr, deliveryEmail, timezoneDelete a schedule
Section titled “Delete a schedule”DELETE /v1/schedules/{id}
Response: 204 No ContentTimezone handling
Section titled “Timezone handling”timezone is an optional IANA name (an invalid one returns 400). Cron then matches the schedule’s local time. Resolution order: the schedule’s zone, then LANTERN_DEFAULT_TIMEZONE, then UTC. With neither set, 0 9 * * * fires at 09:00 UTC.
The deployment default (not a per-schedule zone) also sets the budget day boundary: daily limits roll at local midnight in that zone.
Example: weekday digest to Slack
Section titled “Example: weekday digest to Slack”examples/daily-digest is a workflow (GitHub open PRs, an AI step, a Slack post) on the schedule 0 9 * * 1-5 in America/New_York. Run lantern deploy in that directory (the manifest carries the prompt and workflow), install the Slack and GitHub connectors, then post schedule.json to POST /v1/schedules. The response’s nextFireAt is the next weekday 09:00 New York time.
The scheduler polls every 60 seconds, so a due schedule fires within a minute. To test without waiting for 09:00, set the row due (UPDATE schedules SET next_fire_at = now() WHERE id = ...) and watch GET /v1/runs?agent=.... The e2e suite does exactly this against fixtures (see Testing). An agent over its hard-fail budget does not fire at all, so an unattended schedule cannot run past its cap.
Surfaces trigger runs too
Section titled “Surfaces trigger runs too”Inbound messages on a channel start runs. They are not schedules but follow the same run lifecycle. See Surfaces. Signed webhooks start runs as well; see Webhook triggers.