ScreenshotNeo

BlogAI agents

How to Build a Slack AI Agent for Research

Build a Slack research agent that gathers evidence, cites sources, handles permissions, and responds reliably with Python, Events API, or Socket Mode.

By the ScreenshotNeo team1 October 202610 min read

A practical Slack research agent follows this loop: receive a question, clarify its scope, gather evidence from approved sources, assess what the evidence supports, draft an answer with citations, and reply in a thread. Slack describes the underlying agent loop as “receive input → reason → call tools → stream/render output.” Slack’s agent documentation provides the platform guidance; retrieval, source ranking, verification, and model calls are responsibilities of your application.

What you are building

The reference design has five layers:

  1. Slack transport: Events API over HTTP or Socket Mode.
  2. Conversation handler: validates events, removes duplicates, and acknowledges quickly.
  3. Research worker: searches approved sources, fetches documents, extracts relevant passages, and records metadata.
  4. Answer generator: writes a concise response whose claims map to sources.
  5. Slack renderer: posts progress and the final answer in a thread, with links and uncertainty labels.

Keep transport code separate from research code. You should be able to run the same research function from a Slack event, a scheduled job, or a command-line test.

Prerequisites and decisions

  • A Slack workspace and permission to install an app.
  • A Slack app with a bot user and OAuth scopes appropriate to the conversations it must read and write.
  • A model provider and a retrieval system for your approved sources.
  • A small durable store for event IDs, research jobs, source metadata, and deletion records.
  • A choice between HTTP Events API delivery and Socket Mode.

Slack’s Developer Program can provide a fully featured sandbox for development. Some AI-specific Slack surfaces require a paid workspace plan, so check feature access before depending on them. See Slack’s setup guidance.

HTTP endpoint or Socket Mode?

Decision HTTP Events API Socket Mode
Network exposure Requires a publicly reachable request URL. Uses an outbound WebSocket and does not require a public request URL.
Operations Slack sends callbacks to your web server. Your process maintains a WebSocket connection.
Good fit Platforms already built around HTTPS webhooks. Local development or restricted inbound networks.
Reliability work Verify requests, acknowledge quickly, and handle retries. Reconnect, acknowledge quickly, and monitor connection state.

Both transports use the same event and permission model. If you switch to Socket Mode while receiving events, establish the WebSocket connection promptly because Slack notes that events can be lost during the transition. Read the Events API documentation.

Create and configure the Slack app

  1. Create an app in your development workspace.
  2. Add a bot user.
  3. Request only the OAuth scopes your feature needs. Typical examples include permission to read messages visible to the bot, read thread replies where required, post messages, and add reactions. Exact scopes depend on the events and API methods you use.
  4. Enable Event Subscriptions and subscribe narrowly to the message events your agent handles.
  5. If you use Slack’s agent-oriented surfaces, review the additional events called out in the quickstart: app_context_changed, agent_session_stopped, and agent_session_title_changed.
  6. Install the app and store tokens in a secret manager.

Scopes and conversation membership define visibility. Adding a bot to one private channel does not give it workspace-wide access to other private channels. Explain to workspace administrators what the app can read, where it sends data, and how long it stores it. Slack’s AI app guidance describes these access expectations.

A runnable Python Socket Mode agent

The example below uses Bolt for Python. It acknowledges Slack events immediately, places research work on a background thread, deduplicates event IDs, and posts a cited answer. Replace research() with your retrieval and model implementation.

import os
import threading
from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler

app = App(token=os.environ['SLACK_BOT_TOKEN'])
seen_events = set()
seen_lock = threading.Lock()


def research(question):
    # Replace this with retrieval, source verification, and model synthesis.
    return {
        'answer': f'Research result for: {question}',
        'sources': [
            {'title': 'Approved source', 'url': 'https://example.com/source', 'date': '2026-01-01'}
        ],
        'uncertainty': 'Replace this placeholder with evidence-based uncertainty.'
    }


def format_answer(result):
    lines = [result['answer'], '', f"Uncertainty: {result['uncertainty']}", '', '*Sources*']
    for source in result['sources']:
        lines.append(f"• <{source['url']}|{source['title']}> ({source['date']})")
    return '\n'.join(lines)


def run_research(channel, thread_ts, question):
    try:
        result = research(question)
        app.client.chat_postMessage(
            channel=channel,
            thread_ts=thread_ts,
            text=format_answer(result)
        )
    except Exception:
        app.client.chat_postMessage(
            channel=channel,
            thread_ts=thread_ts,
            text='I could not complete that research request. Try narrowing the question or retry later.'
        )


