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 # => falseCheck 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.
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
Step 4: Verify Custom agents are not visible on agentic chat
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 # => falseVerify the change in the audit log at Admin > Monitor > Audit Events.
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 20New 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 20MR 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)




