Skip to content

Dagster Orchestration

Dagster is the execution runtime for production training runs. The module-level defs object in src/daedalus/definitions.py is the entrypoint that Dagit, the dg CLI, and dagster dev load:

python
# src/daedalus/definitions.py
defs = dg.Definitions(
    assets=[*feature_view_specs, pixai_feed_relevance_training],
    jobs=[training_job],
    schedules=[training_schedule],
    resources={"cfg": DaedalusConfig()},
)

Everything below is defined in src/daedalus/defs/training.py.

The hybrid model: a partitioned graph-backed asset

The top abstraction is a daily-partitioned asset per training service — one partition per day, which is also the schedule cadence. The partition is produced by a graph of ops generated from the compiled operator DAG's engine units (pipeline.units()) — so the graph mirrors the pipeline, not a fixed two-stage split. One op per engine unit, visible in Dagit and individually retriable:

text
compile_pipeline ─▶ source ─▶ enrich ─▶ (asset materialization)

The ops are factory-generated per service from the unit list, so a service with no pythonic unit (no declared embedding columns) yields just compile_pipeline ─▶ source — no enrich op. Each op wraps the executor (pipelines.executor.run_pipeline), selecting its engine unit via only_engines — no feature logic is re-derived. Each emits rich metadata so a run is self-describing in Dagit:

OpEngine unitMetadata emitted
compile_pipelineoperators count, the full dag as JSON
sourcesql / sql_arrow_udf (only_engines=SOURCE_ENGINES)rows, output path
enrichpythonic (only_engines=ENRICH_ENGINES)rows, columns, embedding_columns, output_path, partition

The enrich op reads counts and schema from the parquet metadata only — it never materializes the large 1152-d embedding partition into memory just to report stats.

Feature views as external assets (lineage)

Every feature view in the registry is declared as a Dagster external asset, so the asset graph is the lineage. They appear in the feature_views group keyed as feature_view/<name> with the parquet kind:

python
dg.AssetSpec(
    key=dg.AssetKey(["feature_view", name]),
    group_name="feature_views",
    kinds={"parquet"},
)

Job + schedule

python
training_job = dg.define_asset_job(
    name="pixai_feed_relevance_daily",
    selection=[pixai_feed_relevance_training],
)

training_schedule = dg.build_schedule_from_partitioned_job(training_job)

build_schedule_from_partitioned_job derives a daily schedule directly from the daily partition definition, so production runs follow the partition cadence with no separate cron string to maintain.

The DaedalusConfig resource

Where the engine reads config and writes output is a ConfigurableResource, overridable per run or per environment:

python
class DaedalusConfig(dg.ConfigurableResource):
    runtime_config_path: str = "feature_services/pixai_feed_relevance/settings.yaml"
    aggregation_config_path: str = "config/training/aggregations.yaml"
    feature_views_dir: str = "feature_views"
    feature_services_dir: str = "feature_services"
    output_root: str = "data/training_output"
    enriched_output_root: str = "data/training_output_enriched"
    # Local Ray envelope (prod overrides to 16c / 64 GiB).
    ray_num_cpus: int = 16
    ray_memory_gb: int = 44
    ray_object_store_gb: int = 12

Daily today, hourly later

The cadence is daily end to end. Moving to hourly is a one-line swap to HourlyPartitionsDefinition once the feed supports it.

Running it

bash
# Launch Dagit (the web UI) against the definitions module
uv run dagster dev -m daedalus.definitions

# Validate the definitions load cleanly
uv run dg check defs

Agent CLI: ad-hoc materialization & lineage

For agents and quick checks, two top-level daeda commands wrap the same Dagster asset in-process (src/daedalus/commands/agent.py) — no running Dagit instance required.

daeda materialize-day

Materializes one training-day partition (via dg.materialize on pixai_feed_relevance_training) so you can inspect sample output. Only the pixai_feed_relevance service is supported.

bash
uv run daeda materialize-day pixai_feed_relevance 2026-02-01

It prints a JSON summary pulled from the materialization metadata:

json
{
  "service": "pixai_feed_relevance",
  "date": "2026-02-01",
  "rows": 1234567,
  "output_path": "data/.../dt=2026-02-01",
  "embedding_columns": ["image_embedding", "like_artwork_avg_embeds"]
}

daeda lineage

Shows upstream/downstream lineage for one feature view or service, resolved from the platform registry (not Dagster):

bash
uv run daeda lineage pixai_feed_relevance            # table (default)
uv run daeda lineage user_likes --format json

For a service it lists upstream views and their sources; for a view it lists upstream sources and downstream services.

See also