<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged agent-evaluation</title>
<link>https://coderlegion.com/tag/agent-evaluation</link>
<description>Powered by Question2Answer</description>
<item>
<title>How to evaluate agents with one trace across MCP, A2A and Realtime</title>
<link>https://coderlegion.com/31093/how-to-evaluate-agents-with-one-trace-across-mcp-a2a-and-realtime</link>
<description>&lt;p&gt;A support agent passes every eval in staging. In production the same agent is reached three ways: from an IDE over MCP, from the web app over the OpenAI Responses API, and from a triage agent over A2A. The dashboard stays green for weeks. Then a customer escalation shows the triage agent handed over a ticket without the customer's region, and the support agent quoted the refund policy for the wrong country. No evaluator flagged it, because no evaluator ever looked at the hop.&lt;/p&gt;
&lt;p&gt;The reason is structural. Eval is a property of the path a request took, and the protocol defines the path. Most eval stacks were written against one surface, usually chat completions, so they score whatever still looks like a chat completion and quietly skip the rest. A protocol-neutral eval stack scores the path instead, whichever protocol carried the request.&lt;/p&gt;
&lt;h4 id=&quot;every-protocol-adds-seams-to-the-path&quot;&gt;Every protocol adds seams to the path&lt;/h4&gt;
&lt;p&gt;A plain chat completion has one path: prompt in, response out. One rubric can grade it. Each protocol you add forks that path into more decision points, and each decision point needs its own check.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;MCP adds a tool catalog.&lt;/strong&gt; The path becomes prompt, tool descriptor list, tool selection, call arguments, tool result, final answer. Score each tool call against policy, and score the catalog as a whole, because a poisoned descriptor looks fine at call time and only shows up against the full catalog.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A2A turns the path into a tree.&lt;/strong&gt; Every hop needs three answers: did the calling agent pass enough context, did the called agent stay in scope, and did the returned artifact match the task that was asked for.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Responses API puts three outputs in one envelope.&lt;/strong&gt; Structured JSON gets graded against a schema, streamed text gets graded as it arrives, and a tool call gets graded against a per-tool policy. One end-to-end pass mixes three things that share nothing except the wrapper.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Realtime has no final message.&lt;/strong&gt; Audio streams in both directions, so checks have to run mid-stream, every N tokens or N audio chunks, with a stop or disclaimer action when a rule fires. The end-to-end budget sits below 500ms, which caps how heavy each check can be.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Inline vision needs a second judge.&lt;/strong&gt; When images ride in the same message as tool blocks, the text portion and the image portion need different rubrics, and the image rubric needs a judge that can open the image.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Session state spans turns.&lt;/strong&gt; On a framework with explicit session state, the evaluator has to read state across turns instead of grading each turn alone.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;trace-fidelity-caps-eval-fidelity&quot;&gt;Trace fidelity caps eval fidelity&lt;/h4&gt;
&lt;p&gt;An evaluator can only score what the trace can represent. If your trace has no way to express an agent-to-agent hop or a streaming chunk, no rubric downstream will catch a failure inside one.&lt;/p&gt;
&lt;p&gt;So the first fix is in instrumentation, before any rubric work:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Model both sides of a hop.&lt;/strong&gt; Emit one span kind on the calling agent and another on the called agent, and carry the trace ID across the wire so the whole multi-agent path lands in one trace.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Give each modality its own span kind.&lt;/strong&gt; Voice, image and computer-use spans should be distinguishable from text spans, so each one reaches an evaluator built for that modality.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Record guardrail and evaluator results as spans.&lt;/strong&gt; When a guardrail decision and an evaluator score sit in the same trace as the model call, you can see which check ran on which step.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The MCP spec helps here. The 2026-07-28 revision documents trace context propagation through &lt;code&gt;_meta&lt;/code&gt; (&lt;code&gt;traceparent&lt;/code&gt;, &lt;code&gt;tracestate&lt;/code&gt;, &lt;code&gt;baggage&lt;/code&gt;), so MCP tool calls can join the same trace as the rest of the agent loop.&lt;/p&gt;
&lt;h4 id=&quot;normalize-the-spans-then-write-each-rubric-once&quot;&gt;Normalize the spans, then write each rubric once&lt;/h4&gt;
&lt;p&gt;The same agent identity shows up differently on every protocol. If you write a groundedness check against raw MCP payloads and another against raw Responses payloads, you now maintain two checks that drift apart.&lt;/p&gt;
&lt;p&gt;Normalize first. Map every span to one semantic convention, such as the OpenTelemetry GenAI conventions, before any evaluator reads it. Then write rubrics against the normalized trace. One groundedness rubric can then grade a RAG answer that arrived over the Responses API and a RAG answer that arrived over MCP, and one task-completion rubric can grade the support agent no matter which client reached it.&lt;/p&gt;
&lt;p&gt;This is the same move observability teams made with OpenTelemetry. Teams that locked to one vendor's tracing format rebuilt their instrumentation every time they switched vendors. Agent protocols are repeating that pattern one layer up.&lt;/p&gt;
&lt;h4 id=&quot;gate-releases-per-protocol&quot;&gt;Gate releases per protocol&lt;/h4&gt;
&lt;p&gt;A single overall score hides which surface regressed. A drop in the MCP tool-audit rubric and a drop in the Realtime guardrail are different failures with different fixes.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Keep a regression dataset per protocol surface.&lt;/strong&gt; Run the matching rubric set against it on every change.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Set a threshold per protocol.&lt;/strong&gt; Fail the release when any one surface drops below its own threshold, even if the blended number looks fine.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Key each suite on protocol and version.&lt;/strong&gt; MCP's 2026-07-28 revision removed the &lt;code&gt;initialize&lt;/code&gt; handshake, so every request now carries its protocol version in &lt;code&gt;_meta&lt;/code&gt;, and the new deprecation policy gives every deprecated feature a minimum twelve-month window. Tag each regression case with the revision it ran on, and keep the tests for deprecated features until their window closes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Keep the rubric sets in the repo as code.&lt;/strong&gt; Reviewers should be able to read exactly what gets scored on each surface.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;a-five-step-readiness-audit&quot;&gt;A five-step readiness audit&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Inventory every surface in production.&lt;/strong&gt; MCP servers, A2A endpoints, streaming, voice, vision and computer-use, with the protocol version and client library for each. Most teams find one they did not plan for, usually an MCP server a developer installed last sprint.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Instrument and pull one real trace per surface.&lt;/strong&gt; Confirm the hop spans appear on both sides with the propagated trace ID, and that voice and image calls emit their own span kinds. Fix gaps before writing rubrics.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Write the rubric set for each protocol.&lt;/strong&gt; Per-tool audit on MCP, per-hop audit on A2A, split rubrics on the Responses API, mid-stream checks on Realtime, an image-capable judge for vision.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Run a per-protocol regression suite and gate on per-protocol thresholds.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cluster production failures by protocol.&lt;/strong&gt; Failures that look like noise one at a time often form a pattern once you group them by surface.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you only have half a day, do step 1. The list is usually longer than the team expects, and it tells you which evaluators are scoring the wrong path today.&lt;/p&gt;
&lt;h4 id=&quot;pitfalls-that-keep-showing-up&quot;&gt;Pitfalls that keep showing up&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Evaluators hard-coded to the chat completions shape.&lt;/strong&gt; Every new protocol triggers a rewrite.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scoring each agent in isolation.&lt;/strong&gt; The end-to-end view looks green while a handoff in the middle dropped context.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Post-call checks only on streaming surfaces.&lt;/strong&gt; By the time the evaluator sees the output, the risky tokens are already on the wire.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Accepting tool descriptors from any MCP server without a scan.&lt;/strong&gt; An untrusted server can change what your agent does, and the eval layer never sees it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scoring voice and computer-use on text alone.&lt;/strong&gt; Errors at the ASR layer and the vision layer stay invisible.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The span kinds, the rubric set for each of the six protocols and the full audit are in the &lt;a rel=&quot;nofollow&quot; href=&quot;https://futureagi.com/blog/llm-eval-vs-mcp-protocol-evolution-2026/?utm_source=fagiCoderlegion&amp;amp;utm_medium=organic&amp;amp;utm_campaign=blogdistribution&quot;&gt;longer protocol-neutral eval playbook&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;New protocols and revisions will keep arriving. Score the path the request took, and each new surface plugs into a trace model and a rubric library you already maintain.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31093/how-to-evaluate-agents-with-one-trace-across-mcp-a2a-and-realtime</guid>
<pubDate>Wed, 14 Oct 2026 16:22:41 +0000</pubDate>
</item>
<item>
<title>The four axes generic RAG eval forgets for search agents</title>
<link>https://coderlegion.com/24823/the-four-axes-generic-rag-eval-forgets-for-search-agents</link>
<description>&lt;p&gt;A search-augmented agent looks like RAG until the citation list breaks. The first user clicks through, lands on a 404, and the trust budget spends in one impression. Three months of eval work, groundedness and context adherence and factual accuracy all green, did not catch it.&lt;/p&gt;
&lt;p&gt;The reason is structural. Generic RAG eval was built for a frozen vector store and a stable ground truth. Search agents have neither. The corpus is the live web, the agent writes the literal query string that drives what comes back, sources span government filings and LinkedIn takes in one result set, and users click the URLs. Skip the four axes unique to this shape (query quality, source freshness, source diversity, citation validity) and you ship an agent that confidently cites stale or fabricated URLs. The rest of RAG eval still applies, but the rest of RAG eval was already passing. There is a longer &lt;a rel=&quot;nofollow&quot; href=&quot;https://futureagi.com/blog/evaluating-search-augmented-agents-2026/?utm_source=fagiCoderlegion&amp;amp;utm_medium=organic&amp;amp;utm_campaign=blogdistribution&quot;&gt;version with the rubric code&lt;/a&gt; if you want it.&lt;/p&gt;
&lt;p&gt;Three differences pull this out of the RAG bucket. The corpus is not yours: two runs of the same query twelve hours apart return different URLs, so reproducibility lives in a snapshot, not the results page. The query is the lever: search is sensitive to the literal string the agent writes, and a rewrite that drops a constraint returns a clean-looking set that misses it, which no synthesis prompt recovers from. And the user verifies: a citation is a URL the user clicks, so a fabricated or 404 link is a one-click trust failure that groundedness against the retrieved set never sees.&lt;/p&gt;
&lt;p&gt;Score the four axes per query, not aggregated. A single number hides which one collapsed, and the diagnostic value is in the vector.&lt;/p&gt;
&lt;h3 id=&quot;axis-1-query-quality&quot;&gt;Axis 1: query quality&lt;/h3&gt;
&lt;p&gt;The agent turns the user's prompt into a search string, and that string is what the engine literally reads. Three failure modes show up in almost every run.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Constraint drop.&lt;/strong&gt; &quot;Laptops with battery over 10 hours&quot; compresses to &quot;best laptops 2026 for ML,&quot; and the results average five-hour batteries. Groundedness is fine because the brief grounded itself in the retrieved set. The constraint is gone.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Entity loss.&lt;/strong&gt; A question about a specific company or regulation abstracts to the category, and the brief answers a different question well.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Over-broad rewrite.&lt;/strong&gt; A narrow factual lookup expands into a topic, the search returns a Wikipedia overview, and the brief paraphrases it and misses the fact.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Score it with a judge that takes the original prompt and the rewrite as a pair and rates constraint preservation and entity recall 1 to 5, penalizing unnecessary expansion as much as a drop. Run this over a week of traffic and the first cluster is usually &quot;rewriter dropped the year, version, or region&quot; on roughly one in eight queries. Cheapest fix in the loop, and the one most teams skip.&lt;/p&gt;
&lt;h3 id=&quot;axis-2-source-freshness&quot;&gt;Axis 2: source freshness&lt;/h3&gt;
&lt;p&gt;Freshness is a function of the question, not a global setting. &quot;Capital of France&quot; is insensitive. &quot;Current price of X&quot; is real-time. &quot;FDA guidance on AI as a medical device&quot; is quarter-sensitive. The agent has to decide the regime and behave: prefer recent sources, refuse stale ones, and add a recency caveat when the answer depends on current data.&lt;/p&gt;
&lt;p&gt;Two checks carry it. Classify the freshness regime per query (insensitive, slow, fast, real-time) and attach it as a span attribute. Then attach a recency value per source, take the median recency of the cited sources, and fail when it exceeds the floor for that regime. The stale-fact bug is the one users notice last and complain about loudest: correct the day the source was indexed, wrong every day after, and invisible to every other metric.&lt;/p&gt;
&lt;h3 id=&quot;axis-3-source-diversity&quot;&gt;Axis 3: source diversity&lt;/h3&gt;
&lt;p&gt;A general search returns Wikipedia, vendor blogs, news, government docs, and SEO spam in one set, and the agent cites three to five. The failure is monoculture: every citation from one domain, or a brief that reads like consensus when it summarizes one tab.&lt;/p&gt;
&lt;p&gt;Three checks cover it, and the first two are deterministic and run in microseconds:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Unique-domain count&lt;/strong&gt; per citation set, with a floor by question class. A date lookup can be tight; a synthesis question needs four or more.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Per-domain cap.&lt;/strong&gt; No domain contributes more than two citations, unless the question is about that domain (an EU AI Act question can cite eur-lex repeatedly).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Primary-vs-secondary ratio.&lt;/strong&gt; Classify each citation as primary (a filing, paper, or announcement) or secondary (commentary, aggregator). A regulatory question with zero primary citations is a failure however grounded the brief is.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;axis-4-citation-validity&quot;&gt;Axis 4: citation validity&lt;/h3&gt;
&lt;p&gt;The highest-signal cheap check in the whole stack, and the one most teams find only after the first complaint. Three sub-checks compose it.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The URL resolves.&lt;/strong&gt; HEAD-request every citation; a non-2xx or a redirect to a different domain is a fail. Tens of milliseconds, and it rejects fabricated and stale links before the user sees them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The passage exists on the page.&lt;/strong&gt; Fetch the body and confirm the quoted or paraphrased passage is actually there. A paraphrase that drifts past a similarity threshold is a fail. This catches a real URL with an invented quote.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The claim is supported by that passage.&lt;/strong&gt; Groundedness and chunk attribution, but per claim, not per answer. A brief can be 0.94 grounded as a whole and 0.61 aligned per claim, and users only see the per-claim view.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Wire it as two stages: the deterministic checks (resolves, passage present) as a hard gate, the claim-passage entailment as a judge on the synthesis span. A citation that fails any check is a hard reject: log it, regenerate the answer with that URL excluded, and re-run the gate. Teams that wire this stop seeing the &quot;user clicked a 404&quot; failure almost immediately.&lt;/p&gt;
&lt;h3 id=&quot;cache-the-snapshot-so-ci-does-not-flake&quot;&gt;Cache the snapshot so CI does not flake&lt;/h3&gt;
&lt;p&gt;Two runs of the same prompt hours apart return different URLs, so without a per-test-case cache the gate flakes for reasons unrelated to the agent and the team stops trusting it. Cache three layers: the search call (query and full result JSON), the page fetch (URL, body, timestamp), and the extraction (cleaned text, date, anchors). Replay every run from cache, and re-snapshot deliberately on a schedule that fits the domain (weekly for news, monthly for stable how-to agents), treating each re-snapshot as a new golden-set version with its own baseline.&lt;/p&gt;
&lt;h3 id=&quot;trace-it-so-a-bad-answer-is-debuggable&quot;&gt;Trace it so a bad answer is debuggable&lt;/h3&gt;
&lt;p&gt;The eval scores attach to spans, and the spans are how you debug. Three span kinds carry the load: a retriever span per search call, carrying the literal query the agent sent plus per-source URL, recency, and authority; a tool span for the wire call to the search API, so latency and cost land on the right node; and an LLM span for the synthesis. Splitting the search call from the retriever span keeps the query-quality input and the network cost on separate, queryable nodes.&lt;/p&gt;
&lt;p&gt;Run the four axes per query and read the vector. The axis that drops is the bug, and the standard RAG metrics keep running alongside because they are necessary, just not sufficient.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/24823/the-four-axes-generic-rag-eval-forgets-for-search-agents</guid>
<pubDate>Tue, 18 Aug 2026 09:20:10 +0000</pubDate>
</item>
</channel>
</rss>