<?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: Arthi Arumugam</title>
    <description>The latest articles on DEV Community by Arthi Arumugam (@arthiarumugam).</description>
    <link>https://dev.to/arthiarumugam</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%2F4043798%2F53d9af22-9bb3-4a0a-bd39-7c42fc263bed.jpg</url>
      <title>DEV Community: Arthi Arumugam</title>
      <link>https://dev.to/arthiarumugam</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/arthiarumugam"/>
    <language>en</language>
    <item>
      <title>The 20-minute check I run before swapping an agent to a new model</title>
      <dc:creator>Arthi Arumugam</dc:creator>
      <pubDate>Sat, 25 Jul 2026 02:08:12 +0000</pubDate>
      <link>https://dev.to/arthiarumugam/the-20-minute-check-i-run-before-swapping-an-agent-to-a-new-model-1kgi</link>
      <guid>https://dev.to/arthiarumugam/the-20-minute-check-i-run-before-swapping-an-agent-to-a-new-model-1kgi</guid>
      <description>&lt;p&gt;Every time a new model ships, the same ritual: change the model string, run the agent, read a few replies, they look good, ship it.&lt;/p&gt;

&lt;p&gt;The replies are the one part of an agent that almost never breaks visibly. What breaks is behavior. A tool call quietly disappears, an argument drifts, a refund amount loses its decimal point, and the agent keeps talking like everything is fine. I learned this the hard way when a swap made my agent stop calling &lt;code&gt;cancel_subscription&lt;/code&gt; while it kept telling users their subscription was cancelled.&lt;/p&gt;

&lt;p&gt;With a new frontier model out this week, a lot of model strings are about to change. This is the check I now run before any swap. It takes about 20 minutes and produces a real diff instead of a vibe check.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Record a baseline before you touch anything
&lt;/h2&gt;

&lt;p&gt;This is the step you cannot recover later. Once you swap, the old behavior is gone.&lt;/p&gt;

&lt;p&gt;Start a recording proxy and point your agent at it, no code changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx whatbroke-cli record &lt;span class="nt"&gt;--out&lt;/span&gt; baseline.jsonl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;OPENAI_BASE_URL&lt;/span&gt;=&lt;span class="n"&gt;http&lt;/span&gt;://&lt;span class="m"&gt;127&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;1&lt;/span&gt;:&lt;span class="m"&gt;4141&lt;/span&gt;/&lt;span class="n"&gt;v1&lt;/span&gt;     &lt;span class="c"&gt;# openai sdk
&lt;/span&gt;&lt;span class="n"&gt;ANTHROPIC_BASE_URL&lt;/span&gt;=&lt;span class="n"&gt;http&lt;/span&gt;://&lt;span class="m"&gt;127&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;1&lt;/span&gt;:&lt;span class="m"&gt;4141&lt;/span&gt;     &lt;span class="c"&gt;# anthropic sdk
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run your agent through its real scenarios. Agents are nondeterministic, so run each scenario three times and name the runs &lt;code&gt;refund-flow#1&lt;/code&gt;, &lt;code&gt;refund-flow#2&lt;/code&gt;, &lt;code&gt;refund-flow#3&lt;/code&gt; (an &lt;code&gt;x-whatbroke-run&lt;/code&gt; header per request, or &lt;code&gt;--run&lt;/code&gt;). Three samples per scenario is enough to tell flaps from real changes.&lt;/p&gt;

&lt;p&gt;Pick scenarios where the agent has to &lt;em&gt;do&lt;/em&gt; something: call tools, hit an API, write a record. Pure chat scenarios are where nothing ever visibly breaks, so they tell you the least.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Swap the model. Only the model.
&lt;/h2&gt;

&lt;p&gt;Change the model string. Nothing else. If you also want to tweak the prompt for the new model, do that as a second swap with its own diff, otherwise you will never know which change caused what.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Record the same scenarios again
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx whatbroke-cli record &lt;span class="nt"&gt;--out&lt;/span&gt; swapped.jsonl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same scenarios, same names, three runs each.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Diff
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx whatbroke-cli diff baseline.jsonl swapped.jsonl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read it top down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;breaking&lt;/strong&gt; findings first: dropped tool calls, runs that now fail, outputs that vanished. Any of these means the swap is not a drop-in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;changed&lt;/strong&gt; findings next: argument drift is the sneaky one. The tool still gets called, but with different args. Check every one by hand.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;flap rate&lt;/strong&gt; tells you whether a finding is real. &lt;code&gt;3/3&lt;/code&gt; means it happens every time. &lt;code&gt;1/3&lt;/code&gt; on something your baseline also flapped on is just your agent being itself, and the diff demotes those automatically.&lt;/li&gt;
&lt;li&gt;Cost and latency move on every swap. Regressions past a ratio get flagged (defaults 1.5x latency, 1.25x cost).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. Keep it in CI
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx whatbroke-cli diff baseline.jsonl current.jsonl &lt;span class="nt"&gt;--fail-on&lt;/span&gt; breaking
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exit code 1 on breaking changes, &lt;code&gt;--md&lt;/code&gt; for a report you can drop into a PR comment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Already swapped without a baseline?
&lt;/h2&gt;

