> ## Documentation Index
> Fetch the complete documentation index at: https://docs.blobhub.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Schedules

> Run a workflow once at a set time or on a cron cadence, and keep a history of every fire.

export const Scope = ({type, href}) => {
  const scopes = {
    workflow: ['the ', 'Workflow', ' blob type', '/blob-types/introduction#workflow'],
    scheduler: ['the ', 'Scheduler', ' blob type', '/blob-types/scheduler/overview'],
    onnx: ['the ', 'ONNX Model', ' blob type', '/blob-types/introduction#onnx'],
    worker: ['', 'blobhub-worker', ', which runs on your machines', '/worker/introduction']
  };
  const scope = scopes[type];
  if (!scope) {
    return <p>Unknown scope: {type}</p>;
  }
  return <p className="text-sm text-gray-600 dark:text-gray-400">
      Provided by {scope[0]}<strong>{scope[1]}</strong>{scope[2]}. · <a href={href || scope[3]}>Reference</a>
    </p>;
};

export const Shot = ({src, alt, caption}) => <Frame caption={caption}>
    <img className="block dark:hidden" src={src} alt={alt} />
    <img className="hidden dark:block" src={src.replace(/\.(png|svg)$/, '.dark.$1')} alt={alt} />
  </Frame>;

<Scope type="scheduler" />

A schedule starts a workflow run without anyone asking for it: once, at a moment you set, or again and again on a
cron cadence. Each fire creates an ordinary execution in a session you name, the same kind of run you start from the
debugger or the API, and records whether starting it succeeded.

Schedules are kept in a scheduler blob, which holds a set of them in its one revision. That revision is `managed`:
writable for as long as the blob exists, never committed and never branched, so you edit the set in place and no
earlier version of it is kept. The [scheduler blob type](/blob-types/scheduler/overview) reference has every field.

## One-time and cron

Every schedule fires on one of two cadences, read in the IANA timezone it must name, such as `America/Denver` or
`UTC`:

* **`one_time`** fires once, at the moment you set.
* **`recurring_cron`** fires on a six-field cron expression, such as `0 9 ? * MON-FRI *` for 09:00 on weekdays.

An optional start date and end date bound either one. A schedule is `active` while it can still fire and `completed`
once it can fire no more: a one-time schedule after its fire, a recurring one once its end date has passed. A
completed schedule can be read and deleted, not changed. Short of deleting it, a schedule can also be disabled, which
stops its fires until it is enabled again.

A schedule may carry an alias, unique among its scheduler's schedules, that works anywhere its id does, and a
description of up to 256 characters. The description belongs to the schedule, and every fire hands it to the
execution it starts as that run's own description.

In the web app, a scheduler's page lists its schedules with their cadence, timezone, state, target and workflow, and
**New Schedule** opens the form that creates one; over the API it is
[Create Schedule](/blob-types/scheduler/operations/create-schedule). Two limits bound what a scheduler holds and how
often it fires: `scheduler.schedules_per_revision` caps how many schedules it holds, and
`scheduler.min_interval_seconds` sets the shortest gap allowed between two fires of a cron expression.
[Limits](/general/limits#scheduler) has their values.

<Shot src="/general/images/schedules/schedules.png" alt="A scheduler's schedule grid with recurring and one-time schedules" caption="Each schedule names what it runs, where, and when." />

## What a schedule runs, and where

A schedule names where it runs apart from what it runs, with the run's input beside them:

* **`target`** is where: a workflow blob, named by id or by alias with its organization, a revision of it, and a
  session in that revision.
* **`workflow`** is what: a [workflow reference](/general/workflows#workflow-references), the same object a request
  to start a run takes. It names a definition in the target's own revision, or one in any other workflow blob the
  schedule's creator can read.
* **`input_data`** is what the run receives.

How much of the target is fixed in advance is your choice. A **pinned** target names a revision by id, and its session
is resolved when the schedule is saved and kept as an id, so renaming the session afterwards changes nothing. A
**following** target names `default` in place of a revision and names its session by alias, and every fire resolves
the blob's default revision and that session afresh, so the schedule moves forward with the workflow instead of
running an old revision nobody edits any more. A draft is the only revision that runs, so a following target fails
while the default is committed: each fire in that time is recorded with that reason, and fires start runs again only
once the default is a draft with an open session of that alias. The workflow is resolved again at every fire as well,
so a definition named by alias runs whatever holds that alias at that moment.

Saving a target, when a schedule is created or its target is changed, checks what a fire will check, with the saver's
own standing: write access to the target blob, a revision that belongs to it and still accepts writes, a session in
that revision, and the workflow reference. Every failure gets the same refusal, so a refusal never says whether
something exists. A typo fails when you save rather than at the first fire.

A fire creates an ordinary execution in the named session, with the standing of whoever created the schedule and
carrying its description. From there it is a run like any other: it runs from its own copy of the definition, writes
into its session, and shows in the [debugger](/general/debugger). If that standing no longer reaches the target, the
fire is recorded as failed and no run starts.

## History

Each fire leaves a record in the schedule's history, apart from the execution it starts: which schedule fired, when,
the target as it was invoked, the description it sent, a status and a result. The status covers only the attempt to
start the run: whether the execution was created, refused by a limit, or failed, and for what reason. How the run
itself ended is on the execution, in the debugger. A fire whose target no longer resolves is recorded that way and
never falls back to anything else, and a fire a limit refused does not stop the schedule: a recurring one keeps
firing.

A record is kept for 30 days by default, under `scheduler.run_retention_days`, counted from its fire; a daily sweep
removes older ones, and [Retention](/general/limits#retention) has the rules. A schedule's page in the web app shows
its target field by field and lists its fire records under **Executions**, the API's name for them, and
[List Executions](/blob-types/scheduler/operations/list-executions) pages through them.

<Shot src="/general/images/schedules/history.png" alt="One schedule's properties and its history of fires" caption="Every fire is recorded, with the outcome of starting its run." />

## Schedules as code

The CLI's `scheduler` group keeps a scheduler's schedules in a manifest file in your repository. Each entry is a
schedule in the API's own fields, the ones the web form writes, keyed by its alias and naming blobs, sessions and
definitions by alias or by id. `deploy` checks every target, then creates the schedules that are missing and updates
the ones that differ, creating a target session that does not exist yet. `diff` reports what a deploy would change and
writes nothing, `pull` writes the deployed schedules back into the file, and `ls` lists them. A schedule the manifest
does not list is reported as drift and left alone, which means it keeps firing until it is deleted.

[Scheduler overview](/cli/scheduler/overview) walks through the group, and
[Scheduler manifest](/cli/scheduler/manifest) has the file's format.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.