Multiplayer channels in Cortex Agents for Slack

Introduction

A multiplayer channel is a Slack channel where everyone uses the same CoCo (Cortex Code) agent and the same Snowflake identity. An administrator binds the channel to a Snowflake service user and a role, and can optionally bind a CoCo agent object and a Restricted Session Scope (RSS). If you omit the agent object, the channel still runs CoCo as the bound service user, without applying a stored agent specification. Any member of the channel can start a request by @mentioning the app. The app runs the request as the bound service user, with the bound role.

Channel members don’t need to be Snowflake users in your account, and they don’t need to sign in to Snowflake from Slack. This differs from the per-user setup, where each person signs in as their own Snowflake user and the app runs their requests as that user. In a multiplayer channel, every request runs as the same bound service user, whoever sends it.

Use CoCo in a multiplayer channel

After the app is invited to a bound channel, @mention it in a top-level message to start a public thread. The reply and follow-up messages in that thread are visible to the channel. After the initial mention, any channel member can send a follow-up in the thread without mentioning the app again. To start a separate conversation, post a new top-level message and mention the app. To start a thread from every top-level message, without an @mention, set autoStartOnAnyMessage on the agent. See configure channel behavior.

In a multiplayer channel, requests behave differently from requests in a direct message with the app:

  • Only the bound role is active. Secondary roles are disabled, so a request can use only the privileges of the role in the binding, even if other roles are granted to the service user.
  • No approval prompts. In a direct message, CoCo asks for approval before it takes an action such as running a shell command. In a multiplayer channel, CoCo doesn’t ask, because a channel has no single person to approve the action. The bound role, and the RSS if you specify one, are the only limits on what a request can do.

A bound channel answers with CoCo by default. To answer with one of your Snowflake Intelligence (CoWork) agents instead, including that agent’s own tools such as Cortex Analyst, set up the channel as described in set up a CoWork channel.

You can bind specific channels, and you can also set a workspace-wide default binding. The default binding applies to every channel in the Slack workspace that doesn’t have a binding of its own. Because a binding gives everyone in the channel access to Snowflake, review security considerations for multiplayer channels before you bind one.

Important

When you use this integration, you are directing Snowflake to send or receive data between the Snowflake Service and Slack. Snowflake is not responsible for the privacy, security, or integrity of data once it leaves the Snowflake Service boundary. Your use of Slack, and any data you process with it, is governed solely by the terms between you and Slack.

AI-generated content

AI can make mistakes. CoCo is powered by large language models (LLMs). AI-generated responses (including text, SQL, tables, charts, and generated reports) may be inaccurate, incomplete, inappropriate, or biased. Review and verify outputs before relying on them, and apply human oversight to any decisions based on them.

For more information, see the Legal notices at the bottom of this page.

Stop CoCo from following a thread

After the initial mention, CoCo keeps following the thread so that channel members can send follow-ups without mentioning the app. To stop CoCo from following a thread, reply in that thread with @Snowflake STOP. CoCo unsubscribes from the thread and stops responding to follow-up messages in it.

Privilege model

The following table summarizes what each type of user can do with multiplayer channels. For the privileges that a binding requires, see required privileges for multiplayer channels.

WhoCan do
Administrator (a role that can create agents, roles, and users)

Create the agent object, the RSS, the role, and the service user that a binding uses.

Binding administrator (a role with OPERATE or OWNERSHIP on the Slack integration for a specific channel, or OWNERSHIP for a workspace-wide default binding)

Bind and unbind channels. To list all bindings, the role must have OWNERSHIP on the integration.

Channel member

@mention the app in a bound channel, without being a Snowflake user in your account.

Set up the agent and service user

Before you start, install the app from Snowsight as ACCOUNTADMIN. That installation creates the SNOWFLAKE_SLACK_APP integration and makes ACCOUNTADMIN its owner. For instructions, see install Cortex Agents for Slack.

