Scheduling
A schedule starts an agent on a five-field cron expression, in an IANA timezone, with its own input template. One agent can have many schedules, and a scheduled run looks like any other run in the run list and journal. An agent that is over its hard-fail budget does not fire, so an unattended schedule cannot run past its cap.
1. Created with a zone
The request carries a timezone such as America/New_York next to the cron expression.
- API call
- Control plane
- Data stores
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 midnightCreate a schedule
Section titled “Create a schedule”curl -s -X POST http://localhost:8080/v1/schedules \ -H "Authorization: Bearer $PAL_API_KEY" -H "Content-Type: application/json" -d '{ "agentName": "research-agent", "cronExpr": "0 9 * * 1-5", "timezone": "America/New_York", "inputTemplate": { "mode": "daily-digest" }, "deliveryEmail": "me@example.com", "enabled": true}'The response is 201 Created:
{ "id": "sched-uuid", "tenantId": "...", "agentName": "research-agent", "cronExpr": "0 9 * * 1-5", "inputTemplate": { "mode": "daily-digest" }, "deliveryEmail": "me@example.com", "enabled": true, "nextFireAt": "2026-07-28T13:00:00Z"}deliveryEmail is optional. The create response does not echo timezone, but nextFireAt is already computed in that zone.
Manage schedules
Section titled “Manage schedules”| Action | Request | Response |
|---|---|---|
| List | GET /v1/schedules |
200, a bare array of schedules |
| Update | PUT /v1/schedules/{id} with e.g. { "enabled": false } |
200 with a JSON body that includes id and cronExpr |
| Delete | DELETE /v1/schedules/{id} |
204 No Content |
PUT accepts enabled, cronExpr, deliveryEmail and timezone only. To change inputTemplate, delete the schedule and create a new one.
Timezone
Section titled “Timezone”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 PAL_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, so 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
pal deployin that directory. The manifest carries the prompt and workflow. - Install the Slack and GitHub connectors (Connectors).
- Post
schedule.jsontoPOST /v1/schedules.
The response’s nextFireAt is the next weekday 09:00 New York time.
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 this against fixtures (Testing).
Limits
Section titled “Limits”- Polling: the scheduler polls every 60 seconds, so a due schedule fires within a minute.
- Replicas and downtime: with several control-plane replicas each due window fires at most once. If the process is down at fire time, that occurrence is skipped, not replayed.
- REST only. There is no
pal schedulecommand and no SDK namespace for cron schedules. deliveryEmailalso mails the run output. The fixture suite does not cover that path.
Inbound channel messages (Set up PAL) and webhook triggers start runs too, with the same lifecycle.