Messaging
Chat platform integration
Messaging Guide
Prompt-first procedure: Describe the outcome you want in your agent conversation. The agent should select and load the appropriate AIWG assets, explain material changes, request any needed approval, and report verification evidence. Exact commands and flags appear only in the CLI reference.
AIWG messaging integration connects your project to Slack, Discord, and Telegram for real-time notifications, interactive commands, and 2-way AI chat. The messaging subsystem runs within the daemon process.
Overview
The messaging system provides:
- Event notifications — Receive alerts for agent loop completions, security issues, build failures, and HITL gates
- Interactive commands — Query status, approve gates, and manage workflows from chat
- 2-way AI chat — Ask questions about your project directly from messaging platforms
- Multi-platform — Same event stream delivered to all connected platforms simultaneously
Quick Start
1. Set environment variables
Each platform requires a bot token via environment variable:
# Slack
export AIWG_SLACK_TOKEN="xoxb-your-bot-token"
export AIWG_SLACK_CHANNEL="#aiwg-notifications"
# Discord
export AIWG_DISCORD_TOKEN="your-discord-bot-token"
export AIWG_DISCORD_CHANNEL="channel-id"
# Telegram
export AIWG_TELEGRAM_TOKEN="123456:ABCdefGHIjklMNOpqrsTUVwxyz"
export AIWG_TELEGRAM_CHAT_ID="-1001234567890"
2. Start the daemon
Use AIWG to complete this documented outcome: 2. Start the daemon
Have it inspect the current state, explain the plan, ask before material
changes, and report the result with verification evidence.
The messaging hub auto-discovers enabled adapters from environment variables. No configuration file changes needed.
3. Verify
The daemon log confirms adapter loading:
[messaging] Hub started with 2 adapter(s): slack, telegram
[messaging] 2-way AI chat enabled
Platform Setup
Slack
1. Create a Slack app at https://api.slack.com/apps 2. Add Bot Token Scopes: `chat:write`, `commands`, `app_mentions:read` 3. Install to workspace and copy the Bot User OAuth Token 4. Set environment variables:
export AIWG_SLACK_TOKEN="xoxb-..."
export AIWG_SLACK_CHANNEL="#aiwg-notifications" # Default channel for notifications
Discord
1. Create a Discord application at https://discord.com/developers/applications 2. Add a Bot under the application 3. Enable Message Content Intent in Bot settings 4. Generate an invite URL with `Send Messages` and `Read Message History` permissions 5. Invite the bot to your server 6. Set environment variables:
export AIWG_DISCORD_TOKEN="your-bot-token"
export AIWG_DISCORD_CHANNEL_ID="channel-id" # Right-click channel → Copy ID
To get a channel ID: enable Developer Mode in Discord (Settings → Advanced), then right-click any channel → Copy Channel ID.
Multi-channel configuration
Route different event types to separate Discord channels using `config.rooms` in your daemon configuration:
{
"messaging": {
"discord": {
"botToken": "your-bot-token",
"rooms": [
{
"channel_id": "1234567890",
"label": "dev-notifications",
"is_default": true,
"purpose": "interactive"
},
{
"channel_id": "9876543210",
"label": "security-alerts",
"is_default": true,
"purpose": "notifications"
},
{
"channel_id": "1122334455",
"label": "debug-logs",
"is_default": false,
"purpose": "logs"
}
]
}
}
}
| Field | Description |
|---|---|
| `channel_id` | Discord channel ID (also accepts `channelId`) |
| `label` | Human-readable name for this channel |
| `is_default` | When `true`, the channel receives all broadcast messages (e.g., agent loop completions, health alerts). When `false`, the channel only receives messages sent to it explicitly. Also accepts `isDefault`. |
| `purpose` | Informational. `interactive` (commands), `notifications` (one-way events), `logs` (verbose output) |
Rooms with `is_default: true` receive `broadcastToRooms()` messages. Multiple default rooms can exist — all receive the same broadcast. Rooms with `is_default: false` only receive messages sent via `sendToRoom(message, channelId)`.
Single-channel fallback: If `rooms` is omitted, the adapter uses `AIWG_DISCORD_CHANNEL_ID` or `defaultChannelId` as the sole channel with `is_default: true`.
Telegram
1. Message @BotFather on Telegram 2. Send `/newbot` and follow the prompts 3. Copy the bot token 4. Add the bot to your group/channel 5. Get the chat ID (send a message, then check `https://api.telegram.org/bot<token>/getUpdates`) 6. Set environment variables:
export AIWG_TELEGRAM_TOKEN="123456:ABCdef..."
export AIWG_TELEGRAM_CHAT_ID="-1001234567890"
Multi-room configuration
Route different event types to separate Telegram chats or groups using `config.rooms`:
{
"messaging": {
"telegram": {
"botToken": "123456:ABCdef...",
"rooms": [
{
"chat_id": "-1001234567890",
"label": "dev-team",
"is_default": true,
"purpose": "interactive"
},
{
"chat_id": "-1009876543210",
"label": "security-channel",
"is_default": true,
"purpose": "notifications"
}
]
}
}
}
| Field | Description |
|---|---|
| `chat_id` | Telegram chat/group/channel ID (also accepts `chatId`) |
| `label` | Human-readable name for this chat |
| `is_default` | When `true`, receives broadcast messages. Also accepts `isDefault`. |
| `purpose` | Informational. `interactive`, `notifications`, or `logs` |
Single-room fallback: If `rooms` is omitted, the adapter uses `AIWG_TELEGRAM_CHAT_ID` or `defaultChatId` as the sole room with `is_default: true`.
Commands
All platforms support the same command set. Prefix commands with `/` in chat:
| Command | Permission | Description |
|---|---|---|
| `/help` | read | List available commands |
| `/status` | read | Show project status and daemon health |
| `/ralph-status` | read | Show active agent loop status |
| `/health` | read | Run health check on all subsystems |
| `/ask <question>` | read | Ask the AI a question about your project |
| `/approve <gate-id>` | write | Approve a pending HITL gate |
| `/reject <gate-id> [reason]` | write | Reject a pending HITL gate with optional reason |
Permissions
Commands have two permission levels:
- read — Available to everyone in the channel
- write — Requires explicit permission grant
Grant write permissions via the `writeUsers` option:
const hub = await createMessagingHub({
writeUsers: ['U12345', '[email protected]'],
});
Or in daemon configuration, set `AIWG_WRITE_USERS` environment variable (comma-separated user IDs).
Command Examples
Check project status:
You: /status
Bot: Project: my-project
Phase: Construction
Daemon uptime: 3600s
Health: healthy
Approve a HITL gate:
You: /approve gate-e2c-001
Bot: Gate gate-e2c-001 approved
Ask a question:
You: /ask what is our test coverage?
Bot: Based on the project configuration, the test suite runs via
`npx vitest run` and currently has approximately 2,619 tests...
2-Way AI Chat
Beyond slash commands, you can send free-text messages to the bot for AI-powered responses. The bot uses `claude -p` with your project's full context (CLAUDE.md, codebase, etc.).
How It Works
1. Send a message to the bot (DM or mention in a channel) 2. The messaging hub forwards it to the ChatHandler 3. ChatHandler spawns a `claude -p` process with conversation context 4. The AI response is sent back to the originating platform
Multi-Turn Conversations
The chat system maintains conversation history per chat channel. Each conversation tracks up to 10 message pairs (configurable), so the AI remembers context from recent messages:
You: What testing framework do we use?
Bot: The project uses Vitest for testing...
You: How do I add a new test file?
Bot: Based on the existing test structure, create a new file in
test/unit/ following the pattern...
Configuration
Chat behavior is configured via `createMessagingHub()` options or environment variables:
| Setting | Default | Description |
|---|---|---|
| `maxConcurrent` | 3 | Maximum simultaneous AI processes |
| `maxContextMessages` | 10 | Conversation history depth (message pairs) |
| `timeoutMs` | 120000 | AI response timeout (milliseconds) |
| `maxResponseLength` | 4000 | Maximum response characters |
Concurrency Limits
To prevent resource exhaustion, the chat system limits concurrent AI processes:
- Global limit: Maximum 3 simultaneous `claude -p` processes (configurable)
- Per-chat dedup: Only one AI process per chat at a time
- Busy response: When limits are reached, users receive a "please wait" message
Disabling Chat
To run messaging with notifications and commands only (no AI chat):
const hub = await createMessagingHub({
chatHandler: false,
});
Event Topics
The messaging system forwards events from the internal event bus to all connected platforms. Events are formatted into human-readable messages with severity indicators.
Event Categories
| Category | Topics | Description |
|---|---|---|
| Al | `ralph.started`, `ralph.iteration`, `ralph.completed`, `ralph.failed`, `ralph.aborted` | Agent loop lifecycle events |
| HITL Gates | `gate.pending`, `gate.approved`, `gate.rejected`, `gate.timeout` | Human-in-the-loop gate events |
| Security | `security.critical`, `security.warning`, `security.scan_done` | Security scan results |
| Health | `health.check`, `health.degraded`, `health.recovered` | System health transitions |
| Build | `build.failed`, `build.passed` | Build/test results |
| Daemon | `daemon.started`, `daemon.stopping` | Daemon lifecycle |
| Chat | `chat.message`, `chat.response`, `chat.error` | Chat session events |
Event Severity
Events carry a severity level that affects formatting:
| Severity | Display | Use Case |
|---|---|---|
| `info` | Normal text | Status updates, completions |
| `warning` | Highlighted | Degraded health, gate timeouts |
| `critical` | Alert/urgent | Security issues, failures |
Publishing Custom Events
From your own integrations, publish events to the messaging hub:
hub.publish({
topic: 'custom.event',
source: 'my-integration',
severity: 'info',
summary: 'Deployment completed successfully',
details: { version: '1.2.3', environment: 'staging' },
timestamp: new Date().toISOString(),
});
Custom Adapters
To add support for a new messaging platform, extend `BaseAdapter`:
import { BaseAdapter } from '../adapters/base.mjs';
export class MyPlatformAdapter extends BaseAdapter {
constructor(config) {
super('myplatform');
this.config = config;
}
async initialize() {
// Connect to platform API
this._setConnected();
}
async send(message, channel) {
// Format and send message to platform
this._recordSend();
return { messageId: '...', channelId: channel, success: true };
}
async update(messageId, message) {
// Update an existing message
}
async shutdown() {
this._setDisconnected();
}
}
Required Methods
| Method | Description |
|---|---|
| `initialize()` | Connect to platform, set up event listeners |
| `send(message, channel)` | Send a formatted message |
| `update(messageId, message)` | Update an existing message |
| `shutdown()` | Disconnect and clean up |
Protected Methods (from BaseAdapter)
| Method | Description |
|---|---|
| `_setConnected()` | Mark adapter as connected |
| `_setDisconnected()` | Mark adapter as disconnected |
| `_recordSend()` | Increment sent message counter |
| `_recordReceive()` | Increment received message counter |
| `_recordError(error)` | Record an error |
| `_dispatchCommand(command, args, context)` | Forward command to registered handlers |
| `_dispatchMessage(text, context)` | Forward free-text message to handlers |
Registering Inbound Handlers
For platforms that support inbound messages, call the dispatch methods from your adapter's event listeners:
// In your adapter's initialize():
this.client.on('message', (msg) => {
if (msg.text.startsWith('/')) {
const [command, ...args] = msg.text.slice(1).split(' ');
this._dispatchCommand(command, args, {
chatId: msg.chatId,
from: msg.author,
});
} else {
this._dispatchMessage(msg.text, {
chatId: msg.chatId,
from: msg.author,
});
}
});
Architecture
┌─────────────────────────────────────────────────┐
│ Daemon Process │
│ │
│ ┌──────────┐ ┌──────────────────┐ │
│ │ EventBus │───→│ MessageFormatter │ │
│ └──────────┘ └────────┬─────────┘ │
│ ↑ ↓ │
│ Events from Formatted messages │
│ Al, Health, ┌─────────┬─────────┐ │
│ Security, etc. ↓ ↓ ↓ │
│ ┌───────┐ ┌───────┐ ┌────────┐ │
│ │ Slack │ │Discord│ │Telegram│ │
│ └───┬───┘ └───┬───┘ └────┬───┘ │
│ ↓ ↓ ↓ │
│ Inbound: ┌──────────────────────────────┐ │
│ /commands │ CommandRouter │ │
│ free text │ ChatHandler │ │
│ └──────────────────────────────┘ │
└─────────────────────────────────────────────────┘
Troubleshooting
No adapters loaded
Verify environment variables are set:
echo $AIWG_SLACK_TOKEN
echo $AIWG_DISCORD_TOKEN
echo $AIWG_TELEGRAM_TOKEN
At least one token must be set for messaging to activate.
Bot not responding to commands
- Verify the bot has appropriate permissions in the channel
- Check daemon logs: `tail -f .aiwg/daemon/daemon.log`
- Ensure commands start with `/` (e.g., `/status`, not `status`)
AI chat responses are slow
Each response spawns a `claude -p` process with full project context. For large projects, this can take 30-60 seconds. To speed up:
- Reduce project context (optimize CLAUDE.md)
- Increase `maxConcurrent` if you have resources
- Use `/ask` for quick questions (routes through command system)
Messages truncated
Response length is capped at `maxResponseLength` (default 4000 chars) to respect platform limits. Truncated responses end with `[...truncated]`. Adjust the limit in chat handler configuration.
Rate limiting
If a platform rate-limits the bot, errors appear in daemon logs. The adapters handle rate limits gracefully with backoff, but sustained high-volume notifications may require message batching configuration.
Cross-References
- Daemon Guide — Daemon setup and management
- Al Guide — Agent loops with messaging notifications
- `.aiwg/architecture/adrs/ADR-messaging-bot-mode.md` — Architecture decision
- `.aiwg/architecture/adrs/ADR-2way-chat.md` — Chat architecture
- `tools/messaging/README.md` — Developer documentation