I built Quorum because teams keep re-deciding things they already settled. Someone asks "wait,did we land on Postgres or Mongo?" and nobody can find the thread where it was decided — the callis real, but it's buried in scrollback. Quorum watches for the moment a Slack conversation actually*reaches* a decision, drafts a clean record of it, and waits for one person to hit Approve. Onceapproved, it files that decision to a canvas and a #decision-log channel — and from then on youjust ask /decisions what did we decide about X? and get the answer back, with a link to exactlywhere it happened. It's the institutional memory I always wished Slack had.
Inspiration
Teams make their most important calls in Slack — which database, which vendor, the postmortem'sroot cause, the policy change — and then those decisions vanish into scrollback. A week latersomeone re-litigates a settled question because nobody can find where it was decided. Existingtools summarize conversations; none capture durable decision provenance (what was decided,why, by whom, superseding what) and make it retrievable later. We wanted the workspaceitself to remember its decisions.
What it does
Quorum is a Slack agent with a four-beat loop:
- Detect — the
📌 Capture decisionmessage shortcut on any thread, or a proactive,spam-safe nudge that classifies messages and offers a "capture this decision?" button. - Draft — an LLM extracts a structured Decision Record (title, decision, rationale,participants, tags, source link) from the thread.
- Approve — a durable human-in-the-loop step posts an Approve / Edit / Discard card and*suspends for up to 7 days at zero compute*, resuming the instant someone clicks Approve.
- File & recall — the approved record is filed to a canonical Decision Log Canvas and a
#decision-logchannel; later,/decisions what did we decide about X?(or@Quorum) returnsa synthesized answer with permalink citations back to the records.
Storage is Slack-native — your decisions live in your workspace, no external database.
How we built it
A hybrid architecture that matches each primitive to the shape of the work:
- Capture pipeline → deterministic Vercel Workflow (
"use workflow"): fetch thread → draft →durable approval hook (raced againstsleep("7d")for expiry) → file. Reliability andsuspend-for-approval are the whole point. - Q&A → a Vercel
DurableAgent: branching reasoning over two tools.
All three required Slack technologies are load-bearing:
- Slack AI / Agent — the
DurableAgent+ the Assistant/agent app surface. - MCP server integration — the Q&A agent searches the workspace through the hosted Slack MCPserver (
mcp.slack.com,slack_search_public_and_private), called inside a workflow step. - Real-Time Search (RTS) API —
assistant.search.contextretrieves curated Decision Records,permission-scoped to the asker, with permalink citations.
Stack: pnpm monorepo · workflow@4.3.1 + @workflow/ai · Next.js 15.5 + @slack/bolt +@vercel/slack-bolt (VercelReceiver, HTTP events) · AI SDK v6 + @ai-sdk/mcp · zod v4 · vitest.Deployed on Vercel (iad1); 19 unit + 2 workflow-integration tests.
Challenges we ran into
Every one of these was found and fixed against the live deployment:
@vercel/slack-bolt+ Bolt init. The receiver callsapp.init()itself, so the BoltAppmust be built withdeferInitialization: true— otherwise Bolt throws "no token provided" evenwith a valid token passed. Cost us the longest debugging session.- Slack MCP server is scope-gated. It only exposes tools matching the token's scopes — oursearch-scoped token surfaced only search tools, so canvas/channel writes go through the Bot WebAPI while MCP powers the agent's workspace search.
- RTS
action_token.assistant.search.contextwith a bot token needs anaction_token(only from a live event) →invalid_action_token; the user token doesn't, so record searchuses it. - WDK bundler discipline. The workflow VM inlines the static import graph, so Node-only deps(
node:crypto,@ai-sdk/mcp) must stay out via subpath exports + dynamicimport()inside steps. - pnpm on Vercel. WDK's generated route couldn't resolve
@vercel/oidcuntil we set.npmrc node-linker=hoisted.
Accomplishments we're proud of
A complete, polished, novel agent that ships the whole loop — detect → draft → durable approval→ file → cited recall — with a clean before/after demo, all three required techs genuinely used,durable human-in-the-loop that survives redeploys, and a test suite that proves the workflowactually suspends and resumes.
What we learned
The Slack MCP server is best understood as a scope-gated search/RAG surface, RTS as compliantpermission-scoped retrieval, and Vercel Workflow as the right tool the moment an agent needs to*wait* on a human. Matching control-flow shape (linear pipeline vs. branching agent) to the rightprimitive is what makes the system both reliable and demoable.
What's next
A native Slack Assistant-pane handler; "supersedes" chains that auto-mark older decisions; adecisions digest; and multi-workspace distribution toward the Slack Marketplace.
Built with
slack, slack-bolt, vercel, vercel-workflow, model-context-protocol, real-time-search-api,typescript, next.js, ai-sdk, anthropic-claude, zod, vitest, pnpm
Links
- Live: https://quorum-slack-agent.vercel.app
- Repo: https://github.com/OrionArchitekton/quorum-slack-agent
- Architecture diagram:
docs/architecture.png
Built With
- ai-sdk
- anthropic
- mcp
- next.js
- pnpm
- real-time-search-api
- slack
- slack-bolt
- typescript
- vercel
- vercel-workflow
- vitest
- zod
Log in or sign up for Devpost to join the conversation.