Advanced Agents From First Principles 07: Do Your Agents Agree Too Easily? Use Adversarial Review and Multi-Agent Debate Without Confusing Debate With Truth

Page content

A multi-agent system can look sophisticated while every agent quietly repeats the same mistake.

That is one of the most dangerous failure modes in advanced agent architectures.

You ask one model to solve the problem.

Then you ask a second model to review it.

Then a third model judges the disagreement.

Three calls later, the system sounds more confident than before.

But if all three agents share the same blind spot, the extra machinery has not created independent evidence.

It has created correlated confidence.

This post is about a more disciplined use of adversarial review and multi-agent debate.

The central rule is:

Debate is useful for generating challenges. It is not a source of truth.

A good adversarial system does not ask agents to argue until one sounds convincing.

It asks them to expose assumptions, produce counterexamples, identify missing evidence, and route disputed claims toward stronger verification.

The architecture is closer to this:

proposal
adversarial review
claims / objections / counterexamples
evidence requests
external checks
adjudication
PASS / FAIL / UNKNOWN

The final authority is still the environment, tests, source data, primary evidence, deterministic constraints, or a verifier with a clearly defined contract.

Not the loudest agent.


The searchable problem: “Why do my agents agree even when they are wrong?”

Developers often add more agents after observing unreliable outputs.

The intuition is understandable:

one model can be wrong
therefore
multiple models should correct one another

Sometimes they do.

But only when the additional agents contribute something genuinely different:

  • different evidence,
  • different capabilities,
  • different failure sensitivities,
  • different models,
  • different tools,
  • different search paths,
  • or deliberately different assumptions.

If five agents receive the same context, use the same model family, optimize for the same style of answer, and see the same retrieved evidence, their errors can be strongly correlated.

Then majority agreement tells you very little.

This is the same warning we encountered with self-consistency:

Agreement measures the distribution of generated answers. It does not prove that the shared answer is correct.

Adversarial review becomes valuable when it changes the information available to the runtime.


What adversarial review is actually for

The simplest useful role of an adversarial agent is not to produce a competing final answer.

It is to attack specific claims in the current proposal.

Suppose a coding agent proposes:

The bug is caused by the connection pool timing out.

A weak review asks:

Do you agree?

A stronger adversarial review asks:

What evidence would falsify the timeout hypothesis?
Which logs should contain that evidence?
What alternative failure modes fit the same symptoms?
What assumption is the proposed diagnosis making about retry behavior?

Now the critic is producing useful work even before we know whether its own alternative explanation is correct.

It is generating tests of the proposal.

That distinction matters.


Role diversity is not prompt diversity

A common multi-agent architecture looks like this:

optimist agent
pessimist agent
security agent
performance agent
judge agent

That can work.

But the labels themselves do not create independent capability.

If every role is the same model with the same context and the same underlying information, the system may still collapse toward one shared interpretation.

Useful diversity comes from changing the agent’s actual information or responsibility.

For example:

implementation reviewer -> repository diff + tests
security reviewer       -> taint analysis + auth boundaries
performance reviewer    -> traces + query plans
requirements reviewer   -> original user acceptance criteria
verifier                -> executed checks + external state

Now each role has a different evidence channel.

That is much more meaningful than merely telling five copies of the same model to have different personalities.


A useful contract for adversarial roles

We can represent a challenge as structured data.

from dataclasses import dataclass
from enum import Enum
from typing import Sequence


class ChallengeType(str, Enum):
    ASSUMPTION = "assumption"
    CONTRADICTION = "contradiction"
    COUNTEREXAMPLE = "counterexample"
    MISSING_EVIDENCE = "missing_evidence"
    CONSTRAINT_VIOLATION = "constraint_violation"
    REGRESSION_RISK = "regression_risk"


@dataclass(frozen=True)
class Challenge:
    reviewer: str
    challenge_type: ChallengeType
    target_claim: str
    argument: str
    required_evidence: Sequence[str]
    severity: float

Notice what is missing.

There is no field called:

reviewer_is_correct

The reviewer produces a challenge.

The runtime still has to resolve it.


Debate should produce evidence requests

A useful debate loop looks like this:

proposal
challenge
response
unresolved claims
evidence requests
tools / tests / retrieval
updated claims

This is very different from:

agent A argues
agent B argues
agent A argues again
agent B argues again
...

