back to tutorials

How to Build an Advanced Claude Tag with Multiple Slack Agents

Build a more advanced version of "Claude Tag" in Slack using ChatBotKit blueprints - a set of independent specialized @-agents, each its own Slack bot with its own tools and its own access rules. A coding agent with a real shell sandbox, a research agent with its own report library, and an open support agent - on any plan, with any model you choose, including local ones.

The idea behind Claude Tag is simple and good: tag an agent into a Slack conversation and it does real work using the context around it. The catch is that it's an Anthropic feature limited to Team and Enterprise plans, tied to Anthropic's own models.

This tutorial gives you the same capability - and a more advanced version of it - on ChatBotKit, available to anyone, on any plan. Instead of one general assistant, you'll stand up a set of independent specialized agents, each its own Slack bot you tag in its own channel, each with a different goal, a different toolset, and its own access boundary. And because ChatBotKit is model-agnostic, you can run each agent on whatever model you want - a frontier model for one, a fast cheap model for another, or a local, self-hosted model behind your own firewall for the sensitive ones.

Each agent is assembled as a ChatBotKit blueprint, so it's a versionable, cloneable package. The blueprint previews below are live - copy any of them straight into the Blueprint Designer.

What You'll Build

Three independent Slack agents - no shared workspace, no central coordinator. Each is its own Slack app, so people tag the right specialist in the right channel:

AgentHandleWhat it doesToolsAccess
Engineer@devWrites, runs, and debugs code in a real sandboxShell sandbox (bash, read/write, URL import) - no SpaceLocked to #engineering + the eng lead
Researcher@researchResearches the web and keeps a durable report libraryWeb search + fetch + its own SpaceLocked to #research
Support@supportAnswers questions for the whole teamWeb search + fetch - no sandbox, no SpaceOpen to everyone

The point of the diversification: a coding agent needs an executable sandbox and no long-term storage; a research agent needs persistent storage for its reports but no shell; a support agent needs neither and should be reachable by anyone. Three jobs, three different shapes, three different access rules.

These are three separate blueprints with nothing connecting them. Build each on its own, or drop all three onto one canvas - either way they operate independently.

Prerequisites

  • A ChatBotKit account (any plan)
  • A Slack workspace where you can install apps
  • One Slack app per agent - each needs its own Bot User OAuth Token (xoxb-…) and Signing Secret. See the Slack integration guide for creating them from a generated manifest.
  • The channel IDs and user IDs you want to scope each agent to
  • About 30-45 minutes

Tip: A Slack channel ID looks like C01ABCDE234; a user ID looks like U01ABCDE234. Copy a channel's ID from Channel details → About, or a user's from their profile's ⋮ → Copy member ID.

Step 1: The Coding Agent (@dev) - a Real Shell Sandbox

A coding agent is only useful if it can actually run things. @dev gets three shell abilities that execute in an isolated, ephemeral sandbox - note there is no Space here; the sandbox is created per run and thrown away, which is exactly what you want for compiling, testing, and scratch work.

  • bash (shell/exec) - run commands and scripts
  • rw (shell/rw) - read and write files in the sandbox (with optional line ranges)
  • import (shell/import) - pull a file from a URL into the sandbox

Access is locked to your engineering channel plus the engineering lead. And to make the "any model" point concrete, this agent runs on a code-specialized model - swap it for whatever you prefer.

