Build a Slack feedback triage bot with the Mistral Agents SDK

Coming soon

Build a Slack bot that watches one channel, identifies actionable product feedback, searches Linear for duplicates, and creates or comments on issues. Top-level messages are processed automatically. Replies continue the same conversation only when someone mentions the bot.

You will run both the agent and Slack bot locally:

Slack channel
    ↓ Socket Mode
bot.py
    ↓ Mistral Session API
Agents SDK agent
    ↓ Linear MCP server, authenticated with a Linear API key
Linear

agents serve runs the agent on your computer and registers it with Mistral. bot.py listens to Slack and opens one Mistral session for each Slack thread.

What the bot does

What the bot does

  • Reviews every new top-level message in one Slack channel.
  • Silently ignores irrelevant messages.
  • Asks one short question when feedback is too vague.
  • Classifies specific feedback as a question, idea, bug, or feature request.
  • Searches one Linear project before creating anything.
  • Adds useful evidence to a strong duplicate instead of creating another issue.
  • Creates one Triage issue for new actionable feedback.
  • Shows a temporary status while the agent works.
  • Keeps follow-up context inside the Slack thread.
  • Responds to thread replies only when someone mentions the bot.
The example in the SDK

The example in the SDK

The complete, runnable code lives in the Agents SDK repository — clone it and follow along:

git clone https://github.com/mistralai/dashboard.git
cd dashboard/agents/agents_sdk/examples/slack_feedback_triage_bot
FileWhat it shows
agent/main.pyThe agents.Agent the CLI runs, with the Linear MCP server on its harness
agent/instructions.pyThe full triage instructions: classification order, duplicate handling, safety rules
agent/check_linear_mcp.pyA read-only validation run that searches Linear without writing
agent_client.pyOpening a session, streaming a turn, and extracting the assistant reply
bot.pySlack handlers: one session per thread, mention gating, buffered context, event deduplication

The example's README.md has the full setup and test script.

Prerequisites

Prerequisites

You need:

  • Python 3.12 and uv.
  • A Mistral API key with Workflows access, which agents serve requires.
  • A Linear API key that can read, create issues, and create comments on the target team.
  • Permission to create and install a Slack app in your workspace.
  • A public Slack channel for testing.
1. Create the Linear API key

1. Create the Linear API key

The agent reaches Linear through Linear's hosted MCP server rather than an AI Studio connector, because the key is a fixed credential the agent always runs as. Create one in Linear under Settings → Security & access → Personal API keys, scoped to the team you want to triage into.

It needs three permissions. Read and issue creation cover the common path; comment creation is what the duplicate path uses, and a key without it fails only once the agent finds a duplicate and tries to add evidence:

{"error":"auth_insufficient_scope","message":"Not allowed to access this Linear resource.","status":403}

Choose one Linear project and team for the agent, then set both values at the top of agent/instructions.py. The key may reach more projects than that one; the instructions pick the target, they do not restrict the key.

2. Create the Slack app

2. Create the Slack app

Open Your Apps, select Create New App, then choose From scratch.

Use:

  • App name: AgentsFeedbackBot
  • Workspace: your test workspace

Enable Socket Mode

Open Socket Mode and enable it. Create an app-level token with the connections:write scope. Copy the token beginning with xapp-; Slack only shows it once.

Add bot permissions

Open OAuth & Permissions and add these bot token scopes:

  • channels:history
  • chat:write

Subscribe to messages

Open Event Subscriptions, enable events, and subscribe to this bot event:

  • message.channels

Socket Mode carries the events, so you do not need a public request URL.

Install the app

Select Install to Workspace and complete the approval flow. Copy the bot token beginning with xoxb-.

If your organization reviews Slack apps, submit the app after its name, description, scopes, and event subscription are complete. Reinstall it whenever you change OAuth scopes.

Invite the bot to the test channel:

/invite @AgentsFeedbackBot

To find the channel ID in Slack desktop or web:

  1. Open the channel.
  2. Select the channel name in the conversation header.
  3. Open the About tab.
  4. Scroll to the bottom and copy the channel ID.

Slack channel IDs look like C0123456789.

Warning

Keep both Slack tokens secret. Anyone with them can operate the app.

3. Set up and inspect the agent

3. Set up and inspect the agent

