Skip to main content

Adversarial multi-model debate engine -- pit LLMs against each other, detect hollow consensus, and produce cryptographic decision receipts. Zero required dependencies.

Project description

PyPI version Python 3.10+ License: MIT Tests

aragora-debate

Adversarial multi-model debate engine with decision receipts. Zero required dependencies.

One model's opinion isn't enough when the decision matters. aragora-debate orchestrates structured adversarial debates across multiple LLM providers, detects consensus, tracks dissent, and produces cryptographic decision receipts.

Part of the Aragora Decision Integrity Platform. This standalone package gives you the debate engine with no framework lock-in.

Why adversarial debate?

Problem What debate does
LLMs fail at compositional reasoning (Song et al., 2026) Multiple models cross-check each other's reasoning
Models are overconfident when wrong Structured critique surfaces hidden weaknesses
Agreement between models ≠ correctness Heterogeneous models surface genuine uncertainty
No audit trail for AI-assisted decisions Cryptographic decision receipts with dissent tracking

Multi-agent debate achieves +13.8 percentage points accuracy over single-model baselines (Harrasse et al., 2024) and significantly reduces hallucinations (Du et al., 2023).

Install

pip install aragora-debate

# With provider SDKs
pip install aragora-debate[anthropic]    # Claude
pip install aragora-debate[openai]       # GPT
pip install aragora-debate[mistral]      # Mistral
pip install aragora-debate[gemini]       # Gemini
pip install aragora-debate[all]          # All providers

Quick start

import asyncio
from aragora_debate import Arena, Agent, DebateConfig, Critique, Vote, Message

# 1. Implement agents by wrapping your preferred LLM
class MyAgent(Agent):
    async def generate(self, prompt: str, context: list[Message] | None = None) -> str:
        return await call_my_llm(self.model, prompt)  # your LLM call

    async def critique(self, proposal: str, task: str, **kw) -> Critique:
        resp = await call_my_llm(self.model, f"Critique this proposal:\n{proposal}")
        return Critique(agent=self.name, target_agent=kw.get("target_agent", ""),
                       target_content=proposal, issues=[resp], severity=5.0)

    async def vote(self, proposals: dict[str, str], task: str) -> Vote:
        best = await call_my_llm(self.model, f"Pick the best:\n{proposals}")
        return Vote(agent=self.name, choice=best, reasoning="Most thorough analysis")

# 2. Run a debate
async def main():
    agents = [
        MyAgent("claude", model="claude-sonnet-4-5-20250929"),
        MyAgent("gpt4", model="gpt-4o"),
        MyAgent("mistral", model="mistral-large-latest"),
    ]

    arena = Arena(
        question="Should we migrate from REST to GraphQL?",
        agents=agents,
        config=DebateConfig(rounds=3, consensus_method="supermajority"),
    )

    result = await arena.run()

    print(result.summary())
    print(result.receipt.to_markdown())

asyncio.run(main())

How it works

Round 1          Round 2          Round 3
┌──────────┐    ┌──────────┐    ┌──────────┐
│ PROPOSE   │    │ PROPOSE   │    │ PROPOSE   │
│ All agents│    │ Address   │    │ Final     │
│ respond   │    │ critiques │    │ positions │
├──────────┤    ├──────────┤    ├──────────┤
│ CRITIQUE  │    │ CRITIQUE  │    │ CRITIQUE  │
│ Challenge │    │ Deeper    │    │ Last      │
│ each other│    │ analysis  │    │ challenges│
├──────────┤    ├──────────┤    ├──────────┤
│ VOTE      │    │ VOTE      │    │ VOTE      │
│ Weighted  │    │ May stop  │    │ Final     │
│ votes     │    │ if agreed │    │ tally     │
└──────────┘    └──────────┘    └──────────┘
                                      │
                                ┌─────▼──────┐
                                │  RECEIPT    │
                                │ Consensus,  │
                                │ dissent,    │
                                │ signature   │
                                └────────────┘

Each round has three phases:

  1. Propose — Every agent generates an independent response
  2. Critique — Each agent challenges every other agent's reasoning
  3. Vote — Agents vote on which proposal is strongest

Early stopping kicks in when consensus is reached before all rounds complete. The final decision receipt captures who agreed, who dissented and why, the confidence level, and a cryptographic signature for audit purposes.

Evidence quality & hollow consensus detection

Not all agreement is meaningful. aragora-debate detects when models agree without substantive evidence — a pattern called hollow consensus.

from aragora_debate import EvidenceQualityAnalyzer, HollowConsensusDetector

