# 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=5000` for 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_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` is a legacy projection being removed from setup/verify/runtime read paths
- Refactor target and migration sequence live in `docs/legacy-compat-removal-spec.md`

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 (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)                                |
