<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>AgentCrafts writing</title>
    <link>https://agentcrafts.dev/blog</link>
    <description>Independent automation engineer building AI agents, multi-agent systems and the automation around them. Hire help for a new build or a project that has stalled.</description>
    <language>en</language>
    <copyright>AgentCrafts</copyright>
    <lastBuildDate>Wed, 30 Sep 2026 00:00:00 GMT</lastBuildDate>
    <atom:link href="https://agentcrafts.dev/feed.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Your agent doesn&apos;t need a better prompt, it needs a tool that fails loudly</title>
      <link>https://agentcrafts.dev/blog/tools-that-fail-loudly</link>
      <guid isPermaLink="true">https://agentcrafts.dev/blog/tools-that-fail-loudly</guid>
      <pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate>
      <description>Most agent failures I get called in to fix are not reasoning problems. They are tools that answer plausibly when they should have returned an error.</description>
      <category>reliability</category>
      <category>tool calling</category>
      <category>production</category>
      <content:encoded><![CDATA[<p>A team called me in because their agent kept &quot;hallucinating CRM data&quot;. That was the description. The reality was more boring.</p>
<p>The agent had six tools. One of them, <code>get_contact</code>, hit an internal API. The API returned <code>200 OK</code> with a body of <code>{}</code> whenever the contact ID didn&#39;t exist. No error, no null, just an empty object. The agent read that as &quot;this contact exists and has no fields&quot;, filled in the blanks from the conversation context, and wrote the result to the CRM.</p>
<p>The model was doing exactly what it should. It was handed a tool that lied, and it believed it.</p>
<h2>Make silence impossible</h2>
<p>When I audit a tool layer, the first thing I look for is what happens on the boring failure paths: not found, unauthorised, rate limited, timed out, returned the wrong shape. If any of those come back as a successful empty result, the agent has no way to distinguish &quot;nothing there&quot; from &quot;I got nothing useful&quot;.</p>
<p>The fix in that codebase was about twenty lines. Every tool response got an explicit envelope:</p>
<pre><code class="language-json">{
  &quot;status&quot;: &quot;not_found&quot;,
  &quot;message&quot;: &quot;No contact with id 4821&quot;,
  &quot;retryable&quot;: false
}
</code></pre>
<p><code>status</code> is required. The wrapper throws if a tool returns anything else, which means a bad tool fails at the boundary rather than three steps later inside the model&#39;s reasoning. Suddenly the agent stops inventing contacts and starts telling the user the record doesn&#39;t exist. Which is the correct behaviour, and it took less time to fix than writing the ticket.</p>
<h2>Errors are instructions</h2>
<p>There&#39;s a second benefit that people miss: a well-written error is the cheapest prompt you will ever write.</p>
<p>&quot;Rate limited, retry after 30 seconds&quot; is a better instruction than anything you can put in a system prompt. &quot;The user is not authorised to view billing: ask them to confirm the account owner&quot; tells the agent exactly what to do next. Compare that to a generic <code>500</code> and you can see why agents thrash.</p>
<p>I write tool errors for the model as the primary reader, not the developer. Developers get the stack trace in the logs. The model gets a short, actionable sentence.</p>
<h2>Assertions before the call, not after</h2>
<p>The other half is input validation. If <code>update_record</code> requires an <code>account_id</code>, reject the call when it&#39;s missing instead of letting the database insert a row with a null and discovering it in a reconciliation report a week later.</p>
<p>This is unglamorous schema work. It is also the single highest-leverage change I make in most codebases. Typed tool inputs and explicit status codes turn a class of flaky, hard-to-reproduce agent bugs into loud, immediate, obvious ones.</p>
<h2>What to do on Monday</h2>
<ul>
<li>Pick your five most-used tools.</li>
<li>For each one, write down every way it can fail. All of them, including the ones that &quot;can&#39;t happen&quot;.</li>
<li>Make each failure return a distinct, human-readable status.</li>
<li>Throw on anything unrecognised rather than passing it through.</li>
<li>Add a test that calls the tool with a bad ID and asserts the agent reports a problem instead of inventing an answer.</li>
</ul>
<p>That last test is the one that catches regressions, because someone will &quot;helpfully&quot; make the API return a default again in six months.</p>
<p>The prompt matters. But in my experience, a mediocre prompt on top of honest tools beats a beautiful prompt on top of tools that lie.</p>
]]></content:encoded>
    </item>
    <item>
      <title>What I measure before an agent goes anywhere near customers</title>
      <link>https://agentcrafts.dev/blog/evaluation-before-customers</link>
      <guid isPermaLink="true">https://agentcrafts.dev/blog/evaluation-before-customers</guid>
      <pubDate>Wed, 19 Aug 2026 00:00:00 GMT</pubDate>
      <description>A small evaluation set written in an afternoon has saved more projects than any amount of prompt tuning. Here is how I build one.</description>
      <category>evaluation</category>
      <category>process</category>
      <category>quality</category>
      <content:encoded><![CDATA[<p>Every project I take on hits the same moment. The demo works, everyone is pleased, and someone asks whether we can put it in front of real users next week. The answer is always the same: not until we can tell whether it&#39;s getting better or worse.</p>
<p>That&#39;s not caution for its own sake. Without a measurement, prompt changes become a kind of office folklore. Someone tweaks the system prompt because a support ticket mentioned a weird answer, the fix quietly breaks four other cases, and nobody finds out for a month.</p>
<h2>Start with thirty real examples</h2>
<p>Not synthetic ones. Real inputs, copied from the actual source: real emails, real documents, real questions people have asked. Synthetic test data is written by the same brain that wrote the prompt, so it tests the same assumptions.</p>
<p>I aim for around thirty to start. If a task has obvious categories, I spread them across the categories rather than picking thirty variations of the easy case. Half a day of work, usually less, because the client already has the material.</p>
<p>Each example gets:</p>
<ul>
<li>The input, unchanged apart from removing anything identifying.</li>
<li>What a good answer looks like, in prose. Not a score, an actual answer.</li>
<li>A short note on what would make a response unacceptable.</li>
</ul>
<h2>Grade the shape, not just the wording</h2>
<p>Anyone who has tried to match model output to an exact string knows it fails on a trivial rewrite. So I check two layers separately.</p>
<p>The first is deterministic: did it call the right tool, in the right order, with valid arguments? Did it include the required fields? Did it refuse where it should have refused? These are assertions, and they either pass or they don&#39;t.</p>
<p>The second layer is judgement, and I use a model to grade it against the reference answer with a strict rubric: does the response contain every required fact, does it contradict the source, does it invent anything. Model grading is imperfect, which is exactly why it&#39;s the second layer and not the first.</p>
<h2>The number that matters is the delta</h2>
<p>Nobody should care whether a run scores 82% or 86% in the abstract. The useful signal is directional and comparative:</p>
<blockquote>
<p>Last week&#39;s prompt scored 24 of 30. This week&#39;s scores 29. It also costs 40% less because it stops after three tool calls instead of seven.</p>
</blockquote>
<p>That sentence is what lets you ship a change with confidence, and it&#39;s what stops a well-meaning edit from shipping blind.</p>
<h2>Wire it to the commit</h2>
<p>An evaluation set that lives in a notebook is decoration. It belongs in CI, running on the paths that touch prompts, retrieval configuration or tool definitions. It doesn&#39;t need to block every pull request, but it should make a regression visible within minutes rather than during a customer call.</p>
<p>I keep a short failure log too, one line per real-world failure, appended as it happens. Those lines become the next ten test cases, and they&#39;re worth more than anything I could invent.</p>
<h2>The uncomfortable part</h2>
<p>Writing the evaluation set forces a conversation about what &quot;good&quot; actually means. Teams often disagree about this and didn&#39;t know it. One person thinks a shorter answer is better, another wants the full audit trail. Better to find that out in an afternoon of writing test cases than three weeks into a build.</p>
<p>If you take one thing from this: the evaluation set is not a quality gate you add at the end. It&#39;s the specification. Write it early, and the rest of the project gets noticeably less vague.</p>
]]></content:encoded>
    </item>
    <item>
      <title>The expensive part of an agent is rarely the tokens</title>
      <link>https://agentcrafts.dev/blog/cost-of-an-agent</link>
      <guid isPermaLink="true">https://agentcrafts.dev/blog/cost-of-an-agent</guid>
      <pubDate>Thu, 02 Jul 2026 00:00:00 GMT</pubDate>
      <description>I&apos;ve reviewed enough agent invoices to know where the money actually goes. It isn&apos;t the model calls, and it isn&apos;t the vector database.</description>
      <category>cost</category>
      <category>operations</category>
      <category>architecture</category>
      <content:encoded><![CDATA[<p>When I quote a running cost for an agent project, people expect me to talk about token prices. That&#39;s the number everyone watches, and it&#39;s usually the smallest line on the bill.</p>
<p>Here&#39;s the shape of the last three systems I costed out.</p>
<h2>The model calls are predictable</h2>
<p>A typical agent run in the systems I build makes somewhere between six and twenty model calls. With a mid-tier model, sensible context management and a prompt cache, that lands in the low single-digit cents per run. Even at a few thousand runs a month, you&#39;re talking about a line item that doesn&#39;t threaten anyone&#39;s budget.</p>
<p>The exception is when something goes wrong in a loop. Which brings me to the actual problem.</p>
<h2>Retries are where the money goes</h2>
<p>One system I reviewed was spending four figures a month on a workflow that should have cost about eighty dollars. The agent was calling a flaky internal endpoint. When it failed, the tool returned generic error text, and the agent decided a sensible next step was to try again. Sometimes it tried again in a slightly different way. Occasionally it tried eleven times.</p>
<p>Watch out for:</p>
<ul>
<li><strong>Retry storms.</strong> No cap on attempts, no backoff, no budget check.</li>
<li><strong>Loop drift.</strong> The agent re-plans because it can&#39;t tell whether the last step succeeded, so it redoes work it already paid for.</li>
<li><strong>Re-read everything.</strong> Passing the full conversation and all retrieved documents on every call, so the same 40,000 tokens get billed twenty times.</li>
</ul>
<p>That last one is the most common. Trimming context to what the current step actually needs is normally a 50 to 70% cost reduction with no quality loss, and it&#39;s a few hours of work.</p>
<h2>The real cost is the human time</h2>
<p>Then there&#39;s what never appears on the invoice: the person who checks the output.</p>
<p>If your agent produces work that someone has to read carefully before trusting, you haven&#39;t removed the task, you&#39;ve moved it. And reading someone else&#39;s work critically is often slower than doing it yourself. I&#39;ve watched teams celebrate a 70% time saving that didn&#39;t exist.</p>
<p>The honest question is not &quot;how much does the agent cost to run?&quot; It&#39;s &quot;how much of a human&#39;s attention does it still require?&quot; If the answer is &quot;the same as before, but now with a review queue&quot;, the project hasn&#39;t landed yet.</p>
<p>Ways I&#39;ve seen that fixed:</p>
<ul>
<li>Move the approval gate so it only catches the risky 10%, not every run.</li>
<li>Make the output verifiable in seconds: cite the exact source line, show a diff, put the numbers in a table instead of prose.</li>
<li>Route simple cases straight through and hold only the ambiguous ones.</li>
</ul>
<h2>What I do now</h2>
<p>Every agent I ship has three ceilings and one report:</p>
<ol>
<li>A <strong>max steps</strong> limit per run, after which it stops and asks.</li>
<li>A <strong>spend cap</strong> per run and per day, enforced in code rather than trusted to the prompt.</li>
<li>A <strong>context budget</strong> for each step, with retrieval scoped to that step.</li>
<li>A weekly line in the logs: runs, failures, average steps, average cost, and how many needed a human to intervene.</li>
</ol>
<p>Those four numbers tell you whether the system is healthy far better than watching the model bill. The token cost is a symptom. Steps, retries and human intervention are the disease.</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
