--- description: Check acpx CLI installation, validate debate-acpx.json config, probe each configured agent, and print permission allowlist for unattended operation. allowed-tools: Bash(bash ~/.claude/debate-scripts/acpx-env-snapshot.sh:*), Bash(which npx:*), Bash(acpx:*), Bash(npx acpx@latest:*), Bash(agy:*), Bash(agy models:*), Bash(which agy:*), Bash(bash ~/.claude/debate-scripts/debate-setup.sh:*), Bash(bash ~/.claude/debate-scripts/create-litellm-agent.sh:*), Bash(ls:*), Bash(chmod:*), Bash(mkdir:*), Bash(jq:*), Bash(cp:*), Write(~/.claude/debate-acpx.json), Write(~/.acpx/*), Write(~/.acpx/**), Read(~/.acpx/**), Edit(~/.acpx/**), Write(~/.opencode.json), Read(~/.claude/settings.json), Edit(~/.claude/settings.json), Write(~/.claude/settings.json) --- # debate — acpx Setup Check Verify acpx prerequisites and print everything needed for `/debate:all`. **Environment snapshot:** !`bash ~/.claude/debate-scripts/acpx-env-snapshot.sh` --- ## Step 1: Check tools and set ACPX_CMD Determine the acpx invocation command from the snapshot above: - If `acpx` is found: set `ACPX_CMD=acpx` - If `acpx` is not found but `npx` is: set `ACPX_CMD="npx acpx@latest"` - If neither: stop with error Report: ```text ## debate — acpx Setup Check ### Tools ✅ acpx found at /path/to/acpx (0.13.0) (using: acpx) ✅ jq found at /path/to/jq ✅ opencode found at /path/to/opencode (enables OpenRouter models) ``` If `acpx` is found but older than 0.13.0 (a known-good baseline; codex effort runs direct via the codex CLI, independent of acpx version): ```text ⚠️ acpx version 0.12.1 is older than 0.13.0. Effort auto-scaling for codex seats is unaffected (it runs the codex CLI directly), but newer acpx has fixes. Upgrade: npm install -g acpx@latest ``` If `acpx` is not found but `npx` is: ```text ⚠️ acpx not installed globally — using: npx acpx@latest (slower first run) Install globally: npm install -g acpx@latest ``` If neither `acpx` nor `npx`: ```text ❌ acpx not found. Install: npm install -g acpx@latest ``` Note whether `opencode` is available — it's needed for OpenRouter model access (Step 2c) and LiteLLM proxy access (Step 2d). Both `acpx` (or `npx`) and `jq` are required. Use `ACPX_CMD` for all subsequent acpx invocations in this command. ## Step 2: Check config file ### If config exists (loaded above) Show the parsed config: ```text ### Config: ~/.claude/debate-acpx.json Reviewers: codex → agent: codex (built-in, 120s timeout) antigravity → agent: antigravity (direct CLI, 240s timeout) mercury → agent: mercury (custom/opencode, 120s timeout) ``` Proceed to Step 3. ### If config is missing — Interactive Setup Guide the user through creating a config. **2a. Ask what agents the user wants:** Present three categories: ```text ### Reviewer options Built-in acpx agents (need the agent CLI installed): codex — OpenAI Codex (npm install -g @openai/codex) cursor — Cursor CLI (install Cursor IDE) copilot — GitHub Copilot CLI (gh extension install github/gh-copilot) kimi — Kimi CLI kiro — Kiro CLI qwen — Qwen Code opencode — OpenCode (npm install -g opencode-ai) Direct-CLI agents (no acpx ACP support — invoke-acpx.sh calls them directly): antigravity — Google Gemini via Antigravity CLI (install https://antigravity.google, run `agy` once to sign in) — runs `agy -p` under a Python PTY claude — Claude Code (already installed) ⚠️ requires CLAUDECODE to be unset — invoke-acpx.sh handles this automatically opus — Claude Opus 4.6 (already installed) direct CLI invocation, bypasses acpx entirely — runs `claude --print --model claude-opus-4-6` via stdin OpenRouter models via opencode (need opencode + OpenRouter API key): Any model on OpenRouter — DeepSeek, Mercury, Kimi, Mixtral, GPT, etc. These run through: acpx → opencode → OpenRouter → model LiteLLM proxy via opencode (need opencode + a running LiteLLM proxy): Any model accessible via your LiteLLM proxy — local models (Ollama, LM Studio), self-hosted, or any provider LiteLLM supports. These run through: acpx → opencode → LiteLLM proxy → model ``` "Pick 2-4 reviewers. For independent perspectives inside Claude, skip the `claude` agent. If you want models from OpenRouter or via LiteLLM, I'll set those up via opencode." **2b. For each selected reviewer, determine the type:** - If the user picks a built-in acpx agent name → type: `built-in` - If the user picks an OpenRouter model (or a model name that isn't a built-in agent) → type: `openrouter` - If the user picks a LiteLLM-routed model → type: `litellm` **2c. For OpenRouter reviewers — set up opencode wrappers:** This requires `opencode` to be installed. If not found: ```text ❌ opencode not installed. OpenRouter models require it. Install: npm install -g opencode-ai Then re-run /debate:acpx-setup ``` For each OpenRouter reviewer, ask for: 1. A short name (e.g., `mercury`, `deepseek`) 2. The OpenRouter model ID (e.g., `inception/mercury-2`, `deepseek/deepseek-r1`) Then ask for the user's OpenRouter API key (or check if `OPENROUTER_API_KEY` env var is set). For each OpenRouter reviewer, create a wrapper script and per-agent opencode config: ```bash mkdir -p ~/.acpx/agents/ ``` Write `~/.acpx/agents//.opencode.json`: ```json { "provider": { "openrouter": { "apiKey": "" } }, "agents": { "coder": { "model": "openrouter/" } } } ``` Write `~/.acpx/agents//start.sh`: ```bash #!/bin/bash export OPENCODE_CONFIG_CONTENT='{"model":"openrouter/"}' exec opencode acp "$@" ``` ```bash chmod +x ~/.acpx/agents//start.sh chmod 600 ~/.acpx/agents//.opencode.json ``` Register the custom agent in `~/.acpx/config.json`. Read the existing config first, merge the new agent, and write back: ```json { "agents": { "": { "command": "/Users//.acpx/agents//start.sh" } } } ``` Use absolute paths in the command field — acpx exec's the command directly. **2d. For LiteLLM reviewers — set up opencode wrappers:** This requires `opencode` to be installed (same check as Step 2c above). For each LiteLLM reviewer, ask for: 1. A short name (e.g., `deepseek`, `local-llama`, `mixtral`) 2. The LiteLLM proxy base URL (default: `http://localhost:8200/v1`) 3. A model alias — an OpenAI model name that opencode recognizes (default: `gpt-4o-mini`). LiteLLM must be configured to route this alias to your actual model. 4. An API key (optional — leave blank or say "none" if your proxy doesn't require one) Explain the alias requirement: ```text LiteLLM works by aliasing model names. opencode needs to look up the model in its built-in list, so the alias must be a known OpenAI model name (gpt-4o-mini is a safe default). Configure LiteLLM to route that alias to your actual model: # Example LiteLLM config.yaml model_list: - model_name: gpt-4o-mini ← alias opencode will use litellm_params: model: ollama/deepseek-r1 ← your actual model api_base: http://localhost:11434 ``` Run the helper script to create the wrapper: ```bash bash ~/.claude/debate-scripts/create-litellm-agent.sh "" "" "" "" ``` This creates `~/.acpx/agents//start.sh` and registers the agent in `~/.acpx/config.json`. **2e. Write the debate config:** Write `~/.claude/debate-acpx.json` with all selected reviewers. For OpenRouter reviewers, include a `model_id` field so the summary and future setup checks can display the underlying model: ```json { "claude_reviewers": { "skeptic": ["fable", "opus"], "simplifier": "opus", "operator": "sonnet", "pentester": "auto", "~/personas/data-modeler.md": "opus" }, "reviewers": { "codex": { "agent": "codex", "timeout": 120, "system_prompt": "..." }, "mercury": { "agent": "mercury", "timeout": 120, "model_id": "inception/mercury-2", "system_prompt": "..." } } } ``` **Claude persona reviewers (`claude_reviewers`) — CALL THIS OUT to the user.** An optional top-level object that is the entire Claude side of the panel (skeptic + personas), alongside the acpx CLI reviewers. It maps **persona key → model spec**, and it is the main way to tailor the Claude side, so explain it during setup rather than writing it silently. - **Model spec** — a model or an **array** of models. Each model is `false` (off) · `"opus"` · `"sonnet"` · `"fable"` · `"auto"`. `"auto"` spawns the persona **only when the plan is in its domain** (discretion), using its default model; an array spawns one teammate per model. `"fable"` falls back to `"opus"` if fable is deactivated. - **Skeptic** — `skeptic` is a built-in key, model-tuned: `"fable"` → Fable Skeptic, `"opus"` → Opus Skeptic, `"sonnet"` → Solo Skeptic. Ask the user (AskUserQuestion) whether they want the **tuned pair** `["fable","opus"]` (Fable finds more high-impact behavioral issues but costs ~2x Opus) or a solo `"opus"`. This replaces the old `fable_reviewer` flag. - **Other built-in keys** — `simplifier` (accidental complexity / YAGNI, default opus), `operator` (reliability / failure modes / 3 AM, default sonnet), `pentester` (security / attack surface, default opus). Bodies live in `reviewer-prompts.md`. - **Custom personas** — any key that isn't a built-in name is treated as a **path to your own persona file**; its contents become the reviewer body verbatim. Tell the user they can drop a `You are The …` file anywhere and point a key at it (e.g. `"~/personas/data-modeler.md": "opus"`). `"auto"` works for custom personas too — the orchestrator reads the body and judges relevance. - **Pentester guard** — `pentester` **never** runs on `sonnet` (weak at adversarial security reasoning → coerced to `opus` with a warning); valid values `"opus"`, `"fable"`, `"auto"`, `false`. Recommend `"auto"` as the default so security-relevant plans always get a security lens without noise on routine changes. (The guard applies only to the built-in `pentester`; custom personas may use `sonnet`.) If the file already has the `claude_reviewers` key, keep it and skip the question. Likewise, if it already has a `presets` key, **preserve it verbatim** when rewriting — presets are user-authored named panels (see the README Config reference) and setup must never drop them. Built-in agents do not need `model_id`. OpenRouter agents (created via Step 2c) must have it set to the OpenRouter model ID (e.g., `inception/mercury-2`). LiteLLM agents (created via Step 2d) should set it to a descriptive string like `"deepseek-r1 via LiteLLM"` so the summary can display the underlying model. Set timeout to 240-300 for larger/slower agents, 120 for faster ones. `mode` is optional and defaults to `session`. Set `"mode": "exec"` when an agent answers the first prompt into a session and then returns an empty turn for every later one — the round records a blank review with exit 0 rather than an error. Seen with opencode-backed agents such as `kimi-k3`. The Step 3 probe below only ever sends one prompt, so it cannot detect this; the symptom is a reviewer that goes blank on round 2 of a real debate. `retries` is also optional and defaults to 1. It covers a different failure: an agent that ends a turn with no final message at all, session or not. Raise it to 2-3 for an agent you know to be flaky (`kimi-k3` through opencode qualifies), or set 0 to turn retrying off. Errors and timeouts are never retried. For system prompts, suggest unique review personas for each reviewer. Examples: - **The Executor** — shell correctness, exit codes, race conditions, file I/O - **The Architect** — structural integrity, over-engineering, missing phases, graceful degradation - **The Skeptic** — unstated assumptions, unhappy paths, second-order failures, security - **The Contrarian** — questions conventional wisdom, hidden assumptions, scaling bottlenecks - **The Pragmatist** — what will actually ship, unnecessary complexity, missing happy path steps --- ## Step 3: Probe each agent First scan the acpx registry — a registered-but-unspawnable agent can exist in `~/.acpx/config.json` without being a panel reviewer (a stale entry left behind when a seat was retired), and the panel probe below would never see it. Enumerate every registered agent with its command path, and flag any whose command is missing or not executable: ```bash jq -r '.agents | to_entries[] | "\(.key)\t\(.value.command)"' ~/.acpx/config.json ``` Then run the existence check over every registered agent (not just panel reviewers): ```bash for a in $(jq -r '.agents | keys[]' ~/.acpx/config.json); do CMD="$(jq -r --arg a "$a" '.agents[$a].command' ~/.acpx/config.json)" if [ -z "$CMD" ] || [ ! -x "$CMD" ]; then echo "❌ $a: registered but command '$CMD' is missing/not executable" fi done ``` Report a missing/not-executable wrapper as `❌ : registered but unspawnable (command missing)` and recommend removing the entry from `~/.acpx/config.json` — this covers registered agents that are NOT panel reviewers, so a retired seat's stale entry is caught rather than silently passing. Then, for each configured reviewer: - **Session-mode acpx agents** (anything that is not `antigravity` or `opus`, and whose reviewer does not set `mode: "exec"`): verify the wrapper command exists, then run a quick test via acpx. `sessions ensure` alone is not a health check — it exits 0 and reuses a stale session id even when the agent's `command` points at a missing file, so a registered-but-unspawnable agent passes the ensure step. The prompt is the real check: it surfaces `Failed to spawn agent command: ` on a broken wrapper whether or not a stale session exists. Keep `ensure` for its idempotency (a probe must not accumulate sessions on every setup run): ```bash # 1. The command file must exist — a config entry pointing at a missing wrapper # is the exact "registered but unspawnable" case, and it fails fast here with # a precise message instead of the generic "No acpx session found". CMD="$(jq -r --arg a "" '.agents[$a].command' ~/.acpx/config.json)" if [ -z "$CMD" ] || [ ! -x "$CMD" ]; then echo "❌ : registered but command '$CMD' is missing/not executable" continue fi # 2. Idempotent session ensure — creates on fresh spawn (where it DOES fail on a # broken wrapper), reuses a stale id otherwise. $ACPX_CMD sessions ensure 2>&1 # 3. The prompt is the health check. Classify the output by eye, not by an # automated grep: under a merged 2>&1 capture the completion and the token # summary interleave and the PONG can come back truncated (observed as a lone # "P" or an empty turn while the token summary still counts output=4), so a # grep -q PONG on a merged stream can mislabel a healthy seat as broken. # A genuinely broken wrapper prints "Failed to spawn agent command: " # and matches no token count. echo "Reply with only the word PONG." | $ACPX_CMD --format quiet --approve-reads ``` - **`mode: "exec"` reviewers:** skip `sessions ensure` — a one-shot never opens a session, and probing for one reports a working agent as broken. Probe with the one-shot form instead: ```bash echo "Reply with only the word PONG." | $ACPX_CMD --format quiet --approve-reads exec ``` Use `mode: "exec"` for opencode-backed seats that answer the first prompt into a session and then go blank on every later one (observed with `kimi-k3` and `deepseek-v4-flash` through opencode: the session-form probe returns `input=0 output=0` while the one-shot form returns PONG every time). A seat wired as session-mode that blanks like this is a misconfiguration, not a broken wrapper — the probe's session branch will report it as unspawnable; fix the mode, not the wrapper. - **antigravity agent:** probe using the Antigravity CLI directly (see below): ```bash agy models 2>&1 | head -1 ``` - **opus agent:** probe using direct Claude CLI: ```bash echo "Reply with only the word PONG." | timeout 30 claude --print --model claude-opus-4-6 ``` Report: - Response contains "PONG" (or `agy models` lists a model) → `✅ : responds` - Wrapper command missing/not executable → `❌ : registered but unspawnable (command missing)` — fix or remove the entry from `~/.acpx/config.json` - `sessions ensure` or the prompt prints `Failed to spawn agent command: ` → `❌ : registered but unspawnable — ` — the wrapper exists but fails to exec (bad interpreter, missing dependency, wrong path) - A blank completion (no PONG) where the token summary reports `input=0 output=0` and there was no spawn error → `❌ : blank turn` — for an opencode-backed seat this usually means it should be `mode: "exec"`, not a broken wrapper - Session creation fails or probe times out → `❌ : failed` ### Antigravity agent — direct CLI invocation The `antigravity` agent uses the Antigravity CLI (`agy`) directly — acpx has no native ACP support for it yet. `invoke-acpx.sh` runs `agy -p "" --sandbox` under a Python PTY (the `agy -p` output drops when stdout is not a TTY) with the prompt passed as a positional argument. Works with OAuth (run `agy` once to sign in) or `ANTIGRAVITY_API_KEY` / `GEMINI_API_KEY`. **Probe command for antigravity** (lists models only when signed in): ```bash agy models 2>&1 | head -1 ``` - Lists a model name → `✅ antigravity: agy responds` - "Please sign in to view available models" → `❌ antigravity: not signed in — run 'agy' once to sign in` - Command not found → `❌ antigravity: agy not installed — install the Antigravity CLI (https://antigravity.google)` - `python3` missing → `❌ antigravity: python3 required (agy is run under a PTY)` **If not signed in:** ```text Fix: sign in to the Antigravity CLI: 1. Run `agy` (with no arguments) in a terminal 2. Follow the browser OAuth flow, or paste the authorization code shown 3. Re-run /debate:acpx-setup to verify. Headless / no browser? Set an API key instead: 4. Add to ~/.claude/settings.json: { "env": { "ANTIGRAVITY_API_KEY": "..." } } 5. Restart Claude Code, then re-run /debate:acpx-setup. ``` Ask the user if they want to set up an API key now (headless only). If yes: 1. Ask them to paste the key 2. Read `~/.claude/settings.json`, add or merge `"env": { "ANTIGRAVITY_API_KEY": "" }`, write back 3. Inform them to restart Claude Code for the env var to take effect ### Other agent failure modes For OpenRouter agents (custom opencode-based agents): a common failure is wrong model ID. Suggest verifying the model ID at openrouter.ai/models. For custom agents with no acpx session: try `$ACPX_CMD sessions ensure` first. ## Step 4: Check debate-scripts symlink From the snapshot: `debate-scripts: symlinked -> ` → ✅ ready; `not found` → ❌ run `/debate:setup`. The `SessionStart` hook refreshes this link at the start of every session, so `not found` here means the hook could not create it — usually a sandbox that blocks `ln`. Check that the version in `` matches the plugin version reported above it; a mismatch means the hook did not run. `not a symlink (real path at …)` → ❌ something created a real directory there. Nothing can refresh it, so move it aside (`mv ~/.claude/debate-scripts{,.bak}`) and run `/debate:setup`. ## Step 5: Patch permission allowlist in ~/.claude/settings.json **DO NOT just print the snippet.** Actively patch `~/.claude/settings.json` following the same procedure as `/debate:setup` Step 7 — read, diff, back up, Edit-in-place the missing entries, re-validate with `jq empty`. See Step 7 of `/debate:setup` for the full procedure (backup file, JSON-parse guard, restore-on-failure path). Required entries for unattended `/debate:all`: ``` Bash(bash ~/.claude/debate-scripts/debate-setup.sh:*) Bash(bash ~/.claude/debate-scripts/run-parallel-acpx.sh:*) Bash(bash ~/.claude/debate-scripts/invoke-acpx.sh:*) Bash(rm -rf .tmp/ai-review-:*) Read(.tmp/ai-review*) Edit(.tmp/ai-review*) Write(.tmp/ai-review*) Write(~/.acpx/**) Read(~/.acpx/**) Edit(~/.acpx/**) ``` Plus one **repo-scoped read grant** so review subagents can read this repo's source at its absolute path without prompting. Compute it for the current repo and add the exact entry to the required set: ```bash echo "Read($(git rev-parse --show-toplevel 2>/dev/null || pwd)/**)" ``` Scoped to the repo under review rather than a blanket `Read(**)` — the secret-path deny rules already protect `.env`/`~/.ssh`/`~/.aws`/`secrets/`, so this only grants read of repo source and won't auto-approve reads in unrelated projects. The run commands preflight-check this and warn if it's missing. Report what was added vs already present. If `~/.claude/settings.json` is malformed JSON, stop and surface the parse error — do not rewrite a file you can't parse. **Why this is active rather than instructional:** acpx writes per-job queue locks to `~/.acpx/queues/.lock` on every invocation. Without `Write(~/.acpx/**)` in the settings allowlist, the Claude Code sandbox blocks those writes and reviewer subprocesses exit 144. Because they're spawned via `nohup`, the sandbox-blocked-write error never surfaces as a prompt; `/debate:all` just reports "all reviewers failed" with no obvious cause. Printing the snippet and trusting the user to copy it is how this regression keeps recurring. ## Step 6: Print summary For each reviewer from the config loaded above — built-in agents show `built-in`; OpenRouter agents show `openrouter — openrouter/`; LiteLLM agents show `litellm — `. ```text ### Summary acpx: ✅ ready opencode: ✅ ready (enables OpenRouter and LiteLLM models) Config: ✅ valid (N reviewers) jq: ✅ ready Scripts: ✅ symlinked Reviewers: codex ✅ built-in (120s timeout) antigravity ✅ direct CLI (240s timeout) mercury ✅ openrouter (120s timeout) — openrouter/inception/mercury-2 deepseek ✅ litellm (120s timeout) — deepseek-r1 via LiteLLM You are ready to run: /debate:run — parallel review via acpx (alias: /debate:all) /debate:run codex,mercury — specific reviewers only /debate:run — a named panel from the `presets` object (if configured) ``` If anything is missing, list remaining actions.