PyScheduler
An educational Autosys / Control-M style job scheduler, built from scratch in Python on our own libraries: pynet (event loop and processes), pyweb (web server) and pydb (journalled database). From v0.2, alerts and remote agents also use pymq (message broker).
What It Is
A school runs on a timetable: maths at nine, lunch at twelve, and the science lab only once the kit is out. PyScheduler is that timetable for computer jobs. You describe your jobs in a YAML file and it:
- fires them on cron-style schedules, in their own time zones, on business calendars with holidays, through clock changes;
- waits for dependencies (on success, on failure or on any ending), and works out which business day each run is for;
- retries failures with growing waits, times jobs out, and keeps to a global cap and named pools;
- runs every attempt under a small minder process that writes down how it ended, so a crash of the scheduler loses nothing: on restart it recovers, adopts what is still running and catches up what it missed;
- saves everything write-ahead to a journalled store, in group commits;
- lets operators hold, release, kill, rerun, force and backfill runs over a
JSON HTTP API with bearer tokens, or with the
pyschedcommand line; - forecasts what will run, and checks a config for every mistake before it ever runs.
It is concurrent: one event loop looks after every running job. In a test, 200 real jobs started together and finished in about 3 s; about 35 are alive at any instant, because each attempt pays a Python start-up for its minder. Through it all, 99 % of the loop's 10 ms ticks are less than 50 ms late.
Quick Start
Needs Python 3.14 and uv. POSIX only.
uv sync --all-extras
uv run pysched validate examples/etl.yaml # check the example config
uv run pysched serve examples/etl.yaml # start the scheduler (leave it running)
In another terminal:
uv run pysched trigger extract --run-date 2026-10-16
uv run pysched trigger fetch-rates --run-date 2026-10-16
uv run pysched status --date 2026-10-16
uv run pysched log fetch-rates.2026-10-16.1 --attempt 1 --stderr
examples/etl.yaml is a small nightly chain on a UK business calendar:
extract then transform, and a flaky fetch-rates that fails its first
attempt and succeeds on the retry, all feeding load (which shares a
one-slot warehouse pool with transform), plus a cleanup job that only
runs on a day load fails. Runs use previous_business_day, so Monday's
02:00 run processes Friday.
Phase 2: Alerts and File Watchers
A smoke alarm is no use if nobody hears it. Phase 2 adds the ears:
- Alerts: the scheduler raises an alert when a run
failed, isretrying,blocked,lost, rantoo_shortortoo_long, ornever_startedby itsmust_start_by; when a file watcher gives up (watcher_timeout); and when a config reload fails (reload_failed). A job setsmin_runtime,max_runtimeandmust_start_by, and its ownalerts:to switch kinds on or off. - Routing: every alert has a topic,
alert.<kind>.<criticality>.<job>.routes:match topics with*(one word) and#(any number of words) and pick notifiers. Deliveries are written to the store first, retried with backoff, and replayed after a restart. - Notifiers:
log,command,webhook,slack,telegramandemail, with${SECRET}values kept out of the file. - File watchers: a job with
type: file_watcherwaits for files matchingwatchto arrive and sit still formin_age, then succeeds and hands the list to its dependents as{files}(andPYSCHED_FILES). It uses no pool or running slot while waiting, and raiseswatcher_timeoutaftergive_up_after.
Try it with examples/alerts.yaml:
uv run pysched validate examples/alerts.yaml
uv run pysched serve examples/alerts.yaml # leave it running
In another terminal:
uv run pysched trigger always-fails --run-date 2026-10-16
uv run pysched trigger slow-job --run-date 2026-10-16
touch examples/inbox/trades.csv
uv run pysched trigger inbox --run-date 2026-10-16
uv run pysched alerts
cat examples/logs/alerts.log
pysched alerts lists what was raised and which deliveries were sent,
and pysched replay <alert-id> sends an alert's dead deliveries again. Concept pages:
Alerts, Routing Alerts,
Notifiers and File Watchers.
Phase 3: The Web UI
A noticeboard in the school hall beats asking the secretary. Phase 3 adds one, served by the scheduler itself with no extra install and no build step:
- Login and roles: a
users_file(make a line withpysched hash-password) gives each person a role: viewer, operator or admin. Bearer tokens and browser sessions go through one access layer, with a CSRF token on anything a cookie changes. - The board (
/): a row per run for any date, with state badges, duration against the job's typical duration, filters, and critical failures pinned to the top. Operators get rerun, hold, release, kill and the other actions as buttons. - Run and job pages: attempts, events, alerts and their deliveries, the definition the run used, and a live log tail.
- The dependency graph (
/graph): layered left to right, coloured by state, with failure and cross-date edges drawn differently and a focus mode that lights up everything upstream and downstream. - The timeline (
/timeline): hour, day and week views of when every job really ran, with forecast "ghost" bars for what is still to come; and the forecast table (/forecast). - Admin (
/admin): config versions with a diff against the one before, and the dead-letter tray with a replay button. - Live updates: pages follow the scheduler over server-sent events with numbered ids, so a dropped connection catches up (or reloads) by itself.
Try it with the seeded demo: twelve jobs, three days of real runs, a flaky job, a file watcher and a critical failure. It listens on loopback only.
uv run python examples/ui_demo.py # prints the address; log in as admin / demo
uv run python examples/ui_demo.py --seed-only
Concept pages: The Web UI, Live Updates, Graph Layout and Timelines; the auth modes are in The HTTP API.
Phase 4: Remote Agents
A shop with drivers can deliver across town. Phase 4 lets a job run on another machine:
- Agents:
pysched-agentis a worker that takes sealed orders from a pymq broker, runs them with the same minder the scheduler uses and reports back. A job picks its place withagent: nameoragent_tags: [gpu]. - An
agents:section: the scheduler embeds a broker (listen) or uses an existing one (broker), with a secrets file naming each agent. - Heartbeats: an agent says "still here" every 10 seconds. Silence for
agent_gracemarks it offline; an attempt unheard forlost_afteris failed aslost, and retries apply. - Remote logs: fetched from the agent on demand (503 if it is offline).
- Signed messages: every message carries an HMAC seal, a date and a one-time nonce. The broker itself has no authentication or encryption, so use loopback, a trusted network or a tunnel.
GET /api/agents, the/agentspage andpysched agentsshow who is online.
Try it: a scheduler and two agents in one process, a pinned job and a tagged job, and a printout of which agent ran each:
uv run python examples/agents_demo.py
Concept pages: Remote Agents, Heartbeats and Signed Messages.
Config Reference
The config format (scheduler settings, calendars, pools, defaults and every job key) is described in the docs:
- Config Files and YAML
- Templates and Values for
{run_date}and${SECRET} - Cron Expressions and Calendars and Run Dates
- Validation for every check
pysched validatemakes
The full design is in docs/superpowers/specs/2026-10-17-pyscheduler-design.md.
Concepts
Every concept has a kid-friendly page with an everyday analogy and a runnable snippet. Start at docs/index.md for the learning path:
scheduler, YAML, config files, templates, cron, calendars and run dates, time zones and DST, dependencies, runs and states, retries and backoff, pools and limits, the minder, clocks and launchers, group commit, concurrency, recovery and catch-up, validation, operator actions, forecast, notifiers, routing alerts, alerts, file watchers, the HTTP API, the service and CLI, the web UI, live updates, graph layout, timelines, remote agents, heartbeats and signed messages.
Build the docs site with:
uv run --with mkdocs-material mkdocs build --strict
Roadmap
| Release | Contents |
|---|---|
| v0.1 | Core engine: YAML reader, config and validation, schedules, calendars, time zones, run dates, forecast, journalled store, the minder, local runs, dependencies, retries, pools, holds, catch-up, backfill, rerun, JSON HTTP API, pysched CLI |
| v0.2 (done) | Alerts and file watchers: alert rules (failed, never started, too long, too short, lost), routing through a pymq broker, notifiers (log, command, webhook, Slack, Telegram, email), file-watcher jobs |
| v0.3 (done) | Web UI: login and roles, job board, run detail with live log tail, dependency graph, timeline with forecast, operator actions, live updates over SSE |
| v0.4 (done) | Remote agents: pysched-agent workers over a pymq broker, heartbeats, lost-agent handling, remote log fetch, signed messages |
Development
uv run pytest
uv run ruff check . && uv run ruff format --check .
uv run pyright
See CONTRIBUTING.md.
Related Projects
This project is one of a series that builds real systems "from scratch in Python" to teach how they work. See the series home for the full story.
| Project | What it teaches | Repository | Docs |
|---|---|---|---|
| PyOS | Operating systems | py-os | Docs |
| Pebble | Compilers and programming languages | pebble-lang | Docs |
| PyDB | Relational databases | pydb | Docs |
| PyKV | Key-value stores | pykv | Docs |
| PyDocDB | Document databases | pydoc-db | Docs |
| PyGraphDB | Graph databases | pygraphdb | Docs |
| PyTSDB | Time-series databases | pytsdb | Docs |
| PyVecDB | Vector databases | pyvecdb | Docs |
| PyColDB | Column-family databases | pycoldb | Docs |
| PyCrypt | Cryptography | pycrypt | Docs |
| PyNet | Computer networking | pynet | Docs |
| PyWeb | HTTP web servers | pyweb | Docs |
| PyGit | Version control | pygit | Docs |
| PySearch | Search engines | pysearch | Docs |
| PyMQ | Message queues | pymq | Docs |
| PyLLM | Language models (the brain) | pyllm | Docs |
| PyScheduler | Job scheduling | pyscheduler | Docs |
| PyStack | Full-stack integration | pystack | Docs |
Documentation
Full docs (with kid-friendly explanations of every idea): https://robcox2910.github.io/pyscheduler/
Metadata
Release files for fromscratch-pyscheduler 0.5.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| fromscratch_pyscheduler-0.5.0.tar.gz | 783.7 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| fromscratch_pyscheduler-0.5.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 1.2 MB
Release files / fromscratch_pyscheduler-0.5.0.tar.gz
| Download URL | fromscratch_pyscheduler-0.5.0.tar.gz |
|---|---|
| Size | 783.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d8291e15a78f18f0eb643d8bd4aea1283be34b0f9c43fa088469b69c874b617f
|
|
BLAKE2b-256 checksum How to use checksums |
ad81e9210ef7aa4cd5fb1ed9f048d4e456aeb995587b581c215b0394a310ef87
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 26, 2026.
Transparency logRelease files / fromscratch_pyscheduler-0.5.0-py3-none-any.whl
| Download URL | fromscratch_pyscheduler-0.5.0-py3-none-any.whl |
|---|---|
| Size | 398.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b5df4b76c6be7e83d866b4d63777cd38021c93f819b4111fa72406e47a5ba6f5
|
|
BLAKE2b-256 checksum How to use checksums |
f49c2ee37a167a4c98b8a6cd1d0fbe7ab77331334b7cc46070cc1c527365d09c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 26, 2026.
Transparency logSecurity for fromscratch-pyscheduler 0.5.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for fromscratch_pyscheduler-0.5.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 26, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.