A single-host workflow orchestrator.
Python-native. Airflow-shaped. Built to fit on one machine.
Airflow without the cluster. No Kubernetes, no Celery, no external database, no worker fleet. One process, one host, one UI.
Use it for ETL, internal automation, scheduled jobs embedded in an application, or anything that fits on a VPS.
pip install riverflowfrom datetime import timedelta
from riverflow import DAG, serve
with DAG(dag_id="hourly_rollup", schedule=timedelta(hours=1)) as dag:
@dag.task("extract")
async def extract(): ...
@dag.task("transform")
async def transform(): ...
@dag.task("load")
async def load(): ...
extract >> transform >> load
serve(dag)Open http://localhost:8083/ui.
The repository also includes a local showcase with two DAGs and a Flow that connects them:
uv run python src/main.py --openFor UI or API development, start a blank Riverflow with no registered workflows:
uv run python src/main.py --blank --openOr skip the Python bootstrap entirely:
riverflow serve path/to/dags.py --openFor scripts and tests that just want to run a DAG once:
from riverflow import run
history = run(dag)
assert history.state.value == "success"A DAG organizes tasks. A Flow organizes DAG runs without flattening their history, logs, schedules, or ownership boundaries.
from riverflow import DAG, Flow, serve
with DAG("ibge_extract") as extract:
@extract.task("download")
async def download(): ...
with DAG("medallion") as medallion:
@medallion.task("promote")
async def promote(): ...
with Flow("ibge_pipeline", schedule="0 3 * * *") as flow:
source = flow.add_dag(extract, parameters={"scope": "ibge"})
layers = flow.add_dag(medallion, parameters={"scope": "ibge"})
source >> layers
serve(flow)Each Flow node creates a normal DAG run with explicit parent Flow and node
lineage. Independent branches run concurrently. A node can queue, reject,
or force when its DAG is already running.
Orchestration. Tasks compose into DAGs; DAG runs compose into Flows. Both
support >> dependencies, schedules, trigger rules, and concurrent branches.
DAG tasks also support retries and timeouts.
UI. A Broadsheet-flavoured single-page app. Overview, DAG detail (graph / overview / history / grid / gantt / tasks), run detail with live logs, and a built-in Host page for on-box CPU, memory, disk, and network — four charts in a 2×2 grid with a synchronised crosshair.
Live. WebSocket push — no polling, no full-page refreshes.
Keyboard. ⌘K command palette, / to filter, j / k to move, g d / g l / g h / g s to jump between sections.
Light footprint. Zero heavyweight chart libraries. Initial JS bundle ≈ 75 kB gzipped.
src/riverflow/
├── core/ # DAG, Task, scheduler, executor, logging
├── models/ # Pydantic contracts shared with the UI
└── server/ # FastAPI app, WebSocket, host metrics collector
ui/ # React + Vite + Tailwind, served from the same process
Horizontal scale-out. Multi-tenant auth. A plugin marketplace. If you need those, you need Airflow.
Apache 2.0