analyzer = EvidenceQualityAnalyzer()
score = analyzer.analyze("According to a 2024 Gartner report, 73% of enterprises...")
print(f"Evidence quality: {score.overall:.2f}")  # 0.0–1.0

# Detect hollow consensus across agents
detector = HollowConsensusDetector()
alert = detector.check(
    responses={"claude": "I agree, this is great", "gpt4": "I also think it's great"},
    convergence_similarity=0.95,
)
if alert.detected:
    print(f"Hollow consensus: {alert.challenge}")

The Trickster system uses this automatically during debates to inject challenge prompts when it detects agents converging without evidence:

from aragora_debate import Debate, create_agent

debate = Debate(
    "Should we adopt microservices?",
    enable_trickster=True,       # Inject challenges on hollow consensus
    enable_convergence=True,     # Track proposal similarity across rounds
    trickster_sensitivity=0.7,   # Higher = more interventions
)
debate.add_agent(create_agent("mock", name="analyst"))
debate.add_agent(create_agent("mock", name="critic"))
result = await debate.run()
print(f"Trickster interventions: {result.trickster_interventions}")
print(f"Convergence detected: {result.convergence_detected}")

Events & callbacks

Monitor debates in real-time with the event system:

from aragora_debate import EventEmitter, EventType

def on_event(event):
    print(f"[{event.event_type.value}] round={event.round_num} {event.data}")

debate = Debate("Should we use Kafka?", on_event=on_event)
# Events: debate_start, round_start, proposal, critique, vote,
#         consensus_check, convergence_detected, trickster_intervention,
#         round_end, debate_end

Cross-proposal analysis

Analyze evidence patterns across all proposals:

from aragora_debate import CrossProposalAnalyzer

analyzer = CrossProposalAnalyzer()
analysis = analyzer.analyze({
    "claude": "According to Smith (2024), Kafka handles 1M msgs/sec...",
    "gpt4": "RabbitMQ has better delivery guarantees per Jones (2023)...",
})
print(f"Shared evidence: {len(analysis.shared_evidence)}")
print(f"Contradictions: {len(analysis.contradictions)}")
print(f"Evidence gaps: {len(analysis.evidence_gaps)}")
print(f"Weakest agent: {analysis.weakest_agent}")

Decision receipts

Every debate produces a DecisionReceipt — an auditable artifact:

# Decision Receipt DR-20260211-a3f8c2

**Question:** Should we migrate from REST to GraphQL?
**Verdict:** Approved With Conditions
**Confidence:** 78%
**Consensus:** Reached (supermajority, 75% agreement)
**Agents:** claude, gpt4, mistral

## Dissenting Views

**mistral:**
- Caching complexity underestimated for existing CDN infrastructure
  > REST+BFF achieves 80% of benefits with 20% of migration risk

Export as Markdown, JSON, or HTML. Sign with HMAC-SHA256 for tamper detection.

from aragora_debate import ReceiptBuilder

# Sign for audit compliance
ReceiptBuilder.sign_hmac(result.receipt, key="your-signing-key")

# Export
print(ReceiptBuilder.to_json(result.receipt))

with open("receipt.html", "w") as f:
    f.write(ReceiptBuilder.to_html(result.receipt))

Consensus methods

Method Threshold Use when
majority >50% General decisions
supermajority >66.7% Important decisions
unanimous 100% Safety-critical decisions
weighted Configurable When agent reliability varies
judge N/A One agent decides after hearing debate

Configuration

DebateConfig(
    rounds=3,                          # Number of debate rounds
    consensus_method="supermajority",  # How consensus is determined
    consensus_threshold=0.6,           # For weighted consensus
    early_stopping=True,               # Stop when consensus reached
    early_stop_threshold=0.85,         # Confidence to trigger early stop
    min_rounds=1,                      # Minimum rounds before early stop
    timeout_seconds=300,               # Overall timeout (0 = none)
    require_reasoning=True,            # Agents must explain votes
)

When to use adversarial debate

Good fit:

  • Architecture decisions ("Kafka vs RabbitMQ?")
  • Compliance reviews ("Does this meet SOC 2?")
  • Risk assessments ("What are the risks of this vendor?")
  • Strategy decisions ("Should we enter market X?")
  • Security reviews ("Is this auth model sound?")

Not a good fit:

  • Simple lookups ("What's the capital of France?")
  • Creative generation ("Write me a poem")
  • Real-time responses (debate takes seconds to minutes)

Rule of thumb: If the decision is worth a meeting, it's worth a debate.

Built-in providers

