# 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` 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 (2+ round trips without progress)
          → Arbiter summoned → binding verdict → loop resumes
```

## Verification Isolation

Reviewer and arbiter now run as host processes with role-scoped read-only
guards and sandbox settings. Docker remains in use for verification profiles:

- Scratch workspace copied before execution
- Verification command run inside the container image
- Isolated filesystem view for test/typecheck/build checks
- Runtime image shared with host tooling and evidence inspection

## 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` | Verification execution using the container image |
| `src/container-runtime.ts` | Shared Docker runtime helpers for verification |
| `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) |