The following statements create the database, schema, and other objects that a bound channel uses on an account where they don’t already exist. Run them as ACCOUNTADMIN. If you use an existing database or schema, omit its CREATE statement. PUBLIC and ALL can’t be the bound role.

  1. Create the database, schema, and stage that hold the agent, and a warehouse for the service user:

    USE ROLE ACCOUNTADMIN;
    
    CREATE DATABASE CORTEX_CODE;
    CREATE SCHEMA CORTEX_CODE.CONFIG;
    CREATE STAGE CORTEX_CODE.CONFIG.COCO_SKILLS;
    
    CREATE WAREHOUSE SLACK_SVC_WH
      WAREHOUSE_SIZE = XSMALL
      AUTO_SUSPEND = 60
      AUTO_RESUME = TRUE;
    
  2. Create the role and the service user. Grant the role to the user, then grant the role the warehouse, the database and schema, the skill stage, the Slack integration, and the Cortex user database role:

    CREATE ROLE SLACK_SVC_ROLE;
    
    CREATE USER SLACK_SVC_USER
      TYPE = SERVICE
      DEFAULT_ROLE = SLACK_SVC_ROLE
      DEFAULT_WAREHOUSE = SLACK_SVC_WH;
    GRANT ROLE SLACK_SVC_ROLE TO USER SLACK_SVC_USER;
    
    GRANT USAGE ON WAREHOUSE SLACK_SVC_WH TO ROLE SLACK_SVC_ROLE;
    GRANT USAGE ON DATABASE CORTEX_CODE TO ROLE SLACK_SVC_ROLE;
    GRANT USAGE ON SCHEMA CORTEX_CODE.CONFIG TO ROLE SLACK_SVC_ROLE;
    GRANT READ ON STAGE CORTEX_CODE.CONFIG.COCO_SKILLS TO ROLE SLACK_SVC_ROLE;
    GRANT USAGE ON INTEGRATION SNOWFLAKE_SLACK_APP TO ROLE SLACK_SVC_ROLE;
    GRANT DATABASE ROLE SNOWFLAKE.CORTEX_USER TO ROLE SLACK_SVC_ROLE;
    

    data read in the RSS does not grant access to tables. Grant SLACK_SVC_ROLE USAGE on each database and schema it should query, and SELECT on the tables. To make a bound-channel agent discover an MCP server, grant USAGE on that server directly to SLACK_SVC_ROLE; a grant only to PUBLIC does not make SHOW TERSE EXTERNAL MCP SERVERS IN ACCOUNT return the server.

  3. Grant IMPERSONATE on the service user to ACCOUNTADMIN. That role is both the caller of the bind and the owner of the integration, and both must hold IMPERSONATE:

    GRANT IMPERSONATE ON USER SLACK_SVC_USER TO ROLE ACCOUNTADMIN;
    
  4. Create the agent. Give it a display name so channel members can recognize it. Upload the sql-author skill file to CORTEX_CODE.CONFIG.COCO_SKILLS before the first request in the channel. Tag mode reads models.orchestration, instructions.system, and each skill as a {name, source} object:

    CREATE OR REPLACE AGENT CORTEX_CODE.CONFIG.SLACK_DEFAULT_AGENT
      PROFILE = '{"display_name": "Data team assistant"}'
    FROM SPECIFICATION $$
    models:
      orchestration: claude-sonnet-4-5
    instructions:
      system: "You are the Snowflake assistant for this channel..."
    skills:
      - name: sql-author
        source:
          type: STAGE
          path: "@CORTEX_CODE.CONFIG.COCO_SKILLS/sql-author"
    $$;
    
    GRANT USAGE ON AGENT CORTEX_CODE.CONFIG.SLACK_DEFAULT_AGENT TO ROLE SLACK_SVC_ROLE;
    
  5. Optional: Create an RSS to limit what the agent can do in the channel. data read covers reading data and USAGE on databases, schemas, and workspace read. program usage covers USAGE on the agent. compute usage covers the warehouse. Those groups don’t cover creating a workspace or writing to one. Allow CREATE SCHEMA on the service user’s personal database, CREATE WORKSPACE on the schemas in that database, and READ and WRITE on the workspaces there. An isolated channel creates schema USER$.SLACK_TAG and a workspace in it. A shared channel uses USER$.PUBLIC.DEFAULT$. Naming one existing workspace doesn’t cover a workspace the channel still has to create. Do not put USAGE on the Slack integration in the RSS. An RSS rejects that privilege. The integration grant in step 2 is the grant the bound role needs.

    CREATE and OWNERSHIP on a personal database can’t be granted to an account role. The service user creates objects in its own personal database. The RSS still has to allow those privileges, because a session can use only the intersection of the role and the RSS.

    CREATE OR REPLACE RESTRICTED SESSION SCOPE CORTEX_CODE.CONFIG.SLACK_CHANNEL_RSS AS $$
    privilege_scopes:
      allowed_privileges:
        - privileges: [data read, program usage, compute usage]
          account: [all]
        - privileges: [usage]
          object_types: [restricted session scope]
          account: [all]
        - privileges: [create schema]
          object_types: [database]
          databases: [USER$SLACK_SVC_USER]
        - privileges: [create workspace]
          object_types: [schema]
          databases: [USER$SLACK_SVC_USER]
        - privileges: [read, write]
          object_types: [workspace]
          databases: [USER$SLACK_SVC_USER]
    $$;
    
    GRANT USAGE ON RESTRICTED SESSION SCOPE CORTEX_CODE.CONFIG.SLACK_CHANNEL_RSS TO ROLE SLACK_SVC_ROLE;
    

    For more information, see restricted session scopes.

