Missing transport — webhook-only deployment forces public ingress.
Current state
SlackAdapter in src/chat_sdk/adapters/slack/adapter.py dispatches exclusively via HTTP webhooks. disconnect() is a no-op (adapter.py:375) because there is no persistent connection to tear down. Consumers running behind a firewall, on a laptop, or in a CI tunnel need an external tunneling service (ngrok, Cloudflare Tunnel, etc.) to receive events — or they can't develop locally at all.
Slack offers Socket Mode as a first-class alternative: the bot opens an outbound WebSocket to wss://wss-primary.slack.com/link/ and receives the same events_api, interactive, and slash_commands envelopes that would otherwise be POSTed to a webhook. No public URL required.
slack_sdk.socket_mode.aiohttp.SocketModeClient ships in slack_sdk>=3.x (already our transitive dep via slack_sdk.web.async_client.AsyncWebClient). Wiring is mechanical: open the socket, parse each envelope, dispatch through the existing _handle_events_api_event / _handle_interactive_payload / _handle_slash_command paths, ack the envelope.
Why it matters
- Local dev: today every contributor needs a tunnel. Socket Mode removes that friction.
- Private bots: enterprise deployments that don't want to expose a public webhook URL.
- Rapid prototyping: CI-run integration tests can't hit a webhook without infrastructure; they can hit a socket.
Design
Add a mode field to SlackAdapterConfig:
mode: Literal["webhook", "socket"] = "webhook"
app_level_token: str | None = None # required when mode == "socket"
On initialize(), if mode == "socket":
- Construct
SocketModeClient(app_token=app_level_token, web_client=AsyncWebClient(...)).
- Register an envelope handler that routes to the existing dispatch paths in
adapter.py (the same ones handle_webhook uses after parsing).
- Call
await client.connect().
- On
disconnect(), await client.disconnect().
The existing handle_webhook path stays untouched; the socket path is a parallel entry point to the same dispatchers.
Signature verification (crypto.py) is webhook-only and should skip on the socket path — Slack already authenticated the socket via the app-level token.
Acceptance
Missing transport — webhook-only deployment forces public ingress.
Current state
SlackAdapterinsrc/chat_sdk/adapters/slack/adapter.pydispatches exclusively via HTTP webhooks.disconnect()is a no-op (adapter.py:375) because there is no persistent connection to tear down. Consumers running behind a firewall, on a laptop, or in a CI tunnel need an external tunneling service (ngrok, Cloudflare Tunnel, etc.) to receive events — or they can't develop locally at all.Slack offers Socket Mode as a first-class alternative: the bot opens an outbound WebSocket to
wss://wss-primary.slack.com/link/and receives the sameevents_api,interactive, andslash_commandsenvelopes that would otherwise be POSTed to a webhook. No public URL required.slack_sdk.socket_mode.aiohttp.SocketModeClientships inslack_sdk>=3.x(already our transitive dep viaslack_sdk.web.async_client.AsyncWebClient). Wiring is mechanical: open the socket, parse each envelope, dispatch through the existing_handle_events_api_event/_handle_interactive_payload/_handle_slash_commandpaths, ack the envelope.Why it matters
Design
Add a
modefield toSlackAdapterConfig:On
initialize(), ifmode == "socket":SocketModeClient(app_token=app_level_token, web_client=AsyncWebClient(...)).adapter.py(the same oneshandle_webhookuses after parsing).await client.connect().disconnect(),await client.disconnect().The existing
handle_webhookpath stays untouched; the socket path is a parallel entry point to the same dispatchers.Signature verification (
crypto.py) is webhook-only and should skip on the socket path — Slack already authenticated the socket via the app-level token.Acceptance
SlackAdapterConfig(mode="socket", app_level_token=...)connects and receives eventsinitialize()/disconnect()lifecycle manages the socket cleanly (reconnect on drop, clean shutdown on signal)on_message,on_mention,on_action,on_slash_command, modal submissions) fire under socket mode with equivalent payloadsSocketModeClient, feed an envelope per event type, assert dispatchdocs/ARCHITECTURE.mdgets a short section on webhook-vs-socket deployment choiceCONTRIBUTING.mdfor local dev with Socket Mode