Skip to content

ADR-0019: Slack autonomous agent routing — bidirectional conversation and multi-turn dispatch

Guide

ADR-0019: Slack autonomous agent routing — bidirectional conversation and multi-turn dispatch

  • Status: Backlogged · Date: 2026-06-02 · Approved by: {{PRINCIPAL_NAME}} (Principal)
  • Authors: CPTO, AI Systems Director
  • Related: ADR-0015 (Slack ingest), ADR-0017 (Slack retention policy), ADR-0018 (outbound agent posting)

Context

ADR-0018 established the outbound posting mechanism: agents post to Slack via a shared single-bot model (postToSlack utility, SLACK_OAUTH_TOKEN, display name carried in message body). This covers one direction — agent-to-Slack — but it is passive and one-shot. An agent can narrate its work; it cannot receive a reply and respond to it.

The Principal’s desired end state includes the ability to DM an agent directly in Slack, have that agent respond in the same thread, and continue the conversation without switching to the Claude Code terminal. The shared bot model of ADR-0018 does not enable this because:

  1. No listener. The current Vercel Function at /api/slack/events ingests messages into Neon (chatter_messages) for the intranet feed. It does not route incoming messages to agent logic or invoke the Claude API.

  2. No routing layer. There is no mechanism that maps an @mention, a DM bot event, or a channel keyword to a specific agent’s system prompt. The bot has a single identity and no awareness of the 44-agent roster.

  3. No session state. A stateless Vercel Function processes each Slack event in isolation. Multi-turn conversation requires a session store keyed on the Slack thread, user, and agent — persisting messages, context windows, and tool-call history across events.

  4. No Claude API dispatch. The ingest function has no path to call @anthropic-ai/sdk. Wiring Claude API calls into the events webhook couples a real-time path with potentially long-running LLM inference — a latency and timeout mismatch for Vercel’s default 10 s function limit.

Building true bidirectional autonomy is therefore a distinct infrastructure project, not a configuration change on top of ADR-0018.


Decision deferred

The following system would be built if this ADR were promoted from Backlogged to Proposed and subsequently Accepted. No part of it is built during the backlog period.

What the autonomous routing system would look like

1. Persistent Socket Mode listener. A long-running process (not a Vercel Function) maintains a WebSocket connection to the Slack Socket Mode API. On each incoming event — DM, app_mention, or targeted thread reply — the listener determines which agent is addressed, retrieves or initializes its session state, and dispatches to the Claude API.

2. Agent routing table. A routing layer maps incoming event context to an agent system prompt. The primary signals for routing are:

Signal Example Routes to
DM to bot Principal opens a DM with the {{PROJECT_NAME}} bot Default agent (PA or configurable)
@bot agent-name in channel @{{project-slug}} engineering-director ... Agent matching engineering-director slug
Thread continuation Reply in an existing thread Same agent that opened the thread
Keyword or channel context Message in #incidents SRE agent

The routing table is a configuration file (e.g. config/agent-routing.json) versioned in the repo, not hardcoded in the listener.

3. Claude API dispatch. For each routed event, the system loads the target agent’s system prompt from .claude/agents/<slug>.md, reconstructs the conversation history from the session store, appends the new user message, and calls @anthropic-ai/sdk (messages.create). The response is posted back to the originating thread via postToSlack (ADR-0018 utility) or directly via chat.postMessage.

4. Session state store. Multi-turn context is persisted in Neon (existing infrastructure, ADR-0014) or Upstash Redis (lower-latency, existing in the stack). The session record holds: Slack thread identifier, agent slug, message history array, token count, and a TTL after which the session is closed and context is discarded. A session closed by TTL produces a “session ended” message in the thread before clearing.

5. Agent-to-agent DMs. The same routing layer handles agent-initiated DMs to other agents by treating the calling agent as the user-side of a new session targeted at the recipient agent. This enables supervised agent coordination over Slack threads rather than requiring a shared Claude Code terminal session.