The second loop can consume a large amount of compute while producing no new information.

The first loop is productive because disagreement changes the runtime’s next action.

A disagreement about database behavior should trigger a database query, schema inspection, test, trace lookup, or documentation check.

A disagreement about a repository change should trigger tests, static analysis, diff inspection, or dependency checks.

A disagreement about a research claim should trigger source retrieval.

The strongest adjudicator is often not another model.

It is new evidence.


Evidence-weighted adjudication

We can model an adjudicator as a deterministic runtime over structured claims.

from dataclasses import dataclass
from enum import Enum


class ClaimStatus(str, Enum):
    SUPPORTED = "supported"
    REFUTED = "refuted"
    UNRESOLVED = "unresolved"


@dataclass(frozen=True)
class Evidence:
    source: str
    supports: bool
    strength: float
    fresh: bool = True


@dataclass(frozen=True)
class ClaimDecision:
    claim: str
    status: ClaimStatus
    support_score: float
    refute_score: float


def adjudicate(claim: str, evidence: list[Evidence]) -> ClaimDecision:
    support = sum(
        e.strength for e in evidence
        if e.supports and e.fresh
    )
    refute = sum(
        e.strength for e in evidence
        if not e.supports and e.fresh
    )

    if support >= 1.0 and support > refute:
        status = ClaimStatus.SUPPORTED
    elif refute >= 1.0 and refute > support:
        status = ClaimStatus.REFUTED
    else:
        status = ClaimStatus.UNRESOLVED

    return ClaimDecision(
        claim=claim,
        status=status,
        support_score=support,
        refute_score=refute,
    )

The numbers here are deliberately simple.

The architectural point is more important than the scoring function:

The debate produces claims and challenges. Evidence resolves them.


What about a judge model?

Sometimes external verification is incomplete.

You may need a model to compare arguments, prioritize challenges, or interpret ambiguous evidence.

That is acceptable.

But the judge should be treated as another fallible component.

The architecture should therefore record:

which arguments the judge saw
which evidence it used
which claims remained unresolved
which model produced the judgment
how often similar judgments are later contradicted

A judge model should not silently convert:

uncertain disagreement

into:

verified truth

When strong evidence is unavailable, the correct result may be:

UNKNOWN

That is not a failure of the system.

It is an accurate representation of its epistemic state.


Judge bias is a real architectural problem

A judge can systematically prefer:

  • longer answers,
  • more confident answers,
  • the first answer,
  • the last answer,
  • familiar reasoning patterns,
  • stylistic similarity to its own generations.

So evaluation should include order randomization and argument anonymization when possible.

For example:

import random


def randomized_pair(a: str, b: str) -> tuple[str, str, bool]:
    if random.random() < 0.5:
        return a, b, False
    return b, a, True

Run the same comparison under swapped order.

If the judgment frequently flips, the judge is not stable enough to act as a strong selector.

Useful metrics include:

position sensitivity
order-flip rate
judge agreement with external verifier
false-accept rate
false-reject rate
UNKNOWN rate

Debate loops need hard budgets

Debate has a dangerous failure mode:

critique -> rebuttal -> critique -> rebuttal -> critique -> ...

There is no natural reason that another round will necessarily produce new information.

A bounded debate runtime should therefore track:

maximum rounds
maximum model calls
maximum tool calls
maximum cost
maximum wall time
new-evidence rate
new-challenge rate
resolved-claim rate

If another debate round is not generating new claims or evidence, stop.

A simple termination rule could be:

@dataclass
class DebateBudget:
    max_rounds: int = 3
    max_calls: int = 12
    min_new_information: int = 1


def should_continue(
    round_no: int,
    calls: int,
    new_claims: int,
    new_evidence: int,
    budget: DebateBudget,
) -> bool:
    if round_no >= budget.max_rounds:
        return False

    if calls >= budget.max_calls:
        return False

    if new_claims + new_evidence < budget.min_new_information:
        return False

    return True

The exact rule will vary by domain.

The principle is stable:

Continue debate only while it is producing information that can change the decision.


Correlated critics

Suppose three reviewers all use the same model with the same prompt context.

You observe:

reviewer A: no issue
reviewer B: no issue
reviewer C: no issue

It is tempting to interpret this as three independent confirmations.

It may actually be one failure mode sampled three times.

To estimate reviewer correlation, inject known defects.

