Completed HIGH-001 through HIGH-004: HIGH-001: Discord bot with channel message routing - bot.py: 244 lines with ClaudeCoordinator class - @mention trigger mode for safe operation - Session lifecycle integration with SessionManager - Typing indicators and error handling - 20/20 tests passing HIGH-002: Response formatter with intelligent chunking - response_formatter.py: expanded to 329 lines - format_response() with smart boundary detection - Code block preservation and splitting - 26/26 tests passing HIGH-003: Slash commands for bot management - commands.py: 411 lines with ClaudeCommands cog - /reset with interactive confirmation dialog - /status with Discord embed display - /model for runtime model switching - 18/18 tests passing HIGH-004: Concurrent message handling - Per-channel asyncio.Lock implementation - Same-channel serialization (prevents race conditions) - Cross-channel parallelization (maintains performance) - 7/7 concurrency tests passing Total: 134/135 tests passing (99.3%) Production-ready Discord bot MVP Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
488 lines
13 KiB
Markdown
488 lines
13 KiB
Markdown
# Discord Bot Usage Documentation
|
|
|
|
## Overview
|
|
|
|
The Claude Coordinator Discord bot (`claude_coordinator/bot.py`) provides channel-based message routing to Claude CLI sessions. Each Discord channel maps to a specific project with isolated sessions and custom configurations.
|
|
|
|
**Implementation**: 244 lines of Python code
|
|
**Test Coverage**: 20 comprehensive test cases (455 lines), 100% passing
|
|
|
|
## Architecture
|
|
|
|
### Main Components
|
|
|
|
- **ClaudeCoordinator** (discord.ext.commands.Bot subclass)
|
|
- Message routing and filtering
|
|
- Session lifecycle management
|
|
- Integration with SessionManager, Config, ClaudeRunner
|
|
- Error handling and Discord response formatting
|
|
|
|
### Key Features
|
|
|
|
1. **@Mention Trigger Mode**: Bot only responds when explicitly mentioned (safe for MVP)
|
|
2. **Channel-to-Project Mapping**: Each channel routes to a specific project directory
|
|
3. **Session Persistence**: Sessions maintained per-channel across restarts
|
|
4. **Typing Indicator**: Shows "Bot is typing..." while Claude processes
|
|
5. **Error Handling**: Graceful handling of timeouts, failures, and edge cases
|
|
6. **Response Chunking**: Automatically splits long responses at Discord's 2000 char limit
|
|
|
|
## Installation
|
|
|
|
### 1. Ensure Dependencies are Installed
|
|
|
|
The bot requires:
|
|
- discord.py >= 2.6.4
|
|
- aiosqlite >= 0.22.1
|
|
- pyyaml >= 6.0.3
|
|
|
|
These are already in `pyproject.toml` and should be installed via `uv sync`.
|
|
|
|
### 2. Configuration
|
|
|
|
Create `~/.claude-coordinator/config.yaml`:
|
|
|
|
```yaml
|
|
projects:
|
|
my-project:
|
|
name: "my-project"
|
|
channel_id: "1234567890123456789" # Discord channel ID (as string)
|
|
project_dir: "/path/to/project"
|
|
allowed_tools:
|
|
- "Bash"
|
|
- "Read"
|
|
- "Write"
|
|
- "Edit"
|
|
system_prompt: "You are a helpful coding assistant for this project."
|
|
model: "sonnet" # or "opus", "haiku"
|
|
```
|
|
|
|
To get a Discord channel ID:
|
|
1. Enable Developer Mode in Discord (User Settings → Advanced → Developer Mode)
|
|
2. Right-click a channel → Copy Channel ID
|
|
|
|
### 3. Get Discord Bot Token
|
|
|
|
1. Go to https://discord.com/developers/applications
|
|
2. Create New Application
|
|
3. Navigate to Bot tab → Add Bot
|
|
4. Copy the Bot Token
|
|
5. Set environment variable:
|
|
```bash
|
|
export DISCORD_TOKEN="your-token-here"
|
|
```
|
|
|
|
### 4. Invite Bot to Server
|
|
|
|
1. In Discord Developer Portal → OAuth2 → URL Generator
|
|
2. Select scopes: `bot`
|
|
3. Select permissions: `Send Messages`, `Read Message History`, `View Channels`
|
|
4. Copy generated URL and open in browser
|
|
5. Select server and authorize
|
|
|
|
## Running the Bot
|
|
|
|
### Manual Execution
|
|
|
|
```bash
|
|
cd /opt/projects/claude-coordinator
|
|
export DISCORD_TOKEN="your-token-here"
|
|
uv run python -m claude_coordinator.bot
|
|
```
|
|
|
|
### As a Service (Recommended)
|
|
|
|
Create `/etc/systemd/system/claude-coordinator.service`:
|
|
|
|
```ini
|
|
[Unit]
|
|
Description=Claude Discord Coordinator Bot
|
|
After=network.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=discord-bot
|
|
WorkingDirectory=/opt/projects/claude-coordinator
|
|
Environment="DISCORD_TOKEN=your-token-here"
|
|
ExecStart=/home/discord-bot/.local/bin/uv run python -m claude_coordinator.bot
|
|
Restart=always
|
|
RestartSec=10
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
```
|
|
|
|
Enable and start:
|
|
```bash
|
|
sudo systemctl daemon-reload
|
|
sudo systemctl enable claude-coordinator
|
|
sudo systemctl start claude-coordinator
|
|
sudo systemctl status claude-coordinator
|
|
```
|
|
|
|
View logs:
|
|
```bash
|
|
sudo journalctl -u claude-coordinator -f
|
|
```
|
|
|
|
## Usage
|
|
|
|
### Basic Usage
|
|
|
|
1. Go to a configured Discord channel
|
|
2. Mention the bot with your message:
|
|
```
|
|
@ClaudeCoordinator Can you help me debug this function?
|
|
```
|
|
3. Bot shows typing indicator while processing
|
|
4. Claude's response appears in the channel
|
|
|
|
### Session Continuity
|
|
|
|
Sessions are persistent per-channel:
|
|
- First message creates a new session
|
|
- Subsequent messages resume the same session
|
|
- Session history maintained in SQLite database
|
|
- Sessions persist across bot restarts
|
|
|
|
### Example Conversation
|
|
|
|
```
|
|
User: @ClaudeCoordinator Can you list the files in this project?
|
|
|
|
Bot: I'll list the files in /path/to/project for you.
|
|
[Claude's response with file listing]
|
|
|
|
User: @ClaudeCoordinator Can you read setup.py?
|
|
|
|
Bot: [Reads and displays setup.py contents, maintaining context from previous message]
|
|
```
|
|
|
|
### Project Isolation
|
|
|
|
- Each channel has its own isolated session
|
|
- Sessions run in the configured `project_dir`
|
|
- Only configured `allowed_tools` are available
|
|
- Custom `system_prompt` sets project-specific behavior
|
|
|
|
## Message Filtering Logic
|
|
|
|
The bot processes a message if ALL of the following are true:
|
|
|
|
1. ✅ Message author is NOT a bot
|
|
2. ✅ Bot was mentioned in the message (`@ClaudeCoordinator`)
|
|
3. ✅ Channel is configured in config.yaml
|
|
|
|
Otherwise, the message is silently ignored.
|
|
|
|
## Error Handling
|
|
|
|
### Empty Message
|
|
If you mention the bot with no content:
|
|
```
|
|
User: @ClaudeCoordinator
|
|
|
|
Bot: ❌ Please provide a message after mentioning me.
|
|
```
|
|
|
|
### Claude Failure
|
|
If Claude CLI command fails:
|
|
```
|
|
Bot: ❌ **Error running Claude:**
|
|
```
|
|
Command failed: invalid syntax
|
|
```
|
|
```
|
|
|
|
### Timeout (>5 minutes)
|
|
```
|
|
Bot: ❌ **Timeout:** Claude took too long to respond (>5 minutes).
|
|
```
|
|
|
|
### Unexpected Errors
|
|
All exceptions are caught and reported:
|
|
```
|
|
Bot: ❌ **Unexpected error:**
|
|
```
|
|
[error details]
|
|
```
|
|
```
|
|
|
|
## Testing
|
|
|
|
Run the test suite:
|
|
|
|
```bash
|
|
cd /opt/projects/claude-coordinator
|
|
uv run pytest tests/test_bot.py -v
|
|
```
|
|
|
|
**Test Coverage**:
|
|
- Bot initialization and configuration
|
|
- Message filtering (bot messages, mentions, unconfigured channels)
|
|
- Message content extraction (removing mentions, whitespace)
|
|
- Session management (creation, resumption, persistence)
|
|
- Claude integration (config passing, response handling)
|
|
- Error handling (empty messages, Claude failures)
|
|
- Typing indicator verification
|
|
|
|
**Results**: 20/20 tests passing (100%)
|
|
|
|
## Monitoring
|
|
|
|
### Database Location
|
|
|
|
Session data stored in:
|
|
```
|
|
~/.claude-coordinator/sessions.db
|
|
```
|
|
|
|
### Inspect Sessions
|
|
|
|
```bash
|
|
cd /opt/projects/claude-coordinator
|
|
uv run python << 'EOF'
|
|
import asyncio
|
|
from claude_coordinator.session_manager import SessionManager
|
|
|
|
async def main():
|
|
async with SessionManager() as sm:
|
|
sessions = await sm.get_all_sessions()
|
|
for s in sessions:
|
|
print(f"Channel: {s['channel_id']}")
|
|
print(f" Project: {s['project_name']}")
|
|
print(f" Session: {s['session_id']}")
|
|
print(f" Messages: {s['message_count']}")
|
|
print(f" Last Active: {s['last_active']}")
|
|
print()
|
|
|
|
asyncio.run(main())
|
|
EOF
|
|
```
|
|
|
|
### Logs
|
|
|
|
With systemd service:
|
|
```bash
|
|
# Follow logs in real-time
|
|
sudo journalctl -u claude-coordinator -f
|
|
|
|
# View last 100 lines
|
|
sudo journalctl -u claude-coordinator -n 100
|
|
|
|
# View logs from today
|
|
sudo journalctl -u claude-coordinator --since today
|
|
```
|
|
|
|
Manual execution logs go to stdout:
|
|
```
|
|
2026-02-13 18:00:00 - claude_coordinator.bot - INFO - ClaudeCoordinator bot initialized
|
|
2026-02-13 18:00:01 - claude_coordinator.bot - INFO - Loaded configuration with 3 projects
|
|
2026-02-13 18:00:02 - discord.client - INFO - logging in using static token
|
|
2026-02-13 18:00:03 - claude_coordinator.bot - INFO - Logged in as ClaudeBot (ID: 123456789)
|
|
2026-02-13 18:00:03 - claude_coordinator.bot - INFO - Connected to 1 guilds
|
|
✓ Bot ready: ClaudeBot
|
|
```
|
|
|
|
## Advanced Configuration
|
|
|
|
### Multiple Projects
|
|
|
|
Configure different channels for different projects:
|
|
|
|
```yaml
|
|
projects:
|
|
web-app:
|
|
name: "web-app"
|
|
channel_id: "111111111111111111"
|
|
project_dir: "/home/cal/projects/web-app"
|
|
allowed_tools: ["Bash", "Read", "Write", "Edit", "Grep", "Glob"]
|
|
model: "sonnet"
|
|
|
|
data-pipeline:
|
|
name: "data-pipeline"
|
|
channel_id: "222222222222222222"
|
|
project_dir: "/home/cal/projects/data-pipeline"
|
|
allowed_tools: ["Bash", "Read", "Write"] # More restrictive
|
|
system_prompt: "You are a data engineering assistant. Focus on Python and SQL."
|
|
model: "opus"
|
|
|
|
docs-site:
|
|
channel_id: "333333333333333333"
|
|
project_dir: "/home/cal/projects/docs"
|
|
allowed_tools: ["Read", "Write", "Edit"] # No Bash execution
|
|
system_prompt_file: "/home/cal/prompts/technical-writer.txt"
|
|
model: "haiku" # Faster for documentation
|
|
```
|
|
|
|
### External System Prompts
|
|
|
|
Instead of inline `system_prompt`, use a file:
|
|
|
|
```yaml
|
|
projects:
|
|
my-project:
|
|
# ... other config ...
|
|
system_prompt_file: "/path/to/prompt.txt"
|
|
```
|
|
|
|
The file will be loaded and passed to Claude on each invocation.
|
|
|
|
## Security Considerations
|
|
|
|
1. **Tool Restrictions**: Use `allowed_tools` to limit what Claude can do
|
|
- Production projects: Consider disabling `Bash` tool
|
|
- Read-only access: Only allow `Read`, `Grep`, `Glob`
|
|
|
|
2. **Channel Access Control**: Use Discord role permissions to control who can access bot channels
|
|
|
|
3. **Environment Variables**: Never commit `DISCORD_TOKEN` to git
|
|
- Use environment variables or systemd service config
|
|
- Store in a secure secrets manager
|
|
|
|
4. **Database Permissions**: Session database should only be readable by bot user
|
|
```bash
|
|
chmod 600 ~/.claude-coordinator/sessions.db
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### Bot doesn't respond
|
|
|
|
1. **Check bot was mentioned**: Must use `@ClaudeCoordinator`, not just typing its name
|
|
2. **Check channel is configured**: Channel ID must be in config.yaml
|
|
3. **Check bot is running**: `systemctl status claude-coordinator`
|
|
4. **Check logs**: `journalctl -u claude-coordinator -f`
|
|
|
|
### "Timeout" errors
|
|
|
|
- Claude CLI took >5 minutes to respond
|
|
- This is configured in ClaudeRunner (default: 300 seconds)
|
|
- Increase timeout in `claude_coordinator/claude_runner.py` if needed
|
|
|
|
### Bot crashes on startup
|
|
|
|
1. **Check DISCORD_TOKEN is set**: `echo $DISCORD_TOKEN`
|
|
2. **Check config file exists**: `ls ~/.claude-coordinator/config.yaml`
|
|
3. **Validate config syntax**: `uv run python -c "import yaml; yaml.safe_load(open('~/.claude-coordinator/config.yaml'))"`
|
|
4. **Check database permissions**: `ls -la ~/.claude-coordinator/sessions.db`
|
|
|
|
### Sessions not resuming
|
|
|
|
- Check database exists: `ls ~/.claude-coordinator/sessions.db`
|
|
- Verify session was saved (check logs for "Successfully processed message")
|
|
- Inspect database to confirm session_id was stored
|
|
|
|
## Architecture Details
|
|
|
|
### Message Flow
|
|
|
|
```
|
|
Discord Message
|
|
↓
|
|
on_message() event handler
|
|
↓
|
|
[Filter: Is bot message?] → Yes → Ignore
|
|
↓ No
|
|
[Filter: Bot mentioned?] → No → Ignore
|
|
↓ Yes
|
|
[Filter: Channel configured?] → No → Ignore
|
|
↓ Yes
|
|
_handle_claude_request()
|
|
↓
|
|
Extract message content (remove mentions)
|
|
↓
|
|
[Empty message?] → Yes → Send error
|
|
↓ No
|
|
Start typing indicator
|
|
↓
|
|
Get session from SessionManager (None if new)
|
|
↓
|
|
Run ClaudeRunner with session_id + project config
|
|
↓
|
|
[Success?] → No → Send error message
|
|
↓ Yes
|
|
Save/update session in database
|
|
↓
|
|
Format response (split at 2000 chars if needed)
|
|
↓
|
|
Send response to Discord channel
|
|
```
|
|
|
|
### Session Lifecycle
|
|
|
|
```
|
|
User sends first message
|
|
↓
|
|
SessionManager.get_session(channel_id) → None
|
|
↓
|
|
ClaudeRunner.run(session_id=None) # New session
|
|
↓
|
|
Claude CLI creates new session, returns session_id
|
|
↓
|
|
SessionManager.save_session(channel_id, session_id)
|
|
↓
|
|
[Subsequent messages]
|
|
↓
|
|
SessionManager.get_session(channel_id) → session_data
|
|
↓
|
|
ClaudeRunner.run(session_id=session_data['session_id']) # Resume
|
|
↓
|
|
SessionManager.update_activity(channel_id) # Update timestamp
|
|
```
|
|
|
|
## Development
|
|
|
|
### Running Tests
|
|
|
|
```bash
|
|
# All tests
|
|
uv run pytest tests/test_bot.py -v
|
|
|
|
# Specific test class
|
|
uv run pytest tests/test_bot.py::TestMessageFiltering -v
|
|
|
|
# Single test
|
|
uv run pytest tests/test_bot.py::TestMessageFiltering::test_ignores_bot_messages -v
|
|
|
|
# With coverage
|
|
uv run pytest tests/test_bot.py --cov=claude_coordinator.bot
|
|
```
|
|
|
|
### Adding New Features
|
|
|
|
1. Update `claude_coordinator/bot.py` with new functionality
|
|
2. Add tests to `tests/test_bot.py`
|
|
3. Run full test suite: `uv run pytest tests/test_bot.py -v`
|
|
4. Update this documentation
|
|
|
|
### Code Structure
|
|
|
|
- **`__init__`**: Initialize bot with intents and components
|
|
- **`setup_hook`**: Async initialization (database, config loading)
|
|
- **`on_ready`**: Log connection status
|
|
- **`on_message`**: Main event handler with filtering logic
|
|
- **`_handle_claude_request`**: Process message, call Claude, send response
|
|
- **`_extract_message_content`**: Remove bot mentions from message
|
|
- **`close`**: Clean shutdown of resources
|
|
|
|
## Future Enhancements
|
|
|
|
Potential improvements for future versions:
|
|
|
|
1. **Slash Commands**: Support `/claude <message>` in addition to @mentions
|
|
2. **Thread Support**: Create threads for long conversations
|
|
3. **Reaction Controls**: React with ❌ to stop processing, ♻️ to retry
|
|
4. **Usage Tracking**: Track API costs per channel/user
|
|
5. **Admin Commands**: `/session reset`, `/session info`, `/config reload`
|
|
6. **Rate Limiting**: Prevent spam/abuse
|
|
7. **Multi-user Sessions**: Track per-user sessions instead of per-channel
|
|
8. **Attachment Support**: Process code files attached to messages
|
|
|
|
## Support
|
|
|
|
For issues or questions:
|
|
- Check logs: `journalctl -u claude-coordinator -f`
|
|
- Review test output: `uv run pytest tests/test_bot.py -v`
|
|
- Verify configuration: Ensure config.yaml is valid and channel IDs are correct
|
|
- Test manually: Run `uv run python -m claude_coordinator.bot` to see startup errors
|