4.5 KiB
EJClaw
Dual-agent AI assistant (Claude Code + Codex) over Discord. Originally derived from qwibitai/nanoclaw.
Quick Context
Single unified service (ejclaw) manages three Discord bots (owner, reviewer, arbiter) in one process. Agent types behind each role remain configurable via OWNER_AGENT_TYPE, REVIEWER_AGENT_TYPE, and ARBITER_AGENT_TYPE in .env. Claude Code uses the Agent SDK; Codex uses the Codex SDK (codex exec). Auth via CLAUDE_CODE_OAUTH_TOKEN in .env (1-year token from claude setup-token).
Key Files
| File | Purpose |
|---|---|
src/index.ts |
Orchestrator: state, message loop, agent invocation |
src/agent-runner.ts |
Spawns agent processes, manages env/sessions/skills |
src/channels/discord.ts |
Discord channel (8s typing refresh, Groq/OpenAI Whisper transcription) |
src/ipc.ts |
IPC watcher and task processing |
src/router.ts |
Message formatting and outbound routing |
src/config.ts |
Trigger pattern, paths, intervals |
src/task-scheduler.ts |
Runs scheduled tasks |
src/db.ts |
SQLite operations |
runners/agent-runner/ |
Claude Code runner (Agent SDK) |
runners/codex-runner/ |
Codex runner (SDK, codex exec wrapper) |
groups/{name}/CLAUDE.md |
Per-group memory (isolated) |
Skills
| Skill | When to Use |
|---|---|
/setup |
First-time installation, authentication, service configuration |
/customize |
Adding channels, integrations, changing behavior |
/debug |
Agent issues, logs, troubleshooting |
Development
Run commands directly—don't tell the user to run them.
bun run build # Build main project
bun run build:runners # Install + build both runners
bun run build:runtime # Build host runtime only
bun run dev # Dev mode with hot reload
Service management (Linux):
systemctl --user restart ejclaw # Restart unified service
systemctl --user status ejclaw # Check status
journalctl --user -u ejclaw -f # Follow logs
Deploy:
bun run deploy
deploy rebuilds only the host runtime, and verification also runs directly on
the host runtime.
Service Stack Architecture
Single unified service manages all three Discord bots in one process:
ejclaw.service— Single unified process- Discord bots:
DISCORD_OWNER_BOT_TOKEN(owner),DISCORD_REVIEWER_BOT_TOKEN(reviewer),DISCORD_ARBITER_BOT_TOKEN(arbiter) - Old service-based token names are no longer accepted; migrate them to the canonical role-based keys above before starting the service
- Paired review: owner (
OWNER_AGENT_TYPE, default: codex) ↔ reviewer (REVIEWER_AGENT_TYPE, default: claude-code) - Provider fallback is internal routing only — visible Discord bots stay owner/reviewer/arbiter fixed
- Shared dirs:
store/,groups/,data/ - SQLite WAL mode +
busy_timeout=5000for concurrent access
Debugging Paths
Unified DB + directories (both services share store/, groups/, data/):
| 항목 | 경로 |
|---|---|
| DB | store/messages.db (공유, WAL 모드) |
| 서비스 로그 | journalctl --user -u ejclaw -f 또는 logs/ejclaw.log |
| 그룹별 로그 | groups/{name}/logs/ (공유 채널은 양쪽 봇 로그가 같은 폴더) |
| Claude 세션 | data/sessions/{name}/.claude/ |
| Codex 세션 | data/sessions/{name}/.codex/ |
| Claude 플랫폼 규칙 | prompts/claude-platform.md |
| Codex 플랫폼 규칙 | prompts/codex-platform.md |
| Claude 글로벌 메모리 | groups/global/CLAUDE.md |
Codex SDK
Codex runner uses @openai/codex-sdk (wraps codex exec):
codex.startThread()/codex.resumeThread()for session persistencethread.run(input)for single-shot turn execution (completes all work before returning)approvalPolicy: "never"+sandboxMode: "danger-full-access"for bypass- Per-group: model (
CODEX_MODEL), effort (CODEX_EFFORT), MCP servers viaconfig.toml CODEX_HOMEset to per-group session dir, readsAGENTS.mdfrom there + CWD
Voice Transcription
Audio attachments in Discord are transcribed via Groq Whisper (primary) or OpenAI Whisper (fallback):
GROQ_API_KEY— Groqwhisper-large-v3-turbo, ~200x real-time, free tier (console.groq.com)OPENAI_API_KEY— OpenAIwhisper-1, fallback if Groq key not set- Shared file cache (
cache/transcriptions/) deduplicates across both services .pendingfile coordination prevents duplicate API calls