Apache Pinot Real-Time Ingestion Freshness Sentinel
Classify an Apache Pinot table HEALTHY, STALE, or EMPTY_TABLE from its own ingestion lag, and alert Slack when a streaming consumer stalls.
Audit Docker Model Runner for stale local AI models, alert Slack on breach, and optionally delete them to reclaim disk space.
Docker Model Runner lets a developer or CI machine pull local AI models the same way it pulls container images - and those models behave the same way stale images do: nobody deletes them, and they are multi-gigabyte files, not a few megabytes. A machine that has pulled a dozen embedding and language models over a few months of experimentation can be carrying tens of gigabytes nobody is using anymore. This blueprint lists every model cached on a DMR instance, flags any older than a configured age, alerts Slack on breach, and can delete the stale ones to reclaim disk space.
periodic_model_check (io.kestra.plugin.core.trigger.Schedule) runs daily, always forcing auto_remediate: "false" and dry_run: "true" through its own inputs: override regardless of this flow's defaults. Shipped disabled so you can validate a manual run first.list_local_models (io.kestra.plugin.docker.model.List) fetches every cached model, each carrying id, tags, created (Unix epoch seconds), and config.{format,quantization,parameters,architecture,size} - verified directly from this plugin's own Java source, not just its doc page.evaluate_model_age (io.kestra.plugin.scripts.python.Script) compares each model's created timestamp against max_model_age_days, collecting the stale ones into stale_models with their tag, creation time, and on-disk size.evaluate_model_gate (io.kestra.plugin.core.flow.If) flags a breach whenever stale_count > max_stale_models.then (breach): route_remediation (io.kestra.plugin.core.flow.Switch on auto_remediate) either considers remediation ("true", gated again by dry_run through check_dry_run) or only logs that remediation is disabled ("false"). When both gates allow it, delete_stale_models (io.kestra.plugin.core.flow.Loop) runs delete_stale_model (io.kestra.plugin.docker.model.Delete) for each flagged model. alert_stale_models_risk always fires to Slack regardless of which path ran.else (compliant): log_models_healthy records a clean check.log_audit_complete always runs last, printing the headline numbers regardless of which branch fired.errors block alerts Slack separately if the flow itself fails - an unreachable DMR instance must not read as "no breach".auto_remediate, dry_run) between "stale model found" and "model actually deleted".List and Delete used together as a governance pair, mirroring this repo's existing docker-host-image-retention-reclaim.yaml pattern for container images.Docker Model Runner itself has no retention policy, no age-based cleanup, and no notification path - a model sits on disk until someone runs docker model rm by hand. Kestra supplies the schedule, the age-based risk classification as a first-class branch, two independent authorization gates before anything is deleted, and an execution history that shows exactly which models were reclaimed and when.
dmr_host.Local testing:
Docker Model Runner ships as part of Docker Desktop/Engine itself (version 4.40+), not as a separate container - enable it via docker desktop enable model-runner or the Docker Desktop settings, then pull at least one test model with docker model pull ai/smollm2 so there is something to audit.
docker network connect YOUR_KESTRA_NETWORK <container> does not apply here since DMR is not itself a container with a name to connect to; if the Kestra Worker runs inside a container, ensure it can reach the host's DMR port (12434 by default) via host.docker.internal:12434 or equivalent, rather than localhost.
SLACK_WEBHOOK_URL: Slack incoming webhook used by alert_stale_models_risk and the errors block.dmr_host carries no credentials, matching Docker Model Runner's own unauthenticated local-only REST API.SECRET_, base64-encoded, and read back in flows with {{ secret('NAME') }} - for example SECRET_SLACK_WEBHOOK_URL=$(echo -n 'https://hooks.slack.com/...' | base64). This keeps credentials out of the flow YAML but, per Kestra's own documentation, offers no encryption at rest or access control beyond the host environment; use the Enterprise secrets backend for stronger guarantees.dmr_host (STRING, default http://localhost:12434): Docker Model Runner REST API base URL.max_model_age_days (INT, default 30): age threshold for the stale-model check.max_stale_models (INT, default 0): breach threshold; 0 means any stale model at all breaches.auto_remediate (SELECT: "false", "true"; default "false"): must be "true" for deletion to even be considered.dry_run (SELECT: "true", "false"; default "true"): must be explicitly "false", together with auto_remediate: "true", for a stale model to actually be deleted.outputs.list_local_models.models: the raw model listing (id, tags, created, config) for every run.outputs.evaluate_model_age.vars.stale_count / .stale_models: the computed policy result for every run.docker model pull ai/smollm2); if you need a guaranteed-stale entry immediately, lower max_model_age_days to 0 for the first test run instead of waiting for a real model to age.SLACK_WEBHOOK_URL secret.auto_remediate: "false") and confirm alert_stale_models_risk fires naming the expected model, and that log_remediation_disabled logs without deleting anything.auto_remediate: "true" and dry_run: "true" and confirm log_dry_run_remediation describes the deletion without running it.auto_remediate: "true" and dry_run: "false" and confirm with docker model ls that the flagged model is gone.periodic_model_check once you trust the check; it always runs in the safe auto_remediate: "false" / dry_run: "true" mode regardless of what you leave the flow's own defaults set to.evaluate_model_age to also flag models above a total disk-size budget across the whole host, by summing config.size after parsing its human-readable units (e.g. 260.86MiB).dmr_host values with io.kestra.plugin.core.flow.Loop (ForEach is deprecated) to sweep a fleet of CI runners or developer machines, each reporting to the same Slack channel.evaluate_model_age.Pause approval task instead of (or alongside) the static auto_remediate input, for a human-reviewed deletion on a shared CI runner.host, models, id, tags, created, config.{format,quantization,parameters,architecture,size}, and Delete's model property) were verified directly against the plugin's Java source (List.java, Delete.java, AbstractModel.java in kestra-io/plugin-docker), not from a doc page, since the doc pages only described these fields in prose.config.size is a human-readable string (e.g. 260.86MiB), not a byte count. Verified from the plugin's own source; summing or comparing sizes numerically requires parsing this string first, which this blueprint's age-only check does not need to do.Delete's model property expects a reference like ai/smollm2 or hf.co/org/repo, not the raw id content digest. This blueprint passes the first entry of each flagged model's tags array rather than its id, matching the plugin's own documented identifier format; a model with no tags at all would need its id instead, which this blueprint does not currently fall back to.Delete itself. The double gate (auto_remediate, dry_run) in this flow is what makes deletion safe to test; Delete itself deletes immediately and permanently once called.auto_remediate and dry_run are independent gates, not a single boolean. Both must be "true"/"false" respectively for the delete Loop to run; setting only one leaves the other still blocking.Switch case keys "true"/"false" are quoted strings. auto_remediate renders as the literal string "true" or "false"; unquoted true:/false: YAML map keys would parse as booleans instead and would not match.