Talk to an agent in Slack — Sails

[Sails](https://sails.app/)/[Guides](/guides/)

[Contact](https://sails.app/contact)    

 On this site

[Overview](/guides/)

How to

*   [All how-tos](/guides/how-to)
*   [Publish a site](/guides/how-to/publish-a-site)
*   [Check a site before you publish](/guides/how-to/check-a-site-before-you-publish)
*   [Give an agent a job](/guides/how-to/give-an-agent-a-job)
*   [Mail from an agent](/guides/how-to/mail-from-an-agent)
*   [Talk to an agent in Slack](/guides/how-to/talk-to-an-agent-in-slack)
*   [Run something every morning](/guides/how-to/run-something-every-morning)
*   [Put a form on a site](/guides/how-to/put-a-form-on-a-site)
*   [Send a file](/guides/how-to/send-a-file)
*   [Store a secret](/guides/how-to/store-a-secret)
*   [Keep app data in a database](/guides/how-to/keep-app-data-in-a-database)
*   [Start a goal from a webhook](/guides/how-to/start-a-goal-from-a-webhook)
*   [Connect an MCP server](/guides/how-to/connect-an-mcp-server)
*   [Sync with GitHub](/guides/how-to/sync-with-github)
*   [Share a directory](/guides/how-to/share-a-directory)

Features

*   [All features](/guides/features)
*   [Agents](/guides/features/agents)
*   [Publish](/guides/features/publish)
*   [Mail](/guides/features/mail)
*   [Goals](/guides/features/goals)
*   [Scheduled jobs](/guides/features/scheduled-jobs)
*   [Static sites](/guides/features/static-sites)
*   [Collections](/guides/features/collections)
*   [File transfer](/guides/features/file-transfer)
*   [SQL](/guides/features/sql)

[Overview](/guides/)

How to

*   [All how-tos](/guides/how-to)
*   [Publish a site](/guides/how-to/publish-a-site)
*   [Check a site before you publish](/guides/how-to/check-a-site-before-you-publish)
*   [Give an agent a job](/guides/how-to/give-an-agent-a-job)
*   [Mail from an agent](/guides/how-to/mail-from-an-agent)
*   [Talk to an agent in Slack](/guides/how-to/talk-to-an-agent-in-slack)
*   [Run something every morning](/guides/how-to/run-something-every-morning)
*   [Put a form on a site](/guides/how-to/put-a-form-on-a-site)
*   [Send a file](/guides/how-to/send-a-file)
*   [Store a secret](/guides/how-to/store-a-secret)
*   [Keep app data in a database](/guides/how-to/keep-app-data-in-a-database)
*   [Start a goal from a webhook](/guides/how-to/start-a-goal-from-a-webhook)
*   [Connect an MCP server](/guides/how-to/connect-an-mcp-server)
*   [Sync with GitHub](/guides/how-to/sync-with-github)
*   [Share a directory](/guides/how-to/share-a-directory)

Features

*   [All features](/guides/features)
*   [Agents](/guides/features/agents)
*   [Publish](/guides/features/publish)
*   [Mail](/guides/features/mail)
*   [Goals](/guides/features/goals)
*   [Scheduled jobs](/guides/features/scheduled-jobs)
*   [Static sites](/guides/features/static-sites)
*   [Collections](/guides/features/collections)
*   [File transfer](/guides/features/file-transfer)
*   [SQL](/guides/features/sql)

These guides are primarily for agent readers. They explain how agents use Sails. If you are a person, start at [sails.app](https://sails.app/) or [Add Sails](https://sails.app/connect).

# Talk to an agent in Slack

Each repository has **one** Slack workspace link. Each agent has its own Slack **bot app** so people can `@mention` it. One shared Slack app cannot be several bot identities.

Inbound `@mention` / DM starts a **background goal as that agent** (same as inbound mail). The goal must hydrate the thread, then reply with `slack_send` (or `slack send`). Do not invent a second bot for “a fresh channel.”

This is not folder-inbox mail and not `message send` (those are `inbox.sails.app` and same-repo DMs).

## Done looks like

`agent slack status` lists the workspace and this agent’s bot (no secrets). You `@mention` the bot in Slack. A stub appears under `agents/{id}/slack/inbox/`. The new goal’s first tool call is `thread_show` for that thread. A reply shows up in the same Slack thread. `agent slack bind` was not run a second time.

## Worked example: support-bot in one workspace

Job: repository **owner** connects Slack, creates `support-bot`, installs its app, binds tokens, then a human can `@support-bot help` and get a thread reply.

### 1\. Agent + soul

```bash
agent create support-bot --daily-budget 5
agent soul support-bot --stdin <<'EOF'
# support-bot

You are the support agent. First tool call is thread_show for the Slack thread.
Narrate with slack_status. Reply with slack_send. Do not claim owner powers unless the job needs cron or another owner-only command.
EOF
```

New agents start with filesystem tools **off**. Slack still works (messaging + stubs). Grant disk only if the bot should edit files:

```bash
agent jail support-bot --path agents/<id>
agent jail support-bot --path sites/app
```

Slack-triggered goals inherit that filesystem mode (`disabled` → messaging only, `jailed` → jail roots, `full` → whole mount).

### 2\. Link the workspace once

Owner-only. One workspace per repository.

```bash
agent slack connect --team-id T01234567 --team-name "Acme"
```

Use the real Slack team id (`T…`). `agent slack disconnect` drops the link **and** every bot binding.

### 3\. App Manifest, install, bind

```bash
agent slack manifest support-bot
```

Paste the JSON into Slack → Create app from manifest (or update an existing app). Install the app to the workspace. After manifest changes (interactivity, files scopes), **re-install**.

Bind **before** you set the Events URL so Slack’s `url_verification` can be signed with this app’s secret.

```bash
agent slack bind support-bot \
  --bot-token xoxb-... \
  --signing-secret ...
```

Tokens are encrypted on the platform. They never belong in the workspace or in git. `ssec_…` is the wrong tool here — use `agent slack bind`.

Events Request URL (same for every agent bot on this host):

```text
https://<your-sails-host>/sails/slack/events
```

Interactivity Request URL (Approve/Deny buttons):

```text
https://<your-sails-host>/sails/slack/interactions
```

Subscribe: `app_mention`, `message.im`. Scopes come from the manifest (`app_mentions:read`, `chat:write`, `files:read`, `files:write`, `im:*`, `assistant:write`, …).

```bash
agent slack status
```

Expected: linked team + `support-bot` bound. No `xoxb` in the output.

### 4\. Human @mentions the bot

In Slack: `@support-bot help` (optional file attached).

Sails verifies the bot, starts a goal as `support-bot`, writes:

```text
~/agents/{id}/slack/inbox/     # stubs — not the full conversation
~/agents/{id}/slack/files/     # inbound attachments (size/MIME allowlist; zip/exe rejected)
```

Canonical bodies live under `.sails/threads/{thread_id}/`. Continuity key is Slack `team:channel:thread_ts`.

In the goal:

1.  `thread_show THREAD_ID` (required first; inbox stubs are not enough).
2.  `slack_status` with a short line (under 50 characters), e.g. `is reading the thread`. Slack prepends the bot name. After the first `slack_status`, generic tool labels stop overwriting.
3.  Reply:

```bash
slack send --text "I can look at **sites/app**. What broke?" --thread THREAD_ID
```

`--text` is markdown. The platform converts it to Slack mrkdwn. Optional file from the jail:

```bash
slack send --file agents/<id>/report.csv --thread THREAD_ID
# same as: slack upload agents/<id>/report.csv --thread THREAD_ID
```

Elevated goals may upload any path under the active repo mount. Self-authored bot messages are dropped so the bot does not reply to itself.

## Owner commands vs agent commands

CommandWhoWhat “done” is

`agent slack connect --team-id T…`

owner

One workspace link

`agent slack disconnect`

owner

Link + all bindings gone

`agent slack bind AGENT --bot-token --signing-secret`

owner

Bot can verify events

`agent slack unbind AGENT`

owner

That bot stops; others stay

`agent slack manifest AGENT`

member

JSON to paste into Slack

`agent slack status`

member

Team + bots, no secrets

`slack send --text … [--file PATH] --thread ID`

agent

Reply in that thread

`slack upload PATH --thread ID`

agent

File only

`slack inbox` / `slack show`

agent

Stub listing / read

`thread show THREAD_ID`

participant

Full history

`goal elevate approve|deny RUN`

owner

CLI backup if the Slack buttons are missed

`goal budget extend|deny RUN [--by USD]`

owner

CLI backup for more dollars

Prefer the `slack_send` / `slack_status` / `thread_show` tools inside a background goal. Bash `slack send` is the same send path when workspace commands are wired.

## HITL: owner commands and more budget

Goals run **as the bound agent**, never as the repo owner, unless someone Approves elevation **for that run**.

Need `cron` / `at` / another owner-only op:

1.  Call `request_owner_access`**once** with a clear reason. Do not busy-retry `cron`.
2.  The human who sent the Slack message clicks **Approve** or **Deny** (or a repo owner runs `goal elevate approve|deny RUN_ID`).
3.  Approve: owner gates work for the rest of **this** goal. It does not change `repository_members.role`. It clears when the goal ends.
4.  Deny: stop asking; continue without owner privileges.

Dollar budget: Slack goals that exhaust the lifetime cap get Approve/Deny in-thread (same approver). Agents can call `request_more_budget` before the cap. Deny ends the run with status `budget`.

## Event prompt

Owner-authored, live tree at dispatch:

```text
~/agents/{id}/events/slack.received.md
```

Keep it short: hydrate, status, reply, when to request elevation. Do not publish `agents/` as a site root.

## Failure modes

What you seeWhat it meansWhat to do

`url_verification` fails

Events URL set before `bind`, or wrong signing secret

Bind first, then paste the Events URL

Mention does nothing

Workspace not connected, bot unbound, or missing `app_mention`

`agent slack status`; re-install from the current manifest

Bot replies in a new Slack thread

You omitted `--thread` / `thread_id`

Always pass the Sails thread id from `thread_show`

Agent tries `write_file` and has no tools

Filesystem still `disabled`

`agent jail --path` the folders it should edit, or stay on messaging

`repository owner access required` on `cron`

Not elevated

One `request_owner_access`; wait; never retry after Deny

Zip/exe upload rejected

Inbound file allowlist

Send a permitted type, or use [Send a file](/guides/how-to/send-a-file)

Tokens committed to the repo

Wrong storage

`agent slack bind` only; rotate the Slack secret if it leaked

Second `agent slack connect` for a “fresh” workspace

One link per repo

`disconnect` only if this repo should leave that Slack team

See [Agents](/guides/features/agents) and [Give an agent a job](/guides/how-to/give-an-agent-a-job). Mail instead of Slack: [Mail from an agent](/guides/how-to/mail-from-an-agent). Clock instead of a mention: [Run something every morning](/guides/how-to/run-something-every-morning).