resources:
  '#bot:::dev0engr01':
    type: bot
    data:
      name: '@dev'
      description: Engineering agent with a real sandbox - writes, runs, and debugs code
      backstory: |-
        You are @dev, an engineering agent in Slack with a real sandbox. You
        don't just suggest code - you write it, run it, and iterate until it
        works.

        ## YOUR ROLE
        - Reproduce bugs and investigate them by running commands
        - Write, execute, and test code in the sandbox
        - Draft a short pull-request description for proposed changes
        - Pull in remote files or starter templates when you need them

        ## RULES
        - Always show the command you ran and its actual output
        - Never claim a fix works unless you ran it
        - Call out security and edge-case considerations
        - The current date is ${EARTH_DATE}
      model: gpt-5.3-codex
      skillsetId: '#skillset:::dev0kit01'
      privacy: false
      moderation: false
      visibility: private
      meta: null
  '#skillset:::dev0kit01':
    type: skillset
    data:
      name: Sandbox Tools
      description: Shell execution, file I/O, and URL import in an ephemeral sandbox
      visibility: private
      meta: null
  '#ability:::dev0bash01':
    type: ability
    data:
      skillsetId: '#skillset:::dev0kit01'
      spaceId: null
      name: bash
      description: Execute a shell command or script in the sandbox
      instruction: |-
        template: shell/exec
        parameters: {}
      meta: null
  '#ability:::dev0rw0001':
    type: ability
    data:
      skillsetId: '#skillset:::dev0kit01'
      spaceId: null
      name: rw
      description: Read or write file content in the sandbox, with optional line ranges
      instruction: |-
        template: shell/rw
        parameters: {}
      meta: null
  '#ability:::dev0imp001':
    type: ability
    data:
      skillsetId: '#skillset:::dev0kit01'
      spaceId: null
      name: import
      description: Import data from a URL and save it to a file in the sandbox
      instruction: |-
        template: shell/import
        parameters: {}
      meta: null
  '#slackIntegration:::dev0slk01':
    type: slackIntegration
    data:
      botId: '#bot:::dev0engr01'
      name: '@dev (Slack)'
      description: Engineering agent, reachable in #engineering
      signingSecret: ''
      botToken: ''
      userToken: ''
      contactCollection: false
      sessionDuration: 0
      attachments: true
      references: true
      ratings: true
      visibleMessages: 10
      autoRespond: ''
      allowFrom: |-
        C07ENGCHAN1
        U07ENGLEAD1
      meta: null
positions:
  '#slackIntegration:::dev0slk01':
    x: -160
    'y': 0
  '#bot:::dev0engr01':
    x: 120
    'y': 0
  '#skillset:::dev0kit01':
    x: 380
    'y': 0
  '#ability:::dev0bash01':
    x: 640
    'y': -120
  '#ability:::dev0rw0001':
    x: 640
    'y': 60
  '#ability:::dev0imp001':
    x: 640
    'y': 240
notes: {}
images: {}
frames: {}
tools: {}

Replace C07ENGCHAN1 and U07ENGLEAD1 with your real engineering channel and lead user IDs. Leave botToken and signingSecret empty in the YAML and fill them in through the Designer after importing - they're secrets and shouldn't live in shared blueprint code. Invite the bot to #engineering with /invite @dev, and you can tag it with a stack trace to get a reproduced, tested fix back.

Step 2: The Research Agent (@research) - Its Own Report Library