@app.event('app_mention')
def handle_mention(body, say, logger):
    event_id = body.get('event_id')
    with seen_lock:
        if event_id in seen_events:
            return
        seen_events.add(event_id)

    event = body['event']
    question = event.get('text', '')
    question = question.split('>', 1)[-1].strip()
    thread_ts = event.get('thread_ts') or event['ts']

    say(text='I am researching this and will reply in this thread with sources.', thread_ts=thread_ts)
    threading.Thread(
        target=run_research,
        args=(event['channel'], thread_ts, question),
        daemon=True
    ).start()


if __name__ == '__main__':
    SocketModeHandler(app, os.environ['SLACK_APP_TOKEN']).start()

Install dependencies and run it with:

python -m pip install slack-bolt
export SLACK_BOT_TOKEN='xoxb-your-token'
export SLACK_APP_TOKEN='xapp-your-token'
python app.py

For production, replace the in-memory set with a database table keyed by event_id and expire old records after your retry window.

HTTP delivery with Bolt

Use HTTP when your deployment already exposes a secure HTTPS endpoint. Bolt handles Slack’s request verification when configured with the signing secret.

import os
from flask import Flask, request
from slack_bolt import App
from slack_bolt.adapter.flask import SlackRequestHandler

bolt_app = App(
    token=os.environ['SLACK_BOT_TOKEN'],
    signing_secret=os.environ['SLACK_SIGNING_SECRET']
)
handler = SlackRequestHandler(bolt_app)
flask_app = Flask(__name__)

@flask_app.route('/slack/events', methods=['POST'])
def slack_events():
    return handler.handle(request)

if __name__ == '__main__':
    flask_app.run(host='0.0.0.0', port=int(os.getenv('PORT', '3000')))

Set the Events API request URL to https://your-domain.example/slack/events. Respond to Slack’s URL verification challenge through Bolt, verify signatures, and return an acknowledgment before starting retrieval or model work.

Design the research workflow

1. Clarify the question

Ask one short follow-up when the request lacks a date range, geography, source boundary, or output format. Examples: “Should I use only public sources?” and “Do you need developments from the last 30 days?” Avoid silently filling in constraints that change the answer.

2. Enforce an approved source policy

Represent source rules as configuration: allowed domains, blocked domains, freshness limits, document types, and whether user-provided links are allowed. Store the canonical URL, title, publisher, publication date, retrieval time, and extracted passages for every source used.

3. Retrieve and rank evidence

Search broadly enough to find relevant material, then rank passages by direct relevance, source authority, freshness, and agreement with other sources. Keep the original passages available for citation review. Do not treat a model-generated summary as evidence.

4. Generate claims with citations

Require the answer generator to return claims and supporting source IDs before rendering prose. Reject or label claims with no supporting passage. Put links next to the statements they support, include dates, and state when sources disagree or evidence is incomplete.

5. Reply in a thread

Post a short progress message, then the final answer in the same thread. A useful structure is:

*Answer*
Direct conclusion in two or three sentences.

*Evidence*
• Claim — source link and publication date
• Claim — source link and publication date

*Limits*
What was not verified, which sources were unavailable, and what could change the conclusion.

Slack agent surfaces and onboarding

Slack supports agent-oriented surfaces including a split-view container, top navigation entry point, app threads, text streaming, and suggested prompts. Suggested prompts can steer users toward bounded, source-seeking questions. Keep the answer, evidence, and follow-up in a thread so the exchange remains readable. See Slack’s AI platform overview.

On first interaction, tell users how to sign in, connect an account, accept terms, or review a code of conduct when those steps are required. Add a feedback action such as “Helpful” and “Needs review,” and record the feedback with the request and source IDs.

Permissions, privacy, and governance

  • Request the smallest set of scopes that supports the feature.
  • Restrict indexing to conversations where the app is a member unless an administrator has explicitly approved broader access.
  • Encrypt tokens and stored research data, and separate workspace data by tenant.
  • Document retention, deletion, export, logging, and model-provider processing.
  • Redact secrets and personal data from logs and prompts where possible.
  • Provide a way to delete indexed messages and cached passages when workspace policy requires it.

Slack’s Marketplace guidance discusses zero-copy and zero-LLM-training policies for Marketplace AI apps. Those statements do not establish retention or training practices for every independently built app or model provider. Publish your own policy and verify workspace requirements before indexing messages. Read the policy details.

