buildd
Features

Task Schedules

Create recurring tasks that run on a schedule using cron expressions

Task Schedules

Create recurring tasks that run on a schedule using cron expressions.

Overview

Task schedules automate recurring work without manually creating tasks. Common uses:

  • Daily reports — generate metrics every morning
  • Periodic maintenance — run cleanup weekly
  • Monitoring checks — test an endpoint hourly
  • Data syncs — pull external data on a schedule

The tick that evaluates schedules runs hourly, on the hour. A schedule finer than hourly does not fire more often — it fires at most once an hour, with no error anywhere to tell you. Hourly is the floor for any cadence you write.

Creating a Schedule

Via Dashboard

  1. Navigate to your workspace
  2. Click New Task
  3. Choose Recurring in the segmented control at the top
  4. Enter a schedule name, a cron expression, a timezone (defaults to UTC), and the task template the schedule will stamp out
  5. Click Create Schedule

Via API

Creating, editing or deleting a schedule requires an admin-level API key. A worker-level key gets 401.

curl -X POST https://buildd.dev/api/workspaces/{workspace-id}/schedules \
  -H "Authorization: Bearer bld_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Daily metrics report",
    "cronExpression": "0 9 * * *",
    "timezone": "America/Los_Angeles",
    "taskTemplate": {
      "title": "Generate metrics report",
      "description": "Pull metrics and post a summary",
      "priority": 5
    }
  }'

Validate an expression before saving it with GET /api/workspaces/{workspace-id}/schedules/validate?cron=0+9+*+*+*&timezone=UTC, which returns { valid, description, nextRuns }.

Cron Expression Syntax

minute hour day month weekday

FieldValuesSpecial
Minute0-59* (every), */5 (every 5)
Hour0-23* (every), */2 (every 2)
Day1-31* (every), 1,15 (1st and 15th)
Month1-12* (every), 1-6 (Jan-Jun)
Weekday0-7* (every), 1-5 (Mon-Fri), 0=7=Sun

Expressions are parsed by croner, which also accepts a six-field form with leading seconds and aliases like @daily. Those parse, but the hourly tick still bounds how often anything fires.

Common Examples

# Every hour at :00 — the finest cadence that behaves as written
0 * * * *

# Every day at 9am in the schedule's timezone
0 9 * * *

# Every Monday at 8am
0 8 * * 1

# Every weekday (Mon-Fri) at 6pm
0 18 * * 1-5

# First day of every month at midnight
0 0 1 * *

Why a Schedule Might Not Fire

A due schedule can be skipped without failing. These are the reasons, and each one writes lastDeferralReason on the schedule row so you can tell which applied.

GuardDefaultBehaviour
maxConcurrentFromSchedule1Counts tasks still active from this schedule and skips the run if the cap is reached. A daily schedule whose task takes longer than a day will silently skip runs.
pauseAfterFailures5After this many consecutive failures the schedule sets itself enabled: false. It will not resume on its own.
Seat limit—If the owning account is at its concurrent-session or worker limit, the run is deferred and a schedule:deferred event fires with reason: 'seats_full'.
Mission cost budget—If the schedule's mission has spent its costBudgetUsd, the run is deferred with budget_exhausted until the budget is raised.
Mission not active—If the linked mission is anything other than active, the schedule is disabled, not deferred.
orchestrationMode: 'manual'—The schedule stays dormant until the mission is armed.

One-Off Schedules

Set oneShot: true and the schedule fires once, then sets itself enabled: false with no next run. The dashboard exposes this as a date and time picker rather than a cron field.

Cadence Selects the Model

A schedule's cron interval infers the task's kind and complexity, and those feed the claim-time model router — so cadence indirectly chooses which model runs the work. A frequent schedule is classified as cheap observation work.

Set kind and complexity explicitly on the task template to override the inference; an explicit value always wins over the cadence-derived one.

Triggers

A schedule can be gated on an external value changing instead of firing every tick. Set taskTemplate.trigger:

{
  "trigger": {
    "type": "rss",
    "url": "https://example.com/feed.xml"
  }
}

type is rss or http-json; http-json also takes a path to select a value out of the response, and both accept headers. On each tick the value is fetched and compared to the last one seen — a task is created only when it changed, and an unchanged value is recorded as a check without a run.

Inside the task template, {{triggerValue}} is interpolated into the title and description, and the task's context carries triggerValue, previousTriggerValue and triggerMetadata.

Triggers are configured through the API or MCP only; the dashboard schedule form has no trigger fields.

Managing Schedules

EndpointPurpose
GET /api/workspaces/{id}/schedulesList
GET/PATCH/DELETE /api/workspaces/{id}/schedules/{scheduleId}Read, edit, remove
GET /api/workspaces/{id}/schedules/validate?cron=&timezone=Validate an expression
POST/PATCH/DELETE .../schedules/{scheduleId}/suggestionPropose a change for approval

The suggestion endpoints let an agent propose a cron or enabled change into pendingSuggestion without applying it. Any principal with workspace access can propose; only a signed-in user or an admin key can approve.

For self-hosted cron trigger setup, see the deployment guide.

On this page