Skip to content

fix: stabilize consensus and make it provider-agnostic#64

Merged
aiedwardyi merged 3 commits into
mainfrom
fix/consensus-agnostic-stability
Jul 10, 2026
Merged

fix: stabilize consensus and make it provider-agnostic#64
aiedwardyi merged 3 commits into
mainfrom
fix/consensus-agnostic-stability

Conversation

@aiedwardyi

@aiedwardyi aiedwardyi commented Jul 10, 2026

Copy link
Copy Markdown
Owner
  • Last round no longer fires consensus twice (confidence + final) - Verdict tries Gemini -> Claude -> GPT -> Perplexity (last-resort) - Plain-language timeout/rate-limit/key errors in the thread - Settings note updated; live Perplexity-only smoke passed

Review fixes applied

  • $sha: abort/timeout cleanup, active-model key scope, localized errors, rate-limit match, tier-aware JSON retry

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Enhanced /api/consensus to run a multi-provider consensus workflow across Gemini, Claude, GPT, and Perplexity.
    • Support for multiple provider API keys, optional preferred provider ordering, and anonymous browser key usage.
  • Bug Fixes
    • Improved timeout/rate-limit/invalid-response handling with localized, clearer error messages.
    • More resilient verdict parsing/retries and safer behavior when stopping or receiving malformed verdicts.
  • Tests
    • Added coverage for consensus provider ordering, error-message mapping, and missing-key guidance.
  • Documentation
    • Updated Settings text to clarify consensus key usage and Gemini requirements for document OCR.

Walkthrough

Changes

Multi-provider consensus BYOK

Layer / File(s) Summary
Provider ordering and key resolution
src/lib/consensus-resolve.ts, src/lib/server-provider-keys.ts, src/__tests__/consensus-resolve.test.ts
Adds deterministic provider ordering, soft credential resolution, access-code handling, candidate construction, and localized provider error messages with test coverage.
Provider generation and consensus route
src/app/api/consensus/route.ts, src/lib/providers/claude.ts, src/lib/providers/gpt.ts, src/lib/providers/perplexity.ts
Adds non-streaming provider generators and updates the route to iterate through candidates with timeouts, JSON retries, validation, and fallback behavior.
Client consensus requests and failure handling
src/hooks/useDebateEngine.ts, src/components/SettingsModal.tsx
Sends multiple anonymous provider keys, passes preferred providers, skips final-round confidence requests, and localizes consensus failure and missing-key states.
BYOK route guard coverage
src/__tests__/byok-route-guards.test.ts
Tests missing keys, saved provider keys, provider fallback, request-body BYOK, and Perplexity last-resort selection.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant ConsensusRoute
  participant KeyResolver
  participant Provider
  Client->>ConsensusRoute: Submit preferred providers and user API keys
  ConsensusRoute->>KeyResolver: Resolve ordered consensus candidates
  KeyResolver-->>ConsensusRoute: Return candidate providers and keys
  ConsensusRoute->>Provider: Generate and validate verdict
  Provider-->>ConsensusRoute: Return verdict or generation error
  ConsensusRoute-->>Client: Return verdict or localized failure
Loading

Possibly related PRs


Comment @coderabbitai help to get the list of available commands.

@aiedwardyi

Copy link
Copy Markdown
Owner Author

Fixed in ebc8807 - prettier on consensus route, debate engine, consensus-resolve.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/hooks/useDebateEngine.ts (2)

668-696: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Missing session guard in the !res.ok branch lets a stale response resurrect a cleared message.

The res.status === 402 branch (Line 680) checks sessionIdRef.current !== thisSession before dispatching, and the success branch (Line 697) checks sessionIdRef.current === thisSession. The !res.ok branch (Lines 689-696) has no such check. If a new debate session starts while this fetch is still in flight, the stale-session cleanup at Lines 592-600 will already have cleared analyzingMsg's content to ""; this branch can then resurrect that same message with failMsg, showing a stray error bubble unrelated to the current session.

