# Schedulers

> A scheduler is a cron-driven prompt meant to run headless on your machines. You author it on the Console's Schedulers page (Building blocks → Schedulers) and it publishes with your config. The published openeng engine does not fire schedules yet: openeng headless checks the key and reports its drivers, but the scheduler loop isn't in the engine. As of September 25, 2026, OpenEng AI isn't live yet.

A **scheduler** is a cron-driven prompt meant to run headless on your machines —
author a schedule on the Console's **Schedulers** page (Building blocks →
Schedulers) and it publishes with your config, meant for a node running `openeng headless` to fire
with nobody at the keyboard once the engine's scheduler ships.

> [!WARNING] Status on September 25, 2026: OpenEng AI isn't live yet
> The Console and Web Console interfaces load, but the OpenEng AI API routes and the Broker aren't
> deployed. Nothing can be saved or published, and no AI engine can register or connect. Pro,
> Enterprise and pay-as-you-go launch in Q4 2026 and can't be purchased yet. This page describes
> OpenEng AI as it works once those services are live ([status](ai.md)).

> [!WARNING] Schedules don't fire yet
> The published `openeng` engine doesn't run scheduled jobs yet. `openeng headless` checks that an
> OpenEng key is present, prints which drivers it would run, and exits; the loop that fires
> published schedules isn't part of the engine yet. Once the OpenEng AI services are live
> ([status](ai.md)), you can author schedules and publish them with your config on **Pro** or
> Enterprise (Free includes no schedulers), but nothing runs them until that loop ships.

*Figure: Building blocks → Schedulers — the scheduler editor: identity, the cron builder, the target tabs and run settings, the inline “What the engine receives” preview, and the explicit Save in the header.*

## What it is, and when to use it

Use a scheduler for anything periodic: a nightly repo audit, a morning
digest, log triage at 6 am. The Console authors the definition; a machine of
yours runs the headless engine:

```bash
openeng headless                       # checks the key, then reports its drivers
openeng headless --driver schedulers   # only the scheduler driver (work-pull | schedulers | auto)
```

`openeng headless` needs an OpenEng key on the machine: `--key=<key>` or `OPENENG_KEY`, an API key
from the Console ([API keys & sandboxes](api-keys-sandboxes.md#api-keys)). Without one it refuses
to start. It runs two drivers by default (`auto`): cloud **work-pull** and **schedulers**; today it
prints that selection and exits. Jobs come straight from your published config; the Console never
runs a schedule itself.

## The editor, section by section

### Identity

- **Name** — labels runs in engine logs (the id is the fallback).
- **Enabled** — disabled schedulers stay authored but are skipped at publish;
  the engine never fires them.

### Schedule — the cron builder

The schedule is a **5-field cron expression**: `min hour dom mon dow`. The
builder gives you four ways to write one, all kept in sync:

- **Presets** — Hourly · Daily 06:00 · Weekdays 06:00 · Weekly (Mon) ·
  Monthly (1st).
- **Five field editors** — one input per field.
- **The raw expression** — type it directly.
- A **live plain-language preview** plus the **next 5 occurrences** rendered
  in your chosen preview timezone.

The dialect is deliberately strict, and the Console validates exactly what
the engine parses:

| Allowed | Not allowed |
|---|---|
| `*`, `*/n`, `a`, `a-b`, `a-b/n`, comma lists | `@daily` / `@hourly` shortcuts |
| Sunday = `0` in day-of-week | A seconds field (5 fields exactly) |
| | Names like `MON` or `JAN` |

> **Warning:** Day-of-month **AND** day-of-week must both match (unlike some
> cron dialects that OR them). `0 6 1 * 1` fires only when the 1st is a
> Monday.

### Timezone honesty

The timezone selector is **preview-only** — it affects the next-5 preview,
nothing else. Once it fires schedules, the engine will use the **node's local time**. If your
server is in UTC and you want 06:00 IST, author the UTC equivalent (00:30) —
the badge in the editor reminds you every time.

### Target — what runs

Two tabs; whichever you choose, the **prompt string is what the node
runs** (the engine has no reference fields on schedulers):

- **Prompt** — write the prompt directly.
- **Agent + prompt** — pick an [agent](agents.md); the agent reference is
  stored as a sibling field honestly labeled **not yet consumed by the
  engine** — the prompt body is what runs, so mention the goal for the agent in it.

### Run

- **Mode** — `goal` (autonomous multi-step run — the headless default),
  `chat` (single conversational turn), or `coding` (code-editing run).
- **Working directory** — optional; where the run starts, as a
  **node-local** path.

### What the engine receives

A read-only section at the foot of the editor shows the exact keys a node parses from this
schedule — `id`, `name`, `cron`, `prompt`, `mode` and `workdir` — marks `cron` and `prompt` as
required while they're missing, shows `disabled: true` for a disabled scheduler, and names any
sibling the engine ignores today (such as `agentId`). The preview timezone is stored too, but the
engine ignores it.

## Saving, and why the editor is strict

Like every console editor, the scheduler editor saves **explicitly**: the
header's **Save** button is the only path to the server (the old *Save now*
label is simply **Save**). The engine **silently drops** a scheduler without
both a valid cron expression and a non-empty prompt, so while either is
missing the document holds at *invalid* and Save is **disabled with the
reason** — what's saved is always runnable. Until you Save, edits live in
the browser's local draft mirror ("Restore unsaved draft?" on reopen). The
**What the engine receives** section shows the exact emitted document: only the keys the engine
parses, plus the honestly-labeled siblings. List rows carry trailing **Edit** /
**Delete** actions, delete behind a confirmation; the list also shows each schedule in plain
language and a usage meter against your plan's limit.

## Plan & tier notes

Schedulers are a **Pro** feature: Pro includes **1 scheduler**, Enterprise is
unlimited (an Enterprise account can have its own count set). The meter counts **enabled**
schedulers — disable extras instead of deleting them. Free shows the locked page; the server
enforces the limit at publish, and **New scheduler** is disabled at the limit with the reason. Pro
and Enterprise launch in Q4 2026 and can't be purchased yet ([Plans & billing](account.md)).
`openeng headless` itself needs an OpenEng key on the machine (`--key` or `OPENENG_KEY`).

## Troubleshooting

**The job didn't fire.** Expected today: the published engine doesn't fire scheduled jobs yet (see
the warning at the top). Once it does, check timezone first — the
engine will fire node-local, and the preview timezone is only a preview. Then check
the dom/dow AND rule above.

**`openeng headless` exits straight away.** Also expected today: it checks the key, prints the
drivers it selected (`driver=…`, with work-pull and schedulers on or off), and stops.

**"cannot start: no OpenEng key".** Pass `--key=<key>` or set `OPENENG_KEY` to an API key from the
Console.

**The schedule isn't in the published config.** The scheduler was saved disabled, or it isn't part
of the config that was published; check the config's version in the Console.

**My cron expression is rejected but looks valid.** It's probably valid in
*another* dialect — the platform accepts exactly 5 numeric fields with
`* , - /`. Translate `@daily` to `0 0 * * *`, names to numbers, and drop any
seconds field.