From the agent/ project, copy .env.example to .env and fill in both keys:

MISTRAL_API_KEY=...
LINEAR_MCP_TOKEN="Bearer lin_api_..."

LINEAR_MCP_TOKEN holds the complete Authorization header value, scheme included, because auth_header_env_vars maps a header name to the variable holding that header. Keep the quotes: the space after Bearer ends the value without them, and --env-file reports Failed to parse environment file.

Then install and inspect:

cd agent
uv sync
uv run --frozen --env-file .env agents info

agents info lists tools, connectors, skills, and hooks, but it has no section for MCP servers, so it cannot confirm the Linear MCP is wired. The next step is what actually checks that.

4. Validate Linear without writing

4. Validate Linear without writing

Confirm the agent can reach Linear and search the target project:

uv run --frozen --env-file .env python check_linear_mcp.py

Continue when it returns issues from the intended project, with nothing created, updated, or deleted.

5. Serve the agent

5. Serve the agent

bot.py runs separately from the agent, so it cannot use the in-process Agent object. Start the agent with agents serve so the bot can open sessions against it:

uv run --frozen --env-file .env agents serve

The command registers the agent with Mistral Workflows and runs its worker on your computer. Keep this terminal open.

Mistral identifies served agents by name. The example uses your-id as a placeholder: replace it with a unique lowercase identifier, such as your GitHub handle, everywhere it appears. The bot's AGENT_NAME must match the agent name exactly.

Do this before serving. If someone else already holds the name, agents serve registers the workflow and then exits 1 without printing an error — the missing Press Ctrl+C to stop. banner is the only sign it failed.

6. Run the bot

6. Run the bot

Back in the example root, copy .env.example to .env and fill in both Slack tokens, the channel ID, your Mistral API key, and the agent name:

uv sync
uv run --frozen --env-file .env python bot.py

Continue when it prints:

Listening to Slack channel C0123456789 as bot user U0123456789...

The Slack bot needs its own MISTRAL_API_KEY because it calls Mistral's Session API directly: agents serve runs the agent worker, but the bot still authenticates when it opens and advances sessions.

7. Test the complete flow

7. Test the complete flow

Test silent handling

Post an irrelevant top-level message:

Has anyone seen the HDMI adapter?

🧠 Reviewing this feedback… should appear briefly and then disappear.

Test clarification and buffered context

Post:

The Agents SDK setup is confusing.

The bot should ask one short clarification question without searching Linear.

Reply without mentioning the bot:

Some tools are not disabled as expected.

The bot should remain silent but retain the message. Send a second thread reply containing only:

@AgentsFeedbackBot

The bot should combine the buffered detail with the existing session and continue triage.

Test a duplicate

Use genuine feedback that matches an existing issue. The agent should search the target project, add useful new evidence once, and return the existing issue identifier and link.

This test writes a real Linear comment. Do not use synthetic evidence in a production backlog.

Test a new request

Use genuine, specific feedback with no existing match. The agent should create one Triage issue, include the Slack thread link, and return the new Linear URL.

Test thread gating

After the bot responds, send a normal thread reply without mentioning it. Nothing should happen. Then mention @AgentsFeedbackBot in the thread. The same Mistral session should answer.

Why this example does not deploy

Why this example does not deploy

This agent runs under agents serve only. A fixed-credential MCP reads its credential from the environment where the agent runs, and agents deploy has no way to put a variable there, so a managed deployment would start and then fail the moment it opened a session:

MCP source 'linear' declares environment variable(s) LINEAR_MCP_TOKEN, which are unset or empty here

Binding a workspace secret to an environment variable at deploy time is being added (AGE-1580):

agents deploy . --name feedback-triage-your-id \
  --secret-binding LINEAR_MCP_TOKEN=secret:workspace:linear-triage-key

Once that lands, this example deploys like any other. An agent that authenticates through an AI Studio connector rather than a static credential can already be deployed today — see Connectors & MCP for that trade-off.

Session behavior

Session behavior

Each top-level Slack message creates one Mistral session. The root Slack timestamp maps that thread to its session:

Top-level message → create session → triage
Thread reply without mention → buffer context → stay silent
Thread reply with mention → reuse session → run another turn

