Platform-agnostic AI agent that monitors chat platforms for mentions and responds using Claude Code. Spawns Claude Code as subprocess with workspace directory set, giving Claude full filesystem access and CLAUDE.md context.
Message Ingestion (websocket / webhook / polling fallback)
├── Platform Adapters (Slack first, extensible to Discord/Telegram)
├── Claude Code Executor (subprocess with cwd set to workspace)
└── Config Loader (JSON config with ${ENV_VAR} substitution)
Platform adapters receive new messages via the best available transport:
- WebSocket / real-time API — preferred (lowest latency)
- Webhooks — if the platform supports them and infra allows inbound connections
- Polling — fallback when real-time options are unavailable
The ingestion mechanism is an implementation detail of each platform adapter. The rest of the system receives messages through a uniform interface regardless of transport.
- Receive new message mentioning the bot (via adapter)
- Filter: must not have completion reaction (✅) already
- Add typing indicator reaction (👀)
- Spawn Claude Code subprocess with workspace cwd
- Claude responds, using built-in tools and MCP servers
- Post response as thread reply
- Replace 👀 with ✅
- Loads
config.jsonwith env var substitution (${VAR_NAME}) - Validates required fields
- Schema:
{ "claude": { "model": "claude-sonnet-4-20250514" }, "platforms": ["slack"], "slackChannels": ["#channel"], "slackUsers": ["Jacek Tomaszewski"], "workspaceDir": "/path/to/workspace", "mcpServers": { "name": { "command": "npx", "args": ["..."], "env": { "KEY": "${ENV_VAR}" } } } }
- Spawns
npx @anthropic-ai/claude-codeas subprocess - Sets
cwdto workspace directory (for CLAUDE.md loading, filesystem access) - Passes MCP server config via
--mcp-configflag - Streams NDJSON output and extracts final response
- Receives messages via best available transport (WebSocket > webhook > polling)
- Detects bot mentions (resolves bot user ID on startup via
users_search) - Resolves allowed user display names to IDs on startup (if
slackUsersconfigured) - Filters messages to only respond to allowed users (if configured)
- Manages reactions (👀 typing, ✅ done) as persistent state
- Sends thread replies via MCP
conversations_add_message
- Wraps ClaudeCodeExecutor
- Passes system prompt for Slack assistant behavior
- Delegates tool-use loop to Claude Code subprocess
- Entry point, starts platform adapters and processes incoming messages
- Graceful error handling: responds with error message, cleans up reactions
- Runtime: Node.js 22+ (ESM)
- Language: TypeScript (strict mode)
- Dependencies:
dotenv(Claude Code handles API + MCP) - Build:
tsc→ JavaScript - Dev:
tsxfor on-the-fly TypeScript execution
- Docker multi-stage build (builder + runtime)
- Docker Compose for local dev
- CI/CD: GitHub Actions on version tags → container registry
ANTHROPIC_API_KEY— Anthropic API key (used by Claude Code subprocess)SLACK_XOXC— Slack user token (browser session)SLACK_XOXD— Slack session token (browser session)
- Resolves own user ID from the Slack session on startup
- Handles both raw user ID and
<@U0ABC123>mention format
- Slack emoji reactions (✅) — survives container restarts, prevents reprocessing
- On failure: send error message to thread, remove 👀 reaction
- Graceful degradation — single message failure doesn't crash loop
- E2E tests against a running agent instance
- Verify full flow: mention → 👀 reaction → thread reply with correct content → ✅ reaction
- Test edge cases: non-matching channels (if filter set), messages without mention ignored