Continue existing Duo session on repeat Slack mentions
What does this MR do and why?
Makes a Slack thread keep talking to the same Duo session, so mentioning the bot again continues the conversation instead of starting from nothing. The plumbing to run another turn against an existing session landed separately; this connects it to Slack and moves the flow onto the surface that makes it viable.
All of it sits behind the default-off slack_duo_api_flow flag. With the flag
off, the CI-executed developer/v1 path is untouched.
Finding the session again
A new Ai::Messaging::SlackThreadSessionStore maps a Slack thread to the session
answering it, in Redis, with a 7-day expiry refreshed on every turn.
Redis rather than a column on the workflow, because this is a routing hint and not a fact about the session. Losing it costs the user their context, not their data, and the next mention simply opens a new session. Storing it on the workflow would also mean indexing a jsonb column to find a session by Slack thread. Redis failures are tracked and swallowed for the same reason: answering without the earlier context beats not answering.
The key includes the GitLab user id and not only the thread. Slack sessions are private to whoever started them, and the endpoint a turn runs against accepts only the session's owner, so two people addressing the bot in one thread hold two independent conversations rather than fighting over one.
Deciding whether to continue
AppMentionedService looks the thread up and continues a session only when it
belongs to the mentioning user, was created for the same flow, and is in a status
the flow can resume from.
Sessions that are still running count as resumable, which is worth stating plainly since it looks permissive. Whether an earlier turn is genuinely in flight is the workflow lock's decision and it makes it, refusing a concurrent turn with a message the user can act on. Excluding running sessions here would instead strand a thread forever on a session whose worker died mid-turn.
Sending only what is new
A follow-up carries the thread messages posted since the flow last answered, minus the bot's own posts, which the flow wrote and would otherwise read back as if a user had said them. The recent channel history is not re-sent either; the session was given it when it opened. Besides being cheaper in tokens, this drops a Slack API call per follow-up, which matters because conversation history calls are the ones Slack rate-limits hardest.
Moving the flow to the chat surface
The last commit changes the flow's catalog entry from environment: ambient to
chat, matching what the flow's own config in the Duo Workflow Service already
declares.
This is not cosmetic. It is what makes the whole feature usable, and it pairs
with a Duo Workflow Service change that ends each turn on input_required so the
session survives its answer. ambient is one of the environments
from_pipeline? treats as pipeline-executed, and a pipeline-executed flow
reaching input_required means a human is being waited on, so
UpdateWorkflowStatusService raises a to-do and emails the user. For this flow
that state is simply the end of an answer. Without this commit, every single
Slack message would produce a to-do and an email.
chat is also the more accurate label on its own terms: nothing about these
sessions is ambient. Someone is talking to the assistant and waiting for the
reply, and Workhorse runs them rather than a pipeline.
One consequence for reviewers to weigh. Tool governance keys off the same
attribute. ambient resolved to no surface, which callers read as the web
surface, so namespace tool rules applied. chat is a local surface, so it
resolves to :chat with duo_workflow_local_tool_governance on and
:ungoverned with it off. That is how the other local surfaces already behave,
and this flow's toolset is fixed in its own config, but it is a loosening while
that flag is off and should be an explicit decision rather than a side effect.
Not included
No documentation change. The Slack documentation describes the CI-executed path, which is what users get while the flag is off, and describing flag-only behaviour beside it would mislead. Docs land with the rollout.
References
- Depends on: !255094 (merged)
- Duo Workflow Service side, required for a turn to end on
input_required: gitlab-org/modelops/applied-ml/code-suggestions/ai-assist!6897 (merged)
Deployment order
Two ordering constraints, both one-directional:
- This MR adds the second argument to
ServerSideTurnWorker.perform_async. Perdoc/development/sidekiq/compatibility_across_updates.md, it must not deploy in the same release as the MR that added the parameter. - The
environment: chatcommit should reach production before the Duo Workflow Service change. Rails first is harmless; the reverse produces a to-do and an email per Slack message.
Screenshots or screen recordings
Not applicable: no UI change. The user-visible effect is in Slack, where a follow-up mention is answered with the earlier exchange in context.
How to set up and validate locally
-
Turn the flag on for yourself:
Feature.enable(:slack_duo_api_flow) -
Check out the paired Duo Workflow Service branch,
feature/slack-assistant-human-input, so a turn ends oninput_requiredrather than running to completion. -
Mention the bot in a Slack channel and wait for the answer.
-
Mention it again in the same thread, referring back to the first answer without repeating it, for example "and who maintains it?".
-
Confirm the follow-up is answered in context, and that both turns belong to one session:
Ai::DuoWorkflows::Workflow.where(workflow_definition: 'slack_assistant/v1') .order(id: :desc).limit(3) .pluck(:id, :status, :goal)Before this change each mention produced a row. Now the thread has one row whose checkpoint count grows per turn, sitting at
input_requiredbetween turns. -
Confirm no to-do or email was raised for either turn.
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.