<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Daniel Aydin</title>
    <description>The latest articles on DEV Community by Daniel Aydin (@daniellegaltech).</description>
    <link>https://dev.to/daniellegaltech</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4217874%2F1fbdbdf2-ac93-4d4f-9ab7-34d9a879a60e.png</url>
      <title>DEV Community: Daniel Aydin</title>
      <link>https://dev.to/daniellegaltech</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/daniellegaltech"/>
    <language>en</language>
    <item>
      <title>The two-lane eval: model score vs product score for an MCP agent</title>
      <dc:creator>Daniel Aydin</dc:creator>
      <pubDate>Fri, 30 Oct 2026 20:23:12 +0000</pubDate>
      <link>https://dev.to/daniellegaltech/the-two-lane-eval-model-score-vs-product-score-for-an-mcp-agent-2j53</link>
      <guid>https://dev.to/daniellegaltech/the-two-lane-eval-model-score-vs-product-score-for-an-mcp-agent-2j53</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;two-lane eval&lt;/strong&gt; runs the same cases against the model alone (model lane) and through the whole product (product lane), and keeps the scores separate.&lt;/li&gt;
&lt;li&gt;In August 2026 the same Qwen3-4B agent scored &lt;strong&gt;42/70 in the model lane&lt;/strong&gt; (failed the ship gate) and &lt;strong&gt;46/70 in the product lane&lt;/strong&gt; (passed it).&lt;/li&gt;
&lt;li&gt;One guard, the &lt;strong&gt;stall rule&lt;/strong&gt; (bounce a turn that neither calls a tool nor asks the user), earned the entire gain. The argument repair I was proud of measured zero.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm building a small on-device agent: Qwen3-4B (4-bit, MLX) driving the Lovie Formation MCP server (195 tools) for US company formation. Before training anything I wrote an eval suite. Then I spent two days optimizing a number that, by construction, could not see the product.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Suite:&lt;/strong&gt; 70 journey cases × 3 seeds, scored per dimension with a majority vote, at production sampling. A 14-case "core" subset is the ship gate (needs 12/14).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model lane (&lt;code&gt;golden&lt;/code&gt;):&lt;/strong&gt; calls &lt;code&gt;engine.generate&lt;/code&gt; directly and scores the first tool call the model emits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Baseline:&lt;/strong&gt; 42/70, core 11/14. Fails the gate.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The blind spot
&lt;/h2&gt;

&lt;p&gt;The model lane never runs the orchestrator. So none of these were visible to it: the step map, the payment-placeholder bounce, argument coercion, and two new guards I was adding. It's a clean measure of the model, which is what it was built for, and I'd been reading it as a product metric.&lt;/p&gt;

&lt;h2&gt;
  
  
  A product lane over the same fixtures
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;system-golden&lt;/code&gt; runs the real turn loop over the same fixtures and scores the call the &lt;strong&gt;server received&lt;/strong&gt;, after every repair, bounce and guard.&lt;/p&gt;

&lt;p&gt;It talks to a &lt;code&gt;DryRunMCP&lt;/code&gt;: schemas and server instructions come from the real endpoint (anonymous &lt;code&gt;tools/list&lt;/code&gt; is enough), but &lt;code&gt;callTool&lt;/code&gt; returns a synthetic success and records the request. The fixtures walk a formation through names, addresses and shareholders, which is live company data on the real server, and a measurement lane must not create real records.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Lane&lt;/th&gt;
&lt;th&gt;Total&lt;/th&gt;
&lt;th&gt;Core&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;golden&lt;/code&gt; (model)&lt;/td&gt;
&lt;td&gt;42/70&lt;/td&gt;
&lt;td&gt;11/14&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;system-golden&lt;/code&gt; (product)&lt;/td&gt;
&lt;td&gt;46/70&lt;/td&gt;
&lt;td&gt;12/14&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The model fails the gate and the product passes it. The two lanes answer different questions, so their numbers aren't a before and after.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attribution: an off switch per guard
&lt;/h2&gt;