For each defect class, record which reviewers detect it.

Example:

                 null bug   auth bug   perf bug   stale state
reviewer A          1          0          0          1
reviewer B          1          0          0          1
reviewer C          1          0          0          1

These reviewers are not adding much marginal coverage.

Now compare:

                 null bug   auth bug   perf bug   stale state
static analyzer     1          0          0          0
security model      0          1          0          0
perf profiler       0          0          1          0
state verifier      0          0          0          1

That is real functional diversity.


Measure marginal reviewer value

The right question is not:

Did the reviewer find defects?

It is:

Did this reviewer find verified defects that the existing review stack would otherwise miss?

Useful metrics include:

verified defect precision
verified defect recall
unique verified defects
marginal recall gain
cost per unique verified defect
latency per unique verified defect

If reviewer C finds only defects already found by A and B, C may be unnecessary.

That is true even if C produces excellent-looking critique prose.


Debate versus self-consistency

These techniques are related but solve different problems.

Self-consistency asks:

If I sample several independent trajectories, how stable is the answer?

Debate asks:

Can another reasoning process find a specific defect, contradiction, or missing assumption in this proposal?

Self-consistency is primarily an uncertainty signal.

Adversarial review is primarily a challenge-generation mechanism.

You can combine them, but do not collapse them into one vague “multi-agent” category.


Debate versus planner–executor–critic

Planner–executor–critic separates responsibilities.

Adversarial debate adds conflict intentionally.

For example:

planner
executor
primary critic
adversarial critic
evidence acquisition
verifier

The adversarial critic should not merely repeat the first critic.

It should have a different contract.

Possible contracts:

find a violated requirement
find a counterexample
find a security boundary violation
find an assumption unsupported by evidence
find a regression risk
find a stronger alternative explanation

This makes the disagreement useful even when the adversarial critic is ultimately wrong.


Debate versus Mixture of Experts

Mixture of Experts routes a task to the expert most suited to solve it.

Debate deliberately sends overlapping aspects of the same problem to multiple components so their disagreement can expose uncertainty or defects.

You might use both:

router
primary implementation expert
security adversary + correctness adversary
verifier

But the architecture should explain why each extra reviewer exists.

If the answer is merely:

more opinions should be safer

that is not enough.


A complete adversarial-review skeleton

Here is a compact provider-agnostic runtime.

from dataclasses import dataclass, field
from typing import Protocol


@dataclass
class Proposal:
    text: str
    claims: list[str]


@dataclass
class ReviewResult:
    reviewer: str
    challenges: list[Challenge]


@dataclass
class VerificationResult:
    status: str
    evidence: list[Evidence]


class Reviewer(Protocol):
    name: str

    def review(self, proposal: Proposal) -> ReviewResult:
        ...


class EvidenceResolver(Protocol):
    def resolve(self, challenge: Challenge) -> list[Evidence]:
        ...


@dataclass
class AdversarialReviewRuntime:
    reviewers: list[Reviewer]
    resolver: EvidenceResolver
    max_rounds: int = 2

    def run(self, proposal: Proposal) -> list[ClaimDecision]:
        all_challenges: list[Challenge] = []

        for reviewer in self.reviewers:
            result = reviewer.review(proposal)
            all_challenges.extend(result.challenges)

        decisions: list[ClaimDecision] = []

        for challenge in all_challenges:
            evidence = self.resolver.resolve(challenge)
            decision = adjudicate(
                challenge.target_claim,
                evidence,
            )
            decisions.append(decision)

        return decisions

A production version would add:

  • deduplication,
  • challenge clustering,
  • budgets,
  • reviewer reliability estimates,
  • state identity,
  • evidence provenance,
  • authorization checks,
  • and a final goal verifier.

But the basic architecture remains simple.


Coding agents

Adversarial review is especially useful in coding systems because there are strong external verifiers.

Example pipeline:

implementation
correctness reviewer
security reviewer
performance reviewer
requirements reviewer
tests / linters / analyzers / traces
verifier

The important point is that reviewers should generate checkable claims.

Weak critique:

This code may have concurrency problems.

Stronger critique:

Two workers can update the same row after reading the same version.
Add a concurrent test that starts both transactions before either commit.

Now the challenge can be verified.


Code review

For pull-request review, adversarial agents can focus on different defect classes.

For example:

