Production Slack agent
Configure Slack, OpenRouter, persistence, personality, approvals, memory, and a sandbox as one deployable agent.
This guide builds the complete version of the one-file example. Start by creating a Slack app, then add only the scopes and events your agent actually uses.
Slack configuration
Set the Events request URL to https://YOUR_HOST/webhooks/slack. Subscribe to app_mention. Add message.im, message.channels, or message.groups only when the agent should receive those surfaces. Enable Interactivity with the same URL for approval buttons and cards.
Minimum mention-only bot scopes are app_mentions:read and chat:write. Add history, reaction, file, and assistant scopes only for enabled features. Reinstall the app after changing scopes.
Agent
import { noir } from '@noir-agent/agent'
import { slackAssistant } from '@noir-agent/agent/channels/slack-assistant'
import { postgresStore } from '@noir-agent/agent/store/postgres'
import { observabilityCapability } from '@noir-agent/agent/observability'
import { createOpenRouter } from '@openrouter/ai-sdk-provider'
const assistant = slackAssistant({
prompts: [
{ title: 'Summarize', message: 'Summarize this thread and its decisions.' },
{ title: 'Investigate', message: 'Investigate this and show your evidence.' },
],
})
const database = postgresStore(process.env.DATABASE_URL!)
await database.migrate()
const openrouter = createOpenRouter({ apiKey: process.env.OPENROUTER_API_KEY! })
export default noir({
id: 'slack-operator',
name: 'Slack Operator',
personality: {
role: 'Operations partner',
tone: 'Fast, dry, and useful',
traits: ['funny', 'evidence-led', 'careful'],
},
instructions: `Answer the request directly. Use tools for external facts.
Do not claim an action succeeded until its tool result confirms it.
Ask before destructive actions or messages to another person.`,
model: openrouter('openai/gpt-5-mini'),
channels: { slack: assistant.channel },
database,
plugins: [assistant.capability, observabilityCapability()],
limits: { maxSteps: 12, runTimeoutMs: 120_000, toolResultBytes: 8_000 },
})Environment
OPENROUTER_API_KEY=
SLACK_BOT_TOKEN=xoxb-...
SLACK_SIGNING_SECRET=
SLACK_BOT_USER_ID=U...
DATABASE_URL=postgres://...Deployment behavior
The webhook returns after durable acceptance; the worker processes the event and flushes output. Keep at least one worker polling. On serverless hosts, explicitly schedule drain and flushOutbox after fetch rather than running model generation inside the webhook request.
Verification
Test a mention, DM if enabled, thread reply, file, denied approval, duplicate delivery, invalid signature, model timeout, and restart with queued work. Inspect Slack delivery errors in runtime events; a verified request URL only proves challenge handling, not that subscriptions and scopes are correct.