Skip to content

Latest commit

 

History

History
376 lines (247 loc) · 17.9 KB

README.md

File metadata and controls

376 lines (247 loc) · 17.9 KB

Coding Agent for Slack (Bolt for Python and Claude Agent SDK)

A sample Slack agent that showcases Slack Code built with Bolt for Python and the Claude Agent SDK using models from Anthropic. Mention it in a channel with a task and a project repo, and it decides whether to answer in place or create a dedicated code channel for the work. In a code channel it clones the repo into a temporary workspace and makes changes with the necessary approvals, keeping the channel's plan, context bar, and code tab current as it goes.

Warning

This agent runs commands generated by a language model on the machine running the app. Only run it against trusted repositories on a machine you're comfortable letting it operate. Refer to Command approvals for how command approvals work.

App Overview

The coding agent works like this:

  1. You @Coding Agent fix the flaky login test in <repo> in any channel, or send the agent a direct message.
  2. The agent decides how to handle it, reading the thread or direct message the request came from. A question gets an answer in place. Work that takes more than one message gets a code channel, created with the create_code_channel tool and linked back to your message.
  3. Slack seeds the new channel with your request, mention included, and that mention starts the session there, along with the conversation the request came from. The agent clones the repo, pins it to the context bar, writes a plan, and registers a few slash commands for the channel.
  4. It reads and edits files and runs other commands in the clone. Riskier commands ask for your approval first.
  5. It publishes the diff to the code tab and summarizes what it did.
  6. Keep chatting in the channel (no @-mention needed), comment on the plan, or use its slash commands to iterate. Messages sent while the agent works go into its next turn together.
  7. When you say it's done, the agent posts a summary, which is shared back to your original message, and archives the channel.

Tools

Each code channel method is a tool the model calls when it decides to, in agent/tools/, one file per tool. The tools are served to the model by an in-process MCP server, so it sees each one as mcp__coding-agent__<name>:

Tool Method
create_code_channel agents.conversations.create
set_context_bar agents.conversations.setProperties
publish_diff agents.conversations.setView (diff)
publish_html_view agents.conversations.setView (html)
write_plan canvases.create + agents.conversations.setView (canvas), then agents.conversations.setCanvasContent
read_plan agents.conversations.getCanvas
list_views agents.conversations.listViews
remove_view agents.conversations.removeView
set_commands agents.conversations.setCommands
rename_session agents.sessions.rename
archive_channel chat.postMessage + agents.sessions.setStatus (closed) + agents.conversations.archive
clone_repo Local git clone into a temp directory
read_file, write_file, edit_file Local files in that directory
run_command A local program in that directory, run without a shell

Code channel behavior

  • Plan as a canvas. The plan is attached with comment access, so people can comment but only the agent edits. read_plan gives the agent open comments each turn, and setCanvasContent keeps comments anchored to the sections it didn't change.
  • Agent slash commands. Commands registered with set_commands arrive as slash commands. One catch-all listener (listeners/commands/) turns /run-tests unit into a turn, so the agent decides what each command does.
  • Stop button. On agent_session_stopped, the app flags the session so the in-flight run calls no more tools and posts nothing more, replaces any pending Approve / Deny buttons so they can't resume it, confirms the stop in the channel, and sets the session to active. Slack doesn't change the status on a stop, so the app has to.
  • Archiving from Slack. On channel_archive or group_archive, the app forgets the session and deletes its working directory, the same cleanup archive_channel does when the agent wraps up.
  • Picking up code channels. A message, mention, or slash command in a code channel the app has no state for, whether the seeded request in a channel the agent just created, one started from Slack's UI, or one forgotten after a restart, is recognized with conversations.info (properties.record_channel.record_type == "agent_channel") and picked up as a session. Other channels are looked up once and remembered.
  • The conversation comes along. A new session reads the thread or direct message its channel was started from (properties.agent_session.origin_link) and gives it to the agent's first turn, so a request like "fix this" keeps its context.
  • One turn at a time. Messages that arrive while the agent works wait, and go into its next turn together, each labeled with who sent it. The stop button drops them along with the turn.
  • Creating a channel is the agent's call. The prompt in agent/prompts.py follows Slack's guidance: create a channel for multi-step work like changing code or running tests, and reply in place for anything that fits in one message.
  • Idempotent creation. create_code_channel takes only a title. Its session_id comes from the triggering message, so a repeated call or a retried event reuses the same channel. The first turn only ever starts from the seeded mention, so it runs once.

Command approvals

Every tool call the coding agent makes goes through a Claude Agent SDK PreToolUse hook (agent/guardrails.py), and for run_command it asks command_needs_approval in agent/approvals.py:

  • Read-only commands and local git changes run with no prompt. A path outside the clone always asks. Commands run without a shell, so they can't chain a second command, and what the approval check reads is exactly what runs.
  • File edits don't go through approvals. write_file and edit_file only write inside the clone.
  • Anything else shows Approve and Deny buttons in the code channel, and the command runs only if someone in the channel approves it.