reviewer A -> semantic correctness
reviewer B -> authorization boundary
reviewer C -> backwards compatibility
reviewer D -> tests missing for changed behavior

But you should measure whether each reviewer adds unique validated findings.

If four reviewers repeatedly flag the same naming issue and miss the same state-transition bug, the architecture is mostly noise.


Research agents

Research systems benefit from adversarial roles because claims often depend on uncertain evidence.

Useful roles include:

claim builder
source skeptic
counterexample hunter
methodology reviewer
citation verifier

But the debate should resolve through source inspection.

A reviewer saying:

I do not trust this paper

is not enough.

A useful challenge says:

The conclusion depends on subgroup X, but Table 4 reports only aggregate results. Retrieve the appendix or supplementary material before accepting the claim.

Now the disagreement drives evidence acquisition.


Architecture and design review

Adversarial review is useful when evaluating architectural decisions.

Suppose the proposal is:

Introduce a distributed queue between service A and service B.

Different reviewers might challenge:

operational complexity
failure recovery semantics
ordering requirements
idempotency
observability
latency
migration strategy

But again, the goal is not to make the proposal survive rhetorical attack.

The goal is to expose assumptions that can be tested against workload, SLOs, existing telemetry, and operational constraints.


Incident response

Incident response is an excellent fit because early hypotheses are often wrong.

A useful adversarial loop is:

primary diagnosis
red-team diagnosis
what observation distinguishes them?
query logs / metrics / traces
update incident hypothesis

For example:

hypothesis A: database saturation
hypothesis B: connection leak

The next action should not be another debate round.

It should be the cheapest observation that discriminates between the two.


Customer support

Support systems can use adversarial checking before high-impact actions.

A primary agent may recommend:

refund the transaction

A policy reviewer can challenge:

Is the transaction eligible under the refund window?

A fraud reviewer can challenge:

Does this request match a known abuse pattern?

A customer-state verifier can query the actual order system.

The final outcome comes from policy plus source-of-truth state, not from agents voting on whether the customer “deserves” a refund.


Data and analytics agents

Suppose an analytics agent concludes:

conversion dropped because mobile traffic changed.

An adversarial reviewer can ask:

Did the event schema change?
Did attribution logic change?
Is the denominator stable?
Did bot filtering change?

Those are useful because they create testable alternative explanations.

The runtime can inspect schemas, event counts, deployment history, and cohort definitions.


Browser automation

A browser agent may believe a checkout succeeded because the page contains the text:

Thank you

An adversarial reviewer can challenge:

Was an order ID created?
Did the cart clear?
Did backend state change?
Was payment actually authorized?

The verifier should check the strongest available source of truth.

Again:

The debate identifies what could be wrong. The environment determines what actually happened.


Policy and constraint checking

Adversarial review also works well for systems with hard rules.

Example:

proposal -> travel booking

Reviewers can separately check:

budget constraint
visa constraint
arrival-time constraint
company policy
calendar conflict

Many of these checks should be deterministic.

Do not use debate when ordinary constraint evaluation is stronger.


When debate is useful

Debate tends to help when:

  1. there are multiple plausible interpretations,
  2. early assumptions matter,
  3. different reviewers can access different evidence or tools,
  4. failures are costly enough to justify extra compute,
  5. external verification exists for at least part of the problem,
  6. disagreement can trigger a new observation or test.

When debate is probably unnecessary

Do not add adversarial agents merely because the task is important.

A deterministic workflow may be better when:

rules are known
state is exact
tests are cheap
constraints are explicit
there is one obvious verification path

For example, if the question is whether a database migration applied successfully, query the migration table and inspect the schema.

Do not convene a panel of agents to discuss it.


The “jury of clones” failure mode

One of the most misleading multi-agent patterns is:

same model
same context
same retrieval
same tool access
slightly different role prompt

The system may produce superficially different prose but still share the same latent failure.

Call this the jury of clones.

A useful diagnostic is reviewer overlap:

finding overlap
miss overlap
source overlap
strategy overlap
model overlap

If every dimension is high, adding another reviewer is unlikely to create much new information.


Reviewer independence is not binary

Agents do not have to be fully independent to add value.

Instead, measure marginal diversity.

For reviewer r, estimate:

unique verified defects found by r
--------------------------------
total verified defects found by r

Also inspect which defect classes improve when r is present.

