Route Duo in Slack to the slack_assistant flow behind a flag
What does this merge request do and why?
Warning
This flow is not billed yet. Credits key on the flow reference, and CustomersDot has no entry for slack_assistant/v1. Unmapped keys do not error — they fall through to success — so usage is metered under a key nobody counts.
Decided: register slack_assistant/v1 in CustomersDot mapped to the Duo Developer credit pool, tracked in #628678. Untangling the billing key from the flow reference (#599841) needs cross-team agreement with Fulfillment and is deliberately deferred; a Rails-only attempt was dropped (!254800 (closed)) because it covered only the pre-flight check, leaving a session checked under one key and metered under another.
No code change here when that lands — keying on the flow's own reference is already what the mapping expects. Acceptable while the flag is on for our own group only; #628678 must be live before enabling anywhere else. Blocker on the rollout issue (#628672).
Routes Duo in Slack to the API-only slack_assistant flow when slack_duo_api_flow is enabled for the user's default Duo namespace.
Stacked on !254799 (merged), which registers the flow. Review that first.
Today every Slack mention runs Duo Developer, so a CI job starts and clones a repository before the agent can answer. Most Slack requests are questions, searches, or creating an issue, and none of that needs a repository.
The flagged path also sends no preamble. The flow's own system prompt carries the identity, the Slack formatting rules and what happens to the final message; the <conversation> and <channel_context> blocks describe themselves. The split we settled on is: the client sends data, the flow sends instruction. A prompt that describes the client's goal format dates the moment the client changes it, and breaks when an older instance renders the goal differently.
SLACK_CONTEXT_PREAMBLE is untouched for the unflagged path, because Duo Developer's prompt knows nothing about Slack. So with the flag off, both the flow and the goal are byte-identical to today. That is the kill switch.
The resolved reference is threaded through goal assembly rather than memoized, so the preamble cannot disagree with the flow it was built for. (An ivar would also trip Gitlab/ModuleWithInstanceVariables in an EE module.)
Related to #628428 (closed)
How to set up and validate locally
Needs the GitLab for Slack app wired to your GDK.
- Check out this branch and restart Rails.
- Run the Duo Workflow Service with the
slack_assistantflow available, and pointGITLAB_DUO_WORKFLOW_SERVICE_URLat it. The flow config lives in the AI gateway: usemainif ai-assist!6867 has merged, otherwise check out that branch. - Enable the flag, seed the entry and enable the flow for your group — steps 2–4 of !254799 (merged).
- Mention the bot in a Slack thread.
- Session list — the session runs
slack_assistant/v1, notdeveloper/v1. - Session detail — the goal starts at
<channel_context>or<conversation>, with no preamble prose above it. - Tools and reply — the agent reads GitLab through
gitlab_api_get/gitlab_graphql, and its final message appears in the Slack thread. - CI job log — the "Getting source from Git repository" section still appears, but inside it you should see
Skipping Git repository setup,Skipping Git checkoutandSkipping Git submodules setup, andDUO_WORKFLOW_DEFINITION=slack_assistant/v1. That iscoding_environment: noneworking. Worth checking rather than assuming:CodingEnvironment.resolvefalls back to:fullon anything unrecognised, so a mistake looks like a normal run. - Disable the flag and mention again: Duo Developer runs, with today's preamble intact.
Verified locally
Run against GDK with the real Slack app. Drop the screenshots into the slots below.
Session list — the right flow ran

Session detail — no preamble, the goal is data only

The agent used its API tools and answered in the thread

CI job log — the clone is skipped

Specs
- Flag on: the API reference reaches the resolver and the TriggerBundle, and the goal carries no preamble.
- Flag off: every other example in
app_mentioned_service_spec.rbruns with the flag stubbed off, so they are the guarantee that today's path is unchanged. Specs enable all flags by default, so that stub had to be made explicit. assemble_goaltakes the preamble as its first argument now, so the fail-closed overflow guard's wrapper was updated to match.
51 examples, 0 failures.
Stack — merge in this order:
- !254799 (merged) — registry entry and feature flag.
- !254801 (merged) — routing. The only MR that changes behaviour.
Billing is deliberately out of scope; see the warning above. !254800 (closed) is closed pending that decision.