Symptom
With several council members configured through the same provider — e.g. three models behind one OpenAI-compatible gateway:
{
"providers": [
{"provider": "openrouter", "model": "openai/gpt-5.2", "api_key": "${OPENROUTER_API_KEY}"},
{"provider": "openrouter", "model": "google/gemini-3.1-pro-preview", "api_key": "${OPENROUTER_API_KEY}"},
{"provider": "openrouter", "model": "x-ai/grok-4.3", "api_key": "${OPENROUTER_API_KEY}"}
],
"consensus_threshold": 2
}
a review run produces:
providers_used: ["openrouter", "openrouter", "openrouter"]
quality_ratings: a single "openrouter" key — members overwrite each other
individual_issues: one shared "openrouter" bucket
consensus_issues: unreachable — classify() tracks voters as a set of provider names (consensus.py: providers.add(review.provider)), so all three members count as one voter while num_providers = len(reviews) = 3; all_agree can never be true
- agreement between two same-provider members is misclassified as an individual issue
Root cause
Member identity throughout the pipeline is the provider name: ProviderResponse.provider → ProviderReview.provider → the consensus grouping set and the aggregation dict keys.
Related: resolve_api_keys rebuilds ProviderConfig field by field, silently dropping any field it doesn't enumerate — a hazard for any field later added to ProviderConfig.
Proposed fix
An optional per-entry label that becomes the member's identity in council output, falling back to the provider name (fully backwards compatible). PR follows.
Verified end-to-end against OpenRouter with the three-member config above: with labels, providers_used carries the labels and quality_ratings/individual_issues get three distinct keys, with consensus/majority classification behaving as documented.
Symptom
With several council members configured through the same provider — e.g. three models behind one OpenAI-compatible gateway:
{ "providers": [ {"provider": "openrouter", "model": "openai/gpt-5.2", "api_key": "${OPENROUTER_API_KEY}"}, {"provider": "openrouter", "model": "google/gemini-3.1-pro-preview", "api_key": "${OPENROUTER_API_KEY}"}, {"provider": "openrouter", "model": "x-ai/grok-4.3", "api_key": "${OPENROUTER_API_KEY}"} ], "consensus_threshold": 2 }a
reviewrun produces:providers_used: ["openrouter", "openrouter", "openrouter"]quality_ratings: a single"openrouter"key — members overwrite each otherindividual_issues: one shared"openrouter"bucketconsensus_issues: unreachable —classify()tracks voters as a set of provider names (consensus.py:providers.add(review.provider)), so all three members count as one voter whilenum_providers = len(reviews) = 3;all_agreecan never be trueRoot cause
Member identity throughout the pipeline is the provider name:
ProviderResponse.provider→ProviderReview.provider→ the consensus grouping set and the aggregation dict keys.Related:
resolve_api_keysrebuildsProviderConfigfield by field, silently dropping any field it doesn't enumerate — a hazard for any field later added toProviderConfig.Proposed fix
An optional per-entry
labelthat becomes the member's identity in council output, falling back to the provider name (fully backwards compatible). PR follows.Verified end-to-end against OpenRouter with the three-member config above: with labels,
providers_usedcarries the labels andquality_ratings/individual_issuesget three distinct keys, with consensus/majority classification behaving as documented.