&lt;p&gt;If your agent runs behind Langfuse, LangSmith, or anything emitting OTel GenAI spans, you already have the baseline, you just have not diffed it yet. Export last week's traces and this week's:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx whatbroke-cli import last-week-export.json &lt;span class="nt"&gt;--run&lt;/span&gt; baseline
npx whatbroke-cli import this-week-export.json &lt;span class="nt"&gt;--run&lt;/span&gt; swapped
npx whatbroke-cli diff last-week-export.whatbroke.jsonl this-week-export.whatbroke.jsonl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tool is deterministic, fully offline, MIT licensed, and your traces never leave your machine: &lt;a href="https://github.com/arthi-arumugam-git/whatbroke" rel="noopener noreferrer"&gt;https://github.com/arthi-arumugam-git/whatbroke&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For a real example of what a swap changes while the replies all look fine, I ran the same agent on a 3x smaller model and wrote up &lt;a href="https://dev.to/arthiarumugam/i-swapped-my-agent-to-a-3x-smaller-model-and-diffed-what-actually-changed-33j3"&gt;what actually changed&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you run this before your next swap and it catches something, I would genuinely love to hear what it was.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>llm</category>
      <category>testing</category>
    </item>
    <item>
      <title>I swapped my agent to a 3x smaller model and diffed what actually changed</title>
      <dc:creator>Arthi Arumugam</dc:creator>
      <pubDate>Thu, 23 Jul 2026 12:07:01 +0000</pubDate>
      <link>https://dev.to/arthiarumugam/i-swapped-my-agent-to-a-3x-smaller-model-and-diffed-what-actually-changed-33j3</link>
      <guid>https://dev.to/arthiarumugam/i-swapped-my-agent-to-a-3x-smaller-model-and-diffed-what-actually-changed-33j3</guid>
      <description>&lt;p&gt;Everyone I know has had this conversation. The agent works. The bill arrives. Someone asks whether the small model could do the job. So you swap the model string, run a few chats, the replies read fine, and you ship it.&lt;/p&gt;

&lt;p&gt;The problem is that "the replies read fine" measures the wrong layer. An agent's job is mostly tool calls. It can tell the user their subscription is cancelled while never calling &lt;code&gt;cancel_subscription&lt;/code&gt;. The words pass every vibe check you have. The behavior is broken.&lt;/p&gt;

&lt;p&gt;I wanted to see this happen on the record, with a setup anyone can reproduce for free. No cloud, no API keys, one laptop.&lt;/p&gt;

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

&lt;p&gt;A small customer-support agent for a fictional SaaS. Four scenarios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;cancel-and-refund: cancel my subscription and refund the last charge&lt;/li&gt;
&lt;li&gt;billing-question: how much am I paying, when do I renew&lt;/li&gt;
&lt;li&gt;duplicate-charge: I was charged twice, refund the duplicate&lt;/li&gt;
&lt;li&gt;cancel-only: cancel immediately, do not bill me again&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Four real tools with real schemas: &lt;code&gt;lookup_account&lt;/code&gt;, &lt;code&gt;get_billing_history&lt;/code&gt;, &lt;code&gt;cancel_subscription&lt;/code&gt;, &lt;code&gt;issue_refund&lt;/code&gt;. The tool results are stubbed, the tool calling is not. The system prompt tells the agent to never claim an action happened unless the tool call succeeded.&lt;/p&gt;

&lt;p&gt;The baseline runs on llama3.2:3b through ollama's OpenAI-compatible endpoint. Then I swap one string, &lt;code&gt;llama3.2:3b&lt;/code&gt; to &lt;code&gt;llama3.2:1b&lt;/code&gt;, and run the identical scenarios again. That is the whole change. The cheap-model move in its purest form.&lt;/p&gt;

