Skip to content

feat(reviewer): D1 — run ordinary reviews on codex when claude is cooled (backend-agnostic fallback) - #486

Merged
ProtocolWarden merged 2 commits into
mainfrom
feat/d1-reviewer-codex-fallback
Aug 13, 2026
Merged

feat(reviewer): D1 — run ordinary reviews on codex when claude is cooled (backend-agnostic fallback)#486
ProtocolWarden merged 2 commits into
mainfrom
feat/d1-reviewer-codex-fallback

Conversation

@ProtocolWarden

Copy link
Copy Markdown
Owner

What & why

Completes audit item D1. The self-review sweep's defer branch already carried the marker that this was "a validated follow-up; until then any non-claude selection means claude — the only review backend — is unavailable". This builds that follow-up: the ORDINARY single-reviewer now runs the controller's full claude→codex ladder instead of parking whenever claude is cooled.

Operator directive verbatim: "d1 should have the same mechanisms the fleet has, why no fallback to codex" → give the reviewer the controller's backend ladder; divert reviews to codex when claude is cooled, PARK only when the whole ladder is exhausted.

The change (single-reviewer path only)

At the sweep's backend-selection gate:

  • claude runnable → review on claude/haiku (unchanged; still via _run_direct_review).
  • claude cooled, codex runnableDIVERT the ordinary review to codex_cli/codex and feed its verdict into the same downstream pipeline (verdict parse → self-heal ladder → LGTM-only green-CI merge). Charges codex's budget, not claude's, via the normal _run_member_review usage path.
  • whole ladder exhausted (no runnable backend) → PARK (defer+return, no burn), preserving the #446 1h auto-resume.

backend→model reuses the validated council seat pairing (verdict._COUNCIL_PANEL, codex_clicodex) via a small _review_model_for_backend helper — no new registry. The claude path keeps routing through _run_direct_review (the name the test suite patches; back-compat default preserved); the codex path branches at the call site to the already-backend-agnostic _run_member_review.

Boundary preserved (hard constraint)

Guardrail PRs are untouched. They fork to the K=3 cross-family council before this gate is reached, so a cooled claude still genuinely PARKS the council (the F14 park-cap case) rather than being single-reviewed on codex. Pinned by a new test.

Backend-agnostic downstream

_dispatch_verdict_outcome (verdict parse → self-heal ladder → LGTM-only merge with the green-CI precondition) was already backend-agnostic — it takes a plain {result, failing_checks, summary} dict shared with the council path. No claude-specific assumption found on the ordinary path. Updated one now-imprecise INJ comment that named _run_direct_review.

Validation basis

The 2026-07-15 codex spike: codex exec wrote a schema-conformant verdict.json, caught a planted bug with the exact evidence span, and did NOT author the overall verdict (INJ boundary held) — same trust level as claude-solo today. Codex already runs live as council seat C3.

Tests

  • test_phase1_cooled_claude_runs_ordinary_review_on_codex — cooled claude → runs on codex_cli/codex, verdict flows to merge (not deferred).
  • test_phase1_ladder_exhausted_parks_no_burn — no runnable backend → parks, no runner invoked, no budget charged.
  • test_phase1_guardrail_pr_cooled_claude_does_not_single_review_on_codex — guardrail PR still forks to council; NOT single-reviewed on codex.
  • tests/unit/reviewer/test_d1_codex_fallback.py (CI-run) — _review_model_for_backend pairing + unknown→None; _run_direct_review no-arg back-compat still runs claude/haiku.

Full: 169 reviewer + 8536 unit passed; ruff clean; custodian pre-push clean.

🤖 Generated with Claude Code

…led (backend-agnostic fallback)

The self-review sweep's defer branch already flagged this as "a validated
follow-up": until now, any non-claude backend selection meant claude — the only
review backend — was unavailable, so the ordinary review PARKED. This gives the
ordinary single-reviewer the controller's full claude→codex ladder instead:

- claude runnable            → review on claude/haiku (unchanged path).
- claude cooled, codex up    → DIVERT the ordinary review to codex_cli/codex,
  feeding its verdict into the SAME downstream pipeline (verdict parse →
  self-heal ladder → LGTM-only green-CI merge). Charges codex's budget.
- whole ladder exhausted     → PARK (defer+return, no burn); #446 auto-resume.