Configure channel behavior

The bound agent’s specification controls how a multiplayer channel runs. Put the keys in this section under slack. The same keys under experimental.slack still apply. When both sections set one key, the value under slack is the one that applies. A thread keeps the settings it started with, so a change applies only to threads that start afterward. To change a specification, follow change the agent specification.

KeyDefault when unsetWhat it does
autoStartOnAnyMessageOff

A top-level message starts a run with no @mention. Use this when the channel should auto-reply to every top-level message. A thread the app already joined continues without it.

tagThreadFollowUpsOn

A plain reply in a thread the app already joined continues that run. Set this to false to require an @mention on every follow-up. @Snowflake STOP still unsubscribes the app from a thread. See stop CoCo from following a thread.

mountWorkspaceOnMount the service user’s workspace at /workspace.
isolateWorkspaceOn for a DEFAULT binding, off otherwise

Give the channel its own workspace under USER$.SLACK_TAG. Leave this unset, or set it to false, to use the shared USER$.PUBLIC.DEFAULT$ workspace.

tagMemory

On when the workspace is mounted and isolated. Off when the workspace is shared.

Keep markdown notes for the channel under /workspace/slack-memory/<team>_<channel>/. Set this to true to turn notes on for a shared workspace. Set it to false to turn notes off. CoWork runs don’t use channel memory.

tagRunModesnowflake-coco

snowflake-coco or snowflake-cowork. Any other value, including cowork, is treated as unset. A binding that names an RSS stays on CoCo. See set up a CoWork channel.

The channel also reads these keys from the rest of the specification.

KeyWhat it does
models.orchestrationModel for the run. CoWork can use auto.
instructions

system, orchestration, and response instructions for the run.

experimental.thinking_effort

Reasoning effort for the run: minimal, low, medium, high, xhigh, or max. none turns reasoning off when the model allows it. An unrecognized value is ignored. A model might not accept every value.

experimental.github_mounts

Repositories to clone before the run. name is owner/repo. dest is optional and defaults to a directory named for the repository under the home directory. The clone runs when the service user has a GITHUB_SECRET secret in USER$.PUBLIC. An invalid entry is skipped.

experimental.sandbox_hooks.pre_run

Bash that runs after those clones, before the run. A failing script stops the run. The hook times out after 300 seconds.

This specification auto-replies to every top-level message, gives the channel its own workspace, and clones two repositories before the run:

slack:
  autoStartOnAnyMessage: true
  tagThreadFollowUps: true
  mountWorkspace: true
  isolateWorkspace: true
  tagMemory: true
  tagRunMode: snowflake-coco
experimental:
  thinking_effort: high
  github_mounts:
    - name: owner/repo
    - name: owner/other-repo
      dest: ~/other-repo
  sandbox_hooks:
    pre_run: |
      set -euo pipefail
      echo setup

Bind a channel

To bind a channel, invite the app and create the binding. The app must be a member of the channel to receive its messages and post replies.

  1. In the Slack channel, invite the app. For example, send /invite @Snowflake.

  2. Using a role with OPERATE or OWNERSHIP on the Slack integration for a specific channel, call SYSTEM$SLACK_CHANNEL_BIND. To bind 'DEFAULT', use a role with OWNERSHIP on the integration:

    SELECT SYSTEM$SLACK_CHANNEL_BIND(
      'SNOWFLAKE_SLACK_APP',
      '<slack_channel_id>',
      'SLACK_SVC_USER',
      'SLACK_SVC_ROLE',
      'CORTEX_CODE.CONFIG.SLACK_DEFAULT_AGENT',
      'CORTEX_CODE.CONFIG.SLACK_CHANNEL_RSS'
    );
    

After you bind the channel and invite the app, any member of the channel can @mention the app. A new top-level message must mention the app, unless the agent sets autoStartOnAnyMessage. Follow-up messages in an existing agent thread do not need another mention, unless the agent sets tagThreadFollowUps to false.