A reviewer may be highly redundant overall but uniquely valuable for security regressions.

That can still justify its cost.


Debate can reduce quality

There is another failure mode worth taking seriously.

A correct agent can be talked out of a correct answer by a persuasive but wrong critic.

This is why “revision after criticism” should not be automatic.

A safer flow is:

proposal
challenge
evidence
accept challenge / reject challenge / unresolved
revise only if evidence justifies revision

The same principle appeared earlier in critique-and-revision systems:

A critique is not evidence that something is wrong.

Adversarial review does not change that.


Debate should preserve minority hypotheses

If four agents support A and one supports B, it may still be worth preserving B when:

  • B identifies a high-severity failure,
  • B has stronger evidence,
  • B is the only agent with relevant specialist capability,
  • or the cost of checking B is low.

So aggregation should not reduce everything to majority vote.

A useful challenge priority might be:

priority = severity × evidence_strength × checkability

A low-probability catastrophic issue can deserve verification even when most agents disagree.


Multi-agent debate as active testing

This leads to a more useful mental model.

Do not think:

multiple agents are having a conversation

Think:

multiple hypothesis generators are proposing tests

The runtime then chooses which test has the highest expected information value.

That turns debate into active testing.

This is far more powerful than endless textual argument.


Production telemetry

A production adversarial system should log more than final answers.

Useful fields include:

task_id
proposal_id
reviewer_id
reviewer_model
reviewer_contract
challenge_type
target_claim
evidence_requested
evidence_retrieved
challenge_status
verified_defect
round
latency
cost

Aggregate metrics should include:

verified success rate
false-accept rate
false-reject rate
unique verified defects per reviewer
marginal recall gain
reviewer overlap
judge order-flip rate
debate rounds per task
new evidence per round
cost per verified success
cost per unique verified defect

If you cannot see which reviewer actually improved outcomes, the architecture is very difficult to optimize.


Controlled experiment

Compare at least these variants:

A. single agent
B. single agent + one generic critic
C. single agent + targeted adversarial critic
D. single agent + two functionally distinct critics
E. multi-round debate
F. adversarial review + external evidence acquisition

Measure:

verified task success
false-pass rate
unique verified defects
calls
latency
cost

The key question is not:

Did debate produce a more convincing answer?

It is:

Did debate expose failures that the simpler system missed, and did those discoveries improve externally verified outcomes enough to justify the extra compute?

Failure attribution

A multi-agent debate system should let us answer exactly where it failed.

Proposal failure

No correct hypothesis was generated.

Challenge failure

The proposal contained a defect, but no reviewer challenged it.

Evidence failure

A useful challenge existed, but the runtime did not gather the evidence needed to resolve it.

Adjudication failure

The evidence existed, but the judge or policy interpreted it incorrectly.

Revision failure

A valid challenge was accepted, but the revised result introduced another defect.

Verification failure

The system accepted an outcome despite insufficient or stale evidence.

This decomposition is far more useful than saying:

multi-agent debate failed

A practical escalation ladder

Start simple.

single agent
external verification
targeted critic for a measured blind spot
functionally distinct second critic
evidence-driven adjudication
bounded multi-round debate

Do not start with ten personas and a judge.

Earn each role.


The deeper pattern

Across this entire advanced-agent series, the same principle keeps returning.

Search does not create truth.

Consensus does not create truth.

A planner does not create truth.

A critic does not create truth.

A judge does not create truth.

A debate does not create truth.

These mechanisms help the system decide where to look next.

They allocate compute.

They surface uncertainty.

They generate hypotheses.

They identify possible defects.

They propose tests.

But when the environment can answer the question more directly, the architecture should eventually leave the model and ask the environment.

That gives us the core rule for adversarial agents:

Use debate to discover what must be checked. Use evidence to decide what is true.


Next: adaptive agents

So far, most of our advanced architectures are still configured before the task starts.

We choose:

  • how many samples to generate,
  • how many branches to search,
  • which experts exist,
  • how many critics to run,
  • and which model tier to invoke.

But tasks do not all deserve the same amount of compute.

Some are trivial.

Some become difficult only after a failed verification step.

Some reveal uncertainty halfway through execution.

The next step is to make the architecture respond to those signals.

In Advanced Agents From First Principles 08, we will build adaptive agents that allocate more reasoning, search, specialists, and stronger models only when the task actually needs them.