Skip to main content

Winslow

PyPI Python CI License: MIT

A workflow and state manager that serves terminals and agents.

Winslow runs work as a set of small tasks with declared dependencies. You drive them from where you are:

  • A live terminal dashboard on your machine, winslow run
  • A terminal connected to a shared host, winslow connect
  • An agent over MCP, winslow serve --endpoints ws mcp
  • Headless in cron or CI, winslow run --mode headless

Every task knows two things: how to do its work, and how to tell whether that work is already done. Winslow checks the second before it does the first, so a second run of a workflow only does what still needs doing.

If you have a pile of scripts held together with "did this step already run?" checks, Winslow is that pattern as a framework.


Demo

https://github.com/user-attachments/assets/23426158-0447-421b-ab16-caf4c0d9103e


Install

Each mode is one extra:

uv add "winslow[serve]"      # the shared host
uv add "winslow[mcp]"        # the shared host, with the MCP endpoint
uv add "winslow[tui]"        # one machine: the UI and the sessions together
uvx --from "winslow[connect]" winslow connect ws://host:8866   # a terminal, no project needed

Winslow needs Python 3.12 or later. pip install 'winslow[serve]' works the same way. A headless run in cron or CI needs the bare winslow package only.

Quick start

A workflow is a directory with a Workflow class and the Task classes that belong to it. The file name workflow.py marks the directory as a workflow package, and every .py file next to it belongs to that workflow. Put this in workflows/etl/workflow.py:

import os
from winslow import Workflow, Task


class Etl(Workflow):
    pass  # The name defaults to "etl", the kebab-cased class name.


class DownloadData(Task):
    def run(self):
        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")

Start the terminal UI from that directory:

winslow run

Or serve the directory and connect to it from another terminal:

winslow serve                                                        # on the host
uvx --from "winslow[connect]" winslow connect ws://127.0.0.1:8866    # from a terminal

Or run it headless, for cron and CI:

winslow run --mode headless --workflow etl

The getting started page walks through all three. Serve and connect covers a team on one host and an agent over MCP.

The core idea: run and check

Two methods are the whole contract:

  • run(self) makes a change to the world.
  • check(self) reports whether the wanted end state already holds.

Before it runs a task, Winslow calls check(). If the check passes, the task is skipped. If it fails, run() executes and Winslow calls check() again to confirm the result. A workflow is therefore idempotent and resumable: interrupt it halfway, run it again, and it continues from where it stopped. To force a rerun, winslow run --force-run skips the pre-check.

A task that only verifies state can omit run(). It becomes a read-only check.

Start as a status board and automate later

A workflow built only from checks changes nothing. It reads your systems and reports what is true. That makes adoption incremental:

  1. 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 as it exists today.
  2. 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 also automate part of its work and leave the rest to a person. The docs show how.

What else is there

Each of these is optional. Start with run and check and add the rest when you need it.

  • Dependencies and ordering. Declare dependencies = OtherTask, a tuple, or a task group name. Winslow builds the dependency graph, detects cycles, and runs the tasks in order. Mark foundational steps is_premier and cleanup steps is_terminal.

  • Eligibility and guards. Override is_eligible() to skip a task in some environments, or can_run() to block it until a precondition holds. A reusable rule is a constraint class:

    class DeployTask(Task):
        runnability_constraints = [BusinessHoursOnly]
    
  • Parameterized tasks. One task class fans out into many instances through Parameter declarations, for example one ProcessRegion task per region, each tracked and run on its own.

  • A filter language you can extend. Narrow the view or a run with expressions like build,test, !g deploy, or ~lint & !group nightly, in the search box of the UI or with --filter on the CLI. Subclass TaskFilter to add a !command of your own, and it works everywhere filters do.

  • Declarative caching. Share expensive data such as station lists, calendars or reference tables through GlobalCache (process scope) and WorkflowCache (session scope) classes. Declare fields with @entry (lazy, eager, depends_on, ttl). Eager fields load in parallel before the graph is built, and JsonFileStorage keeps 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 once.

  • Live terminal UI. Watch tasks change state, stream per-task logs, browse the execution history, and run or re-check single tasks by hand, all from the dashboard.

  • Serve and connect. winslow serve starts the engine, the component that owns the sessions, and exposes it over a websocket. Every connected terminal sees the same sessions. --endpoints ws mcp adds an MCP endpoint, so an agent lists, starts, runs and inspects sessions with the same reads and actions a terminal has.

  • A pluggable UI. The dashboard and the workflow screen are assembled from plugins that fill named slots. Subclass UIPlugin to add a tab or a panel, or to replace a built-in one. UI plugins and filters live in their own packages, listed in the settings module of the project.

CLI at a glance

winslow serve                                  # the sessions on 127.0.0.1:8866
winslow serve --host 0.0.0.0 --endpoints ws mcp    # on the network, with /mcp for agents
winslow connect ws://host:8866                 # the TUI against a server
winslow run                                    # the UI and the sessions in one process
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 the workflows from the current directory. An Orchestrator subclass that adds global options is optional.

How it compares

Winslow runs on one host. There is 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 when the world drifts, the next run sees it. Tasks run where the engine runs, on your laptop under winslow run or on the shared host under winslow serve, and cron or CI triggers the unattended runs.

If you need distributed workers, a built-in scheduler, or a central run history, a different tool fits 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 the code of that directory, imported at startup before any prompt. A plugin loads when the settings module of the project lists it, and the plugin guide shows the lists. A serve credential grants every action on the serving host, including starting a workflow, which runs its code. The full trust model and the vulnerability reporting process are in SECURITY.md.

Status

Winslow is early, 0.x, and the API may still change between minor versions. The full documentation lives at winslow-workflow.org.

Questions and feedback

Real use cases are welcome while the API is still settling. What you automate, and what fought you, both shape what 0.x becomes.

License

MIT

Metadata

Release files for winslow 0.7.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for winslow 0.7.0
File Size Uploaded Provenance
winslow-0.7.0.tar.gz 413.1 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for winslow 0.7.0
File Interpreter ABI Platform
winslow-0.7.0-py3-none-any.whl Python 3 none any Details

Total release size: 885.4 kB

Release files / winslow-0.7.0.tar.gz

Download URL winslow-0.7.0.tar.gz
Size 413.1 kB
Tags Source
SHA-256 checksum
How to use checksums
f2de8279ff24c0af5eed44ee1b2597c20ad61fa0f0c8c1dbde218902ae39af86
BLAKE2b-256 checksum
How to use checksums
d87f22c6d4256c54e9e606a224b118ba3a79e7d8b40590738b4f4658962912af
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.1 {"installer":{"name":"uv","version":"0.13.1","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.7.0-py3-none-any.whl

Download URL winslow-0.7.0-py3-none-any.whl
Size 472.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e42bab0ab809a7b2084d677e7a9661562b69b410d73f53aab18ce8be86932e1e
BLAKE2b-256 checksum
How to use checksums
486975767f39a940294a34e6edc22c3516ecbd2afc735134bdabbb2f224ac9c6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.1 {"installer":{"name":"uv","version":"0.13.1","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.7.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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page