The agent gets only these tools. Claude Code's built-in tools, like its own Bash and Edit, are turned off, and so are the settings, CLAUDE.md files, and MCP servers configured on the machine running the app.

Setup

Before getting started, make sure you have a development workspace where you have permissions to install apps.

Developer Program

Join the Slack Developer Program for exclusive access to sandbox environments for building and testing your apps, tooling, and resources created to help you build and grow.

Claude Code, Codex, Cursor, and Other Coding Agents

Install the Slack MCP and Skills Plugin so your coding agent can create, run, and deploy Slack apps, look up Web API methods and scopes, and build Block Kit without guessing.

Claude Code

Run this in a Claude Code session:

/plugin install slack@claude-plugins-official

Codex

Run this in your terminal:

codex plugin marketplace add slackapi/slack-skills-plugin
codex plugin add slack@slack

Cursor

Run this in a Cursor Agent chat:

/add-plugin slack

Other agents

Install the skills with npx skills, using your agent's identifier:

npx skills add slackapi/slack-skills-plugin -y -a <agent>

# For example, OpenCode
npx skills add slackapi/slack-skills-plugin -y -a opencode

Create the Slack app

Using Slack CLI

Install the latest version of the Slack CLI for your operating system:

You'll also need to log in if this is your first time using the Slack CLI.

slack login

Initializing the project

slack create my-coding-agent --template slack-samples/bolt-python-coding-agent --subdir claude-agent-sdk
cd my-coding-agent
Using App Settings

Create Your Slack App

  1. Open https://api.slack.com/apps/new and choose "From an app manifest"
  2. Choose the workspace you want to install the application to
  3. Copy the contents of manifest.json into the text box that says *Paste your manifest code here* (within the JSON tab) and click Next
  4. Review the configuration and click Create
  5. Click Install to Workspace and Allow on the screen that follows. You'll then be redirected to the App Configuration dashboard.

Environment Variables

Before you can run the app, you'll need to store some environment variables.

  1. Rename .env.sample to .env.
  2. Open your apps setting page from this list, click OAuth & Permissions in the left hand menu, then copy the Bot User OAuth Token into your .env file under SLACK_BOT_TOKEN.
SLACK_BOT_TOKEN=YOUR_SLACK_BOT_TOKEN
  1. Click Basic Information from the left hand menu and follow the steps in the App-Level Tokens section to create an app-level token with the connections:write scope. Copy that token into your .env as SLACK_APP_TOKEN.
SLACK_APP_TOKEN=YOUR_SLACK_APP_TOKEN

Initializing the project

git clone https://github.com/slack-samples/bolt-python-coding-agent.git my-coding-agent
cd my-coding-agent/claude-agent-sdk

Setup your python virtual environment

python3 -m venv .venv
source .venv/bin/activate  # for Windows OS, .\.venv\Scripts\Activate instead should work

Install dependencies

pip install -e ".[dev]"

Providers

Anthropic Setup

This app uses Claude through the Claude Agent SDK: claude-sonnet-5-5 for the coding agent and claude-haiku-4-5 for deciding whether a request needs a code channel.

  1. Create an API key from your Anthropic dashboard.
  2. Rename .env.sample to .env, if you haven't already.
  3. Save the Anthropic API key to .env:
ANTHROPIC_API_KEY=YOUR_ANTHROPIC_API_KEY

The Claude Agent SDK runs the Claude Code CLI that comes bundled with it, and the CLI keeps each session's conversation as a transcript under ~/.claude/projects/, including tool inputs and outputs like file contents and command output. Follow-up turns resume from that transcript.

Development

Starting the app

Using the Slack CLI

Slack CLI

slack run
Using the Terminal

Terminal

python3 app.py
Using OAuth HTTP Server (with ngrok)

OAuth HTTP Server

This mode uses an HTTP server instead of Socket Mode, which is required for OAuth-based distribution.

  1. Install ngrok and start a tunnel:
ngrok http 3000
  1. Copy the https://*.ngrok-free.app URL from the ngrok output.
Using Slack CLI

Slack CLI

  1. Update manifest.json for HTTP mode:

    • Set socket_mode_enabled to false
    • Replace ngrok-free.app with your ngrok domain (e.g. YOUR_NGROK_SUBDOMAIN.ngrok-free.app)
  2. Create a new local dev app:

slack install -E local
  1. Update your .env OAuth environment variables:
    • Run slack app settings to open App Settings
    • Copy Client ID, Client Secret, and Signing Secret
SLACK_CLIENT_ID=YOUR_CLIENT_ID
SLACK_CLIENT_SECRET=YOUR_CLIENT_SECRET
SLACK_SIGNING_SECRET=YOUR_SIGNING_SECRET
  1. Start the app:
slack run app_oauth.py
  1. Open https://YOUR_NGROK_SUBDOMAIN.ngrok-free.app/slack/install to install the app to your workspace via OAuth.
Using the Terminal

Terminal

  1. Create your Slack app at api.slack.com/apps/new using manifest.json. Before pasting the manifest, set socket_mode_enabled to false and replace ngrok-free.app with your ngrok domain.

  2. Copy the following values from Basic Information into your .env:

    • Signing Secret
    • Client ID and Client Secret
SLACK_CLIENT_ID=YOUR_CLIENT_ID
SLACK_CLIENT_SECRET=YOUR_CLIENT_SECRET
SLACK_SIGNING_SECRET=YOUR_SIGNING_SECRET
  1. Start the app:
python3 app_oauth.py
  1. Open https://YOUR_NGROK_SUBDOMAIN.ngrok-free.app/slack/install to install the app to your workspace via OAuth.

Note: Each time ngrok restarts, it generates a new URL. You'll need to update the ngrok domain in manifest.json and re-install the app.

Using the App

Once the agent is running, there are several ways to interact:

App Home — Open the agent in Slack and click the Home tab to see how to start a task.

Direct Messages — Open the agent's Messages tab and ask a question or describe a task, or pick a suggested prompt. It answers there, or links a code channel for bigger work.

Channel @mentions — Invite the agent to a channel by typing /invite @agent-name in the message box, then @mention it in a thread with a question about the conversation, like what did we decide here?, and it answers in the thread. @mention it with a task and a repo, like add a "hello world" to the README of <repo>, and it replies with a link to a new code channel for the work.

Code Channels — Follow the work in the new channel. Approve or deny commands as they come up, comment on the Plan tab, review the diff in the code tab, and send follow-ups without an @mention. Say you're done to wrap up and archive the channel.

Linting

# Run ruff check from root directory for linting
ruff check

# Run ruff format from root directory for code formatting
ruff format

Testing

# Run pytest from root directory for unit testing
pytest .

Project Structure

manifest.json

manifest.json is a configuration for Slack apps. With a manifest, you can create an app with a pre-defined configuration, or adjust the configuration of an existing app.

app.py

app.py is the entry point for the application and is the file you'll run to start the server. This project aims to keep this file as thin as possible, primarily using it as a way to route inbound requests. It runs Bolt's AsyncApp in Socket Mode, so a long agent turn never holds up the stop button, an approval click, or another channel, and agent slash commands are routed to it without a slash_command_url.

app_oauth.py

app_oauth.py is an alternative entry point that runs the app in HTTP mode instead of Socket Mode. This is intended for deployments that use OAuth for app distribution. See the OAuth HTTP Server section under Development for setup instructions.

/listeners

Every incoming request is routed to a "listener". This directory groups each listener based on the Slack Platform feature used.

/listeners/events — Handles incoming events:

  • agent_session_stopped.py — Stops the session when the stop button is pressed.
  • app_home_opened.py — Publishes the App Home view with how to start a task.
  • app_mentioned.py — Answers a mention in place or hands it to a code channel, and runs turns for mentions inside one.
  • channel_archive.py — Forgets a code channel archived from Slack.
  • message.py — Answers direct messages like mentions, and runs a turn for messages in a code channel.

/listeners/actions — Handles interactive components:

  • approval_buttons.py — Resumes a paused run when Approve or Deny is clicked.

/listeners/commands — Handles slash commands:

  • agent_command.py — Turns any command the agent registered into a turn.

/agent

  • agent.py — Defines the routing agent and the coding agent with their tools, and starts and resumes their runs. Along with guardrails.py and tools, it's the only code that uses the Claude Agent SDK.
  • prompts.py — Holds both agents' prompts.
  • turns.py — Runs each turn for the listeners: it answers requests, sets the session status, runs the agent, and posts the outcome or the Approve / Deny buttons. It also queues messages that arrive mid-turn and handles approvals and the stop button.
  • outcome.py — Defines the RunOutcome that agent.py returns and turns.py reads.
  • context.py — Reads the thread or direct message a request came from, so the agent can follow the conversation.
  • guardrails.py — Holds the SDK hooks around tool calls: the approval check for run_command, which defers a command that needs approval, and the check that skips every tool call once the session is stopped.
  • sessions.py — Keeps each code channel's session in memory: its working directory, plan canvas, conversation history, and messages waiting for the next turn. It also defines the AgentDeps and RequestDeps dataclasses passed to the agents at runtime, providing access to the Slack client and the session or request.
  • approvals.py — Decides which commands need approval and holds runs paused on one.
  • runtime.py — Holds what the tools and hooks share during a run: its deps, which the SDK can't pass to a tool, and the shape of a tool's reply.
  • workspace.py — Runs commands in the clone without the app's secrets, and keeps file tools inside it.
  • tools — Contains one file per tool the agents can call.