backend→model reuses the validated council seat pairing (verdict._COUNCIL_PANEL,
codex_cli→codex) via a small _review_model_for_backend helper — no new registry.
The claude path keeps routing through _run_direct_review (the name the test
suite patches; back-compat intact); the codex path branches to the already
backend-agnostic _run_member_review. Downstream _dispatch_verdict_outcome was
already backend-agnostic — no claude-specific assumption on the ordinary path.

GUARDRAIL PRs are untouched: they fork to the K=3 cross-family council BEFORE
this gate, so a cooled claude still genuinely PARKS the council (F14) rather than
falling back to a single codex reviewer — pinned by a new test.

Codex was validated as the ordinary reviewer by the 2026-07-15 spike (schema
verdict.json, caught a planted bug with the exact span, did not author the
overall verdict — INJ boundary held) and runs live as council seat C3.

Tests: 3 root integration (codex-runs / ladder-exhausted-parks /
guardrail-not-single-reviewed) + 3 unit under tests/unit/reviewer. 169 reviewer
+ 8536 unit green; ruff clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ProtocolWarden

ProtocolWarden commented Jul 17, 2026

Copy link
Copy Markdown
Owner Author

Resolved: backend cooldown elapsed — automated review resumed

Needs human attention (reason=reviewer_backend_unavailable). Left open — not merged (unresolved) and not closed (work preserved).

reviewer process exited with rc=1 for state_key=OperationsCenter-486-council-codex_cli-codex backend=codex_cli model=codex (stdout_tail='')

5 similar comments
@ProtocolWarden

ProtocolWarden commented Jul 17, 2026

Copy link
Copy Markdown
Owner Author

Resolved: backend cooldown elapsed — automated review resumed

Needs human attention (reason=reviewer_backend_unavailable). Left open — not merged (unresolved) and not closed (work preserved).

reviewer process exited with rc=1 for state_key=OperationsCenter-486-council-codex_cli-codex backend=codex_cli model=codex (stdout_tail='')

@ProtocolWarden

ProtocolWarden commented Jul 17, 2026

Copy link
Copy Markdown
Owner Author

Resolved: backend cooldown elapsed — automated review resumed

Needs human attention (reason=reviewer_backend_unavailable). Left open — not merged (unresolved) and not closed (work preserved).

reviewer process exited with rc=1 for state_key=OperationsCenter-486-council-codex_cli-codex backend=codex_cli model=codex (stdout_tail='')

@ProtocolWarden

ProtocolWarden commented Jul 18, 2026

Copy link
Copy Markdown
Owner Author

Resolved: backend cooldown elapsed — automated review resumed

Needs human attention (reason=reviewer_backend_unavailable). Left open — not merged (unresolved) and not closed (work preserved).

reviewer process exited with rc=1 for state_key=OperationsCenter-486-council-codex_cli-codex backend=codex_cli model=codex (stdout_tail='')

@ProtocolWarden

ProtocolWarden commented Jul 18, 2026

Copy link
Copy Markdown
Owner Author

Resolved: backend cooldown elapsed — automated review resumed

Needs human attention (reason=reviewer_backend_unavailable). Left open — not merged (unresolved) and not closed (work preserved).

reviewer process exited with rc=1 for state_key=OperationsCenter-486-council-codex_cli-codex backend=codex_cli model=codex (stdout_tail='')

@ProtocolWarden

ProtocolWarden commented Jul 18, 2026

Copy link
Copy Markdown
Owner Author

Resolved: backend cooldown elapsed — automated review resumed

Needs human attention (reason=reviewer_backend_unavailable). Left open — not merged (unresolved) and not closed (work preserved).

reviewer process exited with rc=1 for state_key=OperationsCenter-486-council-codex_cli-codex backend=codex_cli model=codex (stdout_tail='')

@ProtocolWarden

Copy link
Copy Markdown
Owner Author

Council review: CONCERNS (cross-family panel, guardrail paths: src/operations_center/entrypoints/pr_review_watcher/main.py)

  • claude_code/sonnet (correctness): LGTM
  • claude_code/opus (security-capability): LGTM
  • codex_cli/codex (convergence-operational): CONCERNS — no_tooling_artifacts

Council concerns (attributed by member):

  • codex_cli/codex (convergence-operational): no_tooling_artifacts

@ProtocolWarden

Copy link
Copy Markdown
Owner Author

Self-review concerns — auto-fixing (up to 6 attempts; re-queued if still unresolved):

