Skip to content

Use cases

Feature flags fall into a few well-known categories (Martin Fowler's toggle taxonomy, Unleash's flag types). This maps them to Airflow, where the flag targets a set of DAGs or tasks and the lever is a pool, queue, executor, priority, or a code path.

Category In Airflow Lever
Release / rollout Ramp a risky platform change to a subset of DAGs, then widen pool / queue / executor
Experiment A/B a model, query, or algorithm inside a task in-task gate + exposure
Ops / kill switch Revert instantly during an incident, no redeploy any flag, flipped off
Permission Gate a feature per team, tenant, or dataset gate on context attributes

Why not deployment canary (Argo Rollouts, Flagger)? Those shift HTTP traffic between container versions and do not support queue workers. Airflow schedules from a pull queue, so they can't express "route these DAGs to the canary pool." A flag evaluated in the scheduler policy can, and it reverts in seconds instead of a redeploy cycle.

New here? Walk through getting-started.md first. Runnable versions of these are in ../example_dags/; the end-to-end drivers are in ../system_tests/.

Placement (the policy)

Enable the policy ([openfeature] enable_policy = True) and register a backend. The policy reads airflow.task.pool, airflow.task.queue, airflow.task.executor, and airflow.task.priority_weight, keyed on dag_id:task_id, and applies whatever the backend returns. The JSON below is flagd's; the same subset expresses as a targeting rule or a percentage ramp in GrowthBook, Unleash, Statsig, or an in-house engine.

1. Airflow 2 to 3 migration

Migrating every DAG at once is unsafe, so Airflow's guidance is to stand up a 3.x worker pool and move DAGs onto it a few at a time. Route the subset with a flag; ramp by widening it, revert by emptying it. No DAG edits.

{ "flags": { "airflow.task.pool": {
  "state": "ENABLED",
  "variants": { "v3": "airflow_3x", "v2": "airflow_2x" },
  "defaultVariant": "v2",
  "targeting": { "if": [ { "in": [ { "var": "dag_id" }, ["etl_alpha", "etl_beta"] ] }, "v3", "v2" ] }
} } }

2. Kubernetes worker / queue migration

Move a subset onto a Kubernetes queue with a percentage ramp. flagd's fractional operator does deterministic sticky bucketing, so a DAG that's in the subset at 20% stays in it at 50%.

{ "flags": { "airflow.task.queue": {
  "state": "ENABLED",
  "variants": { "k8s": "kubernetes", "celery": "default" },
  "defaultVariant": "celery",
  "targeting": { "fractional": [ ["k8s", 20], ["celery", 80] ] }
} } }

3. Canary a KubernetesExecutor change

KubernetesExecutor creates pods serially in the scheduler loop; apache/airflow#68480 adds opt-in concurrent pod creation. A change like that wants a canary. Enable it on a canary KubernetesExecutor, route a subset there with airflow.task.executor, watch queued_duration, and flip the flag off to revert. Per-task executor needs Airflow 3.x (or 2.10+ with multiple executors configured).

{ "flags": { "airflow.task.executor": {
  "state": "ENABLED",
  "variants": { "canary": "KubernetesExecutorCanary", "default": "" },
  "defaultVariant": "default",
  "targeting": { "if": [ { "in": [ { "var": "dag_id" }, ["etl_alpha"] ] }, "canary", "default" ] }
} } }

4. Priority / SLA and cost

Raise airflow.task.priority_weight for a subset during a backfill or an incident, or route a subset of heavy tasks to a cheaper pool with the airflow.task.pool recipe.

5. Kill switch

Placement is a flag, so reverting is a config change, not a deploy: empty the subset or disable the flag and the next parse puts every task back on the default. This is what makes the rollouts above safe. Knight Capital lost $460M in 45 minutes from a change it had no way to switch off; DORA's 2021 report ties elite incident recovery to exactly this kind of instant, deploy-free control.

Experiment (the gate + exposure)

Evaluate a flag for a stable entity inside a task, run the chosen branch, and let the exposure listener record the assignment. This matches how experimentation platforms work: assign, emit an exposure event, and measure downstream in your warehouse.

6. A/B a model or algorithm

from openfeature_airflow.gate import variant

def choose_model(**context):
    entity = context["dag"].dag_id
    return variant("ranking.model_version", entity, default="v1")  # "v1" or "v2" per the split

Define ranking.model_version as a weighted variant (say 90/10) to send 10% of runs to v2. Enable the exposure listener to record the arm for analysis. Full example: example_dags/ab_test_model_example.py.

7. Wait for a rollout to reach a subset

Hold a downstream task until a flag turns on for its DAG.

from openfeature_airflow.sensors.feature_flag import FeatureFlagSensor

wait = FeatureFlagSensor(task_id="wait_for_rollout", flag_key="feature.new_path", conn_id="openfeature")

8. Gradual dependency or behavior rollout

Enable a new library, code path, or risky setting for a subset of runs before it becomes the default, using the same boolean gate as case 6.

9. AI and agent pipelines

Model training, batch inference, and LLM/agent workflows on Airflow are where flag-driven experiments pay off most, since the whole point of the pipeline is to compare versions and measure the result:

  • A/B a model, prompt, or retrieval strategy across a subset of runs (case 6), then read which wins on accuracy, latency, or cost from the exposure plus outcome, instead of shipping a guess.
  • Ramp a new agent tool or reasoning path (case 8) to a fraction of runs before it becomes the default.
  • Kill switch an expensive or misbehaving model call (case 5) the moment cost or quality drifts, with no redeploy.
  • Trade cost for quality by subset by routing some runs to a smaller model and measuring the gap with track_outcome, so the tradeoff is a number rather than an argument.

Assignment and measurement here are the same primitives as any other experiment, so an AI pipeline gets the assign → expose → measure loop without standing up a separate experimentation stack.

Permission

Gate a feature per team, tenant, or dataset by targeting on evaluation-context attributes: pass them as **attributes to the gate, or read them from the task in the policy.