Provider Class Install Default model
Anthropic ClaudeAgent pip install aragora-debate[anthropic] claude-sonnet-4-5-20250929
OpenAI OpenAIAgent pip install aragora-debate[openai] gpt-4o
Mistral MistralAgent pip install aragora-debate[mistral] mistral-large-latest
Google GeminiAgent pip install aragora-debate[gemini] gemini-2.0-flash
Mock MockAgent (included) N/A

Use the factory for quick setup:

from aragora_debate import create_agent

agents = [
    create_agent("anthropic", name="analyst"),
    create_agent("openai", name="challenger"),
    create_agent("mistral", name="devil-advocate"),
]

Extending

Custom agents

Implement Agent.generate(), Agent.critique(), and Agent.vote():

class ClaudeAgent(Agent):
    def __init__(self, name: str = "claude"):
        super().__init__(name, model="claude-sonnet-4-5-20250929")
        import anthropic
        self.client = anthropic.AsyncAnthropic()

    async def generate(self, prompt, context=None):
        resp = await self.client.messages.create(
            model=self.model,
            max_tokens=2048,
            system=self.system_prompt or "You are a careful analyst.",
            messages=[{"role": "user", "content": prompt}],
        )
        return resp.content[0].text

    async def critique(self, proposal, task, **kw):
        prompt = f"Task: {task}\n\nProposal to critique:\n{proposal}\n\nIdentify issues and suggest improvements."
        resp = await self.generate(prompt)
        return Critique(
            agent=self.name,
            target_agent=kw.get("target_agent", "unknown"),
            target_content=proposal,
            issues=[resp],
            severity=5.0,
        )

    async def vote(self, proposals, task):
        formatted = "\n\n".join(f"**{name}:** {text}" for name, text in proposals.items())
        prompt = f"Task: {task}\n\nProposals:\n{formatted}\n\nWhich is strongest? Reply with just the agent name."
        choice = await self.generate(prompt)
        return Vote(agent=self.name, choice=choice.strip(), reasoning="Best analysis")

Accessing debate history

result = await arena.run()

# Full message history
for msg in result.messages:
    print(f"[Round {msg.round}] {msg.agent} ({msg.role}): {msg.content[:100]}")

# All critiques
for c in result.critiques:
    print(f"{c.agent}{c.target_agent}: severity {c.severity}/10")

# Vote breakdown
for v in result.votes:
    print(f"{v.agent} voted for {v.choice} (confidence: {v.confidence})")

Regulatory compliance

Decision receipts help satisfy:

  • EU AI Act Art. 12 — Automatic record-keeping
  • EU AI Act Art. 13 — Transparent, interpretable output
  • EU AI Act Art. 14 — Human oversight (review before acting)
  • SOC 2 CC6.1 — Audit logging
  • HIPAA 164.312(b) — Audit controls

See EU_AI_ACT_COMPLIANCE.md for the full mapping.

Full platform

aragora-debate is the standalone debate engine extracted from the Aragora Decision Integrity Platform. The full platform adds 42+ agent types, knowledge management, enterprise auth, compliance frameworks, and the Nomic Loop for autonomous self-improvement.

License

MIT -- see LICENSE for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

aragora_debate-0.2.1.tar.gz (75.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

aragora_debate-0.2.1-py3-none-any.whl (51.6 kB view details)

Uploaded Python 3

File details

Details for the file aragora_debate-0.2.1.tar.gz.

File metadata

  • Download URL: aragora_debate-0.2.1.tar.gz
  • Upload date:
  • Size: 75.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for aragora_debate-0.2.1.tar.gz
Algorithm Hash digest
SHA256 163da57dd654eb7777613933f9d40b5330acd9f631f92082de5535239bf95a3a
MD5 d3a67e14ac8589b2c9641ec71071ed79
BLAKE2b-256 29c7dfe8eb2300fedd683d5c835741a9242aab7297682fbc171b61304995414e

See more details on using hashes here.

Provenance

The following attestation bundles were made for aragora_debate-0.2.1.tar.gz:

Publisher: publish-aragora-debate.yml on an0mium/aragora

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file aragora_debate-0.2.1-py3-none-any.whl.

File metadata

  • Download URL: aragora_debate-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 51.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for aragora_debate-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 2b736478b49835d9879b119804bb587c456ec0b4c47a050bcfc13b3f34dfef1c
MD5 80b37670f06f0f7260569cbb20600190
BLAKE2b-256 e1b16632c49a3034345397ad911a34510fe90774da7f078bf28e9b402bee4639

See more details on using hashes here.

Provenance

The following attestation bundles were made for aragora_debate-0.2.1-py3-none-any.whl:

Publisher: publish-aragora-debate.yml on an0mium/aragora

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page