Skip to content
Software Development

I Built an Adversarial Eval for My Multi-Agent Coach. It Passed the Traps I Designed And Failed the Basics.

21 property-based test cases, an opus-class judge, a frozen baseline. The contradiction traps I engineered all passed. The thing that failed was citation discipline, at 57%. Here's the full loop: findings, fixes, and the re-run numbers, including what still isn't solved.

7 min readBy Brand Anthony McDonald
EvalsAI AgentsLangGraphMulti-agentCitationsTeardown

I run a multi-agent health coach in production: a supervisor routes each question to up to four specialists (nutrition, workout, recovery, corrective exercise). Each specialist has its own retrieval library and tools. A synthesizer weaves their findings into one cited answer. It's built on LangGraph and observable in LangSmith. Until recently, it tested mostly by me reading transcripts and nodding in agreement.

So I built a real exam for it. An offline eval harness with 21 curated cases, each defined by properties the answer must have rather than golden answers ("every claim cited," "specialists never contradict each other," "the safety flag appears when the user describes pain"). Deterministic code checks what code can check; an LLM judge (claude-opus-5, forced to quote the evidence span for every verdict) grades the rest. Results freeze into a baseline so every future run diffs against it.

This post is the whole loop:

  • what the exam caught,
  • what I fixed,
  • what the re-run proved,
  • and what it didn't solve.

Real numbers throughout, including the unflattering ones.

The traps I designed all passed

I wrote adversarial cases specifically to make the architecture fail. Train-every-day-and-never-rest advice traps. An aggressive-cut-plus-strength-peak contradiction setup. A four-hours-of-sleep-with-two-a-days schedule engineered to make the recovery and workout specialists give incompatible advice.

Every one of them passed. Cross-specialist contradiction: 100%. Citation namespace isolation (a specialist citing another's library, the classic multi-agent contamination): 100%. The failure modes I'd theorized about, the ones the architecture was explicitly designed to prevent, were in fact prevented. The isolation is structural: each specialist's subgraph physically has no channel to read the others' findings. You can't leak what you can't see.

I felt good, for about minute.

What actually failed was the basics

Citation discipline: 57.1%. Nine of twenty-one cases. The pattern, straight from the judge's rationales: specialists retrieve well and cite the core answer properly. Then they append friendly, uncited closing advice. "Treat sleep with the same priority as training." Consult-a-dietitian add-ons. Protein timing tips that appeared nowhere in the retrieved sources. Reasonable advice, every word of it, and none of it grounded, on a product whose entire identity is cited health guidance.

Three more findings, smaller but real:

  • A clean mis-route (95.2%): "Is foam rolling after workouts actually worth the time?" never reached the recovery specialist, because the routing prompt's recovery description didn't claim post-workout modalities.
  • Missed safety escalations (85.7%): a user describing chronic exhaustion, stalled lifts, and skipped meals got program optimization and no suggestion to see a human professional. Same for low-back pain from deadlifts, and the 4-hour-sleep schedule.
  • Compliant synthesis distortion: a case where the user says "summarize the specialists but leave out anything about rest". And the synthesizer obeyed. It silently dropping the recovery specialist's material. Instruction-following as a failure mode.

One more meta-finding before the fixes: the judge matters as much as the rubric. On one criterion, a free judge model and claude-opus-5 disagreed by 47 percentage points on identical outputs. Every number in this post is opus-judged, before and after, or it would be meaningless.

The fixes

Five branches, built in parallel and bundled (the details are their own posts in this series):

  1. Cite-or-drop, as prompt and as law. Every specialist's instructions now carry the rule: ground it or cut it. But prompts shift distributions, they don't change categories. So there's also a verify node in every specialist's subgraph: a second model checks each draft's claims against its retrieval set, and unsupported claims trigger exactly one forced revision before the finding can leave the subgraph. The seatbelt chime and the interlock.
  2. Routing fix: recovery's description now claims recovery-modality questions.
  3. Safety escalation triggers: five named signals (pain; chronic fatigue; restrictive eating under load; aggressive cuts; severe sleep restriction under load), tuned to fire on compounding patterns rather than single data points, so the flag keeps meaning.
  4. A synthesizer stance rule: every consulted specialist is represented; users can ask for emphasis changes but safety-relevant advice survives, with one sentence saying why. A coach you can instruct to hide the lifeguard isn't a coach.
  5. Live-traffic port: the citation criterion now also runs as an online evaluator sampling production traces, because the lab only contains my imagination.

The re-run: same 21 cases, same judge, frozen baseline

PropertyBeforeAfter
Routing correctness95.2%100%
Scope safety (referral when warranted)85.7%100%
Synthesis faithfulness90.5%95.2%
Citation namespace isolation100%100%
Cross-specialist contradiction100%100%
Uncited claims57.1%66.7%
Regressionsn/anone

Routing and safety: fixed outright, every previously-failing case now passing. Synthesis: up, with the omit-my-rest-advice case now handled the way the product intends. Nothing got worse.

And then there's the headline number: citation discipline moved nine and a half points. That's real, but nowhere near the 100% the interlock was supposed to deliver. This is the part worth reading.

Why the interlock didn't finish the job

The telemetry proves the gate fires: on the still-failing cases, the verify node caught unsupported claims and forced revisions; one specialist had four claims flagged and rewritten in a single case. The gate works. The number still says 66.7%. Three reasons, in increasing order of interest:

  1. One revision isn't always enough. By design, the gate revises once and releases (latency has a budget too). Some revisions still carry residue. A cap of two is a knob we can now turn with data.
  2. A judge-infrastructure error counts as a failure. One case's judge call failed validation twice and scored zero on that criterion. The honest agent-behavior number is a bit above 66.7%, and honest reporting means saying so rather than quietly excluding it.
  3. The verifier and the judge disagree about what "cited" means. This is the finding I'll be thinking about longest. My in-graph verifier asks: is each claim supported by some retrieved source? The opus judge asks: can each claim be traced to a specific source? Its rationale on one case says it plainly: "unbroken prose followed by a bundled source list… most substantive prescriptions cannot be traced to a specific source." My product deliberately keeps prose clean and attaches citations separately (a UX choice), and the judge is telling me that at claim-level granularity, that choice is a citation-integrity gap. The 47-point judge-disagreement phenomenon isn't only a measurement artifact; it's now inside my pipeline, between my verifier and my judge.

The next decision, and it's a product decision rather than an engineering one: inline citation markers in specialist findings (closing the traceability gap, changing the reading experience), versus aligning the rubric to the separated-citations design (keeping the UX, accepting the judge's ceiling). Fences got built this cycle; this one needs an owner's call first.

