# Slack

> Post conversation recaps, live conversation threads, and a daily digest to Slack channels you choose.

Hansard can post to Slack when a conversation finishes, mirror conversations
into Slack threads as they happen, and post a daily digest. Slack posting is
off until you set it up.

- App: **Settings → Slack**
- CLI: `hansard notify slack`

## Interactive bot

The **Interactive bot** section adds one private bot with a confirmed
configuration for each channel. A channel can combine these roles:

| Role               | Behavior                                                                                                                                                      |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Broadcaster        | Posts recent archived excerpts into conversation threads. Replies become requests for an isolated continuation. One Broadcaster configuration can be enabled. |
| Tag-to-run         | Starts a configured agent after an approved mention. Replies continue its run after approval. Orchestrator mode uses up to three helpers in the same project. |
| Digest             | Posts recent eligible conversation titles and counts at the configured interval.                                                                              |
| Persistent watcher | Collects permitted channel activity and proposes tasks for owner approval. It never starts work automatically.                                                |
| Approvals          | Sends launch and supported native tool decisions to the thread. Only the owner can approve.                                                                   |

Socket Mode requires Node 22 or later. It connects outward from the Mac and
needs no public endpoint. Native execution currently supports Claude Code and
Codex on macOS. Team memory and other isolated harness adapters are unavailable.

1. Open **Settings → Slack → Interactive bot** and select **Set up bot**.
2. Register the owner, workspace, app, bot, and channel IDs.
3. Add an existing project folder and select its harness, model, and native permission mode.
4. Copy the app manifest into Slack's private app creation flow. Install the app with the listed scopes.
5. Create an app-level token with `connections:write`. Save that token and the bot token through the local credential form.
6. Invite the app to each permitted channel and select **Start connection** in Hansard.
7. Mention the bot with a description, such as “Broadcast the Web project and act as a persistent watcher.”
8. Review the proposed channel settings and confirm them.

The local channel form provides the same preview and confirmation flow.
Message display names and pictures use `chat:write.customize`. Mentions continue
to identify the installed Slack app. Channel instructions can select registered
projects, but they cannot grant new folders or change native permission modes.

Without owner prompts or channel access, each launch and continuation requires
owner approval, and only the owner and listed requesters can submit requests.
Guests, Slack Connect members, bots, unknown users, and direct messages from
anyone but the owner never start agents. Configuration changes invalidate
pending approvals. Redacted or incomplete approval details require review in
the local Settings panel. The panel shows the complete saved task before
execution. Saves that change only owner prompts, channel access, requesters, or
allowed channels apply to a running connection without stopping it. Runs that
the new settings no longer permit stop.

Agents run inside a project filesystem sandbox. They cannot read Hansard's
archive, Slack credentials, or unrelated folders. The sandbox blocks Keychain
authentication and localhost proxies. Account modes that need these facilities
are unavailable. The selected native permission mode remains unchanged.

Keep the app connection or `hansard notify slack bot run` process running.
Stopping it stops its agents. After restart, a new request needs owner approval.
The process that starts the bot owns its controls. Other local processes cannot
change its grants, approve its tasks, or stop its agents. Start the connection
in Settings to answer native questions in the local panel. A bot started by the
CLI has Slack controls, but a separate viewer cannot answer its native questions.
Stop that CLI process before changing its configuration from Settings.

Progress appears as paced status edits and complete responses. Broadcasts use
at most 200 recent conversations within 24 hours and short excerpts from the
archive.
These limits do not guarantee a complete transcript mirror.

The existing outbound notifier below stays independently configured. Enabling
both for the same conversations can produce duplicate posts. Team sharing
remains opt-in and does not grant Slack execution access.

### Prompts from Slack

The **Prompts from Slack** group holds the owner's own switches. Each switch
works only while the one above it is on, and keeps its saved value while it
cannot apply.

