Add GitlabNote messaging adapter for @mention-triggered AI flows

What does this MR do and why?

Introduces Ai::Messaging::Adapters::GitlabNote, a concrete adapter implementing the redesigned Adapters::Base contract (from !236207 (merged)) to handle AI flows triggered by @mention in GitLab notes.

GitlabNote adapter

Implements the Base surface contract:

  • build_callback_context: serializes note/discussion IDs, progress note ID, and service account ID for async delivery
  • on_request_received: creates a progress note ("🔄 Processing...") with early composite identity linking
  • on_flow_started: updates the progress note with a workflow link
  • on_flow_failed: updates the progress note in-place, or falls back to deliver_error
  • deliver_result / deliver_error: posts reply notes in the original discussion

ConversationContextBuilder

New reusable service (Ai::Notes::ConversationContextBuilder) that extracts discussion history as formatted <message> blocks with note IDs and author attribution. Filters system notes, caps at 15 recent messages. Used by both the adapter path and the RunService path.

Routing and feature flag

PostProcessService routes foundational flow triggers through the adapter when ai_use_messaging_adapter_for_mentions is enabled (project-scoped, gitlab_com_derisk). The caller builds the TriggerBundle (goal, service account, flow reference) following the "caller resolves, adapter delivers" pattern from !236207 (merged).

When the flag is disabled, the legacy RunService path is used. Both paths now provide full conversation context (previously RunService only sent the single triggering note).

The CallbackWorker resolves GitlabNote via the static ADAPTER_REGISTRY.

Supports Issue, MergeRequest, and WorkItem noteables. Epics are out of scope (note.project is nil for epics).

Other changes

  • Mention template: uses <gitlab_context>/<conversation> tags with source_context param
  • FlowTrigger: adds foundational_flow? predicate used by routing
  • RunService path: now receives full conversation context via ConversationContextBuilder

Depends on

  • !236207 (merged) — redesigned Adapters::Base with caller-resolves pattern (currently in merge train)

How to set up and validate locally

Specs

bundle exec rspec \
  ee/spec/services/ai/notes/conversation_context_builder_spec.rb \
  ee/spec/services/ai/messaging/adapters/gitlab_note_spec.rb \
  ee/spec/services/ee/notes/post_process_service_spec.rb \
  ee/spec/workers/ai/messaging/callback_worker_spec.rb \
  ee/spec/models/ai/flow_trigger_spec.rb \
  ee/spec/models/ai/catalog/goal_templates/developer/mention_spec.rb

Manual end-to-end testing

Prerequisites

  • Local GDK running with Duo Workflow service connected

  • Developer foundational flow (developer/v1) enabled for a namespace

  • A service account (e.g. @duo-developer-gitlab-duo) configured with an @mention flow trigger on a test project

  • A test issue or MR in that project

  • Feature flag enabled:

    # In bin/rails c
    Feature.enable(:ai_use_messaging_adapter_for_mentions)

Important: Trigger processing runs in Sidekiq (NewNoteWorkerPostProcessService), so code patches require gdk restart rails-background-jobs.

Happy paths

1. Top-level @mention on an Issue

Post @duo-developer-gitlab-duo say hi as a new comment on an issue.

Expected:

  1. A "🔄 Processing..." progress note appears as a reply
  2. Progress note updates to " has started working on this"
  3. Agent response posted as a new note in the same thread

2. @mention in an existing discussion thread

Reply inside an existing thread with @duo-developer-gitlab-duo summarize this discussion.

Expected:

  1. Progress note and reply appear in the same thread
  2. In the agent session, the goal contains <conversation> tags with earlier messages and <gitlab_context> with resource URL/title

Checking adapter state via console

w = Ai::DuoWorkflows::Workflow.where.not(messaging_callback_context: nil).order(id: :desc).first
pp w.messaging_callback_context
# Should include: adapter, project_id, noteable_type, discussion_id, service_account_id, progress_note_id

klass = Ai::Messaging::CallbackWorker::ADAPTER_REGISTRY[w.messaging_callback_context['adapter']]
adapter = klass.from_callback_context(w.messaging_callback_context)

MR acceptance checklist

This checklist encourages us to confirm any changes have been analyzed to reduce risks in quality, performance, reliability, security, and maintainability.

Edited by Thomas Schmidt

Merge request reports

Loading