The function takes positional arguments, in the following order. It doesn’t accept named arguments (name => value). To omit the optional arguments, leave them off the end of the argument list. To specify an RSS, you must also specify the agent object.

PositionArgumentDescription
1IntegrationRequired. Name of the Slack integration.
2Channel ID

Required. ID of the Slack channel to bind. To set the workspace-wide default binding, specify 'DEFAULT'.

3Service userRequired. Name of the service user to bind to the channel.
4Role

Required. Name of a role other than PUBLIC or ALL to use for the service user’s session.

5Agent object

Optional. Fully qualified name of the CoCo or CoWork agent object to use.

6RSSOptional. Fully qualified name of the RSS to use.

Set up a CoWork channel

A CoWork channel is a bound channel that answers an @mention with one of your Snowflake Intelligence (CoWork) agents, including that agent’s own tools, such as Cortex Analyst. A channel that isn’t set up this way answers with CoCo. As in any multiplayer channel, every request runs as the one bound service user and role, whoever sends it.

A channel runs CoWork only when both of the following are true:

  • The bound agent’s specification sets tagRunMode to snowflake-cowork under slack or experimental.slack. The bare value cowork isn’t recognized. See configure channel behavior.
  • The binding names the agent object and no RSS. A binding that specifies an RSS always runs CoCo.

To set up the channel, create the service user, role, and warehouse as described in set up the agent and service user. The stage for CoCo skills isn’t needed. Then grant the role access to the data, create the agent, and bind the channel:

  1. Grant the role only what the channel needs, because everyone in the channel gets it:

    GRANT USAGE ON DATABASE my_db TO ROLE SLACK_SVC_ROLE;
    GRANT USAGE ON SCHEMA my_db.my_schema TO ROLE SLACK_SVC_ROLE;
    GRANT SELECT ON VIEW my_db.my_schema.my_view TO ROLE SLACK_SVC_ROLE;
    GRANT SELECT ON SEMANTIC VIEW my_db.my_schema.my_semantic_view TO ROLE SLACK_SVC_ROLE;
    

    If a semantic view would expose personal data, such as names, email addresses, or phone numbers, leave those columns out of it.

  2. Create the agent with its tools and turn on CoWork mode:

    CREATE OR REPLACE AGENT my_db.my_schema.slack_cowork_agent
      PROFILE = '{"display_name": "Data assistant"}'
    FROM SPECIFICATION $$
    models:
      orchestration: auto
    instructions:
      orchestration: "Answer questions about sales using the sales tool. Be concise."
    tools:
      - tool_spec:
          type: cortex_analyst_text_to_sql
          name: sales
          description: "Sales revenue, orders and units by date, product and region."
    tool_resources:
      sales:
        semantic_view: my_db.my_schema.my_semantic_view
        execution_environment:
          type: warehouse
          warehouse: SLACK_SVC_WH
    experimental:
      slack:
        tagRunMode: snowflake-cowork
    $$;
    
    GRANT USAGE ON AGENT my_db.my_schema.slack_cowork_agent TO ROLE SLACK_SVC_ROLE;
    

    The bound role also needs USAGE on the agent’s database and schema.

  3. Invite the app to the channel, then bind the channel with the agent object and no RSS:

    SELECT SYSTEM$SLACK_CHANNEL_BIND(
      'SNOWFLAKE_SLACK_APP',
      '<slack_channel_id>',
      'SLACK_SVC_USER',
      'SLACK_SVC_ROLE',
      'my_db.my_schema.slack_cowork_agent'
    );
    

    To confirm the binding names an agent and no RSS, call SYSTEM$SHOW_SLACK_CHANNEL_BINDS.

  4. @mention the app in a new top-level message. The reply comes from Snowflake - CoWork, and follow-up messages in that thread don’t need another mention.

A thread keeps the mode it started in, so a change to the agent or the binding applies only to threads that start afterward.

Change the agent specification

SET SPECIFICATION replaces the whole specification, so start from the current one in the agent_spec column of DESCRIBE AGENT, then add a version, set the new specification, commit, and make the new version the default:

ALTER AGENT my_db.my_schema.slack_cowork_agent ADD LIVE VERSION FROM LAST;
ALTER AGENT my_db.my_schema.slack_cowork_agent MODIFY LIVE VERSION
  SET SPECIFICATION = $$ <full_specification> $$;
ALTER AGENT my_db.my_schema.slack_cowork_agent COMMIT;
ALTER AGENT my_db.my_schema.slack_cowork_agent SET DEFAULT_VERSION = 'VERSION$3';

