astronomer/authoring-language-sdk-tasks
The language-neutral foundation for Airflow language SDKs — implement task logic in a non-Python language while the DAG stays in Python. Use when the user wants to run an Airflow task in another language (Java, Kotlin, Go, or other JVM/native languages), asks how the Python `@task.stub` pairs with native task code, how task/DAG IDs must match across the two sides, how data passes via XCom as JSON, or which language SDKs exist. This skill owns the shared Python-stub pattern and conceptual model; for a specific language's native API, build, and runtime, use that language's skill (e.g. authoring-java-sdk-tasks, authoring-go-sdk-tasks).
npx skills add https://github.com/astronomer/agents --skill authoring-language-sdk-tasks
Airflow language SDKs let you implement task logic in a language other than Python while the DAG and its scheduling stay in Python. This skill describes the parts that are identical across every language SDK. Each language has its own companion skill for the native API, build tooling, and runtime — see Per-language skills.
> Experimental. The language SDKs are in preview. APIs and artifact coordinates may change.
A DAG is authored in Python as usual. Tasks that should run in another language are declared as stubs routed to a dedicated queue. At runtime, Airflow hands a stub task to a coordinator that launches a short-lived native subprocess for that one task instance, runs your compiled/native code, and shuts the subprocess down.
Consequences that hold for every language SDK:
Every task has two halves that must agree:
The example below uses the Go SDK to be concrete, but the Python side is identical for every language SDK. The queue name ("golang" here) is an arbitrary label you choose — it just has to match a key in queue_to_coordinator (see configuring-airflow-language-sdks). Pick whatever name fits the SDK you're routing to.
from datetime import timedelta
from airflow.sdk import dag, task
@dag
def sales_pipeline():
@task.stub(queue="golang") # queue selects the coordinator (see configuring-airflow-language-sdks)
def extract(): ...
@task.stub(queue="golang")
def transform(extracted): ... # arg only declares the dependency
@task.stub(queue="golang", retries=1, retry_delay=timedelta(seconds=5))
def load(transformed): ...
@task() # an ordinary Python task can sit downstream
def report(loaded):
print(f"done: {loaded}")
report(load(transform(extract())))
sales_pipeline()
Rules that apply regardless of language:
@dag name (or dag_id=) is the DAG ID. The native side must use these exact IDs.transform(extracted)) exists only to declare the dependency in Python. The value itself is fetched on the native side via XCom — passing it in Python does not hand it to the native code.queue value is what routes the task to a coordinator; the same string must appear in queue_to_coordinator (see configuring-airflow-language-sdks).XCom values are stored as JSON in Airflow's metadata database, so the boundary between Python and any native language is JSON. The Python/JSON side is the same for every SDK:
| Python type | JSON |
|-------------|------|
| int | number (integer) |
| float | number (decimal) |
| str | string |
| bool | boolean |
| None | null |
| list | array |
| dict | object |
Each language SDK maps these JSON types onto its own native types (e.g. a JSON integer becomes a Java Long). The native-type mapping lives in that language's skill. The key portability rule: a value pushed by one task is read by another as JSON, so the consuming side must expect a type compatible with what was stored.
This skill deliberately stops at the shared concepts. The following differ per language and are documented in each language's companion skills:
The Airflow-side wiring (which coordinator runs which queue) is shared in structure but has per-coordinator options; it lives in configuring-airflow-language-sdks.
@dag/dag_id and the native DAG ID. Mismatches surface as "no DAGs" or missing-XCom errors.pass, ..., or a docstring is allowed in the body; any real logic is rejected.retry_policy is rejected on stubs (@task.stub raises ValueError). Use retries/retry_delay instead — a retry-policy callable runs Python in-process and would never fire for a task executing in a native subprocess.authoring-<lang>-sdk-tasks skill that builds on this one.)*Take astronomer/authoring-language-sdk-tasks from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.