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.

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

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:

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.

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

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.

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):

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

Interactivity Request URL (Approve/Deny buttons):

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, …).

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:

~/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:
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:

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…ownerOne workspace link
agent slack disconnectownerLink + all bindings gone
agent slack bind AGENT --bot-token --signing-secretownerBot can verify events
agent slack unbind AGENTownerThat bot stops; others stay
agent slack manifest AGENTmemberJSON to paste into Slack
agent slack statusmemberTeam + bots, no secrets
slack send --text … [--file PATH] --thread IDagentReply in that thread
slack upload PATH --thread IDagentFile only
slack inbox / slack showagentStub listing / read
thread show THREAD_IDparticipantFull history
goal elevate approve|deny RUNownerCLI backup if the Slack buttons are missed
goal budget extend|deny RUN [--by USD]ownerCLI 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:

~/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 failsEvents URL set before bind, or wrong signing secretBind first, then paste the Events URL
Mention does nothingWorkspace not connected, bot unbound, or missing app_mentionagent slack status; re-install from the current manifest
Bot replies in a new Slack threadYou omitted --thread / thread_idAlways pass the Sails thread id from thread_show
Agent tries write_file and has no toolsFilesystem still disabledagent jail --path the folders it should edit, or stay on messaging
repository owner access required on cronNot elevatedOne request_owner_access; wait; never retry after Deny
Zip/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 a “fresh” workspaceOne link per repodisconnect only if this repo should leave that Slack team

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