# Slack Integration

Connect Nomos to Slack so it can respond to DMs and @mentions in channels, read messages, search history, and act as you across multiple workspaces. Messages stream in real-time — users see a "_Thinking..._" indicator followed by progressive text updates.

## Architecture

Nomos uses two components for Slack:

| Component           | Purpose                                                                                       | How it connects                   |
| ------------------- | --------------------------------------------------------------------------------------------- | --------------------------------- |
| **Nomos daemon**    | Listens for DMs/@mentions, responds in real-time (bot mode or user mode)                      | Socket Mode via `SLACK_APP_TOKEN` |
| **nomos-slack-mcp** | Gives the agent proactive Slack tools (read channels, send messages, search, reactions, etc.) | External MCP server over stdio    |

Both can work independently or together. The daemon handles real-time messaging; `nomos-slack-mcp` gives the agent tools to interact with Slack proactively.

### Tool-calling backend: in-process vs native MCP

How the agent _acts on_ Slack (search/read/send/react) is selected by
`NOMOS_SLACK_BACKEND`:

| Value           | What the agent uses                                                             |
| --------------- | ------------------------------------------------------------------------------- |
| `mcp` (default) | **Slack's official native MCP server** (`https://mcp.slack.com/mcp`) over OAuth |
| `tools`         | legacy in-process Slack Web API tools (`slack_*`), using xoxc/xoxp tokens       |

The backend only changes tool-calling. **Receiving** DMs/@mentions and the
real-time streaming reply always run through the daemon's channel adapters — an
MCP server can't receive messages. See [Native MCP backend](#native-mcp-backend-mcpslackcom).

## Quick Start

### Option A: App Manifest (recommended)

Import the included manifest to create a pre-configured Slack app:

1. Go to [api.slack.com/apps](https://api.slack.com/apps) → **Create New App** → **From an app manifest**
2. Select your workspace
3. Paste the contents of [`slack-app-manifest.yaml`](../../slack-app-manifest.yaml) (or `.json`)
4. Review and click **Create**

> **Note:** The manifest includes the local redirect URLs used by the OAuth flows — `https://localhost:8934/oauth/callback` (Settings UI), `:9876` (`nomos slack auth --oauth`), and `:9877` (`nomos slack connect-mcp`, the native-MCP connect). Each uses an ephemeral self-signed certificate. If you need a different redirect URL/port, update it in both the manifest and your Slack app's OAuth settings.

5. Go to **Basic Information** → **App-Level Tokens** → generate a token with `connections:write` scope — this is your `SLACK_APP_TOKEN` (`xapp-...`)
6. Go to **Install App** → install to your workspace → copy the **Bot User OAuth Token** (`xoxb-...`) — this is your `SLACK_BOT_TOKEN`

### Option B: Manual Setup

1. Go to [api.slack.com/apps](https://api.slack.com/apps) → **Create New App** → **From scratch**
2. Follow Steps 1-6 below to configure scopes, events, and Socket Mode

## Step 1: Enable Socket Mode

1. In the app settings sidebar, go to **Socket Mode**
2. Toggle **Enable Socket Mode** to On
3. Create an App-Level Token:
   - Name it `socket-token`
   - Add the scope `connections:write`
   - Click **Generate**
4. Copy the token (`xapp-...`) — this is your `SLACK_APP_TOKEN`

## Step 2: Configure Bot Scopes

1. Go to **OAuth & Permissions** → **Bot Token Scopes**
2. Add:

| Scope               | Purpose                           |
| ------------------- | --------------------------------- |
| `app_mentions:read` | Receive @mention events           |
| `channels:history`  | Read messages in public channels  |
| `channels:read`     | List public channels              |
| `chat:write`        | Send and update messages          |
| `files:write`       | Upload files                      |
| `groups:history`    | Read messages in private channels |
| `groups:read`       | List private channels             |
| `im:history`        | Read direct messages              |
| `im:read`           | View DM metadata                  |
| `im:write`          | Send direct messages              |
| `pins:write`        | Pin and unpin messages            |
| `reactions:write`   | Add emoji reactions               |
| `users:read`        | Look up user names                |

## Step 3: Enable App Home & Direct Messages

1. Go to **App Home** → **Show Tabs**
2. Check **Messages Tab**
3. Enable **"Allow users to send Slash commands and messages from the messages tab"**

## Step 4: Enable Event Subscriptions

1. Go to **Event Subscriptions** → toggle **Enable Events** to On
2. Under **Subscribe to bot events**, add:
   - `app_mention`
   - `message.im`
3. Click **Save Changes**

## Step 5: Install the App

1. Go to **Install App** → **Install to Workspace**
2. Copy the **Bot User OAuth Token** (`xoxb-...`) — this is your `SLACK_BOT_TOKEN`

> Any time you change scopes or events, you must reinstall the app.

## Step 6: Configure

Add tokens to `~/.nomos/.env` (or via Settings UI at `localhost:3456/integrations/slack`):

```bash
SLACK_APP_TOKEN=xapp-...    # App-Level Token (Socket Mode)
SLACK_BOT_TOKEN=xoxb-...    # Bot User OAuth Token
```

### Optional: Restrict to Specific Channels

```bash
SLACK_ALLOWED_CHANNELS=C01ABC123,C02DEF456
```

## Step 7: Start the Daemon

```bash
nomos daemon start
# or development mode:
pnpm daemon:dev
```

You should see:

```
[slack-adapter] Running (bot: U0XXXXXX)
[gateway]   Channels: slack
```

## Multi-Workspace Support

Nomos supports connecting to multiple Slack workspaces simultaneously. Each workspace gets its own user token (`xoxp-`) stored encrypted in the database.

### Adding Workspaces

There are three ways to connect a workspace:

#### Option A: Via `nomos-slack-mcp` OAuth (recommended for multi-workspace)

```bash
npx nomos-slack-mcp add-workspace
```

This opens a browser for OAuth authorization. The token is stored in `~/.nomos/slack/config.json`. On the next daemon start (or workspace add via Settings UI/CLI), tokens are synced to the database.

> **Note:** Multi-workspace OAuth requires your Slack app to have [distribution enabled](#enabling-distribution). For your home workspace only, distribution is not needed.

#### Option B: Manual token

If you already have a `xoxp-` user token:

```bash
nomos slack auth --token xoxp-...
```

Or via the Settings UI: **Integrations → Slack → Manual Token** section. This works for any workspace without distribution.

#### Option C: Settings UI OAuth

Authorize workspaces directly from the Settings UI:

1. Configure `SLACK_CLIENT_ID` and `SLACK_CLIENT_SECRET` in **App Configuration**
2. Click **Authorize Workspace**

> Multi-workspace requires [distribution enabled](#enabling-distribution).

#### Option D: Browser Auth (experimental)

> **Experimental** — enabled by setting `NOMOS_BROWSER_AUTH=true`. This feature captures tokens via a Playwright browser session and may not work reliably in all environments.

```bash
NOMOS_BROWSER_AUTH=true
```

When enabled, a "Sign in via Browser" button appears in the Settings UI. It opens a browser window where you sign in to Slack normally — tokens are captured automatically without needing a Slack app or OAuth credentials.

### Enabling Distribution

To connect workspaces other than your home workspace via OAuth (Options A, C), your Slack app must have distribution enabled:

1. Go to [api.slack.com/apps](https://api.slack.com/apps) → your app → **OAuth & Permissions** → **Redirect URLs**
2. Add `https://localhost:8934/oauth/callback` (this is the local HTTPS callback used by both CLI and Settings UI)
3. Go to **Manage Distribution**
4. Check **"Remove Hard Coded Information"**
5. Click **Activate Public Distribution**

> The OAuth flow uses a temporary local HTTPS server with a self-signed certificate. Slack requires HTTPS for distributed apps — the self-signed cert satisfies this requirement for localhost. If you don't want to enable distribution, use Option B (manual token) instead.

### Managing Workspaces

```bash
nomos slack workspaces       # List all connected workspaces
nomos slack remove T01ABC    # Disconnect a workspace
```

### How Tokens Are Stored

- **Database** (source of truth) — tokens encrypted at rest via AES-256-GCM in the `integrations` table
- **Config file** (runtime sync) — `~/.nomos/slack/config.json` is auto-synced from DB for `nomos-slack-mcp` to read
- Tokens are synced whenever workspaces are added/removed and on daemon startup

## nomos-slack-mcp Tools

When `nomos-slack-mcp` is configured (workspaces in DB or `~/.nomos/slack/config.json`), the agent gets proactive Slack tools:

| Tool                                        | Description                            |
| ------------------------------------------- | -------------------------------------- |
| `slack_read_channel`                        | Read recent messages (up to 100)       |
| `slack_read_thread`                         | Read thread replies                    |
| `slack_send_message`                        | Send messages to any channel or thread |
| `slack_edit_message`                        | Edit previously sent messages          |
| `slack_delete_message`                      | Delete messages                        |
| `slack_search`                              | Search workspace messages              |
| `slack_list_channels`                       | List accessible channels               |
| `slack_user_info`                           | Look up user details                   |
| `slack_react`                               | Add emoji reactions                    |
| `slack_pin_message` / `slack_unpin_message` | Pin/unpin messages                     |
| `slack_list_pins`                           | List pinned items                      |
| `slack_upload_file`                         | Upload files to channels               |
| `slack_set_status`                          | Set your Slack status                  |

These tools work with multi-workspace — each tool accepts an optional `workspace` parameter to target a specific workspace.

## Native MCP backend (mcp.slack.com)

Instead of nomos's in-process Slack tools, the agent can use **Slack's official
native MCP server** — a remote (streamable-HTTP) server maintained by Slack that
exposes tools to search messages/files/users/channels/emoji, read channels and
threads, send messages, add reactions, read/write canvases, fetch user info, and
list members. This is the **default** backend (`NOMOS_SLACK_BACKEND=mcp`); the
in-process `slack_*` tools are not registered (the agent sees `mcp__slack__*`). Set
`NOMOS_SLACK_BACKEND=tools` to fall back to the legacy in-process tools.

### Requirements

- **An eligible Slack app.** Slack's MCP only accepts **internal** apps (installed
  in your own workspace) or **Marketplace-published** apps. "Unlisted" apps (public
  distribution on, but not Marketplace-approved) are **rejected** — so for
  testing/dev, keep the app internal and do **not** turn on Public Distribution. A
  workspace admin must approve the MCP integration.
- **OAuth user token.** The native MCP authenticates with a confidential OAuth 2.0
  **user** token (`xoxp-`) — not the `xoxc-` browser tokens nomos captures for the
  in-process tools. nomos mints and stores this token itself and passes it to
  `mcp.slack.com` as a `Bearer` header (the same pattern as the Google MCP).

### Scopes

Add the user scopes in [`slack-app-manifest.yaml`](../../slack-app-manifest.yaml)
(they're already listed there): the granular `search:read.public`,
`search:read.private`, `search:read.im`, `search:read.mpim`, `search:read.users`,
`search:read.files`, plus `chat:write`, `*:history`, `channels:read`/`groups:read`,
`users:read`(`.email`), `reactions:write`, `emoji:read`, `files:read`, and
`canvases:read`/`canvases:write`.

### Setup

1. Create/confirm an **internal** Slack app (import the manifest, keep distribution
   off) and add the OAuth **user** scopes above. The included manifest enables MCP
   via `settings.is_mcp_enabled: true`; if you configure the app manually, turn on
   MCP in the app's settings.
2. Give nomos the app's OAuth client credentials (either env or the `slack-oauth`
   integration; `SLACK_CLIENT_ID`/`SLACK_CLIENT_SECRET` are reused if already set):

   ```bash
   SLACK_OAUTH_CLIENT_ID=...
   SLACK_OAUTH_CLIENT_SECRET=...
   ```

3. Connect (mints + stores the user token under the local owner):

   ```bash
   nomos slack connect-mcp        # opens Slack OAuth on https://localhost:9877/oauth/callback
   ```

   Or inject a minted user token directly as the MCP bearer — this is
   **env-authoritative** (it overrides any stored token, avoiding stale/revoked
   tokens in the DB). `SLACK_MCP_USER_TOKEN` is an accepted alias:

   ```bash
   SLACK_ACCESS_TOKEN=xoxp-...
   ```

   > The token must be a **valid, un-revoked** `xoxp` user token with the MCP
   > scopes, from an app that has `is_mcp_enabled` (reinstall the app after
   > enabling MCP). `mcp.slack.com` returns `401 invalid_token` for a revoked
   > token or one minted before MCP was enabled — verify with the probe in
   > "Verify" below.

4. The `mcp` backend is already the default. (Only set `NOMOS_SLACK_BACKEND=tools`
   if you want the legacy in-process tools instead.) Restart after connecting.

5. Verify: ask the agent to "search my #general channel" or "summarize #general" —
   it should call `mcp__slack__*` tools and return real data.

> **Fallback:** if no token is connected, the agent simply gets no Slack tools and
> is told Slack isn't connected (it won't hallucinate tools). Set
> `NOMOS_SLACK_BACKEND=tools` to use the legacy in-process xoxc/xoxp tools instead.

## User Mode

In addition to bot mode, Nomos supports **User Mode** — it acts as you instead of as a bot. Messages appear as if you typed them.

```bash
# Connect a workspace
nomos slack auth --token xoxp-...

# Start listening as you
nomos slack listen
```

For full details including draft approval, daemon integration, and required scopes, see [Slack User Mode](slack-user-mode.md).

## Real-Time Streaming

When the bot processes a message:

1. A "_Thinking..._" placeholder appears immediately
2. As the agent generates text, the message updates every ~1.5 seconds
3. During tool use, you'll see indicators like "_Using Read..._"
4. The final complete response replaces the streaming content

For long responses (over 4,000 characters), the streaming message is replaced with properly chunked messages.

## Troubleshooting

### "Sending messages to this app has been turned off"

1. Go to **App Home** → check **Messages Tab** + **"Allow users to send..."**
2. Confirm `im:history`, `im:read`, `im:write` scopes are added
3. Confirm `message.im` is in bot events
4. **Reinstall the app**

### Bot doesn't respond to messages

- Verify both `SLACK_BOT_TOKEN` and `SLACK_APP_TOKEN` are set
- Check daemon logs for connection errors
- For channel messages, invite the bot: `/invite @Nomos`

### "not_in_channel" errors

The bot must be a member of the channel. Invite it with `/invite @Nomos`.

### "invalid_team_for_non_distributed_app"

Your Slack app isn't enabled for multi-workspace distribution. Either:

- Enable distribution: add `https://localhost:8934/oauth/callback` as a redirect URL, then **Manage Distribution** → **Activate Public Distribution**
- Or use manual token entry (`nomos slack auth --token xoxp-...`) which doesn't require distribution

### Socket Mode disconnections

Socket Mode auto-reconnects. If the bot appears offline:

- Check that `SLACK_APP_TOKEN` hasn't been revoked
- Verify Socket Mode is still enabled in app settings
- Confirm the daemon is running: `nomos daemon status`
