DEV Community

Mukunda Rao Katta
Mukunda Rao Katta

Posted on

slack-inbox-triage: Triage Your Slack Inbox with an AI Agent

Monday Morning. 80 Unread Messages.

You open Slack on Monday morning. There are 80 unread messages across a dozen channels and your DMs. You can see from the previews that most of it is noise: a standup thread that ran long, a random link someone dropped in #general, a sales update that does not affect you. But somewhere in there is the thing that actually needs a response today.

The problem is you have to open each one to know. You cannot tell from a notification badge which messages are urgent. So you either spend 30 minutes triaging manually, or you mute aggressively and hope you did not miss something important.

slack-inbox-triage reads your messages, classifies each one as HIGH, MED, or LOW priority, shows you the 5 that need attention today, and drafts suggested replies for the HIGH items. You review every draft before anything gets sent. The agent never posts on its own.

Shape of the Fix

The agent connects to Slack's API, reads your DMs and mentions from a specified time window, classifies each message, and returns a structured report. For HIGH priority messages, it also drafts a reply.

from slack_triage import SlackTriageAgent

agent = SlackTriageAgent(slack_bot_token="xoxb-your-token")

result = agent.run(lookback_hours=18)

# See what was classified as HIGH priority
for item in result.high_priority:
    print(item.channel, item.from_user, item.summary)
    print("Draft reply:", item.draft_reply)
    print("Decision reason:", item.reason)
    print()

# Sample output:
# #prod-incidents @sarah.chen "Payment API returning 503s since 2am"
# Draft reply: "On it. Checking logs now. Will update in 15 min."
# Decision reason: Production incident, customer impact, sender is on-call lead

# Inspect the audit log
print(result.audit_log)
# [{"action": "slack.conversations.history", "channel": "C0ABC123", "messages_read": 14},
#  {"action": "classify", "message_id": "...", "label": "HIGH", "topic": "incident"},
#  ...]
Enter fullscreen mode Exit fullscreen mode

Every classification decision has a reason field. You can see exactly why the agent labeled something HIGH. If it got it wrong, you know which part of the classification logic to adjust.

What It Does NOT Do

The agent never sends a message. This is not a soft rule or a default setting. It is the only mode the tool has. There is no auto_send=True flag. There is no "confirm and send" button in the output. You get draft text. You copy it into Slack yourself, edit it as you see fit, and send it.

This was a deliberate choice. Slack is where a lot of professional relationships live. An agent that posts on your behalf, even with good intent, creates risk: wrong tone, wrong channel, wrong timing. The triage agent's job is to cut the reading time, not to replace the writing and sending step.

It also does not read channels you were not mentioned in. It reads your DMs and any channel where you were @-mentioned or where you have been active in the lookback window. It does not monitor arbitrary channels you have joined but never posted in. The scope is your inbox, not your whole Slack workspace.

It does not learn from feedback in the current version. If you mark a reply draft as bad, that signal does not feed back into future classifications.

Inside the Project

The classification layer uses an LLM with a structured prompt. Each message is passed with context: who sent it, what channel, what time, and whether it is a DM or a mention. The classification output is a JSON object with four fields: label, topic, priority score, and reason. The agent enforces the output schema with a retry loop, so you always get a structured object back.

The prompt for HIGH classification is deliberately narrow. A message is HIGH if it describes a production issue, a time-sensitive decision, a direct question from a stakeholder, or a request that has a deadline in the near term. "Hey cool article" is not HIGH even if it came from your manager. The classification is explicit about this in the reason field.

The side effects of this agent are all reads. The Slack API client is initialized with read_only=True, which means it requests only channels:history, im:history, users:read, and conversations:list scopes. These are all read-only OAuth scopes. The token cannot post even if the code tried to. This is enforced at the credential layer, not just at the application layer.

The draft reply generator runs only on HIGH items. It uses a separate prompt that emphasizes brevity and professional tone. Drafts are short: typically one to three sentences. The goal is a starting point for your reply, not a finished message.

When This Is Useful

This is useful if you get enough Slack traffic that triage itself is a time cost. It works best for individuals who are mentioned across many channels and need to find the signal in a noisy inbox.

It works well for on-call engineers, managers who are looped into many threads, and anyone who returns from time off with a large backlog.

It is not useful if your Slack is quiet enough that you can read everything in a few minutes. It is also not useful if your team communicates primarily in threads that are deeply nested, because thread context is harder to summarize accurately from the top-level message alone.

The draft replies are most useful as a starting point when you are under time pressure. If you have plenty of time to write your own replies, the classification is the valuable part and you can ignore the drafts.

Install or Try It

pip install slack-inbox-triage

# You need a Slack Bot Token with read-only scopes:
# channels:history, im:history, users:read, conversations:list
export SLACK_BOT_TOKEN="xoxb-your-token"
export OPENAI_API_KEY="your-openai-key"  # or ANTHROPIC_API_KEY

# Run a triage over the last 18 hours
python -m slack_triage.cli --lookback-hours 18

# Or use in Python
from slack_triage import SlackTriageAgent
agent = SlackTriageAgent.from_env()
result = agent.run(lookback_hours=18)

for item in result.high_priority:
    print(f"[HIGH] {item.from_user}: {item.summary}")
    print(f"Draft: {item.draft_reply}")
Enter fullscreen mode Exit fullscreen mode

Setting up the Slack app: create a new app at api.slack.com, add the read-only scopes listed above, install it to your workspace, and copy the Bot Token. The agent only needs to be added to the channels it should read.

Related Libraries

Library What it does When to pair it
agent-decision-log Structured log of every agent decision with reasoning Pair to persist WHY each message was classified HIGH/MED/LOW
agent-citation Structured WHERE-layer source attribution Pair to link each claim in a summary back to the original message
agentsnap Snapshots agent call traces Pair to capture a full replay of each triage run
tool-side-effects-tag Tags tool calls as READ/WRITE/IDEMPOTENT/DESTRUCTIVE Pair to enforce at the tool layer that all Slack calls are READ-only
agentguard Egress allowlist for outbound HTTP Pair to lock the agent to Slack API endpoints only

What Is Next

Thread context is the most requested gap. Right now, if a message is a reply in a thread, the agent sees the top-level message and the reply, but not the full thread history. Fetching thread context for HIGH-priority items would make the summaries and draft replies more accurate.

Feedback loop is second. If you edit a draft reply significantly before sending, that edited version is signal about what the agent got wrong. A lightweight feedback capture step after each session, even just "was this draft useful: yes/no," would let the classification prompt improve over time.

Calendar integration is a natural follow-on. "Can we meet Thursday?" is a common Slack message. Right now, the agent just notes it as HIGH if you were directly asked. A calendar-aware version would check your actual availability and draft a reply with a specific time suggestion.

Source: MukundaKatta/slack-inbox-triage

Top comments (2)

Collapse
 
singh_coder profile image
Harpinder

thread history is the bit i'd want next too. a top-level slack msg is often misleading without the replies around it, especially when the real ask is buried a few messages down. i'd do that as a second pass only on HIGH items, and i'd capture the edited draft too, not just yes/no on the label.

Collapse
 
mukundakatta profile image
Mukunda Rao Katta

Yeah, second pass on HIGH only is the right call, full thread history for every message would blow the token budget for no gain on the easy ones. Capturing the edited draft and not just the yes/no label is a good point, that's the real training signal for what a good reply looks like. Both on the list.