Council concerns (attributed by member):

  • codex_cli/codex (convergence-operational): no_tooling_artifacts

@ProtocolWarden

Copy link
Copy Markdown
Owner Author

Council review: CONCERNS (cross-family panel, guardrail paths: src/operations_center/entrypoints/pr_review_watcher/main.py)

  • claude_code/sonnet (correctness): LGTM
  • claude_code/opus (security-capability): LGTM
  • codex_cli/codex (convergence-operational): CONCERNS — no_tooling_artifacts

Council concerns (attributed by member):

  • codex_cli/codex (convergence-operational): no_tooling_artifacts

@ProtocolWarden

Copy link
Copy Markdown
Owner Author

Council review: CONCERNS (cross-family panel, guardrail paths: src/operations_center/entrypoints/pr_review_watcher/main.py)

  • claude_code/sonnet (correctness): LGTM
  • claude_code/opus (security-capability): CONCERNS — code_quality
  • codex_cli/codex (convergence-operational): CONCERNS — no_tooling_artifacts

Council concerns (attributed by member):

  • claude_code/opus (security-capability): code_quality
  • codex_cli/codex (convergence-operational): no_tooling_artifacts

@ProtocolWarden

Copy link
Copy Markdown
Owner Author

Council review: CONCERNS (cross-family panel, guardrail paths: src/operations_center/entrypoints/pr_review_watcher/main.py)

  • claude_code/sonnet (correctness): LGTM
  • claude_code/opus (security-capability): LGTM
  • codex_cli/codex (convergence-operational): CONCERNS — no_tooling_artifacts

Council concerns (attributed by member):

  • codex_cli/codex (convergence-operational): no_tooling_artifacts

@ProtocolWarden

Copy link
Copy Markdown
Owner Author

Needs human attention (reason=fix_pass_no_progress). Left open — not merged (unresolved) and not closed (work preserved).

The automated fix passes exhausted the Self-Heal Ladder (reached L2/2) without changing the branch; a fresh self-review on the same PR head still finds concerns. Further autonomous retries would repeat without progress.

Latest concerns:

Council concerns (attributed by member):

  • codex_cli/codex (convergence-operational): no_tooling_artifacts

@ProtocolWarden

Copy link
Copy Markdown
Owner Author

Council review: CONCERNS (cross-family panel, guardrail paths: src/operations_center/entrypoints/pr_review_watcher/main.py)

  • claude_code/claude-opus-5 (correctness): CONCERNS — no_verdict
  • claude_code/claude-opus-4-8 (security-capability): CONCERNS — no_verdict
  • claude_code/claude-opus-4-7 (convergence-operational): CONCERNS — no_verdict

Council concerns (attributed by member):

  • claude_code/claude-opus-5 (correctness): no_verdict
  • claude_code/claude-opus-4-8 (security-capability): no_verdict
  • claude_code/claude-opus-4-7 (convergence-operational): no_verdict

@ProtocolWarden

ProtocolWarden commented Aug 13, 2026

Copy link
Copy Markdown
Owner Author

Resolved: superseded by new push — re-review resumed

Self-review concerns — auto-fixing (up to 6 attempts; re-queued if still unresolved):

Council concerns (attributed by member):

  • claude_code/claude-opus-5 (correctness): no_verdict
  • claude_code/claude-opus-4-8 (security-capability): no_verdict
  • claude_code/claude-opus-4-7 (convergence-operational): no_verdict

@ProtocolWarden

Copy link
Copy Markdown
Owner Author

Council review: LGTM (cross-family panel, guardrail paths: src/operations_center/entrypoints/pr_review_watcher/main.py)

  • claude_code/claude-opus-5 (correctness): LGTM
  • claude_code/claude-opus-4-8 (security-capability): LGTM
  • claude_code/claude-opus-4-7 (convergence-operational): LGTM

Council unanimous LGTM (claude_code/claude-opus-5 (correctness), claude_code/claude-opus-4-8 (security-capability), claude_code/claude-opus-4-7 (convergence-operational)).

1 similar comment
@ProtocolWarden

Copy link
Copy Markdown
Owner Author

Council review: LGTM (cross-family panel, guardrail paths: src/operations_center/entrypoints/pr_review_watcher/main.py)

  • claude_code/claude-opus-5 (correctness): LGTM
  • claude_code/claude-opus-4-8 (security-capability): LGTM
  • claude_code/claude-opus-4-7 (convergence-operational): LGTM

