Initial commit: Discord-Claude Gateway with event-driven agent runtime

This commit is contained in:
2026-02-22 13:59:57 -05:00
parent b4f340b610
commit f2247ea3ac
28 changed files with 2056 additions and 205 deletions

283
README.md
View File

@@ -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