hussein-awala opened a new pull request, #74726:
URL: https://github.com/apache/airflow/pull/74726
## Discussion-only PoC
This draft explores a native Task SDK API for **publishing asset
availability when it becomes true**, rather than coupling every publication to
successful completion of a task.
The proposed interface is `asset.emit(extra=..., partition_key=...,
partition_date=...)`. This is a proof of concept for discussing ergonomics,
semantics, runtime coverage, and whether a full implementation needs an AIP.
**It is not ready to merge as a supported production API.**
## Motivation
### 1. Publication from callbacks and notifiers
Asset-event publication should be available to task-level and Dag-level
callbacks, and to notifiers invoked at either level. This is not limited to
failure callbacks: completion, success, retry, and other applicable lifecycle
callbacks can have useful follow-up work.
A native API would let these entry points share transport and provenance
handling rather than each implementing REST endpoint discovery and
authentication. A notifier could be a thin adapter over that API.
**Operational workflows are one use case:** a callback or notifier could
publish a signal consumed by a dedicated operational Dag to investigate a
failure (including AI-assisted investigation), clean up temporary resources
after completion, run validation, or initiate reconciliation. The callback
stays lightweight; the consumer Dag owns scheduling, retries, logs, and
visibility. AI is an example, not a dependency or the defining purpose of this
feature.
These workflows should be explicitly opted in. Cleanup needs to be safe to
retry, investigation workflows should avoid triggering themselves, and event
metadata should contain references to relevant runs/resources rather than
secrets or entire logs. Whether workflow-outcome signals should use ordinary
assets or a distinct abstraction is also worth discussing.
### 2. More flexible publication from a running task
Outlets express **“publish this update when the task succeeds.”** An
immediate API expresses **“this specific output is ready now.”** Both are
useful; this proposal does not change existing outlet semantics.
For example, an ingestion task may process a large backlog in batches, using
task state/checkpoint storage to track progress:
```text
Producer Dag: ingestion task remains running
Commit batch 41 to storage
Emit readiness for partition batch-41
Record progress in the processing checkpoint
Commit batch 42 to storage
Emit readiness for partition batch-42
Continue processing batch 43 ...
Consumer Dag
batch-41 readiness -> validate/transform batch 41
batch-42 readiness -> validate/transform batch 42
```
The consumer can process the first committed batches while the producer
continues. A later failure on batch 43 should not necessarily hide already
durable outputs from batches 41 and 42.
Task state records the producer's progress; asset events communicate
availability to other workflows. This also avoids introducing artificial task
boundaries solely to publish an event when work naturally shares a connection,
session, or internal batch loop.
The batch example above illustrates the motivation; **this PoC does not
implement a checkpointed ingestion operator or change state-store semantics**.
External writes, emission, and checkpoint updates are not automatically atomic.
Also, ordinary asset scheduling can coalesce events: distinct batch keys and
appropriate partition timetable semantics are needed when batches should be
processed separately. One `emit()` call does not guarantee one consumer Dag run.
## Proposed API and semantics
```python
receipt = asset.emit(
extra={"rows": 100},
partition_key="2026-10-01",
partition_date=pendulum.datetime(2026, 10, 1, tz="UTC"),
)
print(receipt.id, receipt.timestamp)
```
- Events are persisted immediately, independently of the producer's eventual
outcome.
- Emitting does not mark the source task or Dag successful, and later
failure does not retract the event.
- Existing success-only outlets are unchanged. Using both mechanisms can
create separate events.
- The asset must already be registered and is matched by both name and URI.
- Partition arguments are explicit; omitted values do not inherit the
producer run's partition. A date requires a key and a timezone-aware datetime.
- The event stores its partition key. The date is passed through existing
partition scheduling/mapper logic; this does not introduce an asset-event date
column.
- The receipt acknowledges persistence, not downstream execution.
- Ordinary parse-time emission is rejected.
## What this PoC implements
The current implementation exercises **running Python tasks and Python Dag
lifecycle callbacks through the existing in-process Dag processor transport**.
The broader goal includes task callbacks and task-/Dag-level notifiers;
**notifier adapters and coverage of all callback execution paths are not
implemented here**.
The request path is:
`Asset.emit()` -> typed supervisor message -> Execution API client -> server
route -> existing asset manager.
Task provenance comes from the authenticated task attempt. Dag callback
provenance comes from the trusted parent's actual callback request batch,
without treating a representative TI as the producer. Dag-sourced events retain
Dag/run fields and leave task/TI/map-index fields null. User callback code does
not access the metadata database.
Included examples in
`airflow-core/src/airflow/example_dags/example_asset_emission.py`:
- A task publishes two dated partitions, then deliberately fails.
- A separate failing Dag publishes a partition from its Dag callback without
reading a TI.
- Two partition-aware consumers display partition/date and provenance.
The failure examples demonstrate independence from successful completion;
they are not intended to restrict the API to failure handling.
Local acceptance also exposed a public REST/UI mismatch: the database and
SDK supported a null source map index, while the public response required an
integer. This draft aligns that response and generated clients, links Dag-only
sources to their Dag run rather than `/tasks/null`, and surfaces event-fetch
errors in the asset/run pages.
## Deliberate limits and open questions
- Remote/API-backed Dag processor credentials are **not** admitted to the
emission endpoint. Production callback support needs an explicit
identity/capability design, not relaxed authentication or a fabricated TI.
- No automatic asset creation, aliases, async API, triggerer support,
executor callback workers, or other-language public SDK implementation.
Generated supervisor wire schemas/types are synchronized for the language SDKs;
that synchronization is not implementation of an emission API in those SDKs.
- No durable callback-delivery or exactly-once guarantee. Automatic HTTP
retries are disabled for the event POST, but lost acknowledgements and
caller/task retries can still produce duplicates.
- Existing asset/team filtering is reused. Publication authorization and
per-emission consumer controls need further review.
- API placement (`Asset.emit()` versus a standalone SDK function), partition
inheritance, idempotency, callback coverage, notifier design, and whether this
warrants an AIP remain discussion points.
Related: #67637 explores success-only Dag-level outlets. This PoC explores a
different publication boundary: an explicit runtime operation independent of
task/Dag success.
## Validation
- On the rebased publication branch, **186 SDK/supervisor tests and 268
Core/API/partition/callback tests passed** in Breeze with Python 3.12 / SQLite.
- The callback integration test launches a real supervised parser subprocess
with no TI in its callback context.
- Local scheduler/UI acceptance: both deliberate producer failures produced
their events; all three partitioned consumer runs succeeded. The previously
failing asset-event and upstream-event API requests now return 200 with the
existing callback event, without rewriting its provenance.
- Eight focused UI tests, TypeScript checking, frontend lint, and the
production UI build passed on the tested PoC worktree.
- Fast and manual static checks were run for the publication worktree.
`mypy-airflow-core` reports two httpx/httpx2 override errors in the unchanged
`dag_processing/api_client.py` (lines 211 and 329); **the same two errors
reproduce on clean upstream `f31684af85`**. That hook is explicitly excluded
from the draft commit, not treated as passing. Other hooks and commit signing
remain enabled.
- **The full core/provider/SDK/CLI/UI/API-client/docs matrix reported by
`breeze verify` has not been run. Full CI, cross-database validation, and
production hardening remain pending.** This is intentionally a discussion
draft, not a claim of merge readiness.
---
##### Was generative AI tooling used to co-author this PR?
- [X] Yes — OpenAI coding assistant in Pi
Generated-by: OpenAI coding assistant in Pi following [the
guidelines](https://github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst#gen-ai-assisted-contributions)
---
Drafted-by: OpenAI coding assistant in Pi (no human review before posting)
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]