Council unanimous LGTM (claude_code/claude-opus-5 (correctness), claude_code/claude-opus-4-8 (security-capability), claude_code/claude-opus-4-7 (convergence-operational)).

@ProtocolWarden

Copy link
Copy Markdown
Owner Author

Council review: CONCERNS (cross-family panel, guardrail paths: src/operations_center/entrypoints/pr_review_watcher/main.py)

  • claude_code/claude-opus-5 (correctness): LGTM
  • claude_code/claude-opus-4-8 (security-capability): CONCERNS — code_quality
  • claude_code/claude-opus-4-7 (convergence-operational): LGTM

Council concerns (attributed by member):

  • claude_code/claude-opus-4-8 (security-capability): code_quality

@ProtocolWarden

ProtocolWarden commented Aug 13, 2026

Copy link
Copy Markdown
Owner Author

Resolved: PR merged

Self-review concerns — auto-fixing (up to 6 attempts; re-queued if still unresolved):

Council concerns (attributed by member):

  • claude_code/claude-opus-4-8 (security-capability): code_quality

@ProtocolWarden

Copy link
Copy Markdown
Owner Author

Council review: LGTM (cross-family panel, guardrail paths: src/operations_center/entrypoints/pr_review_watcher/main.py)

  • claude_code/claude-opus-5 (correctness): LGTM
  • claude_code/claude-opus-4-8 (security-capability): LGTM
  • claude_code/claude-opus-4-7 (convergence-operational): LGTM

Council unanimous LGTM (claude_code/claude-opus-5 (correctness), claude_code/claude-opus-4-8 (security-capability), claude_code/claude-opus-4-7 (convergence-operational)).

@ProtocolWarden
ProtocolWarden merged commit 7a8a1d1 into main Aug 13, 2026
25 checks passed
@ProtocolWarden
ProtocolWarden deleted the feat/d1-reviewer-codex-fallback branch August 13, 2026 20:12
ProtocolWarden added a commit that referenced this pull request Aug 13, 2026
Surfaced by rebasing this branch onto main after #486 landed — the branch
predated it, so nothing had run the two together. #486's
_review_model_for_backend derives the ordinary-review fallback model by scanning
_COUNCIL_PANEL for a matching backend. The all-Opus panel carries no codex seat,
so the lookup returns None and #486's own unit test fails:

  assert _review_model_for_backend("codex_cli") == "codex"
  AssertionError: assert None == 'codex'

That test lives in tests/unit/, which CI runs — so this would have gone red on
merge.

The coupling is the defect, not the panel. _COUNCIL_PANEL answers "who
adjudicates guardrail PRs" (a review-policy choice the operator changes freely);
_review_model_for_backend answers "which model reviews when claude is cooled" (a
capacity fallback). Deriving the second from the first means reseating the
council silently disables the fallback: on a host that DOES have codex, every
claude-cooled ordinary review parks instead of diverting, with no error and no
failing test. This host has no codex, so behavior here is unchanged; the latent
trap is the point.

Replaced with an explicit _REVIEW_FALLBACK_MODELS = {"codex_cli": "codex"} — the
same pairing D1 validated, stated directly rather than inferred, so seating and
fallback vary independently. Added a regression test asserting the lookup still
yields codex WHILE the live panel has no codex seat: exactly the combination that
was broken.

