Inspiration
Every team has lived this: someone proposes vendor X, three people reply "sounds good," and thirty seconds later it's decided. Nobody wanted to be the person slowing things down. That's groupthink, and it's expensive - the worst decisions are often the ones that felt the most agreeable in the moment. We wanted the one voice that's structurally missing from most Slack channels: a fair, well-prepared dissenter that isn't afraid to be the "no" in a room full of "yes." Not a contrarian for its own sake - a red team on retainer.
What it does
Devil's Advocate quietly reads the flow of a channel. When it detects the team rapidly converging on one concrete decision - a run of agreement with no real pushback - it jumps into that decision's thread with a structured Block Kit message:
- a one-line restatement of the decision it's about to challenge,
- exactly three substantive objections/risks - not nitpicks, each one something that could actually change the call,
- a closing note that makes its intent explicit: "Not trying to kill it - just want you deciding with your eyes open."
It's deliberately disciplined about when it speaks:
- Confidence-gated: it only fires when its convergence analysis clears a high-confidence threshold, so casual chat, open debate, and Q&A are left alone.
- Cooldown + anti-spam: a per-channel cooldown and an in-flight lock mean it posts at most once per decision and never dogpiles a thread.
- Per-channel opt-in/out:
/devil on,/devil off,/devilto check status - teams stay in control.
How we built it
- Slack-native, no public server: built on Slack Bolt in Socket Mode, so it installs into a workspace with zero inbound webhooks or public URL - a big deal for security-conscious teams.
- Clean separation of concerns: all decision logic lives in
core.pywith zero Slack SDK dependency - it's fully unit-tested (test_core.py) without tokens or network.app.pyis a thin Bolt wiring layer. - Evidence-grounded via MCP: the objections aren't model recall - they're grounded in real external evidence.
gather_evidence()acts as an MCP client: it launches a local web-search MCP server over stdio (the no-API-key DuckDuckGo MCP server, started withuvx), searches for the risks/downsides of the exact decision, then feeds those real results to the LLM so each of the three objections cites a source URL. If the MCP search ever fails, it degrades gracefully to LLM-only recall so the bot never goes silent. - Two-step LLM pipeline:
detect_convergence()classifies whether the recent rolling window (last 20 messages/channel) shows rubber-stamping and returns a structured JSON verdict + confidence;gather_evidence()then runs the MCP-search-then-reason step to produce exactly three sourced counter-arguments. Reasoning runs on Gemini 2.5 Flash on GCP. - Concurrency-correct: Slack dispatches events on a thread pool, so a burst of "sounds good!" messages can enter the pipeline simultaneously. A single gate lock does an atomic enabled/cooldown/in-flight check-and-claim before the slow LLM calls, so exactly one dissent is posted per consensus burst (we actually hit and fixed a "3 dissents per decision" race).
- Structured UX: the dissent is rendered in Block Kit - restatement, divider, three numbered risks, context footer - so it reads as a considered red-team memo, not a wall of text.
Challenges we ran into
- Knowing when to speak - and when to shut up. Tuning convergence detection so it fires on real rubber-stamping but stays quiet during healthy debate was the core design problem; we solved it with a confidence threshold plus a "still-open debate = not converging" rule in the prompt.
- The concurrency race. Multiple agreement messages arriving at once each passed the cooldown gate before the first dissent posted, producing triple replies. Fixed with an atomic gate lock + in-flight set claimed before the LLM calls.
- Being disagreeable without being a jerk. The tone had to be firm but collegial - challenging the decision, never the people.
Accomplishments that we're proud of
- A genuinely novel Slack agent - almost every bot exists to help you go faster; this is the only one whose job is to make you pause and think.
- Testable, concurrency-safe architecture with logic decoupled from Slack.
- A polished, restrained Block Kit UX that respects the channel.
What we learned
- Convergence/groupthink is surprisingly detectable from message flow alone.
- In a chat product, restraint is a feature: the value is as much in the 99% of the time it stays silent as in the 1% when it speaks.
What's next for Devil's Advocate
- Add the Slack Real-Time Search API so objections also cite the team's own past decisions and threads ("your team already debated this in March"), alongside the live external web evidence MCP already provides.
- Adjustable sensitivity (
/devil sensitivity 1-3), per-topic muting, and a weekly "decisions we flagged" digest.
Built with
Slack Bolt, Socket Mode, Block Kit, Python, MCP (Model Context Protocol - DuckDuckGo web-search server over stdio), Google Gemini 2.5 Flash, GCP.
Log in or sign up for Devpost to join the conversation.