Initial commit: Discord-Claude Gateway with event-driven agent runtime
This commit is contained in:
283
README.md
283
README.md
@@ -1,208 +1,233 @@
|
||||
# Discord-Claude Gateway
|
||||
# Aetheel — Discord-Claude Gateway
|
||||
|
||||
An event-driven agent runtime that connects Discord to Claude via the [Claude Agent SDK](https://docs.anthropic.com/en/docs/agent-sdk/overview). Inspired by [OpenClaw](https://github.com/nichochar/open-claw)'s architecture — a gateway in front of an agent runtime with markdown-based personality, memory, and scheduled behaviors.
|
||||
An event-driven AI agent runtime that connects Discord to Claude Code CLI. Inspired by [OpenClaw](https://github.com/nichochar/open-claw)'s architecture — a gateway in front of an agent runtime with markdown-based personality, memory, scheduled behaviors, and proactive messaging.
|
||||
|
||||
## How It Works
|
||||
|
||||
```
|
||||
Discord Users ──► Discord Bot ──► Event Queue ──► Agent Runtime ──► Claude Agent SDK
|
||||
Discord Users ──► Discord Bot ──► Event Queue ──► Agent Runtime ──► Claude Code CLI
|
||||
▲ │
|
||||
Heartbeats ──────────┤ │
|
||||
Cron Jobs ───────────┤ ┌─────────────────────┘
|
||||
Hooks ───────────────┘ ▼
|
||||
Markdown Config Files
|
||||
(soul, identity, memory, etc.)
|
||||
IPC Watcher ───────────── Markdown Config Files
|
||||
(CLAUDE.md, memory.md, etc.)
|
||||
```
|
||||
|
||||
All inputs — Discord messages, heartbeat timers, cron jobs, lifecycle hooks — enter a unified event queue. The agent runtime reads your markdown config files fresh on each event, assembles a dynamic system prompt, and calls the Claude Agent SDK. The agent can write back to `memory.md` to persist facts across sessions.
|
||||
All inputs — Discord messages, heartbeat timers, cron jobs, lifecycle hooks — enter a unified event queue. The agent runtime reads your markdown config files fresh on each event, assembles a dynamic system prompt, and calls the Claude Code CLI. The agent can write back to `memory.md` to persist facts across sessions, and send proactive messages via the IPC system.
|
||||
|
||||
Uses your existing Claude Code subscription — no API key needed.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Node.js** 18+
|
||||
- **Claude Code CLI** — [Install Claude Code](https://docs.anthropic.com/en/docs/claude-code/getting-started) and sign in with your subscription
|
||||
- **Discord Bot Token** — [Create a bot](https://discord.com/developers/applications) with Message Content Intent enabled
|
||||
- Node.js 18+
|
||||
- Claude Code CLI installed and signed in (`npm install -g @anthropic-ai/claude-code && claude`)
|
||||
- A Discord bot token ([create one here](https://discord.com/developers/applications)) with Message Content Intent enabled
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
git clone <your-repo-url>
|
||||
cd aetheel-2
|
||||
npm install
|
||||
|
||||
# Make sure Claude Code CLI is installed and you're signed in
|
||||
claude --version
|
||||
|
||||
# Set required environment variables
|
||||
export DISCORD_BOT_TOKEN=your-discord-bot-token
|
||||
|
||||
# Create config directory with persona files
|
||||
mkdir config
|
||||
```
|
||||
|
||||
Create at minimum `config/soul.md` and `config/identity.md`:
|
||||
|
||||
```bash
|
||||
# config/identity.md
|
||||
echo "# Identity
|
||||
|
||||
- **Name:** Aetheel
|
||||
- **Vibe:** Helpful, sharp, slightly witty
|
||||
- **Emoji:** ⚡" > config/identity.md
|
||||
|
||||
# config/soul.md
|
||||
echo "# Soul
|
||||
|
||||
Be genuinely helpful. Have opinions. Be resourceful before asking.
|
||||
Earn trust through competence." > config/soul.md
|
||||
```
|
||||
|
||||
```bash
|
||||
# Start the gateway
|
||||
cp .env.example .env # Edit with your Discord bot token
|
||||
mkdir -p config
|
||||
# Create config/CLAUDE.md with your persona (see Setup section)
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Or run the interactive setup:
|
||||
```bash
|
||||
bash scripts/setup.sh
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
All settings are via environment variables:
|
||||
### Environment Variables
|
||||
|
||||
Create a `.env` file in the project root (auto-loaded via dotenv):
|
||||
|
||||
| Variable | Required | Default | Description |
|
||||
|----------|----------|---------|-------------|
|
||||
| `DISCORD_BOT_TOKEN` | Yes | — | Discord bot token |
|
||||
| `OUTPUT_CHANNEL_ID` | No | — | Discord channel for heartbeat/cron/hook output |
|
||||
| `CLAUDE_CLI_PATH` | No | `claude` | Path to the Claude Code CLI binary |
|
||||
| `ALLOWED_TOOLS` | No | `Read,Write,Edit,Glob,Grep,WebSearch,WebFetch` | Comma-separated Claude Code tools |
|
||||
| `PERMISSION_MODE` | No | `bypassPermissions` | Claude Code permission mode |
|
||||
| `QUERY_TIMEOUT_MS` | No | `120000` | Query timeout in milliseconds |
|
||||
| `MAX_CONCURRENT_QUERIES` | No | `5` | Max simultaneous Claude queries |
|
||||
| `CONFIG_DIR` | No | `./config` | Path to markdown config directory |
|
||||
| `ALLOWED_TOOLS` | No | `Read,Write,Edit,Glob,Grep,WebSearch,WebFetch` | Comma-separated tools the agent can use |
|
||||
| `PERMISSION_MODE` | No | `bypassPermissions` | Claude Code permission mode |
|
||||
| `QUERY_TIMEOUT_MS` | No | `120000` | Max time per query (ms) |
|
||||
| `MAX_CONCURRENT_QUERIES` | No | `5` | Max simultaneous queries |
|
||||
| `MAX_QUEUE_DEPTH` | No | `100` | Max events in the queue |
|
||||
| `OUTPUT_CHANNEL_ID` | No | — | Discord channel for heartbeat/cron output |
|
||||
| `IDLE_SESSION_TIMEOUT_MS` | No | `1800000` | Session idle timeout (30 min) |
|
||||
| `LOG_LEVEL` | No | `info` | Log level: debug, info, warn, error |
|
||||
|
||||
## Markdown Config Files
|
||||
### Markdown Config Files
|
||||
|
||||
Place these in your `CONFIG_DIR` (default: `./config/`). The gateway reads them fresh on every event — edit them anytime, no restart needed (except `agents.md` and `heartbeat.md` which are parsed at startup for cron/heartbeat timers).
|
||||
Place these in `CONFIG_DIR` (default: `./config/`):
|
||||
|
||||
| File | Purpose | Required |
|
||||
|------|---------|----------|
|
||||
| `CLAUDE.md` | Persona: identity, personality, user context, tools — all in one | Yes |
|
||||
| `agents.md` | Operating rules, cron jobs, hooks (parsed by gateway) | No |
|
||||
| `memory.md` | Long-term memory (agent can write to this) | No (auto-created) |
|
||||
| `heartbeat.md` | Proactive check definitions (parsed by gateway) | No |
|
||||
| `CLAUDE.md` | Persona: identity, personality, user context, tools | Yes |
|
||||
| `agents.md` | Operating rules, cron jobs, hooks (parsed at startup) | No |
|
||||
| `memory.md` | Long-term memory (agent-writable) | No (auto-created) |
|
||||
| `heartbeat.md` | Proactive check definitions (parsed at startup) | No |
|
||||
|
||||
Missing optional files are created with default headers on first run.
|
||||
The gateway reads `CLAUDE.md` and `memory.md` fresh on every event — edit them anytime. `agents.md` and `heartbeat.md` are parsed at startup for timers, so restart after editing those.
|
||||
|
||||
### Heartbeat Config (`heartbeat.md`)
|
||||
### Skills
|
||||
|
||||
Define proactive checks the agent runs on a timer:
|
||||
Drop skill files into `config/skills/{name}/SKILL.md` and they're automatically loaded into the system prompt:
|
||||
|
||||
```
|
||||
config/skills/
|
||||
├── web-research/
|
||||
│ └── SKILL.md → "When asked to research, use WebSearch..."
|
||||
└── code-review/
|
||||
└── SKILL.md → "When reviewing code, focus on..."
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
### Discord Integration
|
||||
- Mention the bot (`@Aetheel hi`) or use `/claude` slash command
|
||||
- `/claude-reset` to start a fresh conversation
|
||||
- Responses auto-split at 2000 chars with code block preservation
|
||||
- Typing indicators while processing
|
||||
|
||||
### Session Management
|
||||
- Per-channel conversation sessions with Claude Code CLI `--resume`
|
||||
- Sessions persist to `config/sessions.json` (survive restarts)
|
||||
- Auto-cleanup of idle sessions after 30 minutes (configurable)
|
||||
|
||||
### Heartbeats (Timer Events)
|
||||
Define in `config/heartbeat.md`:
|
||||
```markdown
|
||||
## check-email
|
||||
Interval: 1800
|
||||
Instruction: Check my inbox for anything urgent. If nothing, reply HEARTBEAT_OK.
|
||||
|
||||
## check-calendar
|
||||
Interval: 3600
|
||||
Instruction: Review upcoming calendar events in the next 24 hours.
|
||||
Instruction: Check my inbox for anything urgent.
|
||||
```
|
||||
|
||||
Interval is in seconds (minimum 60).
|
||||
|
||||
### Cron Jobs (in `agents.md`)
|
||||
|
||||
Define scheduled tasks with cron expressions:
|
||||
|
||||
### Cron Jobs (Scheduled Events)
|
||||
Define in `config/agents.md`:
|
||||
```markdown
|
||||
## Cron Jobs
|
||||
|
||||
### morning-briefing
|
||||
Cron: 0 9 * * *
|
||||
Instruction: Good morning! Check email, review today's calendar, and give me a brief summary.
|
||||
|
||||
### weekly-review
|
||||
Cron: 0 15 * * 1
|
||||
Instruction: Review the week's calendar and flag any conflicts.
|
||||
Cron: 0 8 * * *
|
||||
Instruction: Good morning! Search for the latest AI news and post a summary.
|
||||
```
|
||||
|
||||
### Hooks (in `agents.md`)
|
||||
|
||||
Define lifecycle hook instructions:
|
||||
|
||||
### Lifecycle Hooks
|
||||
Define in `config/agents.md`:
|
||||
```markdown
|
||||
## Hooks
|
||||
|
||||
### startup
|
||||
Instruction: Read memory.md and greet the user.
|
||||
Instruction: Say hello, you just came online.
|
||||
|
||||
### shutdown
|
||||
Instruction: Save any important context to memory.md before shutting down.
|
||||
Instruction: Save important context to memory.md.
|
||||
```
|
||||
|
||||
## Discord Commands
|
||||
### Proactive Messaging (IPC)
|
||||
The agent can send messages to any Discord channel by writing JSON files to `config/ipc/outbound/`:
|
||||
```json
|
||||
{"channelId": "123456789", "text": "Hey, found something interesting!"}
|
||||
```
|
||||
The gateway polls every 2 seconds and delivers the message.
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `@bot <message>` | Send a prompt by mentioning the bot |
|
||||
| `/claude <prompt>` | Send a prompt via slash command |
|
||||
| `/claude-reset` | Reset the conversation session in the current channel |
|
||||
### Message History
|
||||
All inbound/outbound messages stored per channel in `config/messages/{channelId}.json`. Max 100 messages per channel, auto-trimmed.
|
||||
|
||||
## Architecture
|
||||
### Conversation Archiving
|
||||
Every exchange saved as readable markdown in `config/conversations/{channelId}/{YYYY-MM-DD}.md`.
|
||||
|
||||
The system has 5 input types (inspired by OpenClaw):
|
||||
### Retry with Backoff
|
||||
Claude CLI calls retry 3 times with exponential backoff (5s, 10s, 20s) on transient errors. Session corruption errors fail immediately.
|
||||
|
||||
1. **Messages** — Discord mentions and slash commands
|
||||
2. **Heartbeats** — Timer-based proactive checks
|
||||
3. **Cron Jobs** — Scheduled events with cron expressions
|
||||
4. **Hooks** — Internal lifecycle triggers (startup, shutdown, agent_begin, agent_stop)
|
||||
5. **Webhooks** — External system events (planned)
|
||||
### Structured Logging
|
||||
Pino-based structured JSON logging. Set `LOG_LEVEL=debug` for verbose output. Pretty-printed in dev, JSON in production (`NODE_ENV=production`).
|
||||
|
||||
All inputs enter a unified FIFO event queue → processed sequentially by the agent runtime → state persists to markdown files → loop continues.
|
||||
## Deployment
|
||||
|
||||
### systemd (recommended for Linux servers)
|
||||
|
||||
```bash
|
||||
sudo bash scripts/setup.sh # Creates .env, config/, and systemd service
|
||||
```
|
||||
|
||||
Or manually:
|
||||
```bash
|
||||
sudo cp scripts/aetheel.service /etc/systemd/system/
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable aetheel
|
||||
sudo systemctl start aetheel
|
||||
|
||||
# View logs
|
||||
sudo journalctl -u aetheel -f
|
||||
```
|
||||
|
||||
### PM2
|
||||
```bash
|
||||
npm run build
|
||||
pm2 start dist/index.js --name aetheel
|
||||
pm2 save
|
||||
```
|
||||
|
||||
### Dev mode
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
src/
|
||||
├── index.ts # Entry point
|
||||
├── gateway-core.ts # Main orchestrator
|
||||
├── config.ts # Environment variable loader
|
||||
├── discord-bot.ts # Discord.js wrapper
|
||||
├── event-queue.ts # Unified FIFO event queue
|
||||
├── agent-runtime.ts # Core processing engine
|
||||
├── markdown-config-loader.ts # Reads config files from disk
|
||||
├── system-prompt-assembler.ts# Assembles system prompt from configs
|
||||
├── session-manager.ts # Channel-to-session bindings
|
||||
├── channel-queue.ts # Per-channel sequential processing
|
||||
├── response-formatter.ts # Message splitting for Discord's 2000 char limit
|
||||
├── error-formatter.ts # Safe error formatting
|
||||
├── heartbeat-scheduler.ts # Recurring timer events
|
||||
├── cron-scheduler.ts # Cron-expression scheduled events
|
||||
├── hook-manager.ts # Lifecycle hook events
|
||||
├── bootstrap-manager.ts # First-run file validation/creation
|
||||
└── shutdown-handler.ts # Graceful SIGTERM/SIGINT handling
|
||||
├── index.ts # Entry point (loads .env)
|
||||
├── gateway-core.ts # Main orchestrator
|
||||
├── config.ts # Environment variable loader
|
||||
├── logger.ts # Pino structured logger
|
||||
├── discord-bot.ts # Discord.js wrapper
|
||||
├── event-queue.ts # Unified FIFO event queue
|
||||
├── agent-runtime.ts # Core engine: reads configs, spawns CLI, streams output
|
||||
├── markdown-config-loader.ts # Reads CLAUDE.md, agents.md, memory.md
|
||||
├── system-prompt-assembler.ts # Assembles system prompt with sections
|
||||
├── skills-loader.ts # Loads skills from config/skills/*/SKILL.md
|
||||
├── session-manager.ts # Channel → session ID (persisted, idle cleanup)
|
||||
├── message-history.ts # Per-channel message storage
|
||||
├── conversation-archiver.ts # Markdown conversation logs
|
||||
├── ipc-watcher.ts # Polls ipc/outbound/ for proactive messages
|
||||
├── response-formatter.ts # Splits long text for Discord's 2000 char limit
|
||||
├── error-formatter.ts # Sanitizes errors (strips keys, paths, stacks)
|
||||
├── heartbeat-scheduler.ts # Recurring timer events
|
||||
├── cron-scheduler.ts # node-cron scheduled events
|
||||
├── hook-manager.ts # Lifecycle hooks
|
||||
├── bootstrap-manager.ts # First-run file validation/creation
|
||||
├── channel-queue.ts # Per-channel sequential processing
|
||||
└── shutdown-handler.ts # Graceful SIGTERM/SIGINT handling
|
||||
|
||||
config/ # Agent workspace (gitignored)
|
||||
├── CLAUDE.md # Persona
|
||||
├── agents.md # Rules, cron, hooks
|
||||
├── memory.md # Long-term memory (agent-writable)
|
||||
├── heartbeat.md # Heartbeat checks
|
||||
├── sessions.json # Session persistence (auto)
|
||||
├── messages/ # Message history (auto)
|
||||
├── conversations/ # Conversation archives (auto)
|
||||
├── ipc/outbound/ # Proactive message queue (auto)
|
||||
├── skills/ # Skill definitions
|
||||
└── news/ # Example: agent-created content
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
# Run tests
|
||||
npm test
|
||||
|
||||
# Run in dev mode
|
||||
npm run dev
|
||||
|
||||
# Build
|
||||
npm run build
|
||||
|
||||
# Start production
|
||||
npm start
|
||||
npm test # Run tests (85 passing)
|
||||
npm run dev # Dev mode with tsx
|
||||
npm run build # Compile TypeScript
|
||||
npm start # Run compiled JS
|
||||
```
|
||||
|
||||
## Claude Code CLI vs API Key
|
||||
|
||||
This gateway uses the **Claude Code CLI** (`claude -p`) instead of the Anthropic API directly. This means:
|
||||
|
||||
- You use your existing **Claude Code subscription** — no separate API key needed
|
||||
- Just sign in with `claude` in your terminal and you're good to go
|
||||
- The gateway shells out to `claude -p "prompt" --output-format json` for each query
|
||||
- Set `CLAUDE_CLI_PATH` if `claude` isn't in your PATH
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
Reference in New Issue
Block a user