<?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: Foxy_Grandpa</title>
    <description>The latest articles on DEV Community by Foxy_Grandpa (@foxy_grandpa).</description>
    <link>https://dev.to/foxy_grandpa</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%2F4043925%2F8c3d47db-0a00-44fc-8618-50d9ff1a6e4a.png</url>
      <title>DEV Community: Foxy_Grandpa</title>
      <link>https://dev.to/foxy_grandpa</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/foxy_grandpa"/>
    <language>en</language>
    <item>
      <title>Your agent's output is invalid and you'll find out the expensive way</title>
      <dc:creator>Foxy_Grandpa</dc:creator>
      <pubDate>Thu, 23 Jul 2026 13:47:21 +0000</pubDate>
      <link>https://dev.to/foxy_grandpa/your-agents-output-is-invalid-and-youll-find-out-the-expensive-way-269d</link>
      <guid>https://dev.to/foxy_grandpa/your-agents-output-is-invalid-and-youll-find-out-the-expensive-way-269d</guid>
      <description>&lt;p&gt;I build agent pipelines on the side. The failure mode that kept costing me actual money was never the dramatic one — it was always downstream and quiet: the agent generates JSON, an API response, or a SQL query; something subtly invalid slips through (a missing required field, a string where a number should be, a SELEKT typo three joins deep); and I pay for it later. A broken pipeline step, a corrupted record, or my personal favorite — an LLM retry loop that burns tokens re-generating the whole thing because one field was wrong.&lt;br&gt;
Why "just ask the model to check" doesn't work&lt;br&gt;
The obvious fix is self-review: feed the output back and ask the model to verify it. Two problems.&lt;br&gt;
First, it's unreliable in exactly the wrong way. The model that made the mistake is drawing from the same distribution when it reviews — it misses its own errors at a rate that's hard to budget for. Validation is the one place where "usually right" is not a property you want.&lt;br&gt;
Second, it's expensive. A full model call to check a JSON object against a schema is like hiring a lawyer to check whether a number is even. Schema conformance is a solved, deterministic problem — it should cost milliseconds and fractions of a cent, and give the same answer every time.&lt;br&gt;
What I built&lt;br&gt;
machinegrade validate is a small service that checks agent-generated artifacts against a contract before you act on them:&lt;/p&gt;

&lt;p&gt;json_schema — artifact vs. a JSON Schema, all errors collected&lt;br&gt;
openapi_response — response body vs. the response schema for a given path + method + status in an OpenAPI spec&lt;br&gt;
sql — syntax check per dialect&lt;/p&gt;

&lt;p&gt;The design decision I care most about: a verdict is not an error. An invalid artifact returns HTTP 200 with a structured verdict — because for an agent, "invalid" is a normal, expected outcome it needs to act on, not an exception:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"valid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"errors"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/age"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"must be number"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"fix_hint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Change the value at /age to a number (got string)."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"latency_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every error has a path, a stable code, and a fix_hint written to be consumed by the agent on the retry — so the retry is targeted ("fix /age") instead of "regenerate everything and hope".&lt;br&gt;
Actual HTTP errors (bad key, over limit, unsupported type) are typed JSON too — {code, message, hint, docs_url} — never free-form strings. If you've ever written if "rate limit" in error_message you know why.&lt;br&gt;
Try it in 30 seconds&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Get a key (free tier: 500 calls/month)&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.machinegrade.dev/keys &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'content-type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"email": "you@example.com"}'&lt;/span&gt;

&lt;span class="c"&gt;# Validate&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.machinegrade.dev/v1/validate &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'content-type: application/json'&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'X-Api-Key: sk_...'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"type": "json_schema",
       "artifact": {"name": "Ada", "age": "thirty"},
       "contract": {"schema": {"type": "object", "required": ["name", "age"],
         "properties": {"name": {"type": "string"}, "age": {"type": "number"}}}}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For MCP there are two ways in: a remote endpoint you can point a client at directly — &lt;a href="https://api.machinegrade.dev/mcp" rel="noopener noreferrer"&gt;https://api.machinegrade.dev/mcp&lt;/a&gt; (streamable HTTP; tools/list works anonymously, tools/call needs the key) — or a stdio adapter on npm (@machinegrade/validate). With Claude Code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add &lt;span class="nt"&gt;--transport&lt;/span&gt; http validate https://api.machinegrade.dev/mcp &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s2"&gt;"X-Api-Key: sk_..."&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's in the official MCP registry as io.github.machinegrade/validate, and on Smithery.&lt;br&gt;
The part where I'm honest about what this is&lt;br&gt;
This is an early-stage demand test, and I'd rather say that than pretend otherwise. The API contract is stable (breaking changes only via /v2/, never silently), the free tier stays, and the code is MIT-licensed — if the hosted service ever sunsets, keys work for 90 days and you can self-host the same behavior.&lt;br&gt;
The obvious objection is: "I'll just wire up ajv myself." Legitimate! That's precisely the assumption I'm testing — whether one HTTP/MCP call that covers multiple contract types, with agent-readable errors and zero infrastructure to maintain, is worth paying for versus N libraries you integrate and keep updated yourself. (Fun fact from building this: ajv itself doesn't run on Cloudflare Workers — it compiles schemas via new Function(), which the runtime forbids. The things you learn only by deploying.)&lt;br&gt;
If you build agent pipelines: would you use this? What's missing? Open an issue, or just tell me here why you wouldn't — that's the most useful data of all.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/machinegrade/validate" rel="noopener noreferrer"&gt;https://github.com/machinegrade/validate&lt;/a&gt;&lt;br&gt;
API docs: &lt;a href="https://api.machinegrade.dev/openapi.yaml" rel="noopener noreferrer"&gt;https://api.machinegrade.dev/openapi.yaml&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>api</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