Reliability, retries, and rate limits

Slack’s rate-limit documentation lists message posting at generally one message per second per channel and an Events API maximum of 30,000 deliveries per workspace, team, app, and 60-minute period. Limits can vary by method and change, so consult the current method documentation. When Slack returns HTTP 429, wait for the Retry-After value before retrying. Slack rate limits.

  • Acknowledge events quickly and enqueue long jobs.
  • Deduplicate event deliveries using the event ID.
  • Use exponential backoff with a maximum retry count for Slack and upstream services.
  • Throttle progress updates; one useful update is better than many tiny messages.
  • Partition queues by workspace and channel when one busy channel could otherwise delay every tenant.
  • Persist job state so a worker restart can resume or mark a request failed.
  • Use idempotency keys when posting final answers.
  • Monitor WebSocket reconnects in Socket Mode and missed-event risk during transport changes.

Newly created commercially distributed apps that are not Marketplace approved may have additional conversations.history and conversations.replies limits. Check the current method documentation and distribution status before reading history at scale.

Performance and cost planning

Area Practical approach
Slack response time Acknowledge immediately; do retrieval and synthesis asynchronously.
Retrieval latency Cache normalized documents and embeddings; cap source count per question.
Model cost Use a smaller model for query rewriting and passage selection, then a stronger model for final synthesis when needed.
Slack posting Batch the final answer and citations into a small number of messages.
Freshness Store retrieval timestamps and invalidate sources according to their policy.

Your total cost depends on Slack plan entitlements, model usage, retrieval infrastructure, hosting, and storage. The research dossier does not establish pricing for those providers, so measure them in your own workload.

Testing checklist

  • URL verification and request-signature validation.
  • Duplicate event delivery.
  • Messages in channels where the bot is not a member.
  • Private-channel and DM visibility.
  • Thread replies and mentions with empty text.
  • Slow retrieval, model timeout, source outage, and malformed source pages.
  • HTTP 429 responses and reconnects in Socket Mode.
  • Answers with conflicting sources, no sources, stale sources, and inaccessible links.
  • Deletion requests and workspace offboarding.

Troubleshooting

Symptom Likely cause Fix
Slack never verifies the endpoint The URL is not public, the route is wrong, or the request is not passed to Bolt. Check the exact HTTPS URL, deployment logs, and Bolt adapter configuration.
Events arrive twice Slack retried after a slow acknowledgment. Acknowledge immediately and deduplicate by event ID.
The bot cannot read a channel It is not a member or lacks the required scope. Invite the app where appropriate, request the narrow missing scope, and reinstall if scopes changed.
Answers contain unsupported claims The generator was allowed to write before evidence was attached. Require claim-to-passage mappings and label unsupported or uncertain statements.
Messages fail with 429 A method or channel rate limit was exceeded. Honor Retry-After, back off, and reduce update frequency.
Socket Mode disconnects Network interruption, invalid app token, or process restart. Verify the xapp token, enable reconnect handling, and monitor connection health.
Research jobs disappear after restart Work was kept only in memory. Persist queued and running job state in durable storage.

Or skip the browser setup

If your agent needs screenshots of research pages, ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP, or PDF. Cookie and consent banners are accepted and removed before capture, along with more than 60 known consent platforms, newsletter popups, and chat widgets. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers.

See the ScreenshotNeo API documentation for all options.

cURL

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

Node.js

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

ScreenshotNeo also offers an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. It supports full-page and element captures, dark mode, device presets, custom CSS and JavaScript, waits, blocked resources, headers, cookies, geolocation, resizing, caching, signed links, asynchronous jobs, bulk capture, and a usage API. Plans include 1,000 free shots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

FAQ

Should I use Socket Mode in production?

Choose based on your network and deployment architecture. Socket Mode avoids an inbound public endpoint; HTTP fits webhook-oriented infrastructure. Both require prompt acknowledgment, retries, deduplication, and monitoring.

Can the agent search every Slack message?

No. Access follows OAuth scopes, user visibility, and conversation membership. Expand access only with explicit administrator approval and a documented retention policy.

How do I stop the model from inventing citations?

Generate citations from stored source records, require claim-to-passage mappings, and refuse or label claims without supporting evidence.

How should long research requests appear to users?

Acknowledge immediately, post one progress update, and return the final evidence-backed answer in the originating thread. Persist the job so a worker restart does not lose it.