Skip to content

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.

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 minutes
0 0 1 * * # first day of every month at midnight
POST /v1/schedules
Content-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"
}
GET /v1/schedules
Response: 200 OK — bare array
[
{ "id": "sched-uuid", "agentName": "...", "cronExpr": "...", "enabled": true, ... }
]
PUT /v1/schedules/{id}
Content-Type: application/json
{ "enabled": false } // pause. Also accepts cronExpr, deliveryEmail, timezone
DELETE /v1/schedules/{id}
Response: 204 No Content

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.

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.

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.