ADR 0007: Airflow UI Compatibility for the MVP¶
Status: Accepted Date: 2026-05-21
Context¶
Building a workflow orchestrator means building a UI: graph view, grid view, log viewer, manual triggers, task clearing. This is months of frontend work.
Apache Airflow has a mature, battle-tested UI (rewritten in React for Airflow 3). It is open source under the same Apache 2.0 license that Leoflow uses.
Decision¶
For the MVP, Leoflow uses the unmodified Airflow 3.2.x UI. The Leoflow Control Plane exposes a REST API that matches the Airflow 3.2.x public API (/api/v2/...) closely enough that the UI works without modification.
We do not fork the UI in the MVP. We ship the Airflow Docker image and configure it to point at the Leoflow API.
Rationale¶
- Time saved. Months of frontend work avoided.
- Familiar to users. Airflow users transfer their muscle memory directly.
- Free updates. When the Airflow UI improves, Leoflow benefits.
- Forces good API design. Compatibility with a real UI keeps us honest.
How Compatibility Is Achieved¶
The Go Control Plane implements the subset of /api/v2/ endpoints that the UI consumes. Endpoints not consumed by the UI are not implemented in the MVP.
The internal Leoflow domain model is richer than Airflow's (cleaner state machine, native versioning, multi-tenancy). Internal types are translated to Airflow-compatible DTOs in the HTTP handler layer.
This is an anti-corruption layer in domain-driven-design terms: the public API speaks Airflow, the internal core speaks Leoflow.
Concrete Endpoints in the MVP¶
| Endpoint | Method | Purpose |
|---|---|---|
/auth/token |
POST | Issue JWT for username/password |
/api/v2/dags |
GET | List DAGs |
/api/v2/dags/{dag_id} |
GET, PATCH | Get DAG, pause/unpause |
/api/v2/dags/{dag_id}/dagRuns |
GET, POST | List runs, trigger run |
/api/v2/dags/{dag_id}/dagRuns/{run_id} |
GET | Run details |
/api/v2/dags/{dag_id}/dagRuns/{run_id}/taskInstances |
GET | List task instances |
/api/v2/dags/{dag_id}/dagRuns/{run_id}/taskInstances/{task_id} |
GET | Task instance details |
/api/v2/dags/{dag_id}/dagRuns/{run_id}/taskInstances/{task_id}/logs/{try} |
GET | Task logs |
/api/v2/dags/{dag_id}/clearTaskInstances |
POST | Clear (re-run) tasks |
/api/v2/xcoms/{...} |
GET | Read-only XCom view |
The OpenAPI spec is in docs/api/openapi.yaml.
What Does Not Work Out of the Box¶
Some Airflow features need backend support that the MVP does not implement:
- Backfill UI. Disabled or stubbed for the MVP.
- Manual mark success/failed. Stubbed; v1.1 feature.
- DAG dependencies graph. Stubbed; v1.1 feature.
- Plugins, providers list, connections UI. Stubbed.
These are all areas where the UI will either show empty states or refuse to function. Acceptable for the MVP.
Future: Custom UI¶
In a later version (v2.x), Leoflow may ship its own UI optimized for the richer internal model. At that point, the Airflow-compatible API remains as a backward-compatibility layer.
Consequences¶
- Operators deploy two containers:
leoflow-server(Go) andairflow-webserver(Python). The Airflow webserver is configured withAIRFLOW__API__BACKENDpointing at the Leoflow API. - The Leoflow team must track Airflow 3.x API changes. Minor version bumps in Airflow may require Leoflow patches.
- Authentication must work both ways: the UI obtains a JWT via
/auth/tokenand forwards it on subsequent calls. - The CORS configuration in
leoflow-servermust allow the Airflow webserver's origin.
Alternatives Rejected¶
- Fork the Airflow UI. Rejected for the MVP due to maintenance burden. Open option for v2.
- Build a new UI from scratch. Rejected for the MVP due to time cost.
- Use a third-party UI (Argo UI, etc.). Rejected because they do not match Airflow's mental model and would surprise users.
Revision History¶
- 2026-05-23 (Phase 5). The premise was refined during implementation. This
ADR assumed the Airflow 2.x model: a separate Airflow webserver pointed at the
Leoflow API via
AIRFLOW__API__BACKEND, with/api/v2parity sufficient to render the UI. Airflow 3.x invalidated that โ the React SPA targets an internal, non-public/ui/*API (AIP-84), not just/api/v2. Leoflow therefore embeds the pinned Airflow 3.2.1 SPA inleoflow-server(single binary, no second container) and implements/ui/*pinned to 3.2.1. The two-container deployment and CORS notes above are superseded by the single-origin embed. See ADR 0017 (asset serving), ADR 0018 (custom UI as the strategic north star), anddocs/ui-compatibility.mdfor the full learnings. The original decision โ reuse Airflow's UI rather than build one for the MVP โ still holds.