&lt;p&gt;Because small local models are nondeterministic even at temperature 0.2, each scenario runs 3 times per model. whatbroke reads the &lt;code&gt;#1 #2 #3&lt;/code&gt; suffixes and compares every baseline sample against every after sample, so each finding comes with a rate instead of an anecdote. Anything the 3b model already flip-flopped on between its own samples gets demoted to noise, because that behavior predates the swap.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the diff found
&lt;/h2&gt;

&lt;p&gt;Totals first: 0 breaking, 3 changed, 12 info.&lt;/p&gt;

&lt;p&gt;I expected a horror story about dropped cancellations. I almost got to write one, and the tool stopped me. More on that in a second. What survived the noise filter was stranger.&lt;/p&gt;

&lt;p&gt;The finding I care about most, from the duplicate-charge scenario:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gs"&gt;! lookup_account called with different args (email, type, required, properties) (6/9 run pairs)
&lt;/span&gt;    - {"email":"maya.iyer@example.com"}
    + {"type":"object","required":["email"],"properties":{"email":{"description":"customer email","type":"string"}}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read that diff line again. In 6 of 9 run pairs, the 1b model called the tool with the tool's own JSON schema as the arguments. Not the customer's email. The parameter definition, echoed back as the parameters. The reply text that followed was calm, competent customer-service prose about processing the refund. Nothing in the transcript reads like a model that just sent &lt;code&gt;"type": "object"&lt;/code&gt; to a billing lookup.&lt;/p&gt;

&lt;p&gt;The other changed findings were latency: up 61% on duplicate-charge (17.7s to 28.5s, 3/9 pairs) and up 63% on one cancel-and-refund pair. Which is its own small lesson, because the 1b model was faster on the three scenarios it handled cleanly (the cancel flow went from 25.4s to 12.3s). Cheaper was only faster where the model didn't get confused. Where it struggled, it looped and got slower.&lt;/p&gt;

&lt;h2&gt;
  
  
  The headline that didn't survive
&lt;/h2&gt;

&lt;p&gt;Here is the finding I would have led with if I had run each scenario once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;i tool call dropped: cancel_subscription (6/9 run pairs, also flaps in the baseline)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The 1b model skipped the actual cancellation call in most pairs while telling the customer everything was cancelled. That is the nightmare scenario this whole tool exists for, and it happened. But look at the annotation: also flaps in the baseline. The 3b model dropped the same call between its own three baseline samples. The behavior predates the swap. Blaming the downgrade would have been a lie with a screenshot.&lt;/p&gt;

&lt;p&gt;Same story on cancel-only, where both models passed garbage as the account id (the 3b sent an empty object, the 1b sent the literal string "lookup_account"). Baseline flakiness, honestly labeled. My agent has a pre-existing condition, and the diff told me that instead of letting me blame the new model for it.&lt;/p&gt;

&lt;p&gt;A single before/after run would have let me cherry-pick either a horror story or an all-clear. Nine pairs per scenario gave me rates instead, and the rates changed the story.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why text diffs and vibes miss this
&lt;/h2&gt;

&lt;p&gt;A text diff of the final replies would compare two polite paragraphs and find some reworded sentences. It has no opinion on whether &lt;code&gt;issue_refund&lt;/code&gt; ran, what amount it was called with, or whether the cancel actually happened. Eyeballing transcripts is worse, because the failure mode of a smaller model is rarely gibberish. It is confident prose wrapped around a missing or malformed tool call. The reply is the last place the damage shows up.&lt;/p&gt;

&lt;p&gt;Evals help, but they answer a different question. An eval scores a version against a rubric you had to write. A behavioral diff answers what exactly changed between these two versions, at the tool-call level, with nothing to write and no judge to pay. It is the five-minute check you run before deciding whether the eval suite even needs to run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run it on your own agent
&lt;/h2&gt;

&lt;p&gt;The whole experiment is a model pull and five commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull llama3.2:3b &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ollama pull llama3.2:1b
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; whatbroke-cli
node agent.mjs llama3.2:3b traces/before.jsonl 3
node agent.mjs llama3.2:1b traces/after.jsonl 3
whatbroke diff traces/before.jsonl traces/after.jsonl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent script is 190 lines of plain fetch calls, no framework. For your own agent you do not even need the SDK: &lt;code&gt;whatbroke record&lt;/code&gt; starts a local proxy, you point &lt;code&gt;OPENAI_BASE_URL&lt;/code&gt; at it, and run your agent unchanged. Everything is offline and the traces never leave your machine.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/arthi-arumugam-git/whatbroke" rel="noopener noreferrer"&gt;https://github.com/arthi-arumugam-git/whatbroke&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you run this against your own model downgrade and it catches something, I collect these stories now.&lt;/p&gt;

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