&lt;p&gt;I added env flags (&lt;code&gt;LOVIE_NO_STALL_BOUNCE&lt;/code&gt;, &lt;code&gt;LOVIE_NO_ARG_REPAIR&lt;/code&gt;) and re-ran the product lane:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stall bounce&lt;/th&gt;
&lt;th&gt;Arg repair&lt;/th&gt;
&lt;th&gt;Total&lt;/th&gt;
&lt;th&gt;Core&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;on&lt;/td&gt;
&lt;td&gt;on&lt;/td&gt;
&lt;td&gt;46/70&lt;/td&gt;
&lt;td&gt;12/14&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;off&lt;/td&gt;
&lt;td&gt;on&lt;/td&gt;
&lt;td&gt;42/70&lt;/td&gt;
&lt;td&gt;11/14&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;on&lt;/td&gt;
&lt;td&gt;off&lt;/td&gt;
&lt;td&gt;46/70&lt;/td&gt;
&lt;td&gt;12/14&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;off&lt;/td&gt;
&lt;td&gt;off&lt;/td&gt;
&lt;td&gt;42/70&lt;/td&gt;
&lt;td&gt;11/14&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The stall bounce is the entire gain.&lt;/strong&gt; The rule: if an assistant turn neither calls a tool nor asks the user a question, bounce it once. On the earlier 78-case run, 27 of 28 baseline failures were turns that called nothing, and 14 of those neither called nor asked ("I'll make it a C corporation. Let's proceed with that." and then nothing). In this product, a turn that neither acts nor asks is never correct, so it's a deterministic trigger, not a guess about intent.&lt;/p&gt;

&lt;p&gt;Design constraints, each pinned by a test:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It never names the tool to call. Forcing the "canonical next call" would make the model invent values on cases where the right move is to present options.&lt;/li&gt;
&lt;li&gt;It stays clear of legitimate endings: a question, a refusal that asks instead of inventing, a closing summary after a tool ran, plain chat, a denied call.&lt;/li&gt;
&lt;li&gt;It fires at most once per turn, so a second stall ends the turn instead of looping.&lt;/li&gt;
&lt;li&gt;The refusal category held at 8/8. The bounce never pushed the model into inventing an address.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The fix that measured zero
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ArgumentRepair&lt;/code&gt; reshapes tool arguments from the tool's own &lt;code&gt;inputSchema&lt;/code&gt; before the call is sent: flat ↔ wrapped values, nested objects and arrays, and enum canonicalization when the match against the schema's list is unambiguous ("LLC" → &lt;code&gt;ENTITY_TYPE_LLC&lt;/code&gt;). Invariant: it only re-shapes what the model sent, never adds or drops a field, and leaves an ambiguous enum alone.&lt;/p&gt;

&lt;p&gt;It measured &lt;strong&gt;zero&lt;/strong&gt;. No category moved. Earlier I'd claimed a smoke run as proof it worked because &lt;code&gt;ENTITY_TYPE_LLC&lt;/code&gt; showed up in the executed call. The model had emitted that value itself. I saw a correct output and credited my code without checking whether it did anything.&lt;/p&gt;

&lt;p&gt;It stays for a maintenance reason, stated as such: it replaces a hard-coded four-field list that grew after live incidents with a rule derived from the tool contract.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed in the process
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Two lanes, never mixed: the model lane answers "did training change the model?", the product lane answers "does the product work?"&lt;/li&gt;
&lt;li&gt;The ship gate reads the product lane.&lt;/li&gt;
&lt;li&gt;Every new guard ships with an off switch, so its effect is measured, not assumed.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you're evaluating an LLM product (legal or otherwise), ask whether a reported score came from the model or the product, and which component earns it.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What is the stall rule in AI agents?&lt;/strong&gt;&lt;br&gt;
It bounces an assistant turn once when the turn neither calls a tool nor asks the user a question. Here it moved the product lane from 42/70 to 46/70 on its own.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why separate the model lane from the product lane?&lt;/strong&gt;&lt;br&gt;
A model-only eval can't see orchestrator guards, so the same model can fail a model gate and pass a product gate. The numbers answer different questions and aren't comparable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do you attribute a gain to a specific guardrail?&lt;/strong&gt;&lt;br&gt;
Ship every guard with an off switch (an env flag) and re-run the same seeds with each one disabled.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who is Daniel Aydin?&lt;/strong&gt;&lt;br&gt;
I'm Daniel Aydin. I lead LegalTech products at Lovie in San Francisco and host Legal AI Vibe Lab, a recurring San Francisco meetup where lawyers and engineers build legal AI together. I trained as a lawyer in Türkiye and I'm not a US-licensed attorney.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Daniel Aydin is General Manager of LegalTech Products at Lovie (&lt;a href="https://www.lovie.co" rel="noopener noreferrer"&gt;lovie.co&lt;/a&gt;). He trained as a lawyer in Türkiye, is not a US-licensed attorney, and hosts Legal AI Vibe Lab at SF Vibe House in San Francisco. Lovie Formation (&lt;a href="https://www.lovie.co/formation" rel="noopener noreferrer"&gt;lovie.co/formation&lt;/a&gt;) is an AI-native US company formation service with an MCP server that lets AI agents like Claude and ChatGPT start a company; a person checks every filing before it goes to a state.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://danielaydin.substack.com/p/did-they-test-the-model-or-the-product" rel="noopener noreferrer"&gt;Substack&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>llm</category>
      <category>ai</category>
      <category>testing</category>
      <category>machinelearning</category>
    </item>
  </channel>
</rss>
