Admin Control To Disable Custom Agents

What does this MR do and why?

This MR adds a setting to control AI catalog custom agents at the instance or top-level group level. When custom agents are disabled, they become inaccessible to users. Database changes for this setting are included in this MR.

Current state: The setting defaults to true, so there is no impact on existing custom agents. Additionally, there is no UI for this setting yet, so users cannot toggle it from the frontend. See the verification section below for testing instructions.

References

Issue - #594615 (closed)

MR for DB changes - !231887 (merged)

MR for Custom Flows - !232698 (merged)

How to set up and validate locally

Prerequisites

TOKEN="<personal-access-token-of-owner-user-of-TLG>"
BASE_URL="http://gdk.test:3000"

Step 1: Check TLG Setting

Verify the default state in Rails console:

top_level_group = Group.find_by_full_path('gitlab-duo') # Namespace setting
top_level_group.namespace_settings.duo_custom_agents_enabled # => true (default)

cs = Gitlab::CurrentSettings.current_application_settings # Instance setting
cs.duo_custom_flows_enabled # => true (default)

Step 2: Disable Custom Flows via API

curl --request PUT \
    --header "PRIVATE-TOKEN: $TOKEN" \
    --header "Content-Type: application/json" \
    --data '{ "duo_custom_agents_availability": false }' \
    "$BASE_URL/api/v4/groups/gitlab-duo"

Verify the cascade in Rails console:

top_level_group.reload.namespace_settings.reload.duo_custom_agent_enabled # => false

Check the audit log at GitLab Duo > Secure > Audit Events to confirm the change was recorded. Re-enabling the setting will trigger an additional audit event.

image

Step 3: Verify Custom Agents Are Disabled

Navigate to Automate > Agents:

  • Enabled tab: Custom agents should not appear here
  • Managed tab: Custom agents created in the project are still visible
  • Custom flow details page: Users cannot perform actions on disabled agents (only report the agent).
  • Known issue: The "Enable" and "New Agent" buttons remain visible but non-functional (error on click). Frontend changes needed to hide these buttons
Screenshots

image image

Step 4: Verify Custom agents are not visible on agentic chat

Screenshots

image

Step 5: Disable Custom Agents at Instance Level

Disable the instance-level setting via API:

Note: For below you need use admin user's PAT.

  curl --request PUT \
    --header "PRIVATE-TOKEN: $A_TOKEN" \
    --header "Content-Type: application/json" \
    --data '{ "duo_custom_agents_availability": false }' \
    "$BASE_URL/api/v4/application/settings"

Restart the Rails console to ensure the application changes are fully loaded:

cs = Gitlab::CurrentSettings.current_application_settings
cs.duo_custom_flows_enabled # => false

Verify the change in the audit log at Admin > Monitor > Audit Events.

Screenshot

image

Step 6: Verify Cascade Lock Behavior

Once the instance-level setting is disabled, group-level settings cannot be changed. Attempting to enable the setting at the group level will return an error:

  curl --request PUT \
    --header "PRIVATE-TOKEN: $TOKEN" \
    --header "Content-Type: application/json" \
    --data '{ "duo_custom_agents_availability": true }' \
    "$BASE_URL/api/v4/groups/gitlab-duo"

Important: Even if custom agents are enabled at the top-level group, they will not be accessible if disabled at the instance level. The instance-level setting takes precedence and locks all descendant namespaces.

Testing Notes

  • Use an owner or maintainer account for testing
  • Do not test with the root user

Database Queries

Existing Query

Query plan - https://console.postgres.ai/gitlab/gitlab-production-main/sessions/51391/commands/151899

Click to expand
SELECT
    "ai_catalog_item_consumers"."id",
    CASE
        WHEN "item"."id" IN (348, 5403, 5391, 1004583) THEN 1
        WHEN "item"."id" IN (2337, 2334) THEN 2
        WHEN "item"."verification_level" = 100 THEN 3
        WHEN "project_authorizations"."access_level" IS NOT NULL THEN 4
        ELSE 5
    END AS consumer_priority