Options considered

Option A — Socket Mode persistent listener (dedicated host). A Node.js process running on a small always-on host (e.g. Railway, Fly.io, or a VPS) maintains the Socket Mode WebSocket. Routing, dispatch, and session state all live in this process. Preferred direction: this avoids Vercel’s function timeout and execution cost model for long-running inference. Trade-off: introduces a new hosting surface outside the current Vercel + Neon stack, requires a deployment pipeline, and needs uptime monitoring.

Option B — Queue-backed serverless approach. Incoming Slack events are enqueued (e.g. Upstash QStash or a Neon-backed job queue). A Vercel Function processes jobs asynchronously, calling the Claude API per job and posting the response when complete. Avoids a persistent process. Trade-off: Vercel function timeout still applies per job; true multi-turn requires the queue to hold ordered context across jobs; Slack’s 3-second acknowledgement window still requires an immediate ACK endpoint separate from the job processor. More moving parts than Option A for equivalent capability.

Option C — Hybrid (ACK endpoint + async worker). A minimal Vercel Function immediately ACKs every Slack event and enqueues it. A separate persistent worker (same as Option A) processes the queue, handles routing, calls Claude, and posts replies. Cleaner separation of concerns than Option B; functionally equivalent to Option A in hosting requirements. If Option A is chosen, Option C is a natural evolution rather than a distinct build.

The preferred direction is Option A, with Option C as the expected final shape once the ACK/processing split is needed at scale. No option is selected; this is a direction preference, not a binding decision.


Promotion gate

This ADR moves from Backlogged to Proposed when the following condition is met:

The Principal attempts to have a back-and-forth conversation with an agent via Slack (not the Claude Code terminal) more than 3 times in a week and finds the terminal-only flow limiting.

Measurement: Principal self-reports. CPTO reviews at each quarterly org review. If the gate has tripped, CPTO drafts the Proposed revision; Principal approves before any build work begins.


Non-goals during backlog period

The following are explicitly blocked until this ADR is promoted:

  • Creating per-agent Slack bots or per-agent SLACK_BOT_TOKEN credentials
  • Deploying or prototyping a Socket Mode listener or equivalent persistent process
  • Modifying /api/slack/events to dispatch to Claude API or agent system prompts
  • Adding DM routing, app_mention routing, or thread-continuation logic to any existing Vercel Function
  • Adding im:write, mpim:write, or any DM-related OAuth scopes to the existing Slack app
  • Any agent-to-agent message routing prototype in Slack, regardless of mechanism

The shared single-bot model from ADR-0018 remains the only sanctioned Slack posting mechanism during the backlog period. Agents continue to narrate work in natural voice via postToSlack; they do not receive or respond to messages in Slack.


Consequences

Positive (deferring):

  • No premature infrastructure: a persistent listener and routing layer are non-trivial to operate and monitor. Deferring avoids operational overhead until there is demonstrated need.
  • The backlog record preserves the design intent and preferred architecture. If the gate trips, the options analysis above is already done — the Proposed revision can reference this ADR rather than re-deriving the context.
  • The promotion gate is grounded in observed Principal behavior, not speculation. Building before the gate trips risks shipping infrastructure that goes unused.

Negative (deferring):

  • Agent interaction remains terminal-only during the backlog period. Conversations with agents require opening a Claude Code session, not a Slack DM.
  • The org’s chatter feed stays one-directional (agents narrate; Principal does not converse back through Slack). This is a known and accepted limitation during Phase 2.

Follow-on decisions (if promoted):

  • Select a hosting provider for the persistent listener (ADR required).
  • Define the agent routing configuration schema and initial routing table.
  • Specify the session state schema and TTL policy in Neon or Upstash.
  • Determine whether agent-to-agent DMs are in scope for the initial build or a subsequent milestone.
  • Security review: the routing layer will have broad access to agent system prompts and the Claude API key — threat model needed before build begins.