Sessions and buffered messages are held in bot.py memory. Restarting the bot clears that mapping. If someone mentions the bot in an older thread after a restart, it asks them to repost the feedback as a new top-level message.

This is deliberate for the demo. Production code should persist the Slack thread-to-session ID mapping.

Security and limitations

Security and limitations

Warning

Everyone who can post in the channel shares one Linear identity — the key in LINEAR_MCP_TOKEN. Scope that key when you create it, because it is the only real boundary here. The target project in instructions.py is a prompt instruction, and Linear's MCP server advertises its full tool surface regardless: the agent can see tools for deleting, merging, and administering, and only Linear's own scope check stops them. Anything the key can do, anyone in the channel can eventually get the agent to do.

A personal API key also attributes every issue and comment to the person who created it. Use a key belonging to a service account if you do not want the bot's writes showing up under your name.

  • Everyone who can post in the configured public channel can trigger the agent.
  • Tool restrictions are partly prompt-enforced because save_issue can create and update issues.
  • Slack event deduplication lasts only for the current bot process.
  • Session mappings and buffered replies disappear when bot.py restarts.
  • Text attachments, screenshots, and edited messages are ignored.
  • The bot handles one configured channel and one Linear project.
  • The agent runs under agents serve only, so the bot and the agent both run on your computer.

For production, add durable event idempotency, persistent session mappings, user or channel allowlists, attachment handling, observability, and an integration layer that validates every Linear write independently of the model.

Inspect sessions in Mistral Studio

Inspect sessions in Mistral Studio

Sessions handled by an agent started with agents serve appear in Agent sessions in Mistral Studio. Open a session to review its messages, tool calls, and status.

Troubleshooting

Troubleshooting

uv sync returns 403 Forbidden

An environment-level package index is overriding the PyPI index in pyproject.toml. Run:

UV_DEFAULT_INDEX=https://pypi.org/simple uv sync

Then use uv run --frozen so subsequent commands do not resolve dependencies again.

uv run --frozen cannot find uv.lock

Run uv sync once in that project, then retry the command.

Failed to parse environment file on LINEAR_MCP_TOKEN

Quote the value. The space after Bearer truncates it otherwise.

The session fails naming LINEAR_MCP_TOKEN

MCP source 'linear' declares environment variable(s) LINEAR_MCP_TOKEN, which are unset or empty here

The variable is read where the agent runs, so it has to exist in the terminal running agents serve, not just in the bot's.

Linear returns auth_insufficient_scope

The key is missing a permission. Read plus issue creation is not enough: the duplicate path comments on an existing issue, so the key also needs comment creation on that team.

agents serve exits immediately without an error

The agent name is already registered by someone else. Replace your-id everywhere with a unique identifier. A successful start ends with Press Ctrl+C to stop.; without that line it exited.

agents serve cannot register the agent

Check that MISTRAL_API_KEY is valid and has Workflows access. A key that can call a model may still lack permission to register a worker.

The bot does not answer

Confirm that:

  • agents serve is still running.
  • bot.py is still running.
  • AGENT_NAME exactly matches the served agent name.
  • The app is invited to the channel in SLACK_TARGET_CHANNEL_ID.
  • Socket Mode and message.channels are enabled.
  • The bot token and app token start with xoxb- and xapp- respectively.

After sleep, a network change, or a BrokenPipeError, restart both processes.

The bot ignores a thread reply

That is expected unless the reply mentions @AgentsFeedbackBot. Unmentioned replies are buffered only while the bot has an active session for that thread.

The bot says the session is no longer active

bot.py was restarted after the thread began. Repost the feedback as a new top-level message.

The agent describes future actions instead of performing them

Restart agents serve after changing agent/instructions.py, then begin a fresh Slack thread. An existing session may still reflect the previous agent definition.

The temporary status remains or changes to an error

Inspect both terminals. The status is updated only after the Mistral turn and any Linear MCP calls complete. A timeout closes that thread's in-memory session so a retry cannot continue from an uncertain state.

Resources and next steps

Resources and next steps

  • Connectors and MCP explains AI Studio connectors and custom MCP servers, and when a static credential means reaching for an MCP source instead.
  • Sessions explains the conversation history and event stream used by bot.py.
  • Run modes compares direct calls, self-hosted workers, and managed deployments.
  • Agent documents the agents.Agent the CLI loads from main.py.