FROM (
    (
        SELECT *
        FROM (
            VALUES
                (-1::integer, 5403::integer, NULL::integer, 278964::integer, NULL::timestamptz, NULL::timestamptz, TRUE::boolean, TRUE::boolean, NULL::text, NULL::integer, NULL::integer),
                (-2::integer, 5391::integer, NULL::integer, 278964::integer, NULL::timestamptz, NULL::timestamptz, TRUE::boolean, TRUE::boolean, NULL::text, NULL::integer, NULL::integer),
                (-3::integer, 348::integer,  NULL::integer, 278964::integer, NULL::timestamptz, NULL::timestamptz, TRUE::boolean, TRUE::boolean, NULL::text, NULL::integer, NULL::integer)
        ) AS ai_catalog_item_consumers (
            id,
            ai_catalog_item_id,
            group_id,
            project_id,
            created_at,
            updated_at,
            enabled,
            locked,
            pinned_version_prefix,
            service_account_id,
            parent_item_consumer_id
        )
    )
    UNION ALL
    (
        SELECT
            id,
            ai_catalog_item_id,
            group_id,
            project_id,
            created_at,
            updated_at,
            enabled,
            locked,
            pinned_version_prefix,
            service_account_id,
            parent_item_consumer_id
        FROM ai_catalog_item_consumers
    )
) AS ai_catalog_item_consumers
INNER JOIN "ai_catalog_items" "item"
    ON "item"."id" = "ai_catalog_item_consumers"."ai_catalog_item_id"
LEFT OUTER JOIN "project_authorizations"
    ON "project_authorizations"."project_id" = "item"."project_id"
    AND "project_authorizations"."user_id" = 26121709
WHERE "ai_catalog_item_consumers"."project_id" = 278964
ORDER BY
    CASE
        WHEN "item"."id" IN (348, 5403, 5391, 1004583) THEN 1
        WHEN "item"."id" IN (2337, 2334) THEN 2
        WHEN "item"."verification_level" = 100 THEN 3
        WHEN "project_authorizations"."access_level" IS NOT NULL THEN 4
        ELSE 5
    END ASC,
    "ai_catalog_item_consumers"."id" DESC
LIMIT 20

New Query

Query plan - https://console.postgres.ai/gitlab/gitlab-production-main/sessions/51414/commands/151988

Click to expand
SELECT
    "ai_catalog_item_consumers"."id",
    CASE
        WHEN "item"."id" IN (348, 356, 1003596, 1004583) THEN 1
        WHEN "item"."id" IN (2337, 2334) THEN 2
        WHEN "item"."verification_level" = 100 THEN 3
        WHEN "project_authorizations"."access_level" IS NOT NULL THEN 4
        ELSE 5
    END AS consumer_priority
FROM (
    (
        SELECT *
        FROM (
            VALUES
                (-1::integer, 348::integer, NULL::integer, 278964::integer, NULL::timestamptz, NULL::timestamptz, TRUE::boolean, TRUE::boolean, NULL::text, NULL::integer, NULL::integer)
        ) AS ai_catalog_item_consumers (
            id,
            ai_catalog_item_id,
            group_id,
            project_id,
            created_at,
            updated_at,
            enabled,
            locked,
            pinned_version_prefix,
            service_account_id,
            parent_item_consumer_id
        )
    )
    UNION ALL
    (
        SELECT
            id,
            ai_catalog_item_id,
            group_id,
            project_id,
            created_at,
            updated_at,
            enabled,
            locked,
            pinned_version_prefix,
            service_account_id,
            parent_item_consumer_id
        FROM ai_catalog_item_consumers
    )
) AS ai_catalog_item_consumers
INNER JOIN "ai_catalog_items" "item"
    ON "item"."id" = "ai_catalog_item_consumers"."ai_catalog_item_id"
LEFT OUTER JOIN "project_authorizations"
    ON "project_authorizations"."project_id" = "item"."project_id"
    AND "project_authorizations"."user_id" = 26121709
WHERE "ai_catalog_item_consumers"."project_id" = 278964
    AND (
        "ai_catalog_item_consumers"."ai_catalog_item_id" IN (348, 356, 1003596, 1004583)
        OR "item"."item_type" != 1
    )
ORDER BY
    CASE
        WHEN "item"."id" IN (348, 356, 1003596, 1004583) THEN 1
        WHEN "item"."id" IN (2337, 2334) THEN 2
        WHEN "item"."verification_level" = 100 THEN 3
        WHEN "project_authorizations"."access_level" IS NOT NULL THEN 4
        ELSE 5
    END ASC,
    "ai_catalog_item_consumers"."id" DESC
LIMIT 20

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.

Related to #594615 (closed)

Edited by Jaydip Pansuriya

Merge request reports

Loading