Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Apache Airflow is a platform for programmatically authoring, scheduling, and monitoring workflows. Learn to write DAGs, use operators, set up connections, configure scheduling, and deploy with Docker Compose.
.claude/skills/terminalskills-airflow/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-02 | ✗→✓ | ▲ Improved | 73% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 5% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 2% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 51% | 0% |
| case-17 | ✗→✓ | ▲ Improved | 34% | 0% |
Apache Airflow lets you define workflows as Directed Acyclic Graphs (DAGs) in Python. Each DAG consists of tasks connected by dependencies, scheduled and monitored via a web UI.
yaml# docker-compose.yml: Airflow with LocalExecutor (simplified) services: postgres: image: postgres:16 environment: POSTGRES_USER: airflow POSTGRES_PASSWORD: airflow POSTGRES_DB: airflow volumes: - postgres-data:/var/lib/postgresql/data airflow-webserver: image: apache/airflow:2.9.0 depends_on: [postgres] environment: AIRFLOW__CORE__EXECUTOR: LocalExecutor AIRFLOW__DATABASE__SQL_ALCHEMY_CONN: postgresql+psycopg2://airflow:airflow@postgres/airflow AIRFLOW__CORE__FERNET_KEY: '' AIRFLOW__WEBSERVER__SECRET_KEY: changeme volumes: - ./dags:/opt/airflow/dags ports: - "8080:8080" command: bash -c "airflow db migrate && airflow users create --username admin --password admin --firstname Admin --lastname User --role Admin --email admin@example.com && airflow webserver" airflow-scheduler: image: apache/airflow:2.9.0 depends_on: [postgres] environment: AIRFLOW__CORE__EXECUTOR: LocalExecutor AIRFLOW__DATABASE__SQL_ALCHEMY_CONN: postgresql+psycopg2://airflow:airflow@postgres/airflow volumes: - ./dags:/opt/airflow/dags command: airflow scheduler volumes: postgres-data:
bash# Start Airflow docker compose up -d # UI at http://localhost:8080 (admin/admin)
python# dags/hello_world.py: Simple DAG with PythonOperator from datetime import datetime, timedelta from airflow import DAG from airflow.operators.python import PythonOperator from airflow.operators.bash import BashOperator default_args = { 'owner': 'data-team', 'retries': 2, 'retry_delay': timedelta(minutes=5), } with DAG( dag_id='hello_world', default_args=default_args, description='A simple hello world DAG', schedule='@daily', start_date=datetime(2026, 1, 1), catchup=False, tags=['example'], ) as dag: def extract(**kwargs): import requests data = requests.get('https://api.example.com/data').json() kwargs['ti'].xcom_push(key='raw_data', value=data) def transform(**kwargs): data = kwargs['ti'].xcom_pull(key='raw_data', task_ids='extract') transformed = [{'id': d['id'], 'value': d['amount'] * 100} for d in data] kwargs['ti'].xcom_push(key='transformed', value=transformed) extract_task = PythonOperator(task_id='extract', python_callable=extract) transform_task = PythonOperator(task_id='transform', python_callable=transform) load_task = BashOperator(task_id='load', bash_command='echo "Loading data..."') extract_task >> transform_task >> load_task
python# dags/taskflow_etl.py: Modern TaskFlow API with decorators from datetime import datetime from airflow.decorators import dag, task @dag( schedule='@daily', start_date=datetime(2026, 1, 1), catchup=False, tags=['etl'], ) def taskflow_etl(): @task() def extract(): return {'users': 100, 'revenue': 50000} @task() def transform(data: dict): return { 'users': data['users'], 'avg_revenue': data['revenue'] / data['users'], } @task() def load(summary: dict): print(f"Users: {summary['users']}, Avg Revenue: {summary['avg_revenue']}") raw = extract() transformed = transform(raw) load(transformed) taskflow_etl()
python# dags/operators_demo.py: Various operator examples from airflow.providers.postgres.operators.postgres import PostgresOperator from airflow.providers.http.operators.http import SimpleHttpOperator from airflow.sensors.filesystem import FileSensor # SQL execution create_table = PostgresOperator( task_id='create_table', postgres_conn_id='my_postgres', sql=""" CREATE TABLE IF NOT EXISTS daily_stats ( date DATE PRIMARY KEY, total_users INT, revenue NUMERIC ); """, ) # HTTP request fetch_api = SimpleHttpOperator( task_id='fetch_api', http_conn_id='my_api', endpoint='/api/stats', method='GET', response_filter=lambda r: r.json(), ) # Wait for file wait_for_file = FileSensor( task_id='wait_for_file', filepath='/data/incoming/report.csv', poke_interval=60, timeout=3600, )
bash# connections.sh: Set up connections via CLI airflow connections add 'my_postgres' \ --conn-type 'postgres' \ --conn-host 'localhost' \ --conn-schema 'mydb' \ --conn-login 'user' \ --conn-password 'pass' \ --conn-port 5432 # Set variables airflow variables set 'api_key' 'abc123' airflow variables set 'config' '{"batch_size": 1000}' --serialize-json # Trigger a DAG airflow dags trigger hello_world --conf '{"date": "2026-02-19"}'
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 15,622 | 10,910 | -30% | 1 | 1 | 0% | 2,629 | 4,133 | +57% | 0 | 0 | — |
case-02 | fail→pass | 8,017 | 6,104 | -24% | 1 | 1 | 0% | 1,604 | 2,768 | +73% | 0 | 0 | — |
case-03 | fail→pass | 14,945 | 8,279 | -45% | 1 | 1 | 0% | 3,362 | 3,518 | +5% | 0 | 0 | — |
case-04 | fail→fail | 11,479 | 9,723 | -15% | 1 | 1 | 0% | 2,425 | 3,743 | +54% | 0 | 0 | — |
case-05 | fail→fail | 7,307 | 4,061 | -44% | 1 | 1 | 0% | 1,450 | 2,241 | +55% | 0 | 0 | — |
case-06 | fail→fail | 12,555 | 7,307 | -42% | 1 | 1 | 0% | 2,530 | 2,996 | +18% | 0 | 0 | — |
case-07 | pass→pass | 10,648 | 5,273 | -50% | 1 | 1 | 0% | 1,984 | 2,620 | +32% | 0 | 0 | — |
case-08 | pass→pass | 5,010 | 4,338 | -13% | 1 | 1 | 0% | 997 | 2,401 | +141% | 0 | 0 | — |
case-09 | pass→pass | 8,186 | 2,521 | -69% | 1 | 1 | 0% | 1,678 | 1,979 | +18% | 0 | 0 | — |
case-10 | fail→pass | 8,913 | 2,344 | -74% | 1 | 1 | 0% | 1,857 | 1,890 | +2% | 0 | 0 | — |
case-11 | pass→pass | 5,049 | 2,752 | -45% | 1 | 1 | 0% | 914 | 1,994 | +118% | 0 | 0 | — |
case-12 | fail→pass | 8,025 | 4,747 | -41% | 1 | 1 | 0% | 1,590 | 2,403 | +51% | 0 | 0 | — |
case-13 | pass→pass | 9,055 | 5,771 | -36% | 1 | 1 | 0% | 1,873 | 2,786 | +49% | 0 | 0 | — |
case-14 | pass→pass | 7,881 | 4,162 | -47% | 1 | 1 | 0% | 1,923 | 2,333 | +21% | 0 | 0 | — |
case-15 | pass→pass | 7,886 | 4,277 | -46% | 1 | 1 | 0% | 1,487 | 2,314 | +56% | 0 | 0 | — |
case-16 | fail→fail | 3,844 | 2,656 | -31% | 1 | 1 | 0% | 651 | 1,963 | +202% | 0 | 0 | — |
case-17 | fail→pass | 6,140 | 1,593 | -74% | 1 | 1 | 0% | 1,251 | 1,673 | +34% | 0 | 0 | — |
case-18 | pass→pass | 3,320 | 2,637 | -21% | 1 | 1 | 0% | 421 | 1,932 | +359% | 0 | 0 | — |
case-19 | pass→pass | 7,408 | 1,926 | -74% | 1 | 1 | 0% | 1,474 | 1,749 | +19% | 0 | 0 | — |
case-20 | pass→pass | 7,157 | 6,821 | -5% | 1 | 1 | 0% | 1,794 | 2,959 | +65% | 0 | 0 | — |
case-21 | pass→pass | 8,148 | 5,059 | -38% | 1 | 1 | 0% | 1,187 | 2,462 | +107% | 0 | 0 | — |
case-22 | pass→pass | 5,423 | 4,755 | -12% | 1 | 1 | 0% | 874 | 2,373 | +172% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +23 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.