Skip to content

feat(operator): slack-personal channel — your own Slack account via an MCP backend - #1264

Merged
edwin-zvs merged 2 commits into
mainfrom
spec/slack-personal-channel
Aug 18, 2026
Merged

feat(operator): slack-personal channel — your own Slack account via an MCP backend#1264
edwin-zvs merged 2 commits into
mainfrom
spec/slack-personal-channel

Conversation

@edwin-zvs

@edwin-zvs edwin-zvs commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Implements the slack-personal operator channel end-to-end, together with its design specs (0201: the channel kind; 0202: shared-identity autonomy). The channel acts through the user's own Slack account via a user-configured MCP backend — no Slack app, no xapp-/xoxb- tokens, no workspace-admin install — and everything it posts appears as the user, which drives every safety default below.

How it works

  • operator/mcp.rs — a minimal stdio MCP client (spawn via sh -c, initialize handshake, tools/call). Classic daemon logic; no agent or model turn anywhere in the channel.
  • operator/slack_personal.rs — the adapter. Polls the backend's slack_sweep_messages as a simulated event subscription with its own timestamp cursor (started at "now": history while no daemon ran is never replayed through an operator that speaks as you). Deliveries route into the shared operator router under the same workspace:channel:thread_ts key as the Socket Mode kind; thread context is fetched and fenced with the same untrusted-text boundary; outstanding deliveries survive daemon restart via the same reconciliation path — except failures are logged and cleared, never posted, because failure prose would appear as the user's own words.
  • Scope policy (spec 0202 v1 slice) — DMs are in scope by default (trigger=dm); channel messages need trigger=all plus an explicit channel-allowlist entry (empty allowlist = no channels, inverted from the bot kind on purpose). The user's own messages trigger the operator only in their DM with themself — the private command line.
  • Response modesdraft (default: the reply lands in the user's Slack drafts; their deliberate send makes the words theirs) and auto (posts directly, with an 🤖 sent by an agent disclosure marker on by default; opt-out is explicit). Echo suppression is by recorded sent-ts, never by author.
  • Tool contract — the channel is defined against four JSON-in-text tools (slack_sweep_messages, slack_read_thread, slack_send_message, slack_create_draft); any conforming MCP server is a backend. examples/fake_slack_mcp.rs is a contract-conforming fake for tests and hand-driven runs.
  • Surfaces — protocol fields + published option values, supervisor lifecycle (outbound task, restart on material edit, stop on pause/disable), TUI channel editor (third kind in the cycle, per-field help including the identity warning), and web UI form. Kind-mismatched options are refused per spec 0180.

Testing

  • Unit: MCP client protocol (duplex fakes: handshake, tool errors, non-JSON rejection, dead-backend fast-fail, a real sh subprocess spawn), sweep policy (scope/trigger/self rules, cursor advance over rejections, echo dedup, ts instant-compare at f64's edge), config layer (save/report/preserve-on-omit, kind-mismatch refusals, poll floor, safe-default snapshot), supervisor desired-set, TUI editor cycle + defaults.
  • Full workspace suite: green (cargo test --workspace, exit 0).
  • Live end-to-end against an isolated daemon with a real claude harness and the fake backend: DM swept → routed session created → reply delivered as a Slack draft with correct channel/thread; config hand-edit picked up by the file watcher (task rebound) → second DM auto-sent with the disclosure suffix. (Reply text happened to be a 529-overload notice from the API during the run — the harness's genuine output; the channel mechanics were exact.)

Not in this slice (per spec, follow-ups)

  • Observe / acknowledge / ask-to-send rungs of the autonomy ladder; grace period; per-scope overrides; keyword/mention triggers.
  • Additional backend adapter profiles (claude.ai proxy via OAuth, korotovsky-style servers) — the contract + fake establish the seam.

🤖 Generated with Claude Code

Records two accepted design decisions ahead of implementation:

- 0201: a second Slack channel kind, slack-personal, that acts through
  the user's own account via an MCP backend chosen by tool contract
  (no Slack app, no bot token, no required vendor account), polling as
  simulated event subscription over the shared operator router.
- 0202: per-scope autonomy for the shared identity — trigger policy plus
  a response-mode ladder (observe / acknowledge / draft / ask-to-send /
  auto-send), disclosure on by default, grace-period yielding.
… via an MCP backend

Implements specs 0201/0202:

- operator/mcp.rs: a minimal stdio MCP client (initialize handshake,
  tools/call with JSON-in-text results, close-aware pending map). No
  agent or model turn is involved; the daemon drives the backend with
  classic logic.
- operator/slack_personal.rs: the channel adapter. Polls the backend's
  slack_sweep_messages as a simulated event subscription, keeps its own
  timestamp cursor, dedups echoes of its own posts by recorded ts (never
  by author — the user's self-DM is a private command line), and routes
  deliveries into the shared operator router under the same
  workspace:channel:thread key as the Socket Mode kind.
- Response modes per spec 0202: draft (default — the reply lands in the
  user's Slack drafts) and auto (posts directly, with an agent-disclosure
  marker on by default). Failed turns are logged and cleared, never
  posted: this channel's words appear as the user's own.
- Scope policy: DMs are in scope by default (dm trigger); channel
  messages need trigger=all plus an explicit allowlist entry — an
  unconfigured scope forwards nothing.
- Protocol, TUI channel editor, and web UI grow the new kind and its
  options (mcp_command, trigger, response_mode, disclosure,
  poll_interval_secs), refused on kinds that do not read them (0180).
- examples/fake_slack_mcp.rs: a contract-conforming fake backend for
  tests and hand-driven end-to-end runs.
@edwin-zvs edwin-zvs changed the title spec: slack-personal operator channel and shared-identity autonomy feat(operator): slack-personal channel — your own Slack account via an MCP backend Aug 18, 2026
@edwin-zvs
edwin-zvs merged commit 437e08d into main Aug 18, 2026
1 check passed
@edwin-zvs
edwin-zvs deleted the spec/slack-personal-channel branch August 18, 2026 20:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant