<?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: Saeed Allam</title>
    <description>The latest articles on DEV Community by Saeed Allam (@saeed_allam_5f71578da546b).</description>
    <link>https://dev.to/saeed_allam_5f71578da546b</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%2F4232737%2F6e351db3-f680-4619-a7fa-5805b294691a.jpg</url>
      <title>DEV Community: Saeed Allam</title>
      <link>https://dev.to/saeed_allam_5f71578da546b</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/saeed_allam_5f71578da546b"/>
    <language>en</language>
    <item>
      <title>My AI agent said "Forecast for undefined" and no test caught it, so I built a chaos testing tool</title>
      <dc:creator>Saeed Allam</dc:creator>
      <pubDate>Sat, 07 Nov 2026 19:55:07 +0000</pubDate>
      <link>https://dev.to/saeed_allam_5f71578da546b/my-ai-agent-said-forecast-for-undefined-and-no-test-caught-it-so-i-built-a-chaos-testing-tool-2adk</link>
      <guid>https://dev.to/saeed_allam_5f71578da546b/my-ai-agent-said-forecast-for-undefined-and-no-test-caught-it-so-i-built-a-chaos-testing-tool-2adk</guid>
      <description>&lt;p&gt;Every AI agent demo looks great. The tools answer instantly, the data is clean, and the agent writes a confident, correct reply.&lt;/p&gt;

&lt;p&gt;Then you ship it, and the tools start doing what tools do in production: they time out, they rate limit, and sometimes they answer &lt;code&gt;200 OK&lt;/code&gt; with an empty body.&lt;/p&gt;

&lt;p&gt;I wanted to know what my agent does in those moments. I couldn't find a simple way to test it in TypeScript, so I built one: &lt;strong&gt;&lt;a href="https://github.com/saeedallam/agentquake" rel="noopener noreferrer"&gt;agentQuake&lt;/a&gt;&lt;/strong&gt;, an open source library that makes your agent's tools fail on purpose and tells you how the agent handled it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5g2jd1nnmewz1ccw28xo.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5g2jd1nnmewz1ccw28xo.gif" alt="Terminal demo: a naive agent replies &amp;quot;Forecast for undefined&amp;quot; and agentQuake flags LEAKED GARBAGE, while a careful agent admits it could not get the forecast" width="732" height="650"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The failure that scared me
&lt;/h2&gt;

&lt;p&gt;Loud failures are mostly fine. If a tool throws a 503, the AI SDK passes the error to the model, and most models will say "sorry, I couldn't get that".&lt;/p&gt;

&lt;p&gt;The quiet ones are the problem. Here's a Vercel AI SDK tool that passes the API's answer straight through:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;getForecast&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Daily forecast for a city&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;days&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;getForecast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;days&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If the API returns &lt;code&gt;{}&lt;/code&gt; with status 200, nothing throws. The model gets an empty object, and in my example the agent happily replies:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Forecast for undefined: no data.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;No exception, no error log, every test green. And this isn't just my paranoia: a 2026 research paper on agent robustness (&lt;a href="https://www.developersdigest.tech/blog/agentchaos-fault-injection-agent-robustness" rel="noopener noreferrer"&gt;AgentChaos, ASE 2026&lt;/a&gt;) found that empty or truncated responses hurt agents about as much as crashes, and are much harder to notice after the fact.&lt;/p&gt;
&lt;h2&gt;
  
  
  Testing it like any other unit test
&lt;/h2&gt;

&lt;p&gt;agentQuake runs inside Vitest or Jest. No server, no database:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i &lt;span class="nt"&gt;-D&lt;/span&gt; agentquake
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;quake&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;agentquake&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;survives a flaky weather API&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;quake&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;preset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;silent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nf"&gt;weatherAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;What is the weather in Cairo?&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;getWeather&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;getWeather&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getWeather&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;falseSuccess&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;run.tool()&lt;/code&gt; wraps your tool function. Depending on the preset or scenario, a call might be slowed down, fail with a 503 or 429, time out, or come back as a &lt;code&gt;200&lt;/code&gt; with an empty or cut-off body.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;run.agent()&lt;/code&gt; wraps the whole run, so agentQuake sees both sides: what the tools did and what the agent finally said. That's what makes these two checks possible:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;falseSuccess&lt;/code&gt;&lt;/strong&gt;: a tool failed, nothing recovered after it, and the answer still reads like a success. A retry that works, or a fallback to another tool, counts as recovering.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;leakedGarbage&lt;/code&gt;&lt;/strong&gt;: a tool's answer was corrupted and &lt;code&gt;undefined&lt;/code&gt;, &lt;code&gt;null&lt;/code&gt; or &lt;code&gt;NaN&lt;/code&gt; ended up in the reply.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a tool really matters, mark it. Then an agent that lost its booking, called a few other tools and still said "Booked!" gets caught:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bookFlight&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bookFlight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;critical&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Same seed, same faults
&lt;/h2&gt;

&lt;p&gt;Agents are already non-deterministic, so the chaos shouldn't be. Whether a fault fires depends only on the seed, the call number and the fault id. A failing test prints its seed, and you can replay exactly the same faults while you fix it.&lt;/p&gt;
&lt;h2&gt;
  
  
  What fixed my agent
&lt;/h2&gt;

&lt;p&gt;The careful version of the example agent checks the tool's answer with zod before the model ever sees it, retries once, and otherwise tells the model the tool failed:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;forecastSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;days&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;day&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="na"&gt;tempC&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;})).&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;checkedForecast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;getForecast&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;days&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;forecast&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;forecastSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getForecast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;days&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;forecast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;city&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;forecast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;days&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;days&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;forecast&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// try once more&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;The forecast service gave an unusable answer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The lesson surprised me: the fix wasn't a smarter model or a better prompt. It was treating tool output as untrusted input, the same way we treat user input.&lt;/p&gt;
&lt;h2&gt;
  
  
  Not only for functions
&lt;/h2&gt;

&lt;p&gt;If your tools are HTTP APIs or MCP servers, the same package has a command line:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx agentquake proxy &lt;span class="nt"&gt;--preset&lt;/span&gt; flaky &lt;span class="nt"&gt;--tool&lt;/span&gt; &lt;span class="nv"&gt;weather&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://api.weather.com
npx agentquake stdio &lt;span class="nt"&gt;--preset&lt;/span&gt; silent &lt;span class="nt"&gt;--&lt;/span&gt; node weather-server.js   &lt;span class="c"&gt;# MCP over stdio&lt;/span&gt;
npx agentquake report
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;The repo has a &lt;a href="https://github.com/saeedallam/agentquake/tree/main/examples/weather-agent" rel="noopener noreferrer"&gt;Vercel AI SDK example&lt;/a&gt; with a naive and a careful agent. It runs without an API key:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/saeedallam/agentquake
&lt;span class="nb"&gt;cd &lt;/span&gt;agentquake
npm ci &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm run example
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;It's early (0.4, Apache-2.0). The default false-success check looks for English failure words, so pass your own check for other languages. Scoring across many runs is next.&lt;/p&gt;

&lt;p&gt;I'd really like to hear what failures you've seen agents mishandle in production. Those are the faults I want to add next.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/saeedallam" rel="noopener noreferrer"&gt;
        saeedallam
      &lt;/a&gt; / &lt;a href="https://github.com/saeedallam/agentQuake" rel="noopener noreferrer"&gt;
        agentQuake
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Chaos testing for AI agents
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;
  
    
    &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fsaeedallam%2FagentQuake%2FHEAD%2Fassets%2Fwordmark-light.svg" width="420" alt="agentQuake"&gt;
  
&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;b&gt;Chaos testing for AI agents.&lt;/b&gt;&lt;br&gt;
Break your agent before production does.&lt;/p&gt;
&lt;p&gt;
  &lt;a href="https://www.npmjs.com/package/agentquake" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/7b3067ecf9d983731a369752169a6eab8c1f79a8246d91238e16abdbd3c17213/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f762f6167656e747175616b653f636f6c6f723d464635413146266c6162656c3d6e706d" alt="npm version"&gt;&lt;/a&gt;
  &lt;a href="https://github.com/saeedallam/agentquake/actions/workflows/ci.yml" rel="noopener noreferrer"&gt;&lt;img src="https://github.com/saeedallam/agentquake/actions/workflows/ci.yml/badge.svg" alt="CI"&gt;&lt;/a&gt;
  &lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/3ad5a869f3b43dd50313ef05c91b885ba037d366eb72e7104458a5c6a3620379/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7374617475732d6561726c792d464635413146"&gt;&lt;img src="https://camo.githubusercontent.com/3ad5a869f3b43dd50313ef05c91b885ba037d366eb72e7104458a5c6a3620379/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7374617475732d6561726c792d464635413146" alt="Status: early"&gt;&lt;/a&gt;
  &lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/ed94d2f4715dee77567e9d502b4a08c8e51b679078c0ea99972aa17e8cbfbe25/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6e6f64652d32322e31322532422d3546413034453f6c6f676f3d6e6f6465646f746a73266c6f676f436f6c6f723d7768697465"&gt;&lt;img src="https://camo.githubusercontent.com/ed94d2f4715dee77567e9d502b4a08c8e51b679078c0ea99972aa17e8cbfbe25/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6e6f64652d32322e31322532422d3546413034453f6c6f676f3d6e6f6465646f746a73266c6f676f436f6c6f723d7768697465" alt="Node.js 22.12+"&gt;&lt;/a&gt;
  &lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/a9ef9f7ea399328e404955986813b224897ebb1aa9439758a7f92ea39de06fce/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f547970655363726970742d362d3331373843363f6c6f676f3d74797065736372697074266c6f676f436f6c6f723d7768697465"&gt;&lt;img src="https://camo.githubusercontent.com/a9ef9f7ea399328e404955986813b224897ebb1aa9439758a7f92ea39de06fce/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f547970655363726970742d362d3331373843363f6c6f676f3d74797065736372697074266c6f676f436f6c6f723d7768697465" alt="TypeScript 6"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;
  &lt;a href="https://github.com/saeedallam/agentQuake#quick-start" rel="noopener noreferrer"&gt;Quick start&lt;/a&gt; ·
  &lt;a href="https://github.com/saeedallam/agentQuake#command-line" rel="noopener noreferrer"&gt;Command line&lt;/a&gt; ·
  &lt;a href="https://github.com/saeedallam/agentQuake#scenarios" rel="noopener noreferrer"&gt;Scenarios&lt;/a&gt; ·
  &lt;a href="https://github.com/saeedallam/agentQuake#team-server" rel="noopener noreferrer"&gt;Team server&lt;/a&gt; ·
  &lt;a href="https://github.com/saeedallam/agentQuake#roadmap" rel="noopener noreferrer"&gt;Roadmap&lt;/a&gt; ·
  &lt;a href="https://github.com/saeedallam/agentQuake/docs/adr" rel="noopener noreferrer"&gt;Design decisions&lt;/a&gt;
&lt;/p&gt;




&lt;p&gt;agentQuake makes your agent's tools fail on purpose (slow answers, errors
timeouts) and records what the agent does about it, so you can see whether
it copes, retries, loops or claims a success that never happened.&lt;/p&gt;
&lt;p&gt;
  &lt;a rel="noopener noreferrer" href="https://github.com/saeedallam/agentQuake/assets/demo.gif"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fsaeedallam%2FagentQuake%2FHEAD%2Fassets%2Fdemo.gif" width="732" alt="Terminal demo: the same weather agent written two ways. With an empty tool answer, the naive agent replies 'Forecast for undefined' and agentQuake flags LEAKED GARBAGE; the careful agent admits it could not get the forecast."&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/saeedallam/agentQuake/examples/weather-agent" rel="noopener noreferrer"&gt;weather-agent example&lt;/a&gt;: &lt;code&gt;npm ci &amp;amp;&amp;amp; npm run example&lt;/code&gt;. No API key needed.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Quick start&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;Start inside your tests, with no server and no database:&lt;/p&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npm i -D agentquake&lt;/pre&gt;

&lt;/div&gt;

&lt;div class="highlight highlight-source-ts notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt; &lt;span class="pl-s1"&gt;quake&lt;/span&gt; &lt;span class="pl-kos"&gt;}&lt;/span&gt; &lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s"&gt;'agentquake'&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
&lt;span class="pl-en"&gt;it&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;'survives a flaky weather API'&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-k"&gt;async&lt;/span&gt; &lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt; &lt;span class="pl-c1"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt;
  &lt;span class="pl-k"&gt;const&lt;/span&gt; &lt;span class="pl-s1"&gt;run&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-en"&gt;quake&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-kos"&gt;{&lt;/span&gt; &lt;span class="pl-c1"&gt;preset&lt;/span&gt;: &lt;span class="pl-s"&gt;'flaky'&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-c1"&gt;seed&lt;/span&gt;: &lt;span class="pl-c1"&gt;42&lt;/span&gt; &lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;

  &lt;span class="pl-k"&gt;await&lt;/span&gt; &lt;span class="pl-s1"&gt;run&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;agent&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt; &lt;span class="pl-c1"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="pl-en"&gt;weatherAgent&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;'What is the weather in Cairo?'&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt;
      &lt;span class="pl-c1"&gt;getWeather&lt;/span&gt;: &lt;span class="pl-s1"&gt;run&lt;/span&gt;&lt;/pre&gt;…
&lt;/div&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/saeedallam/agentQuake" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



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