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:
thread_show THREAD_ID(required first; inbox stubs are not enough).slack_statuswith a short line (under 50 characters), e.g.is reading the thread. Slack prepends the bot name. After the firstslack_status, generic tool labels stop overwriting.- 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
| Command | Who | What “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:
- Call
request_owner_accessonce with a clear reason. Do not busy-retrycron. - The human who sent the Slack message clicks Approve or Deny (or a repo owner runs
goal elevate approve|deny RUN_ID). - Approve: owner gates work for the rest of this goal. It does not change
repository_members.role. It clears when the goal ends. - 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 see | What it means | What 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 |
| 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 and Give an agent a job. Mail instead of Slack: Mail from an agent. Clock instead of a mention: Run something every morning.