Winslow
A state and workflow management framework with a terminal UI.
Winslow lets you describe work as a set of small, dependency-aware tasks and then run them from a live terminal dashboard — or headless in CI. Its guiding idea is simple: every task knows both how to do its work and how to tell whether that work is already done. Winslow checks the latter before doing the former, so re-running a workflow only does what still needs doing.
If you've ever written a pile of scripts held together with "did this step already run?" checks, Winslow is that pattern, made first-class.
Demo
https://github.com/user-attachments/assets/23426158-0447-421b-ab16-caf4c0d9103e
Install
uv add "winslow[tui]" # or: pip install 'winslow[tui]'
Requires Python 3.12+. The tui extra pulls in the terminal UI; for headless
runs only (cron, CI), the bare winslow package is enough.
Quick start
A workflow is a directory containing a Workflow class and the Task classes
that belong to it. The workflow.py filename marks the directory as a
workflow package — every .py file next to it belongs to that workflow.
Drop this in workflows/etl/workflow.py:
import os
from winslow import Workflow, Task
class Etl(Workflow):
pass # name defaults to "etl" (kebab-cased class name)
class DownloadData(Task):
def run(self):
# ... fetch the file ...
download("s3://bucket/raw.csv", "/data/raw.csv")
def check(self):
return os.path.exists("/data/raw.csv")
class TransformData(Task):
dependencies = DownloadData
def run(self):
transform("/data/raw.csv", "/data/clean.csv")
def check(self):
return os.path.exists("/data/clean.csv")
Then launch the terminal UI from that directory:
winslow run
Or run it headless (handy for cron and CI):
winslow run --mode headless --workflow etl
The core idea: run + check
These two methods are the whole contract:
run(self)makes a change to the world.check(self)reports whether the desired end state already holds.
Before running a task, Winslow calls check(). If it's already true, the
task is skipped; otherwise run() executes and Winslow calls check()
again to confirm success. That makes workflows idempotent and resumable by
construction — interrupt one halfway and re-run it, and it picks up where it
left off. (Need to force a redo? winslow run --force-run skips the pre-check.)
A task that only verifies state can omit run() entirely — it becomes a
read-only check.
Start as a status board, automate later
Because run() is optional, a workflow built only from checks changes
nothing — it just reads your systems and reports what's true. That makes
adoption incremental:
- Describe the runbook you already have as checks — one
check()per step, automated or manual. The TUI is now a live status board over your process exactly as it exists today. Zero migration. - Add
run()to one task at a time, wherever automation pays off. The rest stay checks, and the workflow is usable at every step in between.
A task can even automate part of its work and leave the rest to a human — see the docs for how that plays out.
A few things you'll probably reach for
Each of these is optional — start with run/check and add the rest as
you need it.
-
Dependencies & ordering. Declare
dependencies = OtherTask(or a tuple / task-group name). Winslow builds the dependency graph, detects cycles, and runs things in the right order. Mark foundational stepsis_premierand cleanup stepsis_terminal. -
Eligibility & guards. Override
is_eligible()to skip a task in some environments, orcan_run()to block it until a precondition holds. For reusable rules, attach composable constraints:class DeployTask(Task): runnability_constraints = [BusinessHoursOnly]
-
Parameterized tasks. One task class can fan out into many instances via
Parameterdeclarations — e.g. oneProcessRegiontask per region — each tracked and run independently. -
A task filter language — that you can extend. Narrow the view (or a run) with expressions like
build,test,!g deploy, or~lint & !group nightly, in the UI's search box or via--filteron the CLI. Add your own commands by subclassingTaskFilter: define a!commandand what it matches, and it's available everywhere filters are. -
Declarative caching. Share expensive data — station lists, calendars, reference tables — through
GlobalCache(process scope) andWorkflowCache(session scope) classes. Declare fields with@entry(lazy, eager,depends_on,ttl); eager fields load in parallel before the graph is built, andJsonFileStoragekeeps a cache warm across processes. -
Error telemetry. Report task and workflow errors to Sentry (
winslow[sentry]) or OpenTelemetry (winslow[otel]) by setting their environment values — each error reaches the backends exactly once. -
Live terminal UI. Watch tasks change state in real time, stream per-task logs, browse execution history, and run or re-check individual tasks by hand — all from the dashboard. Everything also works headless in
--mode headless. -
A pluggable UI. The dashboard and workflow views are assembled from plugins that fill named slots. Add your own tab or panel — or replace a built-in one — by subclassing
UIPlugin, no fork required. Both UI plugins and custom filters can be autodiscovered or shipped as separate packages via entry points (winslow.tui_plugins,winslow.filter_plugins).
CLI at a glance
winslow run # interactive terminal UI
winslow run --workflow etl # UI, pre-selecting a workflow
winslow run --mode headless --workflow etl # headless run
winslow run --mode headless --check ... # check completion without running
winslow run --dry-run ... # call dry_run() instead of run()
winslow run --clear-cache ... # invalidate every cache entry first
winslow show # list workflows
winslow show --initialize --workflow etl # initialize one workflow, list its tasks
winslow show --initialize --workflow etl --with-deps # ...with each task's dependencies
Winslow discovers your workflows from the current directory; defining an
Orchestrator subclass to customize global options is optional.
How it compares
Winslow is a local-first workflow tool. There is no server, no scheduler
daemon, and no metadata database: done-ness is whatever check() observes
right now — a file, a table, an API response, a merged PR — so if the world
drifts, the next run sees it. Tasks run where you invoke winslow, and cron
or CI is the intended trigger for unattended runs.
If your situation calls for distributed workers, a built-in scheduler, or a central run history, a different tool will serve you better. The tool selector compares Winslow with the common alternatives under the same rules — tick what your situation requires and see what fits.
Trust model
Treat a workflow directory like a Makefile or a conftest.py: running
winslow in a directory runs that directory's code, imported at startup
before any prompt. Plugin and filter autodiscovery is likewise opt-out —
installed packages exposing winslow entry points load on startup; the
plugin guide shows how to constrain
or allowlist them. The full trust model and the vulnerability reporting
process are in SECURITY.md.
Status
Winslow is early (0.x) — the API may still shift between minor versions. Full documentation lives at winslow-workflow.org.
Questions & feedback
- Questions, use cases, and ideas → Discussions
- Reproducible bugs → Issues
Real-world use cases are especially welcome while the API is still settling — what you automate, and what fought you, both shape what 0.x becomes.
License
Metadata
Release files for winslow 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 | |
|---|---|---|---|---|
| winslow-0.5.0.tar.gz | 128.7 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| winslow-0.5.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 302.6 kB
Release files / winslow-0.5.0.tar.gz
| Download URL | winslow-0.5.0.tar.gz |
|---|---|
| Size | 128.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
73ab3e3c820aecfd324d7d5ebcbdb9b66e0b4ee1ba91f8fe0aa32bf7fbc03228
|
|
BLAKE2b-256 checksum How to use checksums |
7d47ed3551e9dca92569ea048dd7345e764fb2719e05304e3195bd96c5f70c5e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.4 {"installer":{"name":"uv","version":"0.12.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / winslow-0.5.0-py3-none-any.whl
| Download URL | winslow-0.5.0-py3-none-any.whl |
|---|---|
| Size | 173.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0d77eca6aa956bf2e8b8c5a30f3fde9348ff582e0e2fdb44c02f520f39ef2bd0
|
|
BLAKE2b-256 checksum How to use checksums |
48701e007d475371f22111ac61efa15e42accf62af8f6292c603e4b44bcb262c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.4 {"installer":{"name":"uv","version":"0.12.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for winslow 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.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A 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.