diff --git a/README.md b/README.md index a9f4df9..31361e6 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,11 @@ cp .env.example .env # Configure tokens and settings bun run deploy # Or: bun run dev ``` +## Documentation + +- [Architecture](docs/architecture.md) — Data flow, room model, container isolation, key files +- [Configuration](docs/configuration.md) — Full `.env` reference, debugging paths + ## Development ```bash diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..a953b08 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,106 @@ +# Architecture + +## Service Stack + +Single unified service (`ejclaw`) manages all three Discord bots in one process: + +- `ejclaw.service` — Single unified process +- Discord bots: `DISCORD_BOT_TOKEN` (Claude), `DISCORD_CODEX_BOT_TOKEN` (Codex-main), `DISCORD_REVIEW_BOT_TOKEN` (Codex-review) +- Paired review: owner ↔ reviewer (agent types configurable per role) +- Reviewer fallback: Claude exhaustion → codex-review auto-handoff +- Shared dirs: `store/`, `groups/`, `data/` +- SQLite WAL mode + `busy_timeout=5000` for concurrent access + +## Data Flow + +``` +Discord ──► SQLite (WAL) ──► GroupQueue ──┬──► Owner (host process) + │ │ + │ ▼ (auto-trigger) + ├──► Reviewer (Docker container, :ro mount) + │ │ + │ Verdict routing + │ ├─ DONE → change detection → finalize or re-review + │ ├─ BLOCKED → Arbiter (if enabled) or User + │ └─ Feedback → Owner (loop) + │ + ├──► Arbiter (on-demand, fresh session each time) + │ │ + │ ┌───┴─── MoA (if enabled) ───┐ + │ │ Ref model A ──► opinion │ + │ │ Ref model B ──► opinion │ + │ │ → injected into prompt │ + │ └────────────────────────────┘ + │ │ + │ PROCEED/REVISE/RESET/ESCALATE + │ + IPC polling ◄── follow-up messages + │ + ┌────────── Router ──────────┐ + ▼ ▼ + paired_turn_outputs Discord (display only) + (agent ↔ agent data) (user observation, @mention) +``` + +## Room Assignment Model + +Per-room routing uses an explicit assignment model: + +- `room_settings` is the room-level source of truth (SSOT) +- Each room stores: + - `room_mode`: `single` or `tribunal` + - `owner_agent_type`: `codex` or `claude-code` +- Public room assignment uses `assign_room` +- `registered_groups` remains as a materialized capability/read-model layer + +Operationally: + +- `single` → one owner bot +- `tribunal` → per-room owner + globally configured reviewer + optional arbiter + +Tribunal is no longer inferred from "two bots registered on one room"; it is an explicit room setting. + +## Tribunal 3-Agent System + +``` +User message + → Owner responds (implementation, answer, etc.) + → Reviewer auto-triggered (critical review, design check) + → Verdict: + DONE → Owner finalizes → Task completed → @user ✅ + DONE_WITH_CONCERNS → Owner addresses feedback → loop + BLOCKED/NEEDS_CONTEXT + ├─ Arbiter enabled → Arbiter judges → PROCEED/REVISE/RESET/ESCALATE + └─ Arbiter disabled → Escalate to user → @user ⚠️ + → Owner BLOCKED/NEEDS_CONTEXT → Arbiter (same path as reviewer) + → Deadlock (3+ round trips without progress) + → Arbiter summoned → binding verdict → loop resumes +``` + +## Container Isolation + +Reviewer and arbiter run inside a Docker container with: + +- Read-only source mount (kernel-level write protection) +- Both Claude (agent-runner) and Codex (codex-runner) supported +- Runner selected at `docker exec` time based on agent type +- tmpfs overlays for test runner caches +- IPC via filesystem for follow-up messages +- Credentials injected per-exec (never baked into container) + +## 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/container-runner.ts` | Docker container execution for reviewer/arbiter | +| `src/channels/discord.ts` | Discord channel (8s typing refresh, 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) | diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 0000000..349e86e --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,83 @@ +# Configuration + +All configuration in a single `.env` file. + +## Discord Bots + +```bash +DISCORD_BOT_TOKEN= # Claude bot +DISCORD_CODEX_BOT_TOKEN= # Codex-main bot (owner) +DISCORD_REVIEW_BOT_TOKEN= # Codex-review bot (arbiter) +``` + +## Agent Types & Models + +```bash +# Agent types per role +OWNER_AGENT_TYPE=codex # codex | claude-code +REVIEWER_AGENT_TYPE=claude-code # claude-code | codex +ARBITER_AGENT_TYPE=codex # codex | claude-code (optional, enables 3rd agent) + +# Per-role model overrides +OWNER_MODEL=gpt-5.4 +REVIEWER_MODEL=claude-opus-4-6 +ARBITER_MODEL=gpt-5.4 + +# Per-role effort level +OWNER_EFFORT=xhigh +REVIEWER_EFFORT=high +ARBITER_EFFORT=xhigh + +# Per-role fallback toggle +REVIEWER_FALLBACK_ENABLED=true # Auto-handoff to codex when Claude exhausted +ARBITER_FALLBACK_ENABLED=false + +# Response language +AGENT_LANGUAGE=Korean # Injected into all agent prompts +``` + +## Authentication + +```bash +CLAUDE_CODE_OAUTH_TOKEN= # Claude Code OAuth token +CLAUDE_CODE_OAUTH_TOKENS= # Comma-separated for multi-account rotation +OPENAI_API_KEY= # Codex API key (if not using OAuth) +``` + +## Voice Transcription + +```bash +GROQ_API_KEY= # Groq whisper-large-v3-turbo (primary) +OPENAI_API_KEY= # OpenAI whisper-1 (fallback) +``` + +## Mixture of Agents (MoA) + +```bash +MOA_ENABLED=true +MOA_REF_MODELS=kimi,glm # Configurable reference model list + +# Per-model config (pattern: MOA_{NAME}_{SETTING}) +MOA_KIMI_MODEL=kimi-k2.5 +MOA_KIMI_BASE_URL=https://api.kimi.com/coding +MOA_KIMI_API_KEY=sk-kimi-xxx +MOA_KIMI_API_FORMAT=anthropic + +MOA_GLM_MODEL=glm-5.1 +MOA_GLM_BASE_URL=https://open.bigmodel.cn/api/anthropic +MOA_GLM_API_KEY=xxx +MOA_GLM_API_FORMAT=anthropic +``` + +## Debugging Paths + +| Item | Path | +|------|------| +| **DB** | `store/messages.db` (shared, WAL mode) | +| Service log | `journalctl --user -u ejclaw -f` or `logs/ejclaw.log` | +| Per-group logs | `groups/{name}/logs/` | +| Claude sessions | `data/sessions/{name}/.claude/` | +| Codex sessions | `data/sessions/{name}/.codex/` | +| Claude platform rules | `prompts/claude-platform.md` | +| Codex platform rules | `prompts/codex-platform.md` | +| Global memory | `groups/global/CLAUDE.md` |