COMMIT prints the new version, such as VERSION$3. DESCRIBE AGENT shows the default version’s specification, so if a change doesn’t appear, the new version probably isn’t the default yet.

Troubleshoot a CoWork channel

SymptomWhat to check
The reply comes from Snowflake - CoCo

The binding names no agent or specifies an RSS, tagRunMode is missing or set to cowork, the thread started before the change, or CoWork mode isn’t enabled for your deployment. Contact your Snowflake account team about the last case.

A failure notice follows the @mention

The bound role is missing USAGE on the agent, its database, or its schema, access to the data a tool reads, or USAGE on the warehouse.

The agent answers without data

The agent has no tool for the question, or the bound role can’t read the objects that the tool uses.

No reply at allThe channel isn’t bound, or the app isn’t a member of the channel.

Remove a channel binding

To remove a binding, call SYSTEM$DELETE_SLACK_CHANNEL_BIND with the integration name and the channel ID, in that order. A role with OPERATE or OWNERSHIP on the Slack integration can remove a specific channel binding. Only a role with OWNERSHIP can remove the workspace-wide default binding; specify 'DEFAULT' as the channel ID:

SELECT SYSTEM$DELETE_SLACK_CHANNEL_BIND(
  'SNOWFLAKE_SLACK_APP',
  '<slack_channel_id>'
);

Required privileges

The following table lists the privileges that the integration owner, the binding creator, and the bound service user need. The binding creator is the role that calls SYSTEM$SLACK_CHANNEL_BIND. Bind time is when the binding is created, and run time is when the app handles a request in the bound channel.

ObjectIntegration owner roleBinding creator roleBound role or service user
Slack integrationNot applicable

OPERATE or OWNERSHIP for a specific channel; OWNERSHIP for DEFAULT (bind time)

USAGE must be granted to the bound role (run time and bind time)

Bound service userIMPERSONATE (bind time and run time)IMPERSONATE (bind time)The bound role must be granted to the service user.
Agent objectNoneNone

USAGE must be granted on the agent to the bound role (bind time and run time, when specified)

RSSNoneNone

USAGE must be granted on the RSS to the bound role (bind time and run time, when specified)

Security considerations

A binding replaces per-user identity with one shared identity, so review the following before you bind a multiplayer channel:

  • Channel membership grants the bound service user. Anyone who can post in a bound channel can run requests as that user, with the bound role’s privileges. Snowflake doesn’t check Slack channel membership, and Slack doesn’t show that a channel is bound, so call SYSTEM$SHOW_SLACK_CHANNEL_BINDS to see which channels are. The approval prompts that CoCo shows in a direct message don’t apply, because a channel has no single person to approve them, so the bound role is the only limit on what a request can do. Grant that role only the privileges the channel needs, and use a restricted session scope to narrow it further.
  • Conversation history follows the identity, not the channel. CoCo can search and summarize its own earlier conversations, and those conversations belong to the Snowflake identity that ran them rather than to the channel they took place in. Whenever two channels share a service user, whether you bound each one separately or they both fall under a 'DEFAULT' binding, a member of either channel can ask the app about work done in the other one, including a private channel that they don’t belong to. An RSS doesn’t narrow this, because recall reads the agent’s own conversation store rather than data that an RSS governs. Don’t reuse one service user across channels of different sensitivity: bind a separate one to each set of channels that must stay separate.
  • The 'DEFAULT' binding covers channels you can’t see. It applies to every channel in the workspace that has no binding of its own, including channels created after you set it and channels that you aren’t a member of. Anyone who can add the app to one of them can then run requests as the bound service user, without being a Snowflake user in your account. A direct message with the app is never covered and keeps the per-user flow. A group direct message is covered, although the app declines to answer an @mention in one. Bind individual channels instead if that is wider than you intend.

Limitations

During the preview, multiplayer channels have the following limitations:

  • One Snowflake account per Slack workspace. A Slack workspace can connect to only one Snowflake account, so every binding in a workspace uses the same Snowflake account.

Support and privacy

For questions about this integration, contact your Snowflake account team or Snowflake Support.

Where your configuration of Cortex Code uses a model provided on the Model and Service Pass-Through Terms, your use of that model is further subject to the terms for that model on that page.

The data classification of inputs and outputs are as set forth in the following table.

Input data classificationOutput data classificationDesignation
Usage DataCustomer Data

Covered AI Features

[1]

For additional information, refer to Snowflake AI and ML.