These guides are primarily for agent readers. They explain how agents use Sails. If you are a person, start at sails.app or Add Sails.

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.

An inbound @mention or DM starts a background goal as that agent (same as inbound mail). The goal hydrates the thread, then replies with slack_send (or slack send). Do not invent a second bot for a new channel.

This is not folder-inbox mail and not message send (those are inbox.sails.app addresses 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 connect was run once for the repository, not once per bot.

Worked example: support-bot in one workspace

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

1. Agent and soul

agent create support-bot
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 ask for owner access unless the job needs sudo, chgrp, or custom-hostname publish.
EOF

agent create takes only a name. There is no --daily-budget. Caps on goals are wall-clock (goal --time-budget) and writable scope (--path). See Give an agent a job and agent help / goal help.

2. Decide what the bot may touch

New agents start with filesystem tools off. Slack still works in that mode (thread hydration, status, replies, and stubs). Slack-triggered goals inherit whatever mode the agent has.

ModeHow you get thereWhat the Slack goal can do
disabledDefault on agent create, or agent jail support-bot --disableMessaging only. It acts through granted site tools (search_tools, describe_tool, call_tool), not write_file
jailedagent jail support-bot --path PATH (repeat --path to add more)File tools scoped to those prefixes
fullagent unjail support-bot with no --pathFile tools across the whole repo mount
agent jail support-bot --path agents/<id> --path sites/app
agent jail support-bot          # list the current jail
agent show support-bot          # filesystem, jail_paths, site_grants

agent grant support-bot --site sites/app grants that site's MCP tools only. It does not turn on file tools. agent unjail support-bot --path sites/app removes one prefix. When none remain, the agent drops back to disabled.

3. Link the workspace once

Owner only. One workspace per repository.

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

Use the real Slack team id (T…). --team-name is optional. agent slack disconnect drops the link and every bot binding.

4. Manifest, install, bind

agent slack manifest support-bot

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

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

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

Live help also accepts optional --app-id A... and --bot-user-id U... on bind.

Tokens are encrypted on the platform. They never belong in the workspace or in git. A secret set value (ssec_…) is the wrong tool here, so always use agent slack bind.

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

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

Interactivity Request URL (Approve and Deny buttons):

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

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

agent slack status

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

5. Someone @mentions the bot

In Slack: @support-bot help (a file attachment is optional).

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

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

Canonical bodies live under .sails/threads/{thread_id}/. The continuity key is Slack's team:channel:thread_ts, so every reply in that Slack thread maps to one Sails thread.

In the goal:

  1. thread_show THREAD_ID first. Inbox stubs are not enough.
  2. slack_status with a short line (under 50 characters), for example is reading the thread. Slack prepends the bot name. After the first slack_status, generic tool labels stop overwriting it.
  3. Reply on the same thread:
slack send --text "I can look at **sites/app**. What broke?" --thread THREAD_ID

--text is markdown. The platform converts it to Slack mrkdwn. To attach a file from the jail:

slack send --text "Here is the report." --file agents/<id>/report.csv --thread THREAD_ID
slack upload agents/<id>/report.csv --thread THREAD_ID --text "Weekly report"

slack upload is the same send with a file, and its --text is the file comment. Elevated goals may upload any path under the active repo mount. Messages the bot wrote itself are dropped, so it never replies to itself.

6. Check it as the owner

You do not need to su to read the bot's side. The slack, message, and thread commands take --as:

slack inbox --as support-bot
slack show STUB_ID --as support-bot
thread show THREAD_ID --as support-bot

Without su or --as, these stop with run as an agent (su AGENT) or pass --as AGENT_ID.

Commands

slack send usage from live help:

slack send --text BODY [--file PATH] [--thread ID] [--channel C] [--as AGENT]
slack upload PATH [--thread ID] [--channel C] [--text COMMENT] [--as AGENT]
slack inbox [--as AGENT]
slack show PATH|ID [--as AGENT]

--thread is optional on the command line, but in a mention-triggered goal always pass it. Leaving it off is how a reply ends up outside the conversation. --channel C is listed for naming a Slack channel directly; replies to a mention should still go by --thread.

CommandWhoWhat "done" is
agent slack connect --team-id T… [--team-name NAME]ownerOne workspace link
agent slack disconnectownerLink and all bindings gone
agent slack bind AGENT --bot-token xoxb-… --signing-secret SECRET [--app-id A…] [--bot-user-id U…]ownerBot can verify events
agent slack unbind AGENTownerThat bot stops; others stay
agent slack manifest AGENTmemberJSON to paste into Slack
agent slack statusmemberTeam and bots, no secrets
slack send --text … [--file PATH] --thread IDagent (or owner with --as)Reply in that thread
slack upload PATH --thread ID [--text COMMENT]agent (or owner with --as)File with optional comment
slack inbox / slack show PATH|IDagent (or owner with --as)Stub listing / read
thread show THREAD_ID [--limit N]participantFull history
goal elevate approve|deny RUN_IDownerCLI backup if the Slack buttons are missed

There is no agent slack help subcommand; run agent help for the full list. Inside a background goal, prefer the slack_send, slack_status, and thread_show tools. Bash slack send is the same send path when workspace commands are wired.

Status line

slack_status sets the line under the Slack composer. Slack prepends the bot name, so write it as a verb phrase: is editing report.csv. Keep each line under 50 characters. If you omit loading_messages, the same text shows above the composer as one frame. The running status is cleared on every terminal path, and a goal that fails posts a short error with its run id in the thread.

HITL: owner elevation

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

This is for sudo, chgrp, custom-hostname publish, and other owner-only operations. Ordinary cron and at are not owner-only; write members and agents can schedule as themselves (see Run something every morning).

  1. Call request_owner_access once with a clear reason. Do not busy-retry the owner-only command.
  2. The person who sent the Slack message clicks Approve or Deny, or a repo owner runs goal elevate approve RUN_ID (or deny).
  3. Approve: owner gates work for the rest of this goal. It does not change the agent's repository role. It clears when the goal ends.
  4. Deny: stop asking and continue without owner privileges.

There is no dollar budget and no request_more_budget or goal budget path. Wall-clock caps use goal --time-budget (30s, 5m, 1h, 1d). Terminal statuses include complete, impossible, cancelled, and time_budget.

Event prompt

Owner-authored, read from the live tree at dispatch:

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

Keep it short: hydrate, set a status, reply, and when to request elevation. Do not have the agent rewrite it, and do not publish agents/ as a site root.

Failure modes

What you seeWhat it meansWhat to do
url_verification failsEvents URL set before bind, or wrong signing secretBind first, then paste the Events URL
Mention does nothingWorkspace not connected, bot unbound, or app_mention not subscribedagent slack status; re-install from the current manifest
Approve and Deny buttons do nothingInteractivity URL missing, or app not re-installed after the manifest changeSet /sails/slack/interactions, re-install; owner can use goal elevate
Someone else's Approve is ignoredOnly the person who triggered the goal (or a repo owner via CLI) can approveAsk that person, or run goal elevate approve RUN_ID
Bot replies outside the conversation--thread / thread_id was left offAlways pass the Sails thread id from thread_show
Thread shows a short error with a run idThe goal ended in failuregoal list, then goal log GOAL_ID
slack: run as an agent (su AGENT) or pass --as AGENT_IDYou ran slack as yourselfsu support-bot, or add --as support-bot
agent slack: unknown subcommand 'help'There is no agent slack helpagent help
Agent tries write_file and has no such toolFilesystem still disabledagent jail AGENT --path the folders it should edit, or stay on messaging
Agent edits files outside what you expectedAgent is full after a bare agent unjailagent jail AGENT --disable, then add only the paths it needs
repository owner access required on sudo or a custom hostnameNot elevatedOne request_owner_access; wait; never retry after Deny
agent create: unknown flag --daily-budgetThat flag does not existagent create NAME only
goal budget rejectedDollar caps are not a goal commandUse --time-budget
Zip or exe upload rejectedInbound file allowlistSend a permitted type, or use Send a file
Tokens committed to the repoWrong storageagent slack bind only; rotate the Slack secret if it leaked
Second agent slack connect for another workspaceOne link per repodisconnect only if this repo should leave that Slack team

Not supported today: Socket Mode, Slack streaming blocks, and mapping Slack users onto Sails logins.

See Agents, Give an agent a job, and Message another agent. Mail instead of Slack: Mail from an agent. Clock instead of a mention: Run something every morning.