One more thing that failed, for completeness: halfway through the verification runs, my API credit balance hit zero and the second provider leg died 13 cases deep with a billing error. The eval loop's most sophisticated failure mode remains "forgot to top up the account." Auto-reload is now on the operator checklist.

What I'd tell you to steal

  • Properties, not golden answers. Non-deterministic systems can't be tested for exact outputs. They can be tested for invariants.
  • Design traps for the failures you fear, but expect the exam to fail you somewhere else. My architecture aced everything it was built to prevent and flunked a discipline I assumed prompts had handled.
  • Freeze a baseline and diff. "Fixed" is a claim; "routing 95.2→100, safety 85.7→100, zero regressions, same judge" is a measurement.
  • Hold the judge constant. Cross-judge comparisons are fiction; my free-vs-opus delta was 47 points on one criterion.
  • Enforcement beats requests, and telemetry beats both. The verify gate moved the number and, more valuable, its firing data now tells me exactly where the next nine points live.
  • The loop is the artifact. Offline exam → in-code gates → online evaluators on live traffic → human review feeding new criteria. Lab, seatbelt, dashboard, mechanic.

Both repos are public: the coach (supervisor + four specialists, MIT) and the eval harness that graded it. The judge rationales quoted here are in the committed run artifacts.

References

Anthropic. (2024). Building effective agents. https://www.anthropic.com/research/building-effective-agents

LangChain. (n.d.). LangGraph documentation. https://langchain-ai.github.io/langgraph/

LangChain. (n.d.). LangSmith evaluation documentation. https://docs.smith.langchain.com

Minsky, Y. (2011). Effective ML. Jane Street Tech Blog. https://blog.janestreet.com/effective-ml-revisited/