Full suite on the rebased branch: 10382 passed, 5 failed — the same 5
pre-existing failures, zero new.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ProtocolWarden added a commit that referenced this pull request Aug 13, 2026
…ite their verdict (#497)

* feat(reviewer): seat an all-Opus council — the codex seat was inert here

Operator decision: this host has no codex subscription, so the C1 cross-family
panel could never reach quorum. Because an unrunnable seat parks every guardrail
PR fail-closed (min_council_members: 3), the council was not weaker than
designed — it was inert. _COUNCIL_PANEL is now three pinned Opus versions on
claude_code: claude-opus-5 (correctness), claude-opus-4-8 (security/capability),
claude-opus-4-7 (convergence/operational), each keeping its distinct lens.

Versions are pinned rather than aliased on purpose. "opus" resolves to whatever
the CLI calls latest, which would silently collapse two seats onto one model and
turn the panel into a duplicate vote that still reports 3/3.

The seating change alone would have introduced a silent quota bug.
_member_on_cooldown compared a seat's model string to the cooldown record's by
equality, but the usage store only ever speaks the limit classifier's four-token
vocabulary (sonnet/opus/haiku/codex) — that is all detect_model can parse out of
a CLI limit message. A seat named claude-opus-5 would therefore match no "opus"
cooldown, so a rate-limited council would report itself fully available and burn
the quorum dispatching three doomed reviews. Both sides now normalize through
detect_model, which is the identity for bare tokens, so alias-style seats keep
their existing behavior. Regression test covers version seats cooling on a
family-token cooldown while sonnet/haiku stay runnable.

Two consequences are accepted deliberately and documented in COUNCIL_VERDICT.md
rather than left to be rediscovered:

1. Diversity is now version + lens, NOT family. The same-family
   generator/evaluator gap C1 exists to close is no longer closed by panel
   composition; three Opus versions share training lineage and can share a
   blind spot. Restoring a genuine second family is the standing fix.
2. Availability is all-or-nothing. Every seat draws on one subscription and
   normalizes to one family token, so any claude cooldown — model-scoped,
   account-wide, or the budget guard's synthetic budget_reserve — cools the
   whole council. min_council_members: 2 is now unreachable via the cooldown
   store; the degraded-quorum test drives the seat predicate directly and says
   so.

All four current Opus IDs were probed against the live CLI before pinning.
Verification: reviewer + verdict suites 209 passed; full suite 10347 passed with
5 pre-existing failures that reproduce identically with this change stashed
(sandbox file-deletion race guards + test_custodian_sweep) — zero new failures.
ruff check / ruff format --check clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(reviewer): grant council members write access — every review scored CONCERNS

Found by running the review watcher for real, not by a test. Every member exited
rc=0 saying it had written verdict.json; the reviewer found none, logged "no
verdict from member review", and the fail-safe scored the PR CONCERNS — posting a
FAILING reviewer-verdict status and burning a fix-ladder attempt on a PR nothing
had actually reviewed. Left running it would have walked the backlog to
max_fix_attempts and begun closing PRs.

build_member_argv passed no permission mode, so a non-interactive `-p` run cannot
write at all. Probed in an empty tmpdir:

  (default)                      -> "Write permission was denied, so
                                      `verdict.json` was not created."   rc=0
  --permission-mode acceptEdits  -> "Written."   verdict.json present
  --dangerously-skip-permissions -> "Written."   verdict.json present

The removed comment claimed the flagless form matched production, so the previous
host must have had a permissive user-level Claude settings file masking this. A
fresh install does not.

Uses acceptEdits, deliberately not --dangerously-skip-permissions: a member reads
attacker-influenceable text (the PR diff), so COUNCIL_VERDICT.md's injection
threat is live and bypassPermissions would hand an injected instruction full
Bash. Verified on this host that a Bash escape under acceptEdits is refused and
writes stay confined to the member's temp cwd. Covers the ordinary single
reviewer too — same argv builder.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(reviewer): decouple the D1 fallback pairing from council seating

Surfaced by rebasing this branch onto main after #486 landed — the branch
predated it, so nothing had run the two together. #486's
_review_model_for_backend derives the ordinary-review fallback model by scanning
_COUNCIL_PANEL for a matching backend. The all-Opus panel carries no codex seat,
so the lookup returns None and #486's own unit test fails:

  assert _review_model_for_backend("codex_cli") == "codex"
  AssertionError: assert None == 'codex'

That test lives in tests/unit/, which CI runs — so this would have gone red on
merge.

The coupling is the defect, not the panel. _COUNCIL_PANEL answers "who
adjudicates guardrail PRs" (a review-policy choice the operator changes freely);
_review_model_for_backend answers "which model reviews when claude is cooled" (a
capacity fallback). Deriving the second from the first means reseating the
council silently disables the fallback: on a host that DOES have codex, every
claude-cooled ordinary review parks instead of diverting, with no error and no
failing test. This host has no codex, so behavior here is unchanged; the latent
trap is the point.

Replaced with an explicit _REVIEW_FALLBACK_MODELS = {"codex_cli": "codex"} — the
same pairing D1 validated, stated directly rather than inferred, so seating and
fallback vary independently. Added a regression test asserting the lookup still
yields codex WHILE the live panel has no codex seat: exactly the combination that
was broken.

Full suite on the rebased branch: 10382 passed, 5 failed — the same 5
pre-existing failures, zero new.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant