Eyejoker 14d70f345a refactor: extract resolveActiveRole() as single source of status→role mapping
resolveCursorKey and resolveChannel both duplicated the same
taskStatus→role switch. Now resolveActiveRole() is the single
mapping, and both functions derive from it.
2026-03-30 22:58:00 +09:00
2026-03-29 07:33:13 +09:00
2026-02-03 17:14:17 +02:00
2026-03-19 03:31:41 +09:00
2026-03-25 03:57:52 +09:00

EJClaw

Claude Agent SDK Codex SDK Node Discord

Dual-agent AI assistant (Claude Code + Codex) over Discord with autonomous paired review.

Originally derived from qwibitai/nanoclaw, now maintained as EJClaw for personal production use. Prompt design inspired by Q00/ouroboros, adapted for EJClaw's Discord and dual-service workflow.

Overview

A single unified service (ejclaw) manages three Discord bots in one process:

  • Codex-main (@codex) — Owner agent. Handles user requests, writes code.
  • Claude (@claude) — Reviewer agent. Critically reviews owner's work, verifies design direction.
  • Codex-review (@codex-review) — Fallback reviewer. Takes over when Claude hits rate limits (429).

Owner and reviewer agent types are configurable via OWNER_AGENT_TYPE and REVIEWER_AGENT_TYPE in .env.

Paired Review

The core workflow is an autonomous owner-reviewer ping-pong:

User message
  → Owner responds (implementation, answer, etc.)
    → Reviewer auto-triggered (critical review, design check)
      → Verdict:
          DONE              → Owner gets final turn to finalize → Task completed
          DONE_WITH_CONCERNS → Owner addresses feedback → Reviewer re-reviews
          BLOCKED            → Escalate to user (needs decision)
          NEEDS_CONTEXT      → Escalate to user (missing information)

The system stops autonomously when the reviewer approves or escalates. No manual intervention needed for the happy path.

Features

  • Paired review — Autonomous owner/reviewer ping-pong with verdict-based control
  • Container-isolated reviewer — Reviewer runs in persistent Docker container with read-only source mount (kernel-level write protection)
  • Post-approval change detection — Re-triggers review if owner modifies code after reviewer DONE (git tree hash comparison)
  • Configurable agent types — Owner and reviewer roles independently set to claude-code or codex
  • Reviewer fallback — Claude 429/exhaustion → automatic handoff to codex-review
  • Credential proxy — API keys never exposed to containers, injected via HTTP proxy
  • Voice transcription — Groq Whisper (primary) / OpenAI Whisper (fallback), shared file cache with dedup
  • Bidirectional images — Receive Discord attachments as multimodal input, send screenshots back
  • Token rotation — Claude 429 / usage exhaustion → automatic multi-account rotation
  • MCP integration — Memento (persistent memory) + EJClaw host tools (send_message, schedule_task, watch_ci, etc.)
  • Browser automationgstack browse skill, headless Chromium daemon
  • Priority queue — Per-group serialization, global concurrency limit
  • Session persistence — Resume conversations across restarts (separate sessions per role)
  • Scheduled tasks — Cron/interval/once via MCP tool
  • Mid-turn steering — Inject follow-up messages while agent is working

Architecture

Discord ──► SQLite (WAL) ──► GroupQueue ──┬──► Owner (host process)
                                          │       │
                                          │       ▼ (auto-trigger)
                                          └──► Reviewer (Docker container, :ro mount)
                                          │       │
                                          │   Verdict routing
                                          │       ├─ DONE → change detection → Owner finalizes or re-review
                                          │       ├─ BLOCKED/NEEDS_CONTEXT → User
                                          │       └─ Feedback → Owner (loop)
                                          │
                                     IPC polling ◄── follow-up messages (mid-turn steering)
                                          │
                                     Router ──► Discord (text, images, files)

Owner (host process):
  ├── Stable per-channel worktree (session persists across tasks)
  ├── MCP tools, Bash skills, per-group memory
  └── Full read-write access

Reviewer (persistent Docker container):
  ├── Owner workspace mounted read-only (kernel-level protection)
  ├── Credential proxy (API keys never in container)
  ├── pnpm store mounted read-only (if applicable)
  └── tmpfs for test caches (vitest, jest, npm)

Setup

Prerequisites

  • Linux (Ubuntu 22.04+) or macOS
  • Node.js 20+ (24 recommended, fnm for version management)
  • Docker (required for reviewer container isolation)
  • Claude Code CLI
  • Codex CLI (npm install -g @openai/codex)
  • Discord bot tokens (3: Claude, Codex-main, Codex-review)

Install

git clone https://github.com/phj1081/EJClaw.git
cd EJClaw
npm install
npm run build:runners
npm run build

Environment

All configuration in a single .env file:

# Discord bots (3 tokens for 3 bots)
DISCORD_BOT_TOKEN=               # Claude bot
DISCORD_CODEX_BOT_TOKEN=         # Codex-main bot (owner)
DISCORD_REVIEW_BOT_TOKEN=        # Codex-review bot (fallback reviewer)

# Agent type configuration
OWNER_AGENT_TYPE=codex            # codex | claude-code
REVIEWER_AGENT_TYPE=claude-code   # claude-code | codex

# API keys
CLAUDE_CODE_OAUTH_TOKEN=          # Claude Code OAuth token
CLAUDE_CODE_OAUTH_TOKENS=         # Comma-separated tokens for multi-account rotation
OPENAI_API_KEY=                   # For Codex
GROQ_API_KEY=                     # Voice transcription (Groq Whisper)

# Bot names
ASSISTANT_NAME=claude             # Claude bot trigger name
CODEX_ASSISTANT_NAME=codex        # Codex bot trigger name

Authentication

Multi-account OAuth token rotation is supported via CLAUDE_CODE_OAUTH_TOKENS (comma-separated). When one account hits a rate limit, the system automatically rotates to the next.

Token auto-refresh runs on the Claude service only, refreshing access tokens 30 minutes before expiry using rotating refresh tokens from ~/.claude/.credentials.json (account 0) and ~/.claude-accounts/{n}/.credentials.json (account 1+). Generate tokens with claude setup-token (account 0) or CLAUDE_CONFIG_DIR=~/.claude-accounts/{n} claude setup-token (account 1+).

Systemd Service (Linux)

Single unified service:

systemctl --user restart ejclaw    # Restart
systemctl --user status ejclaw     # Check status
systemctl --user enable ejclaw     # Enable on boot
journalctl --user -u ejclaw -f     # Follow logs

Deploy

Build on server, not locally:

ssh clone-ej@100.64.185.108 'cd ~/EJClaw && git pull && npm run build && npm run build:runners && systemctl --user restart ejclaw'

Development

npm run build                # Build main project
npm run build:runners        # Install + build both runners
npm run dev                  # Dev mode with hot reload
npm test                     # Run tests

License

MIT — Originally derived from qwibitai/nanoclaw

Description
EJClaw — GitHub main base + local owner modifications (claude-bot)
Readme MIT 16 MiB
Languages
TypeScript 96.8%
CSS 2.5%
Kotlin 0.4%
JavaScript 0.1%
Shell 0.1%