Appearance
Slack agents
The Slack channel puts your agent in Slack. Two products: the Cursor-hosted connection (cursorAccount: true), or a dedicated Socket Mode app created in the dashboard wizard (agent-sdk slack create). To own the Slack app yourself, run agent-sdk slack init --manual and paste the manifests at api.slack.com. Socket Mode has no public Request URL. Replies stream in threads, with tool "thinking" steps, suggested prompts, and opt-in approval buttons.
The companion skill is skills/setup-slack/SKILL.md.
Define the channel
Author agent/channels/slack.ts with slackChannel() from @cursor/july/channels/slack:
ts
import { slackChannel } from "@cursor/july/channels/slack";
// Single agent: reads SLACK_BOT_TOKEN + SLACK_APP_TOKEN
export default slackChannel();
// Multi-agent serve: one Slack app (and token pair) per agent
export default slackChannel({ envPrefix: "WEATHER_AGENT" });
// → WEATHER_AGENT_SLACK_BOT_TOKEN + WEATHER_AGENT_SLACK_APP_TOKENBy default the channel connects over Socket Mode (the app connects outward, so it works from behind any firewall) and responds only to app_mention events and direct messages. It also refuses Slack Connect users, guests, and senders from other workspaces. Threads bind to sessions with continuationToken = channelId:threadTs, so a thread is one durable conversation. Replies stream (chat.startStream / appendStream / stopStream) with a plain-message fallback, a rotating status shows while the agent works, and the pack sets thread titles and suggested prompts. When a Slack API is unavailable (method_not_found, missing_scope), the pack degrades to postMessage instead of failing.
Missing tokens leave the channel idle (channel idle … missing credentials) rather than failing serve. That's useful when you mount many agents and only some have Slack apps.
Use the Cursor Slack connection
If the Cursor Slack app is already installed in your workspace and linked to your Cursor account, skip the dedicated Slack app:
ts
import { slackChannel } from "@cursor/july/channels/slack";
export default slackChannel({
cursorAccount: true,
agentName: "Weatherbot", // single token — no spaces; defaults from mount slug (PascalCase)
agentIcon: { emoji: ":robot_face:" },
});Sign the host in (agent-sdk login or CURSOR_API_KEY), then mention the agent in Slack as @Cursor Weatherbot …. Thread replies and DMs keep going to the same agent. Messages appear as the Cursor app under that agent's name and icon, with live updates as the turn progresses.
Use a dedicated Socket Mode Slack app when you need your own bot user, channel watching (engagement.channelPosts), or approval buttons. On cursorAccount, agents must be explicitly addressed (@mention, DM, or claimed-thread reply). Channel watching and toolApprovals / interactivity are Socket Mode only; the Cursor connection does not relay Block Kit clicks. Agent names must be unique on the host; an unmatched @Cursor <name> stays on Cursor's normal Slack agent.
Control who can message the agent
External senders are blocked by default. Slack Connect users, guests, and people whose home workspace is not the install team never reach the handler. That applies to Socket Mode and cursorAccount: true. Set blockExternals: false only when the agent should serve people outside your org:
ts
export default slackChannel({
policy: { blockExternals: false },
});Sessions are thread-scoped by default, so anyone in the thread can continue. Restrict follow-ups to the person who started the session with respondTo: "author":
ts
export default slackChannel({
policy: { respondTo: "author" },
});Set it up
For a dedicated Socket Mode bot, run agent-sdk slack create. The CLI opens the signed-in Cursor dashboard wizard. Slack consent, bot name, and admin-approval wait happen there. Tokens are stored as deployment secrets and written to .env.local. They are never shown. Do not paste a manifest at api.slack.com unless you are on the manual setup path.
Bots that already have tokens in .env.local keep working. Skip the wizard unless you want a Cursor-managed app.
Provision with the CLI
bash
agent-sdk slack create --dir . # dev app (default)
agent-sdk slack create --dir . --prod # prod appslack create needs a signed-in host (agent-sdk login or CURSOR_API_KEY). It scaffolds agent/channels/slack.ts when the file is missing, opens the dashboard wizard, and waits. Finish Add Slack to this agent in the browser as the same Cursor account. The CLI writes <PREFIX>_SLACK_BOT_TOKEN and <PREFIX>_SLACK_APP_TOKEN into <dir>/.env.local, then runs doctor. Serve and smoke it as described below.
If Slack needs a workspace admin to approve the app, keep the CLI running. Managed install does not file the request — open Slack's Request approval page (the CLI prints the link; the same URL is Send a reminder after you submit). After an admin approves, click Retry in the wizard.
Useful companions: --name / --icon / --channel-posts prefill the wizard. --slack-team T0123ABCD picks a workspace when several are connected. A second slack create for the same agent and env overwrites that Slack app. It keeps the Slack app id and replaces the manifest and tokens. agent-sdk slack destroy deletes the provisioned app. agent-sdk slack icon <source> updates the icon later.
Provision from the dashboard
The same wizard is on Deployed Agents. Open Integrations on an agent and click Add Slack to this agent, or follow the URL slack create printed.
A first-run agent that has no hosted engine yet shows Not deployed yet. Local agent-sdk serve works from .env.local. The next Deploy injects the stored secrets; there is no Slack-only Redeploy.
If Slack needs admin approval, the wizard waits. Click Request approval to file the request in Slack (that same page later sends a reminder). After an admin approves, click Retry.
The app row's menu covers later care: rename, set an icon, rotate tokens, remove the app, or disconnect the workspace.
Wire the env and verify
agent-sdk slack create already wrote the tokens to .env.local, so after provisioning skip straight to doctor. serve / dev / slack doctor load that file (real environment variables always win).
bash
agent-sdk slack doctor --prefix MY_AGENTProceed when app_token, connections_open, bot_token, and auth_test are all green. Then serve and smoke it:
bash
agent-sdk serve --dir . --dev
# expect: [agent-sdk/slack] Socket Mode connectedInvite the bot to a channel and @mention it (or DM it). Expect the Thinking… status, then a threaded streaming reply, with correlated server logs (inbound kind=app_mention, session start, reply delivered via stream|postMessage).
Manual setup
slack init --manual is for a Slack app you own at api.slack.com. You paste the generated manifests and mint tokens yourself. Use this when you want an unmanaged app, or when the dashboard wizard is not an option.
bash
agent-sdk slack init --manual --dir . --name "My Agent"That writes agent/channels/slack.ts, importable manifests at .agent-serve/slack/manifest.{dev,prod}.json, env.example, and setup-status.json. --no-prefix uses shared SLACK_* variables on a single-agent host. --prefix CUSTOM overrides the directory-derived prefix. --channel-posts subscribes the manifests to channel-post events.
slack manifest --env both regenerates the JSON files without touching the channel file.
Create the apps
This phase needs someone with permission to create Slack apps.
- Open api.slack.com/apps → Create New App → From a manifest.
- Paste
manifest.dev.jsonand create the dev app. - Repeat with
manifest.prod.json. Dev and prod are separate Slack apps with separate tokens.
Install and mint tokens
For each app: Install to Workspace and copy the Bot User OAuth Token (xoxb-…). Then under Basic Information → App-Level Tokens create a token with scope connections:write and copy it (xapp-…).
Put the pair in .env.local using the names from env.example, then run slack doctor and serve as above.
Choose when the agent engages
Mentions and DMs are on by default. Watching channels means dispatching on new posts without a mention. That's an explicit opt-in:
ts
export default slackChannel({
envPrefix: "TRIAGE",
engagement: {
// mentions / directMessages default to true
channelPosts: {
allow: ["#triage-alerts"], // explicit allowlist; no wildcard exists
posts: "top-level", // default: thread replies never dispatch
debounceMs: 15_000, // optional: let rapid edits settle
includeBotPosts: false, // default: bot-authored posts never dispatch
},
},
onChannelPost: async (ctx, message) => {
// same contract as onAppMention: return null to skip
return message.markdown.length > 20 ? {} : null;
},
});Channel watching needs the message.channels / message.groups events on the Slack app (Socket Mode only; not available with cursorAccount: true). Pass --channel-posts on slack create or slack init --manual. The bot must also be a member of each watched channel.
Set includeBotPosts: true when the posts worth watching come from bots: alert feeds, webhook integrations, or other agents posting notes. The watching app's own posts stay dropped either way, matched by the bot_id and bot user id from auth.test, so an agent can never dispatch on its own replies. The alert investigator example watches a bot-fed alerts channel this way.
Prepare work on the host
Mention and DM handlers may return a prepared message, workspaceFiles, or cloud block. It's the same host-prep pattern as custom channels. PR agents use it: extract a PR URL from the mention text and run the same host path as the HTTP channel.
Add approval buttons
Tools with needsApproval park until a person decides. Route that through Slack with one flag:
ts
export default slackChannel({
toolApprovals: true, // posts Block Kit Approve/Deny cards + routes clicks
});Approval cards need interactivity on the Slack app. Recreate with slack create if clicks do nothing. Composing events by hand: spread buildToolApprovalEvents({ credentials }) into events and set interactivity: true on the channel so Socket Mode routes the clicks.
Approval buttons need Socket Mode. slackChannel({ cursorAccount: true }) rejects toolApprovals and interactivity at construction, since the Cursor Slack connection does not relay Block Kit clicks. Use a dedicated Slack app to run approvals for a cursor-account agent.
Cards show redacted, truncated arguments (Block Kit size limits); execution still uses the full validated input, so review sensitive tools in the playground when the arguments may exceed the card. Approvals exist for execution: "server" tools on the local runtime only, and parked calls do not survive a host restart. The full lifecycle is in Human-in-the-loop.
Run several agents on one host
One Slack app and token pair per agent. Never share a pair across agents in the same process. envPrefix keeps them apart (WEATHER_AGENT_SLACK_*, TRIAGE_SLACK_*, …), and agents without tokens mount with their Slack channel idle while everything else serves normally.
Keep the channel healthy
Two habits matter most.
- Don't
awaitlong work inside Slack dispatch handlers. The pack dispatches throughwaitUntiland streams as the turn progresses. - In
--dev(loopback) or--allow-anonymous(trusted shared host), the playground can list and stream Slack sessions and resolve their parked approvals (the audit trail records the HTTP caller). Bearer-auth hosts stay strict: Slack approvals must come from Slack interactivity or a matching principal.
CLI reference
The slack subcommands cover setup end to end.
bash
agent-sdk slack setup # two-product chooser plus manual phases
agent-sdk slack create --dir . # dashboard wizard (dev app)
agent-sdk slack create --dir . --prod # prod app
agent-sdk slack destroy --dir . # delete the provisioned app
agent-sdk slack icon ./icon.png --dir . # set the provisioned app's icon
agent-sdk slack init --manual --dir . # manifests to paste at api.slack.com
agent-sdk slack manifest --env both # regenerate those JSON files
agent-sdk slack doctor --prefix MY_AGENT # token / connectivity checksWhat's next
Continue with these pages:
- Human-in-the-loop: the approval lifecycle behind
toolApprovals - Webhooks and custom channels: the mechanism this pack is built on