ADR 0010 — Slack as the two-way daily surface (Socket Mode bridge)
Context
The daily loop needed a push channel (roadmap 1.3) and Kelyn wants to talk back — record verdicts, request drafts, query memory — from the same place the digest lands. Options: email (one-way), a web dashboard (deferred, Tier 6), or Slack.
Inbound Slack requires receiving events. The Events API over HTTP needs a public HTTPS endpoint — wrong for a pipeline that runs on a laptop. Socket Mode holds an outbound websocket instead: no ingress, no tunnel.
Decision
slackbridge/ package, split by dependency weight:
- Push path is dependency-free.
SlackClientspeaks the Web API with httpx (same pattern as the OpenRouter client):chat.postMessagewith Block Kit bodies for the digest and next-actions pushes. Works in cron with zero optional deps. - Pull path is an optional extra. The Socket Mode listener
(
python -m slackbridge) needsuv sync --extra slack(slack-sdk), import-guarded like anthropic/mem0. It turns messages (verdict 123 apply,actions,prep 45,recall gitlab) into the same Storage/runner/memory calls the CLI and MCP server make — a third thin adapter, zero business logic. - Safety rules in the listener: ack every envelope before processing
(Slack redelivers un-acked envelopes — that would double-execute
billed commands); ignore bot messages and subtypes (loop protection);
honor
SLACK_CHANNELas a conversation filter; every command handler catches its own errors and replies with a message — a typo can never kill the daemon. - Karani drafts, Kelyn sends. The bridge posts materials and worklists into Slack; it never messages anyone else or submits anything on his behalf (vision non-goals).
Consequences
- Positive: digest, fast-lane alerts, and follow-up reminders arrive where Kelyn already is; reacting takes one reply, feeding verdicts and memory with near-zero friction.
- Positive: command dispatch is a pure function
(
handle_command) — fully testable without slack-sdk or a socket. - Negative: a third surface to keep in sync with CLI/MCP. Mitigation: same rule as ADR 0008 — capability lands in Storage/runners first.
- Negative: Socket Mode needs a long-running process. Acceptable — it can run under launchd next to the cron loop; pushes still work when it’s down.
- Neutral: requires a one-time Slack app setup (Socket Mode on,
chat:write+im:historyscopes,message.imevent subscription); documented inslackbridge/listener.pyand.env.example.
Last updated on