Run Duo note messaging callbacks in a high-urgency worker

What does this MR do and why?

Splits messaging-callback dispatch so the DB-only @GitLabDuo note path runs at :high urgency, without paying the resource cost of scheduling a worker that only no-ops.

Ai::Messaging::CallbackWorker served both the Slack adapter (which calls the Slack API) and the gitlab_duo_note adapter (DB-only). Because it declared worker_has_external_dependencies!, it could not also be urgency :high — RuboCop forbids the combination. That kept the @GitLabDuo MR-reply path at :low urgency, adding noticeable scheduling delay before Duo posts a reply.

This MR introduces a single high-urgency dispatcher that resolves the adapter once and either delivers inline or forwards:

  • Adds an Adapters::Base.has_external_dependencies? capability flag (default false); Slack overrides it to true.
  • Extracts the shared dispatch logic into an Ai::Messaging::CallbackDispatch concern with a forward_event hook (default: terminal, never forwards).
  • New Ai::Messaging::CallbackDispatchWorker (urgency :high, no external deps) is the sole EventStore subscriber. It resolves the workflow's adapter once (cheap, DB-only); DB-only adapters are delivered inline (the latency-sensitive path), external-dependency adapters are forwarded to CallbackWorker via perform_async.
  • CallbackWorker becomes terminal external/Slack delivery (keeps worker_has_external_dependencies!) and handles inline whatever adapter it is handed.

This mirrors an existing in-domain pattern — CreateCheckpointService#enqueue_messaging_progress_delivery checks a capability on the adapter class (supports_live_progress?) and does a targeted enqueue. New adapters route automatically by their has_external_dependencies? value.

Feature flag: the dispatch is gated behind gitlab_duo_note_callback_high_urgency (gitlab_com_derisk, default off), per Sidekiq compatibility across updates — a new worker must not be scheduled before the Sidekiq fleet has the class. The if: is evaluated per publish, so it is runtime-toggleable:

  • flag off → the pre-existing CallbackWorker subscribes and handles everything inline (today's behavior). Safe fallback — the forward target already exists in production.
  • flag onCallbackDispatchWorker subscribes (sole) and forwards Slack to CallbackWorker.

Note: the DB-only path runs Notes::CreateService, which must stay within the high-urgency SLO (median <1s, p99 <10s). Ping @gitlab-com/gl-infra/data-access/durability if the expected shard-load increase exceeds 5%. Slack gets one extra scheduling hop (dispatch → forward → deliver), which is acceptable since it is external-dependency / not latency-critical.

Screenshots or screen recordings

N/A — backend-only change.

File Before After
ee/app/workers/ai/messaging/callback_worker.rb sole subscriber, dispatches all adapters, :low urgency, external deps terminal external/Slack delivery + forward target; handles inline whatever it is handed
ee/app/workers/ai/messaging/callback_dispatch_worker.rb (did not exist) sole subscriber, urgency :high, no external deps; inline DB-only, forward external
ee/app/workers/concerns/ai/messaging/callback_dispatch.rb (did not exist) shared dispatch + forward_event hook

How to set up and validate locally

  1. Enable the flag: Feature.enable(:gitlab_duo_note_callback_high_urgency) in a Rails console.
  2. Mention @GitLabDuo in an MR comment on a project with Duo Code Review enabled; confirm the reply posts and arrives faster (delivered inline by CallbackDispatchWorker at :high urgency).
  3. Disable the flag and repeat; confirm the reply still posts (served by the legacy CallbackWorker subscriber).
  4. From Slack, mention Duo and confirm Slack replies/reactions still work (forwarded to CallbackWorker when the flag is on; handled directly when off).

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

References

Edited by Kinshuk Singh

Merge request reports

Loading