5.2 KiB
5.2 KiB
Architecture
Service Stack
Single unified service (ejclaw) 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) - 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=5000for concurrent access
Data Flow
Discord ──► SQLite (WAL) ──► GroupQueue ──┬──► Owner (host process)
│ │
│ ▼ (auto-trigger)
├──► Reviewer (host process, read-only guarded)
│ │
│ 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_settingsis the room-level source of truth (SSOT)- Each room stores:
room_mode:singleortribunalowner_agent_type:codexorclaude-code
- Public room assignment uses
assign_room registered_groupsremains as a materialized capability/read-model layer
Operationally:
single→ one owner bottribunal→ 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 (2+ round trips without progress)
→ Arbiter summoned → binding verdict → loop resumes
Verification Execution
Reviewer and arbiter now run as host processes with role-scoped read-only guards and sandbox settings. Verification profiles run directly on the host with restricted environment variables and snapshot checks:
- Fixed-profile direct execution (
test,typecheck,build) - Snapshot comparison before/after execution
- Root-level runtime/build directories excluded from verification snapshot
- Role-scoped reviewer safeguards remain independent of verification execution
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/verification.ts |
Fixed-profile verification execution with snapshot checks |
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) |