Build a Slack feedback triage bot with the Mistral Agents SDK
Coming soonBuild 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
Linearagents 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
- 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 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| File | What it shows |
|---|---|
agent/main.py | The agents.Agent the CLI runs, with the Linear MCP server on its harness |
agent/instructions.py | The full triage instructions: classification order, duplicate handling, safety rules |
agent/check_linear_mcp.py | A read-only validation run that searches Linear without writing |
agent_client.py | Opening a session, streaming a turn, and extracting the assistant reply |
bot.py | Slack handlers: one session per thread, mention gating, buffered context, event deduplication |
The example's README.md
has the full setup and test script.
Prerequisites
You need:
- Python 3.12 and
uv. - A Mistral API key with Workflows access, which
agents serverequires. - 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
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
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:historychat: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 @AgentsFeedbackBotTo find the channel ID in Slack desktop or web:
- Open the channel.
- Select the channel name in the conversation header.
- Open the About tab.
- Scroll to the bottom and copy the channel ID.
Slack channel IDs look like C0123456789.
Keep both Slack tokens secret. Anyone with them can operate the app.
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 infoagents 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
Confirm the agent can reach Linear and search the target project:
uv run --frozen --env-file .env python check_linear_mcp.pyContinue when it returns issues from the intended project, with nothing created, updated, or deleted.
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 serveThe 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
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.pyContinue 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
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:
@AgentsFeedbackBotThe 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
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 hereBinding 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-keyOnce 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
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 turnSessions 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
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_issuecan create and update issues. - Slack event deduplication lasts only for the current bot process.
- Session mappings and buffered replies disappear when
bot.pyrestarts. - Text attachments, screenshots, and edited messages are ignored.
- The bot handles one configured channel and one Linear project.
- The agent runs under
agents serveonly, 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
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
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 syncThen 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 hereThe 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 serveis still running.bot.pyis still running.AGENT_NAMEexactly matches the served agent name.- The app is invited to the channel in
SLACK_TARGET_CHANNEL_ID. - Socket Mode and
message.channelsare enabled. - The bot token and app token start with
xoxb-andxapp-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
- 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.Agentthe CLI loads frommain.py.