| Setting                     | Behavior                                                                                                                 |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| Run your prompts            | The owner's own messages start and continue runs without an approval click.                                              |
| Direct messages             | The owner can message the bot directly. Anyone else's direct message starts nothing and gets one short reply.            |
| Project for direct messages | The granted project that a direct message uses when it names none.                                                       |
| Agents on this Mac          | Direct messages from the owner can reach agents that run in a terminal or are paused on this Mac, as Running lists them. |

> **Caution:** Agents on this Mac are the owner's own agents. They run with full access on
> the Mac, outside the Slack sandbox, with the permissions they already have.

An app installed before direct messages were on lacks their scopes. After
turning on **Direct messages**, open **App manifest**, update the Slack app
from it, and reinstall the app. The manifest then adds the `im:history` and
`im:read` scopes, the `message.im` event, and the app's Messages tab.
`hansard notify slack bot manifest --direct-messages on` prints the same
manifest.

A top-level direct message is a task for the default project. A message that
starts with `project:<id or name>` uses that granted project instead. Quote a
name with spaces, as in `project:"Web App" Fix the build`. Without a default
project or a prefix, the bot replies with the granted project IDs. Each
top-level message starts its own thread, and a reply in that thread continues
the same run. `help` or an empty message gets a short usage reply. These tasks
run in the project's sandbox, like channel runs. The bot doesn't read attached
files yet, so send each task as text.

With **Agents on this Mac** on:

- `agents` lists up to 20 agents, numbered. A list number stays valid for 30 minutes.
- `agent:<number> <words>`, or `agent:` followed by six or more characters of the session ID and the words, sends the words. A reply in that thread sends more words to the same agent.
- A paused agent resumes where it ran, as **Resume** in Running does. Hansard delivers the words once the agent has been idle at its prompt for 5 seconds. If it does not start within 3 minutes, the bot says so.
- An agent that waits on an approval or a question is never typed into. The words wait until it returns to its prompt.
- For Claude Code and Codex, the agent's next complete reply appears in the thread. For other harnesses, check the agent on the Mac.

`agents` and `agent:` in a channel start nothing. The bot replies that live
agents take messages only in a direct message.

When an agent asks you a question with `ask_user`, the question also arrives
in your direct message with the bot as a card, with **Approve**, **Deny**, and
**Deny with a note**. Only your own press on the card answers it. After
**Deny with a note**, your reply in the card's thread is the note.

### Channel access

**Channel access** lists each allowed channel. Its **Edit** dialog sets who
besides the owner can send there and how their runs start. A channel without
its own setting keeps the behavior above.

