Skip to content

Task discovery

For retry, bulk actions, and the dashboard’s task picker to work, the agent needs a task registry - a list of every task name the engine can run. Engines don’t usually expose this directly; we probe.

Each engine adapter tries these sources in order. The first one that yields a non-empty result wins.

Celery has app.tasks (a dict of name → task). Dramatiq has dramatiq.get_broker().actors. Use these when present.

Celery: app.control.inspect().registered() - asks running workers for their registry. Slow; avoided on hot paths.

Some apps declare tasks in pyproject.toml entry points (rare but elegant). We scan z4j.tasks group.

Walk sys.modules for objects decorated with @app.task / @dramatiq.actor / @rq.job / etc. Run on agent startup, cached for 15 minutes.

Every task name we see in an event is added to the registry with source: observed. Covers dynamically-registered tasks that the other layers miss.

  • Layers 1, 4 run at agent boot.
  • Layer 2 runs every 5 minutes in the background (Celery only).
  • Layer 5 is real-time.

The registry is shipped to the brain on every change (dedup’d). The dashboard shows an “as of” timestamp per agent.

  • Lazy imports - tasks defined in modules that haven’t been imported at agent boot won’t appear until they’re first enqueued. Layer 5 catches them.
  • Multi-agent discrepancies - two agents with different import sets will report different registries. The UI shows the union with per-agent breakdown.
  • Dynamic task names - tasks whose name is constructed at runtime show up only via Layer 5.
  • “Enqueue task” form - populated from the registry. Tasks not in the registry cannot be enqueued from the UI (but can still be enqueued from code).
  • Bulk retry filter - matches against registry + observed names.
  • Agent drawer - shows registry size and source breakdown per layer.