Move developer flow goal construction from AIGW to Rails

What does this MR do?

Moves the goal routing logic for the developer/v1 flow from the AIGW Jinja prompt template into Rails. Each trigger point now constructs a complete natural-language goal, so the AIGW prompt can simply pass it through as {{ goal }}.

The frontend changes ("Generate MR with Duo" button) have been split into a separate MR: !232720 (merged)

Problem

Currently in the developer flow in AIGW, the prompt has fragile string-matching ('Input:' in goal, '/-/merge_requests/' in goal) to route different trigger types to different instructions. Adding new trigger types required changes to the AIGW prompt template. The logic on how an agent should generally behave should be kept in the system prompt to make it a reusable building block. The user prompt (where we pass the goal) should be purposed for the task.

We want to get to a point where the developer can be used in a flexible way and where adding new triggers (e.g. like here) is straightforward.

Note that the current developer/v1 will support custom goals better after this is merged: gitlab-org/modelops/applied-ml/code-suggestions/ai-assist!5265 (merged) . Before that, this MR would be blocked. We are also working on a new developer_unstable that should ship with %19.0 which then would just pass through the user goal when this MR makes it in (follow-up work to be done).

Solution

Goal templates are defined via a GoalTemplates::Base contract and stored in flow-specific subclasses (e.g. GoalTemplates::Developer). The base class provides a shared safe interpolation helper; subclasses implement resolve to map event types to templates. Templates are keyed by event type and resource type, resolved at the trigger point in Rails.

  • Mention triggers: Goal with the note URL (#note_123) and the mention content in XML tags
  • Assign triggers (work items/issues): Goal to solve the assigned work item and create an MR
  • Assign triggers (merge requests): Goal to assess MR state (pipelines, review feedback, discussions) and act accordingly
  • Assign reviewer triggers: Goal to review MR diffs and leave comments with suggestion blocks
  • IDE/LSP: Already sends free-form goals, no change needed

Why goals are constructed server-side

This MR moves goal construction from the AIGW Jinja prompt template to Rails. This separates two concerns:

  1. System prompt (AIGW): Defines how the agent behaves — its personality, reasoning approach, tool usage patterns. This lives in the AI Gateway and iterates independently of GitLab releases.
  2. User prompt / goal (Rails): Defines what task the agent should perform. Constructed by Rails based on the trigger context (mentions, assigns, button clicks), versioned with the GitLab release.

Why not keep goals in AIGW? Previously, the AIGW prompt contained routing logic to decide what instructions to give based on the trigger type. Every new trigger required a coordinated AIGW change. By constructing complete goals in Rails, AIGW just passes {{ goal }} through.

Why not store goals in a prompt registry? Goal templates are tightly coupled to the trigger context (resource type, event, note URL). They are versioned with the GitLab release, which provides auditability. A prompt registry adds indirection without clear benefit here.

Key changes

File Change
ee/app/models/ai/catalog/goal_templates/base.rb Base class with resolve contract and safe interpolate helper
ee/app/models/ai/catalog/goal_templates/developer.rb Goal templates for mention, assign, assign_reviewer with resource-type routing
ee/app/models/ai/catalog/foundational_flow.rb Added goal_templates attribute
ee/app/services/ai/flow_triggers/run_service.rb Template-based goal resolution via goal_templates
ee/app/services/ee/notes/post_process_service.rb Passes note_id for mention triggers

Deployment note

The companion AIGW change (simplifying the developer/v1 user prompt to {{ goal }}) should be deployed first or simultaneously. Without it, the enriched goals may be double-wrapped by the existing Jinja if/else routing.

Related: #574990

How to set up and validate locally

Prerequisites

  • A Duo Developer trigger configured on a test project with the developer/v1 flow (with both assign and mention event types)

Test 1: Mention trigger

  1. Go to an issue in the test project
  2. Write a comment mentioning the Duo Developer service account: @duo-developer Please summarize the discussion
  3. Verify in Langsmith trace that the user prompt matches the MENTION_TEMPLATE:
    • Contains You were mentioned in a note on this issue: followed by the issue URL with #note_<id>
    • Mention text wrapped in <mention> XML tags

Test 2: Assign trigger on issue

  1. Assign the Duo Developer service account to an issue
  2. Verify in Langsmith trace that the user prompt matches ASSIGN_ISSUE_TEMPLATE:
    • Contains You were assigned to solve the following issue: followed by the issue URL

Test 3: Assign trigger on MR

  1. Assign the Duo Developer service account to a merge request (not as reviewer)
  2. Verify in Langsmith trace that the user prompt matches ASSIGN_MERGE_REQUEST_TEMPLATE:
    • Contains You were assigned to a merge request: followed by the MR URL
    • Includes instructions about fetching diffs, pipeline status, and review comments

Test 4: Assign as reviewer on MR

  1. Assign the Duo Developer service account as a reviewer on a merge request
  2. Verify in Langsmith trace that the user prompt matches ASSIGN_MERGE_REQUEST_REVIEW_TEMPLATE:
    • Contains You were assigned as a reviewer on a merge request:
    • Includes instructions about suggestion blocks and not approving

Test 5: Fix pipeline flow (regression check)

  1. Set up a trigger for the fix_pipeline/v1 flow on a test project
  2. Push a commit that causes a pipeline failure
  3. Verify: The fix pipeline flow triggers and receives the pipeline URL as its goal

Test 6: User input with %{...} sequences (safety check)

  1. Write a comment: @duo-developer Fix the %{resource_url} formatting in the README
  2. Verify in Langsmith trace: The literal %{resource_url} appears inside <mention> — not substituted
Edited by Thomas Schmidt

Merge request reports

Loading