Loading
Wire Slack adapter into live progress streaming
What does this MR do and why?
While a Duo Developer workflow runs from a Slack @mention, the user now sees a live checklist in the thread that updates as the agent works.
- As soon as we receive the mention, we post an acknowledgement task card ("Working on your request") so the thread immediately shows something is happening.
- When the agent writes or updates its todo list (
todo_write), we render the plan as a native Slackplanblock and edit that same message in place. Tasks are shown upfront aspendingand advance throughin_progress→completeas the agent goes. - The agent's current reasoning (its "inner monologue") is shown as a markdown block below the plan, so the user gets readable narration even when the raw tool calls aren't meaningful on their own.
- When the workflow finishes, the message is replaced with the final answer plus a link to the GitLab session.
Why a few things are the way they are (handover notes)
These are the non-obvious decisions, written down so nobody has to rediscover them:
- We use a
planblock, nottask_cardblocks.planandtask_cardare mutually exclusive in Slack's Block Kit. Theplanblock supports all statuses includingpending, so tasks are shown upfront and advance as the agent works. - The status value is
complete, notcompleted. Easy to get wrong —completedis rejected. Confirmed in Block Kit Builder. - We post the acknowledgement early, on request received (before the workflow exists). The workflow takes ~10–20s to spin up its CI container. If we waited for that, the first couple of todo updates would be lost. Posting early means the Slack message ID exists from the very first checkpoint, so no progress is dropped — and the user sees feedback right away.
- We render from the full current state, not just "what changed." The progress reader hands the adapter the whole todo list every time (a cumulative snapshot), not just the new entries. Slack edits replace the entire message, so if we only had the delta, a tick that carried only an agent message (no todo) would wipe the task list. The reader still also exposes the pure delta (
new_messages) for any future append/stream consumer. - The monologue is scoped to the current step (reasoning since the latest
todo_write). Without this, a finished task's reflection briefly bled onto the next task's card. - Latency was mostly a red herring. Updates felt slow/bunched, but the real cause was
invalid_blocksrejections from the oldtask_cardapproach — most edits were silently dropped. With theplanblock, the standard 2s debounce is plenty; no special scheduling tricks were needed.
Slack write errors now also log the blocks payload, so a future invalid_blocks shows exactly what we sent (the error's json-pointer indexes straight into it).
References
- Issue: #602540 (closed)
- Streaming backbone MR: !241689 (merged)
- Original PoC: !238628 (closed)
How to set up and validate locally
- Configure a Slack integration with a Duo Developer workflow.
- Trigger a workflow by
@mention-ing the app with a task that uses a todo list. - Watch the thread:
- an acknowledgement card appears immediately,
- all tasks appear upfront as
pending, then advance (in_progress→complete) as the agent works, with its current reasoning shown below the plan, - the final answer replaces the card when the run finishes.
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.
Edited by Thomas Schmidt