diff --git a/README.md b/README.md index de13fa6..1302d9f 100644 --- a/README.md +++ b/README.md @@ -14,13 +14,15 @@ Tribunal arbiter system inspired by multi-agent consensus architectures. ## Overview -A single unified service (`ejclaw`) manages three Discord bots in one process: +A single unified service (`ejclaw`) runs a **Tribunal** of three roles: -- **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`) — Arbiter agent. Summoned on-demand to break deadlocks between owner and reviewer. +| Role | Purpose | Default | +|------|---------|---------| +| **Owner** | Handles user requests, writes code | Codex (`@codex`) | +| **Reviewer** | Critically reviews owner's work, verifies design direction | Claude Code (`@claude`) | +| **Arbiter** | On-demand deadlock breaker between owner and reviewer | Codex (`@codex-review`) | -All agent types and models are independently configurable per role via `.env`. +Each role's agent type and model are independently configurable via `.env` (`OWNER_AGENT_TYPE`, `REVIEWER_AGENT_TYPE`, `ARBITER_AGENT_TYPE`, `*_MODEL`). Three Discord bots provide the identity layer — which bot speaks is determined by the active role, not hardcoded. ## Room Assignment Model @@ -60,22 +62,22 @@ User message ### Mixture of Agents (MoA) -When enabled, the arbiter collects opinions from external models (Kimi, GLM, etc.) before rendering its verdict: +When enabled, the arbiter collects opinions from configurable external reference models before rendering its verdict: ``` -Deadlock detected → MoA reference queries (Kimi + GLM, parallel) +Deadlock detected → MoA reference queries (parallel, configurable via MOA_REF_MODELS) → Opinions injected into arbiter's prompt - → SDK arbiter (subscription-based) aggregates all perspectives + → Arbiter aggregates all perspectives → Final verdict: PROCEED / REVISE / RESET / ESCALATE ``` -No extra SDK processes. External references use lightweight API calls (Anthropic-compatible). +No extra SDK processes. External references use lightweight OpenAI/Anthropic-compatible API calls. ## Features - **Tribunal 3-agent system** — Owner/reviewer/arbiter with on-demand deadlock resolution - **Discord-independent communication** — Agent-to-agent data flows directly via DB, Discord is display-only -- **Mixture of Agents** — External model opinions (Kimi, GLM) enrich arbiter verdicts +- **Mixture of Agents** — Configurable external reference models enrich arbiter verdicts - **Per-role model selection** — `OWNER_MODEL`, `REVIEWER_MODEL`, `ARBITER_MODEL` + effort + fallback toggle - **Container-isolated reviewer** — Persistent Docker container with read-only source mount - **Global failover** — Account-level Claude failure → all channels switch to codex, auto-recovers @@ -107,8 +109,8 @@ Discord ──► SQLite (WAL) ──► GroupQueue ──┬──► Owner (ho ├──► Arbiter (on-demand, fresh session each time) │ │ │ ┌───┴─── MoA (if enabled) ───┐ - │ │ Kimi API ──► opinion │ - │ │ GLM API ──► opinion │ + │ │ Ref model A ──► opinion │ + │ │ Ref model B ──► opinion │ │ │ → injected into prompt │ │ └────────────────────────────┘ │ │ diff --git a/container/Dockerfile b/container/Dockerfile index 6ef6f21..e54ebea 100644 --- a/container/Dockerfile +++ b/container/Dockerfile @@ -52,18 +52,24 @@ RUN curl -fsSL https://bun.sh/install | bash # Create app directory WORKDIR /app -# Copy host's agent-runner (same code, same SDK version) -COPY runners/agent-runner/package*.json ./ -RUN bun install -COPY runners/agent-runner/ ./ -RUN bun run build +# Copy agent-runner (Claude Code) +COPY runners/agent-runner/package*.json ./agent/ +RUN bun install --cwd ./agent +COPY runners/agent-runner/ ./agent/ +RUN bun run --cwd ./agent build + +# Copy codex-runner +COPY runners/codex-runner/package*.json ./codex/ +RUN bun install --cwd ./codex +COPY runners/codex-runner/ ./codex/ +RUN bun run --cwd ./codex build # Create workspace directories RUN mkdir -p /workspace/project /workspace/group /workspace/global \ /workspace/ipc/messages /workspace/ipc/input -# Create entrypoint script -RUN printf '#!/bin/bash\nset -e\ncat > /tmp/input.json\nbun /app/dist/index.js < /tmp/input.json\n' \ +# Entrypoint (fallback — persistent containers use docker exec with explicit runner path) +RUN printf '#!/bin/bash\nset -e\ncat > /tmp/input.json\nbun /app/agent/dist/index.js < /tmp/input.json\n' \ > /app/entrypoint.sh && chmod +x /app/entrypoint.sh # Set ownership to node user (non-root) diff --git a/prompts/arbiter-paired-room.md b/prompts/arbiter-paired-room.md index d0803e0..48af0bd 100644 --- a/prompts/arbiter-paired-room.md +++ b/prompts/arbiter-paired-room.md @@ -22,6 +22,15 @@ You have been summoned because the owner and reviewer reached a deadlock after m - The situation cannot be resolved without user input, regardless of technical agreement - The same NEEDS_CONTEXT or BLOCKED is repeated after a prior PROCEED — this means your PROCEED did not resolve the issue +## MoA (Mixture of Agents) Reference Opinions + +You may receive reference opinions from external models appended to your prompt. When present: + +- **Cite them explicitly** in your verdict — e.g., "Reference model A agrees that...", "Reference model B raises a concern about..." +- **Cross-reference** their opinions against the owner/reviewer conversation and code evidence +- **Resolve conflicts** — if reference opinions disagree with each other or with owner/reviewer, state which view you adopt and why +- Do NOT blindly follow reference opinions — they are inputs to your judgment, not authorities + ## Rules - Base your verdict on evidence (code, test output, logs), not on who said what first diff --git a/src/container-runner.ts b/src/container-runner.ts index de25084..500c297 100644 --- a/src/container-runner.ts +++ b/src/container-runner.ts @@ -484,7 +484,11 @@ export async function runReviewerContainer(args: { } } } - execArgs.push(containerName, 'bun', '/app/dist/index.js'); + const isCodexAgent = (group.agentType || 'claude-code') === 'codex'; + const runnerPath = isCodexAgent + ? '/app/codex/dist/index.js' + : '/app/agent/dist/index.js'; + execArgs.push(containerName, 'bun', runnerPath); return new Promise((resolve) => { const proc = spawn(CONTAINER_RUNTIME_BIN, execArgs, {