Flowlit
A lightweight, event-driven workflow execution engine. Hand it a YAML plan — a set of steps, their dependencies, and per-step instructions — and Flowlit validates it as a DAG, runs it, and hands each step to whichever executor is registered for that step's type, running every step the DAG allows to run at once genuinely concurrently, not one at a time.
pip install -e ".[dev]"
flowlit examples/hello_workflow.yaml
-> greet (noop): submitted
step greet (noop): Hello from Flowlit!
<- greet: completed
-> build (noop): submitted
step build (noop): Pretending to build the project...
<- build: completed
-> test (noop): submitted
step test (noop): Pretending to run tests...
<- test: completed
-> report (noop): submitted
step report (noop): Build and test both finished.
<- report: completed
Workflow hello-workflow: completed
Why Flowlit
- The DAG alone decides concurrency. Two steps that share nothing but
a parent run genuinely at once, as independent
asyncio.Tasks — not serialized because they happen to share a step type, and not artificially parallelized either. See Concurrency model. - Data flow is explicit and auditable. A step reads a prior step's
output only via a small, closed
${{ steps.<id>.output.<path> }}placeholder syntax — no expressions, no filters, nothing a semi-trusted plan author (an AI agent proposing a workflow, say) could use to smuggle in arbitrary logic. See Plan format. - Failure handling is a first-class, per-step decision.
optional,blocking,silent_failures, and per-edgeblocking_overrideslet two different dependents of the same step disagree about whether they're blocked by its failure — expressive enough for "best-effort logging that should never stop a required deploy step." See Plan format. - Branching without an expression language.
when/when_not/elsegate a step on a prior step's output or outcome, reusing the same closed placeholder syntax — no new DSL to audit. A branch step is implicitly optional, so picking one path over another can't itself abort the workflow. See Branching. - Clean, layered architecture, built to extend. Four layers
(
domain→application→infrastructure→interfaces), each only depending on the ones below it, with ports (Protocols) separating contracts from implementations. Add a step type without forking the repo via one call toregister_executor(). See Architecture. - Built for both a blocking CLI and a future async server.
run_to_completion()blocks until done (right for a CLI);start_workflow()- polling
get_status()doesn't (right for an MCP server or any poll-driven client) — same DAG, same engine, no separate code path. See API reference.
- polling
Everything runs in memory — nothing survives a process restart. That's a
deliberate v1 choice, not an oversight: the goal is to get the
architecture right first, with the WorkflowRepository/EventBus ports
already shaped so a persistent implementation can be dropped in later
without touching orchestration logic. The eventual goal is to run this as
an MCP server so agent tools (e.g. VS Code GitHub Copilot) can submit and
drive plans.
Key features
| Real concurrency | Independent steps run as genuinely parallel asyncio.Tasks, driven purely by the dependency graph. |
| Four built-in executors | noop, sleep, shell (with timeout), http (with retry/backoff) — see Executors. |
| Typed, defaulted specs | Any executor can declare a pydantic spec_model and get validation for free, at the earliest honest point. |
| Pluggable executors | register_executor("my_type", MyExecutor()) — no fork required. |
| Rich failure semantics | Required vs. optional steps, blocking vs. non-blocking dependents (with per-edge overrides), silent vs. visible failures, cascading skips. |
| Clean cancellation | A required failure — or an explicit cancel_workflow() — stops every in-flight step promptly and deterministically. |
| Output passing | ${{ steps.<id>.output.<path> }} placeholders, resolved just before dispatch. |
| Two execution modes | run_to_completion() (blocking) and start_workflow() + poll (non-blocking), from the exact same engine. |
| JSON-ready DTOs | WorkflowService returns primitives-only dataclasses — no domain leakage across the API boundary. |
Quickstart
python -m venv .venv
source .venv/bin/activate # or `.venv\Scripts\activate` on Windows
pip install -e ".[dev]"
flowlit examples/hello_workflow.yaml
pytest # run the test suite
ruff check src tests # lint
mypy src # type-check
See Getting started for a full walkthrough, including using flowlit as a library and reading its CLI output.
Documentation
Detailed, modular guides live under doc/:
| Guide | Covers |
|---|---|
| Getting started | Install, quickstart, CLI flags, using flowlit as a library. |
| Plan format | The YAML plan schema: steps, dependencies, output-passing placeholders, failure-handling fields. |
| Architecture | The four-layer design, execution flow, event bus, composition root — with diagrams. |
| Concurrency model | How the DAG drives real concurrency, cancellation semantics, max_concurrent_steps. |
| Executors | The noop/sleep/shell/http executors, adding your own, registering one from outside the repo. |
| API reference | WorkflowService, ports, DTOs, events, and the full exception hierarchy. |
| Configuration | CLI flags, build_app_context() parameters, per-step configuration. |
| Observability | Logging setup and live progress via the event bus. |
| Testing | Test suite layout and what each part covers. |
| Examples | A guided tour of every plan in examples/. |
| Changelog | What changed in each release, including any breaking changes. |
A minimal plan
id: build-and-deploy
name: Build and Deploy
steps:
- id: fetch_source
type: shell
spec:
command: "echo '{\"commit_sha\": \"abc123\"}'"
depends_on: []
- id: notify
type: noop
spec:
message: "deployed commit ${{ steps.fetch_source.output.commit_sha }}"
depends_on: [fetch_source]
type selects the executor; spec is whatever that executor needs;
depends_on must form a DAG. See Plan format for
the complete schema, including optional/blocking/silent_failures
and blocking_overrides.
Project layout
src/flowlit/
├── domain/ Step/Workflow entities, DAG validation -- stdlib only
├── application/ use cases, ports (Protocols), orchestration
├── infrastructure/ YAML loading, in-memory event bus/repository, executors
├── interfaces/ CLI today; a future MCP server; the composition root
└── plugins.py register_executor() -- the public extension seam
examples/ runnable YAML plans
tests/ unit + integration test suite
See Architecture for the reasoning behind this layering.
License
MIT — see pyproject.toml.
Release files for flowlit 0.2.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 | |
|---|---|---|---|
| flowlit-0.2.0.tar.gz | 157.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| flowlit-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 246.9 kB
Release files / flowlit-0.2.0.tar.gz
| Download URL | flowlit-0.2.0.tar.gz |
|---|---|
| Size | 157.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ee8b815d946671747d38b54a0e4ae5b9895cc7ff8b3057e1bef6ba41082fa2ae
|
|
BLAKE2b-256 checksum How to use checksums |
d79a2f428e4aed9ea59be88827926d4bb56a02a530f117386a9462d6327c0834
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|
Release files / flowlit-0.2.0-py3-none-any.whl
| Download URL | flowlit-0.2.0-py3-none-any.whl |
|---|---|
| Size | 89.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ed99c2807101cfecb8dc15bf2de125914223822dc65aa436bff58d4a9b7a7cdc
|
|
BLAKE2b-256 checksum How to use checksums |
66b1e2b903585bcfc0144e587f1346140e31db322fd0320314aec2962e537f6b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|