The researcher has a different shape. It reads the web and writes durable reports it can reuse later - so it gets web tools plus its own Space (a persistent file store, not shared with anyone). The space/storage/* abilities are bound to that Space.

  • Search Web (search/web) and Fetch Web Page (fetch/text/get) - gather sources
  • List / Read / Write Report (space/storage/*) - maintain the research library in its own Space
resources:
  '#bot:::7rvn6c8shig':
    type: bot
    data:
      name: '@research'
      description: Research agent - sourced reports backed by its own library
      backstory: |-
        You are @research, a research agent in Slack. You gather information
        from the web and keep a durable library of sourced reports.

        ## YOUR ROLE
        - Search the web and read sources on a topic
        - Produce structured, sourced reports
        - Save finished reports to your Research Library so they can be reused

        ## REPORT FORMAT
        1. Executive summary (3-5 sentences)
        2. Key findings (bullets)
        3. Supporting detail
        4. Sources

        ## RULES
        - Cite a source for every claim; separate fact from speculation
        - Before researching, check the library for an existing report
        - The current date is ${EARTH_DATE}
      model: claude-4.6-sonnet
      skillsetId: '#skillset:::c9g6vp921v8'
      privacy: false
      moderation: false
      visibility: private
      meta: null
  '#skillset:::c9g6vp921v8':
    type: skillset
    data:
      name: Research Tools
      description: Web search, fetch, and a private report library
      visibility: private
      meta: null
  '#space:::au2pb21gb7c':
    type: space
    data:
      name: Research Library
      description: Private store of finished research reports and notes
      visibility: private
      meta: null
  '#ability:::eoiqlvfsu7':
    type: ability
    data:
      skillsetId: '#skillset:::c9g6vp921v8'
      spaceId: null
      name: Search Web
      description: Search the web for specific keywords
      instruction: |-
        template: search/web
        parameters: {}
      meta: null
  '#ability:::862nbgearo4':
    type: ability
    data:
      skillsetId: '#skillset:::c9g6vp921v8'
      spaceId: null
      name: Fetch Web Page
      description: Fetch the content of a web page from a URL and convert it to text
      instruction: |-
        template: fetch/text/get
        parameters:
          url: ''
      meta: null
  '#ability:::79poi7fmn8g':
    type: ability
    data:
      skillsetId: '#skillset:::c9g6vp921v8'
      spaceId: '#space:::au2pb21gb7c'
      name: List Reports
      description: List files in the research library
      instruction: |-
        template: space/storage/list
        parameters: {}
      meta: null
  '#ability:::7t60p2q3r1c':
    type: ability
    data:
      skillsetId: '#skillset:::c9g6vp921v8'
      spaceId: '#space:::au2pb21gb7c'
      name: Read Report
      description: Read a report from the research library
      instruction: |-
        template: space/storage/read
        parameters: {}
      meta: null
  '#ability:::aemqipcknms':
    type: ability
    data:
      skillsetId: '#skillset:::c9g6vp921v8'
      spaceId: '#space:::au2pb21gb7c'
      name: Write Report
      description: Write a report to the research library
      instruction: |-
        template: space/storage/write
        parameters: {}
      meta: null
  '#slackIntegration:::ds86bvbbb5':
    type: slackIntegration
    data:
      botId: '#bot:::7rvn6c8shig'
      name: '@research (Slack)'
      description: Research agent, reachable in
      signingSecret: ''
      botToken: ''
      userToken: ''
      contactCollection: false
      sessionDuration: 0
      attachments: true
      references: true
      ratings: true
      visibleMessages: 10
      autoRespond: ''
      allowFrom: C07RSCHCHN1
      meta: null
positions:
  '#bot:::7rvn6c8shig':
    x: 120
    'y': 0
  '#skillset:::c9g6vp921v8':
    x: 380
    'y': 0
  '#space:::au2pb21gb7c':
    x: 1035
    'y': 180
  '#ability:::eoiqlvfsu7':
    x: 640
    'y': -180
  '#ability:::862nbgearo4':
    x: 640
    'y': 0
  '#ability:::79poi7fmn8g':
    x: 640
    'y': 180
  '#ability:::7t60p2q3r1c':
    x: 640
    'y': 360
  '#ability:::aemqipcknms':
    x: 640
    'y': 540
  '#slackIntegration:::ds86bvbbb5':
    x: -160
    'y': 0
notes: {}
images: {}
frames: {}
tools: {}

Scope it to #research (C07RSCHCHN1). Now @research analyse the top three vector databases on price and latency will search, fetch sources, write a report into its library, and post a sourced summary - and next time, it can read the saved report instead of redoing the work.

Step 3: The Support Agent (@support) - Lean and Open

The support agent is the opposite of the coder: no sandbox, no Space, just web lookup and a friendly answer. Because it serves the whole team, its allowFrom is * (everyone), and references + ratings are on so answers carry sources and collect 👍/👎 feedback. contactCollection is on to log who's asking.

resources:
  '#bot:::sup0help01':
    type: bot
    data:
      name: '@support'
      description: Front-line support agent - quick, sourced answers for everyone
      backstory: |-
        You are @support, a front-line support agent in Slack. You answer
        questions quickly and clearly for anyone in the channel.

        ## YOUR ROLE
        - Answer product and how-to questions
        - Look things up on the web when you are unsure
        - Escalate to a human when a request is out of scope

        ## RULES
        - Be concise and friendly; lead with the answer
        - Cite a source when you looked something up
        - Never guess at account-specific details - ask the user to confirm
        - The current date is ${EARTH_DATE}
      model: claude-4.6-sonnet
      skillsetId: '#skillset:::sup0kit01'
      privacy: false
      moderation: false
      visibility: private
      meta: null
  '#skillset:::sup0kit01':
    type: skillset
    data:
      name: Support Tools
      description: Web search and fetch - no sandbox, no storage
      visibility: private
      meta: null
  '#ability:::sup0srch01':
    type: ability
    data:
      skillsetId: '#skillset:::sup0kit01'
      spaceId: null
      name: Search Web
      description: Search the web for specific keywords
      instruction: |-
        template: search/web
        parameters: {}
      meta: null
  '#ability:::sup0ftch01':
    type: ability
    data:
      skillsetId: '#skillset:::sup0kit01'
      spaceId: null
      name: Fetch Web Page
      description: Fetch the content of a web page from a URL and convert it to text
      instruction: |-
        template: fetch/text/get
        parameters:
          url: ''
      meta: null
  '#slackIntegration:::sup0slk01':
    type: slackIntegration
    data:
      botId: '#bot:::sup0help01'
      name: '@support (Slack)'
      description: Support agent, open to the whole workspace
      signingSecret: ''
      botToken: ''
      userToken: ''
      contactCollection: true
      sessionDuration: 0
      attachments: true
      references: true
      ratings: true
      visibleMessages: 10
      autoRespond: ''
      allowFrom: '*'
      meta: null
positions:
  '#slackIntegration:::sup0slk01':
    x: -160
    'y': 0
  '#bot:::sup0help01':
    x: 120
    'y': 0
  '#skillset:::sup0kit01':
    x: 380
    'y': 0
  '#ability:::sup0srch01':
    x: 640
    'y': -80
  '#ability:::sup0ftch01':
    x: 640
    'y': 100
notes: {}
images: {}
frames: {}
tools: {}

allowFrom: '*' lets anyone use it. If you'd rather keep it to one public channel, replace * with that channel's ID.

Step 4 (Optional): Make the Researcher Proactive

A great Claude Tag also shows up on its own. Add a trigger that wakes @research every Monday to summarise what's new in its library and post a digest - and a Post to Slack ability so it can deliver the message to the research channel.

This version of the research blueprint adds two nodes (a slack/conversation/start[by-id] ability and a triggerIntegration) on top of Step 2:

resources:
  '#bot:::s59hc8kj0jk':
    type: bot
    data:
      name: '@research'
      description: Research agent - sourced reports backed by its own library
      backstory: |-
        You are @research, a research agent in Slack. You gather information
        from the web and keep a durable library of sourced reports.

        ## YOUR ROLE
        - Search the web and read sources on a topic
        - Produce structured, sourced reports
        - Save finished reports to your Research Library so they can be reused
        - On a schedule, post a digest of what's new

        ## REPORT FORMAT
        1. Executive summary (3-5 sentences)
        2. Key findings (bullets)
        3. Supporting detail
        4. Sources

        ## RULES
        - Cite a source for every claim; separate fact from speculation
        - Before researching, check the library for an existing report
        - The current date is ${EARTH_DATE}
      model: claude-4.6-sonnet
      skillsetId: '#skillset:::his444ffcus'
      privacy: false
      moderation: false
      visibility: private
      meta: null
  '#skillset:::his444ffcus':
    type: skillset
    data:
      name: Research Tools
      description: Web search, fetch, a private report library, and Slack posting
      visibility: private
      meta: null
  '#space:::0d64h3ujih8':
    type: space
    data:
      name: Research Library
      description: Private store of finished research reports and notes
      visibility: private
      meta: null
  '#ability:::lt7i2uqrdeo':
    type: ability
    data:
      skillsetId: '#skillset:::his444ffcus'
      spaceId: null
      name: Search Web
      description: Search the web for specific keywords
      instruction: |-
        template: search/web
        parameters: {}
      meta: null
  '#ability:::vrs855er7to':
    type: ability
    data:
      skillsetId: '#skillset:::his444ffcus'
      spaceId: null
      name: Fetch Web Page
      description: Fetch the content of a web page from a URL and convert it to text
      instruction: |-
        template: fetch/text/get
        parameters:
          url: ''
      meta: null
  '#ability:::icvh6t5k4co':
    type: ability
    data:
      skillsetId: '#skillset:::his444ffcus'
      spaceId: '#space:::0d64h3ujih8'
      name: List Reports
      description: List files in the research library
      instruction: |-
        template: space/storage/list
        parameters: {}
      meta: null
  '#ability:::ujmkdq5mbsg':
    type: ability
    data:
      skillsetId: '#skillset:::his444ffcus'
      spaceId: '#space:::0d64h3ujih8'
      name: Read Report
      description: Read a report from the research library
      instruction: |-
        template: space/storage/read
        parameters: {}
      meta: null
  '#ability:::o91bag8pp5k':
    type: ability
    data:
      skillsetId: '#skillset:::his444ffcus'
      spaceId: '#space:::0d64h3ujih8'
      name: Write Report
      description: Write a report to the research library
      instruction: |-
        template: space/storage/write
        parameters: {}
      meta: null
  '#ability:::f7ej2m6giuk':
    type: ability
    data:
      skillsetId: '#skillset:::his444ffcus'
      name: Post to Slack
      description: Start a Slack conversation by posting a message to a channel
      instruction: |
        template: slack/conversation/start[by-id]
        params:
          slackIntegrationId: '#slackIntegration:::3qkgi4f5jb'
          channel: ''
          text: ''
      meta: null
  '#slackIntegration:::3qkgi4f5jb':
    type: slackIntegration
    data:
      botId: '#bot:::s59hc8kj0jk'
      name: '@research (Slack)'
      description: Research agent, reachable in
      signingSecret: ''
      botToken: ''
      userToken: ''
      contactCollection: false
      sessionDuration: 0
      attachments: true
      references: true
      ratings: true
      visibleMessages: 10
      autoRespond: ''
      allowFrom: C07RSCHCHN1
      meta: null
  '#triggerIntegration:::p676m3cu82o':
    type: triggerIntegration
    data:
      botId: '#bot:::s59hc8kj0jk'
      name: Weekly Research Digest
      description: |-
        Every Monday morning, review the Research Library, summarise what's new,
        and post a digest to the research channel.

        Weekly research digest.

        1. Use "List Reports" and "Read Report" to review the most recent
           reports in the library.
        2. Write a short digest: what's new, what changed, what deserves
           attention this week.
        3. Post the digest to the research channel with "Post to Slack".
      authenticate: true
      sessionDuration: 0
      schedule: 0 9 * * 1
      timezone: Europe/London
      meta: null
positions:
  '#bot:::s59hc8kj0jk':
    x: 120
    'y': 0
  '#skillset:::his444ffcus':
    x: 380
    'y': 0
  '#space:::0d64h3ujih8':
    x: 1035
    'y': 180
  '#ability:::lt7i2uqrdeo':
    x: 640
    'y': -180
  '#ability:::vrs855er7to':
    x: 640
    'y': 0
  '#ability:::icvh6t5k4co':
    x: 640
    'y': 180
  '#ability:::ujmkdq5mbsg':
    x: 640
    'y': 360
  '#ability:::o91bag8pp5k':
    x: 640
    'y': 540
  '#ability:::f7ej2m6giuk':
    x: 640
    'y': 720
  '#slackIntegration:::3qkgi4f5jb':
    x: -160
    'y': 0
  '#triggerIntegration:::p676m3cu82o':
    x: -160
    'y': 260
notes: {}
images: {}
frames: {}
tools: {}

The trigger fires 0 9 * * 1 (09:00 every Monday, Europe/London) and posts through the Post to Slack ability. The same pattern works for any agent - give it a send ability and a schedule.

Access Control, Per Agent

Because each agent is its own integration, you tune access independently with allowFrom - and it's enforced before the model runs, so a blocked sender's message never reaches the agent. No prompt-injection trick gets past it.

AgentallowFromEffect
@dev#engineering channel + eng leadOnly engineering can reach the sandbox agent
@research#research channelScoped to the research team
@support*Anyone in the workspace

Rules of thumb:

  • Use IDs, not @username/#channel-name, for anything security-sensitive - names can be renamed or transferred, IDs are stable.
  • An empty allowFrom denies everyone; * allows everyone. There's no "default open" by accident.
  • Give the most powerful agent (the one with the shell) the tightest boundary.

Choose Your Models (Including Local)

ChatBotKit is model-agnostic, so each agent's model is an independent choice. In this tutorial @dev runs on a code-specialized model while @research and @support run on a general one - but you can mix freely:

  • A frontier model for hard reasoning (architecture, research synthesis)
  • A fast, cheap model for high-volume support
  • A local, self-hosted model for agents that touch sensitive code or data - point the bot at your own model endpoint and nothing leaves your network

Change a bot's model field, or set it in the Designer, and the same blueprint runs on entirely different infrastructure. Nothing else in the agent has to change.

Going Further

  • More agents - add a @data analyst (SQL + chart abilities), a @notes second-brain (its own Space), or a @releases bot on a triggered schedule. Each is another small, independent blueprint.
  • Datasets - attach a Dataset to @support for retrieval-augmented answers grounded in your own docs.
  • MCP - connect external tools (CRM, ticketing, data warehouse) via an MCP server resource and expose them as abilities to just the agent that needs them.
  • Versioning - clone a blueprint to experiment, and download the YAML for source control. See Blueprints for the full resource reference.

You now have an advanced Claude Tag without the Claude Tag plan requirement: independent specialized agents, each scoped to the right people, each running on the model you choose - including local ones - all built from blueprints anyone can use.