| Setting                  | Choices                                                                                                                                                                                         |
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Who can send             | **Only you**, **People you list** (the owner and the listed requester IDs, one list for all channels), or **Everyone in the channel** (any full member, checked against the channel's members). |
| Approval                 | **You approve each run** (the default) or **Runs start without approval**.                                                                                                                      |
| Runs per person each day | Runs without approval for each person over 24 hours, from 1 to 100. The default is 10.                                                                                                          |
| Runs at once             | Runs without approval from people on the channel's default at the same time, from 1 to 5. The default is 2.                                                                                     |

Runs start without approval only when the channel's project uses Claude Code's
Manual or Plan mode, where Claude Code asks before every edit, every command
that is not read-only, and every read outside the project, Claude Code's Auto
mode, where Claude Code's classifier decides tool use inside the same sandbox,
or Codex's Read only mode, where Codex asks before every edit and every command
that is not read-only. Under any other mode, Accept edits and Codex's Auto and Full access
included, requests still wait for the owner, and the dialog says so. Tool
approvals from these runs still go only to the owner. A person starts new
threads and continues only their own runs without approval. Another person's
run, or a thread with archived context, needs the owner. Over a limit, the bot
replies with the limit and starts nothing. A person can have at most three
requests waiting for approval.

**People in this channel**, at the end of the **Edit** dialog, lists everyone
the bot has seen send a message in that channel, by their Slack name. The bot
keeps each person's member ID, name, and when it last saw them, never what
they wrote. Each person gets one **Access** choice:

| Choice               | Behavior                                                                                                         |
| -------------------- | ---------------------------------------------------------------------------------------------------------------- |
| Channel default      | The channel's project, approval, and limits.                                                                     |
| You approve each run | The owner approves each run, and the person uses only the channel's project.                                     |
| Their own scope      | The projects the person can use, whether their runs need approval, and their own runs each day and runs at once. |
| Can't send here      | In a channel for **People you list** only, for someone who is not on the list.                                   |

In a channel open to **Everyone in the channel**, a person that the owner has
not reviewed starts at **You approve each run**. A person on the list of
requesters starts at **Channel default**. **Runs at once** in the channel
counts the runs of everyone on the channel's default together, and a person
with their own scope has their own count. A mention that starts with
`project:<id or name>` runs in another granted project that the person can
reach. Quote a name with spaces, as in `project:"Web App"`. A reply runs in
its thread's project.

Before a save gives anyone more access, such as a new audience, another
person, another project, runs without approval, or higher limits, Settings
shows a notice. It says who can start runs on the Mac, in which projects, and
with which limits, what they can't do, and the risks. **Accept and save**
saves the change and records when the owner accepted. **Cancel** saves
nothing. A save that narrows access shows no notice. From the CLI,
`hansard notify slack bot configure --file <json>` prints the same notice and
saves only when run again with `--accept-coworker-access` and the code it
prints.

Guests, people from other organizations (Slack Connect), and bots can never
send. Other people's runs stay in the channel's project inside the sandbox and
reply only in their thread. Each such run, approved or not, gets a provider
home of its own that leaves out the owner's personal instruction and memory
files (Claude Code's `CLAUDE.md` and rules, Codex's `AGENTS.md`), the owner's
Claude Code allow rules, plugins, hooks, MCP servers, and the project's own
Claude Code settings, and the owner's Codex MCP servers, connectors, and
plugins. A Codex run for another person holds no sign-in: it reaches its model
only through the router in Accounts, with a key that works only for that run
while it works, so these runs need the router with a Codex account. Such a run
has no other network, so a command that needs the internet fails even when the
owner approves it. The sandbox keeps every such run's reads to the project, its
own home, and system folders. These runs still follow the project's own
instructions: Claude Code reads the project's `CLAUDE.md` and the files it
imports from inside the project, such as `AGENTS.md`, and Codex reads the
project's `AGENTS.md`.
The home is removed 15 minutes after the run's last reply. A reply in
the thread after that starts a new run without the earlier context, as a new
request would, and the bot says so in the thread.

## What Hansard posts

| Post            | Content                                                                                                                                                                                | Default                 |
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- |
| Summaries       | One recap per conversation after it has been quiet for 10 minutes: the goal, the result, edited files, and activity counts.                                                            | On once Slack is set up |
| Live threads    | Every user and agent message, posted whole into one thread per conversation, in batches every 45 seconds. Tool calls are left out unless `notify.slack.stream.includeTools` is `true`. | Off                     |
| Daily digest    | One summary per day, grouped by person or device, with conversation, repository, model, and token totals.                                                                              | Off                     |
| Behavior digest | A comparison of one period with the previous one. Posts only when you run it.                                                                                                          | Manual                  |

Hansard posts from its archive, with redaction applied, never from the
harness's original files. Subagent runs, SDK batch jobs, and imported ChatGPT,
Claude.ai, and Gemini conversations never post as summaries or live threads. A
conversation you resume later opens a new thread with only the new messages.

## Set up the Slack app

> **Slack receives conversation content:** Summaries and live threads contain what was said in your conversations. Once
> posted, messages follow your Slack workspace's retention, export, and admin
> access rules. Redaction catches known secret patterns only. A posting rule
> with no repositories posts every eligible conversation.

Creating the Slack app is a CLI step. Before you start, create the channels
you want to post to and decide which repositories may post.

1. Start the guided setup:

   ```sh
   hansard notify slack setup
   ```

   It opens Slack's app creation page with a prepared manifest. The app asks
   for `chat:write`, `chat:write.customize` so live threads can show each
   speaker's name, and `channels:read` plus `groups:read` so setup can list
   channel names. It cannot read Slack messages. Paste the bot token when
   asked, then enter channel IDs. Setup ends by sending a test post.

2. In Slack, invite the bot to each channel.

3. Check the saved configuration and preview a test message:

   ```sh
   hansard notify slack status
   hansard notify slack test --dry-run
   ```

4. Send the test when the preview is right:

   ```sh
   hansard notify slack test
   ```

## Manage Slack in the app

1. Open **Settings → Slack**. If Slack is not connected yet, select **Add bot
   token**, enter a label, and paste a bot token from your Slack app.

2. Use **Post to Slack** to pause or resume all posting. Your rules and digest
   stay saved while it is off.

3. Under **Bot tokens**, the token marked **Default** posts for every rule and
   digest that does not choose another. To change it, choose **Set as
   default** from another token's menu. Select **Add bot token** to save
   another token.

4. Under **Posting rules**, select **Add rule** or edit a rule to set which
   conversations post, the summary channel and quiet time, the live thread,
   and **Post as**.

5. Under **Daily digest**, turn on **Post a daily digest** and set the
   **Channel ID**, **Send at** as a local hour, the period it **Covers**, and
   **Post as**. Changes save automatically.

The app's name and icon in Slack come from Slack. Change them in your Slack
app settings, which **Open Slack app settings** under **Bot tokens** opens.

The app does not post a digest preview. To see the complete digest before
anything posts, run:

```sh
hansard notify slack digest run --dry-run
```

An empty digest posts nothing.

## Configure posting from the CLI

Turn summaries and live threads on or off separately:

```sh
hansard notify slack enable \
  --summaries on \
  --summary-channel C0123456789 \
  --quiet-minutes 10 \
  --firehose off \
  --repos github.com/example/app
```

With no `--repos`, every eligible conversation posts.

### Route conversations to different channels

Routes send different conversations to different channels or through
different bots. A conversation posts to every route it matches, in order. A
route marked `--exclusive` stops later routes from also receiving it.

Store a bot token under a name so it never appears on a command line:

```sh
hansard notify slack token add example-bot
```

Named tokens live in a local file readable only by you. Status output shows
only the name and the last characters of the token.

Add a route that uses the stored token:

```sh
hansard notify slack route add \
  --match-owners example \
  --summary-channel C0123456789 \
  --token ref:example-bot \
  --exclusive
```

Routes can also match repositories (`--match-repos`), repository hosts
(`--match-hosts`), harnesses (`--match-providers`), archive scopes
(`--match-scopes`), local folders (`--match-path`), portfolios
(`--match-containers`), and devices (`--match-devices`). A route with no match
options matches every conversation. List routes and their channels without
posting:

```sh
hansard notify slack route list
```

`hansard notify slack route reorder --order <id,id>` sets route priority.
Use the route IDs from `route list` in the desired order.

### Post a Behavior digest

```sh
hansard notify slack digest behavior --period 1m --dry-run
```

It uses the daily digest's channel and token. Remove `--dry-run` to post.

## Open conversations from Slack

Summaries and live thread headers include an **Open in Hansard app** link. It
opens the Mac app and selects the conversation. Run `hansard app --no-fallback`
to confirm the app is installed. To link to a hosted viewer instead, set
`notify.slack.viewerBaseUrl` to its address. Set it to an empty string to leave
the link out.

## When posts are delayed

Going offline pauses posting, never archiving. Failed posts keep their place
and retry about once a minute, so everything posts after you reconnect. A post
can occasionally appear twice. When one conversation has more than 20 messages
waiting, Hansard posts the latest 20 and a note with the number skipped. The
full conversation stays in your archive.

## Stop posting

```sh
hansard notify slack disable
```

Messages already in Slack stay there. Delete them with Slack's controls, and
revoke the app's token in Slack when you retire the connection.
