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.
In plain terms"Every weekday at 9am, summarize my inbox," with nobody clicking a button. A cron expression is five fields: minute, hour, day of month, month, weekday. Scheduled runs appear in the dashboard like any other run.
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 minutes
0 0 1 * * # first day of every month at midnightDay-of-month and day-of-week are ANDed. Classic Vixie cron fires on either when both are set. Lantern requires both to match, so
0 9 1-7 * 1 fires on the first Monday of the month. If you port a crontab that relied on the OR behavior, rewrite it as two schedules. The search for the next fire time gives up after two years.Schedule API
Create a schedule
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"
}List schedules
GET /v1/schedules
Response: 200 OK — bare array
[
{ "id": "sched-uuid", "agentName": "...", "cronExpr": "...", "enabled": true, ... }
]Update a schedule
PUT /v1/schedules/{id}
Content-Type: application/json
{ "enabled": false } // pause. Also accepts cronExpr, deliveryEmail, timezoneDelete a schedule
DELETE /v1/schedules/{id}
Response: 204 No ContentTimezone 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.
Surfaces trigger runs too. Inbound messages on a channel start runs. They are not schedules but follow the same run lifecycle. See Surfaces.