🐛 Proposed fix
                 } else if (!res.ok) {
+                  if (sessionIdRef.current !== thisSession) return
                   const failMsg = await consensusFailureMessage(res, locale)
                   logDebate("verdict:failed", { status: res.status, message: failMsg })
                   dispatch({
                     type: "UPDATE_MESSAGE",
                     id: analyzingMsg.id,
                     content: failMsg,
                   })
                 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/useDebateEngine.ts` around lines 668 - 696, Add a session guard to
the !res.ok failure branch before computing or dispatching failMsg, matching the
existing 402 and success-branch checks: return when sessionIdRef.current !==
thisSession. Keep logging and UPDATE_MESSAGE strictly limited to the active
debate session within the verdict-fetch flow.

800-829: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Same missing session guard in handleStop's !res.ok branch.

Mirrors the issue in the final-verdict flow above. Line 809's 402 branch checks sessionIdRef.current !== stoppedSession and the second .then (Line 833) also checks it, but the !res.ok branch (Lines 820-828) dispatches UPDATE_MESSAGE unconditionally. A superseded stop-time consensus fetch can revive an already-cleared "analyzing" message with a stale error after a new debate has started.

🐛 Proposed fix
           if (!res.ok) {
+            if (sessionIdRef.current !== stoppedSession) return null
             const failMsg = await consensusFailureMessage(res, locale)
             dispatch({
               type: "UPDATE_MESSAGE",
               id: analyzingMsg.id,
               content: failMsg,
             })
             return null
           }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/useDebateEngine.ts` around lines 800 - 829, Guard the non-OK
response handling in handleStop’s fetchConsensus chain with the same session
check used by the 402 branch and subsequent .then: before dispatching
UPDATE_MESSAGE with consensusFailureMessage, return without updating when
sessionIdRef.current differs from stoppedSession. This prevents stale stop-time
requests from modifying a newer debate.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/app/api/consensus/route.ts`:
- Around line 370-374: Replace the hardcoded 30_000 timeout in the generateRaw
malformed-JSON retry with the same tier-aware timeout used by the primary
provider call, ensuring Pro retries receive the 120_000 allowance while other
tiers retain their configured limits.
- Around line 155-162: Update withTimeout to create and return an
AbortController signal, clear the timeout when the provider promise settles, and
abort the controller on timeout. Thread the signal through generateRaw and pass
it to generateClaudeVerdict, generateGptVerdict, and generatePerplexityVerdict
so timed-out requests are cancelled before trying the next candidate.

In `@src/lib/consensus-resolve.ts`:
- Line 1: Run Prettier in write mode on src/lib/consensus-resolve.ts and verify
the resulting formatting passes the repository’s Prettier check.
- Around line 111-119: In the rate-limit detection logic, replace the broad
lower.includes("rate") check with a more precise match for rate-limit
terminology, such as the standalone token or phrase “rate limit,” while
preserving the existing 429, quota, and resource-exhausted checks. Update the
condition in the error-classification function containing this block.

---

Outside diff comments:
In `@src/hooks/useDebateEngine.ts`:
- Around line 668-696: Add a session guard to the !res.ok failure branch before
computing or dispatching failMsg, matching the existing 402 and success-branch
checks: return when sessionIdRef.current !== thisSession. Keep logging and
UPDATE_MESSAGE strictly limited to the active debate session within the
verdict-fetch flow.
- Around line 800-829: Guard the non-OK response handling in handleStop’s
fetchConsensus chain with the same session check used by the 402 branch and
subsequent .then: before dispatching UPDATE_MESSAGE with
consensusFailureMessage, return without updating when sessionIdRef.current
differs from stoppedSession. This prevents stale stop-time requests from
modifying a newer debate.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 287db974-0c19-4961-984f-fa3b3894415d

📥 Commits

Reviewing files that changed from the base of the PR and between 7a0abfa and 8d130c9.

📒 Files selected for processing (10)
  • src/__tests__/byok-route-guards.test.ts
  • src/__tests__/consensus-resolve.test.ts
  • src/app/api/consensus/route.ts
  • src/components/SettingsModal.tsx
  • src/hooks/useDebateEngine.ts
  • src/lib/consensus-resolve.ts
  • src/lib/providers/claude.ts
  • src/lib/providers/gpt.ts
  • src/lib/providers/perplexity.ts
  • src/lib/server-provider-keys.ts
📜 Review details
⚠️ CI failures not shown inline (2)

GitHub Actions: CI / ci: fix: stabilize consensus and make it provider-agnostic

Conclusion: failure

View job details

##[group]Run npx prettier --check .
 �[36;1mnpx prettier --check .�[0m
 shell: /usr/bin/bash -e {0}
 ##[endgroup]
 Checking formatting...
 [�[33mwarn�[39m] src/app/api/consensus/route.ts
 [�[33mwarn�[39m] src/hooks/useDebateEngine.ts
 [�[33mwarn�[39m] src/lib/consensus-resolve.ts
 [�[33mwarn�[39m] Code style issues found in 3 files. Run Prettier with --write to fix.
 ##[error]Process completed with exit code 1.

GitHub Actions: CI / 0_ci.txt: fix: stabilize consensus and make it provider-agnostic

Conclusion: failure

View job details

##[group]Run npx prettier --check .
 �[36;1mnpx prettier --check .�[0m
 shell: /usr/bin/bash -e {0}
 ##[endgroup]
 Checking formatting...
 [�[33mwarn�[39m] src/app/api/consensus/route.ts
 [�[33mwarn�[39m] src/hooks/useDebateEngine.ts
 [�[33mwarn�[39m] src/lib/consensus-resolve.ts
 [�[33mwarn�[39m] Code style issues found in 3 files. Run Prettier with --write to fix.
 ##[error]Process completed with exit code 1.
🧰 Additional context used
🪛 ESLint
src/__tests__/consensus-resolve.test.ts

[error] 31-31: Move this regular expression to module scope to avoid re-compilation on every call.

(e18e/prefer-static-regex)


[error] 35-35: Move this regular expression to module scope to avoid re-compilation on every call.

(e18e/prefer-static-regex)


[error] 39-39: Move this regular expression to module scope to avoid re-compilation on every call.

(e18e/prefer-static-regex)

src/__tests__/byok-route-guards.test.ts

[error] 187-187: Move this regular expression to module scope to avoid re-compilation on every call.

(e18e/prefer-static-regex)

🪛 GitHub Actions: CI / 0_ci.txt
src/app/api/consensus/route.ts

[warning] 1-1: Prettier --check reported a formatting/style warning in this file.

src/lib/consensus-resolve.ts

[warning] 1-1: Prettier --check reported a formatting/style warning in this file.

src/hooks/useDebateEngine.ts

[warning] 1-1: Prettier --check reported a formatting/style warning in this file.

🪛 GitHub Actions: CI / ci
src/app/api/consensus/route.ts

[warning] 1-1: Prettier --check reported code style issues in this file.

src/lib/consensus-resolve.ts

[warning] 1-1: Prettier --check reported code style issues in this file.

src/hooks/useDebateEngine.ts

[warning] 1-1: Prettier --check reported code style issues in this file.

🔇 Additional comments (11)
src/lib/server-provider-keys.ts (1)

18-79: LGTM!

src/__tests__/consensus-resolve.test.ts (1)

9-41: LGTM!

src/app/api/consensus/route.ts (1)

254-332: LGTM!

src/lib/providers/gpt.ts (1)

58-88: LGTM!

src/lib/providers/perplexity.ts (1)

92-129: LGTM!

src/lib/providers/claude.ts (1)

91-99: 🎯 Functional Correctness

No issue: claude-sonnet-4-6 is a valid Anthropic model ID and this pin is fine.

			> Likely an incorrect or invalid review comment.
src/hooks/useDebateEngine.ts (3)

5-5: LGTM!

Also applies to: 48-66, 114-142, 144-156


502-505: LGTM!

Correctly implements the PR objective — the last round no longer fires the intermediate confidence consensus call, avoiding a double consensus request against the final-stage call.

Also applies to: 524-555, 628-630


872-872: LGTM!

src/components/SettingsModal.tsx (1)

295-296: LGTM!

Also applies to: 323-324

src/__tests__/byok-route-guards.test.ts (1)

21-36: LGTM!

Also applies to: 117-119, 175-175, 185-192, 242-244, 261-305, 387-424

Comment thread src/app/api/consensus/route.ts Outdated
Comment thread src/app/api/consensus/route.ts
Comment thread src/lib/consensus-resolve.ts
Comment thread src/lib/consensus-resolve.ts
@aiedwardyi

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ebc8807925

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/hooks/useDebateEngine.ts Outdated
Comment thread src/lib/consensus-resolve.ts Outdated
Comment thread src/lib/consensus-resolve.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR stabilizes the /api/consensus “verdict” step by making it provider-agnostic (Gemini → Claude → GPT → Perplexity) and improving key resolution + user-facing error messaging during consensus.

Changes:

  • Added multi-provider consensus candidate resolution (including anonymous multi-key BYOK) and provider-order selection.
  • Implemented non-streaming “verdict” calls for Claude/GPT/Perplexity and updated the consensus route to fall back across providers.
  • Improved client UX for consensus failures (plain-language timeout/rate-limit/key messaging) and avoided double-firing consensus in the last round.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/lib/server-provider-keys.ts Exposes access-code validation and adds “soft” key resolution for multi-provider fallback without emitting 402s directly.
src/lib/providers/perplexity.ts Adds non-streaming verdict generation for consensus fallback.
src/lib/providers/gpt.ts Adds non-streaming verdict generation for consensus.
src/lib/providers/claude.ts Adds non-streaming verdict generation for consensus.
src/lib/consensus-resolve.ts New consensus provider ordering + candidate resolution + human-friendly error mapping.
src/app/api/consensus/route.ts Reworks consensus route to try multiple providers, adds JSON extraction/repair, and plain-language error responses.
src/hooks/useDebateEngine.ts Sends all anonymous browser keys, avoids last-round double consensus, and surfaces server-provided error messages.
src/components/SettingsModal.tsx Updates settings copy to reflect consensus provider fallback behavior.
src/tests/consensus-resolve.test.ts Adds unit tests for provider ordering and human error mapping.
src/tests/byok-route-guards.test.ts Extends BYOK route-guard tests for multi-provider verdict fallback and request-body key usage.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/app/api/consensus/route.ts Outdated
Comment thread src/app/api/consensus/route.ts Outdated
Comment thread src/app/api/consensus/route.ts Outdated
Comment thread src/app/api/consensus/route.ts Outdated
Comment thread src/app/api/consensus/route.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/__tests__/consensus-resolve.test.ts`:
- Around line 38-44: Hoist the regular expressions used by the “does not treat
generate as a rate limit” and “localizes Korean timeouts” tests in
consensus-resolve.test.ts to module scope, then update the expect matchers to
reference those shared regex constants instead of inline literals, avoiding
e18e/prefer-static-regex lint errors.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0f6445e0-90e2-45e1-ba91-b7f86a5f5025

📥 Commits

Reviewing files that changed from the base of the PR and between 8d130c9 and b03cf62.

📒 Files selected for processing (4)
  • src/__tests__/consensus-resolve.test.ts
  • src/app/api/consensus/route.ts
  • src/hooks/useDebateEngine.ts
  • src/lib/consensus-resolve.ts
📜 Review details
🧰 Additional context used
🪛 ESLint
src/__tests__/consensus-resolve.test.ts

[error] 39-39: Move this regular expression to module scope to avoid re-compilation on every call.

(e18e/prefer-static-regex)


[error] 43-43: Move this regular expression to module scope to avoid re-compilation on every call.

(e18e/prefer-static-regex)

🔇 Additional comments (6)
src/lib/consensus-resolve.ts (1)

99-156: LGTM!

src/__tests__/consensus-resolve.test.ts (1)

9-27: LGTM!

src/app/api/consensus/route.ts (2)

155-172: LGTM!


264-351: LGTM!

Also applies to: 389-393, 434-449

src/hooks/useDebateEngine.ts (2)

114-147: LGTM!

Also applies to: 503-559, 624-648


666-736: LGTM!

Also applies to: 799-871

Comment thread src/__tests__/consensus-resolve.test.ts
@aiedwardyi
aiedwardyi merged commit 5f6a449 into main Jul 10, 2026
1 check passed
@aiedwardyi
aiedwardyi deleted the fix/consensus-agnostic-stability branch July 10, 2026 17:46
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.

2 participants