<?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: PRATHAP K</title>
    <description>The latest articles on DEV Community by PRATHAP K (@dev-prathap).</description>
    <link>https://dev.to/dev-prathap</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%2F2340093%2F264cf990-69e9-48ba-8c83-575413fa8b5d.png</url>
      <title>DEV Community: PRATHAP K</title>
      <link>https://dev.to/dev-prathap</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dev-prathap"/>
    <language>en</language>
    <item>
      <title>The six states of "done": why AI agents say they finished when they didn't</title>
      <dc:creator>PRATHAP K</dc:creator>
      <pubDate>Tue, 13 Oct 2026 11:33:00 +0000</pubDate>
      <link>https://dev.to/dev-prathap/the-six-states-of-done-why-ai-agents-say-they-finished-when-they-didnt-2j30</link>
      <guid>https://dev.to/dev-prathap/the-six-states-of-done-why-ai-agents-say-they-finished-when-they-didnt-2j30</guid>
      <description>&lt;p&gt;WhatsApp solved a problem most AI agents still haven't.&lt;/p&gt;

&lt;p&gt;One grey tick: sent. Two grey ticks: delivered. Two blue ticks: read.&lt;/p&gt;

&lt;p&gt;Three states. Nobody has to explain them. My parents understand them. You know exactly where your message is without asking anyone.&lt;/p&gt;

&lt;p&gt;Now look at most AI agents. You ask one to send an email, update a CRM record or book a slot, and it comes back with one green tick and one word: "Done."&lt;/p&gt;

&lt;p&gt;That one word hides six very different things.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feuccs3z5cbw21bs5qmlq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feuccs3z5cbw21bs5qmlq.png" alt="WhatsApp shows three states. Most AI agents show one green tick for six." width="800" height="534"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The ladder
&lt;/h2&gt;

&lt;p&gt;When an agent says it did something, it could mean any of these:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Intended.&lt;/strong&gt; The agent decided to do it. Nothing has happened in the world yet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Attempted.&lt;/strong&gt; It called the tool. The request left your system.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accepted.&lt;/strong&gt; The API returned 200. The other side received the request.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Applied.&lt;/strong&gt; The real system shows the change. The email is in the Sent folder. The row exists in the database. The calendar has the event.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Correct.&lt;/strong&gt; The change matches what the person actually asked for. Right contact, right amount, right thread.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Effective.&lt;/strong&gt; The outcome happened. The customer got the email. The payment cleared. The person showed up.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Most agents report step 3 and call it step 6.&lt;/p&gt;

&lt;p&gt;That gap is where every "it said it sent the email" story comes from.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why step 3 feels like the end
&lt;/h2&gt;

&lt;p&gt;When you build an agent, the tool call is the last thing your code sees. The function returns, the status is 200, there's an id in the response, and the trace shows a green step. From inside the agent, that is the end of the story.&lt;/p&gt;

&lt;p&gt;But a 200 only means the other system accepted the request. It doesn't mean:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the mail actually left the outbox,&lt;/li&gt;
&lt;li&gt;a workflow rule didn't overwrite your CRM value a second later,&lt;/li&gt;
&lt;li&gt;the calendar didn't silently drop an attendee it couldn't match,&lt;/li&gt;
&lt;li&gt;the message went to the person you meant.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I wrote about the first half of this in &lt;a href="https://devprathap.online/blog/200-ok-is-not-proof" rel="noopener noreferrer"&gt;a 200 OK is not proof your agent did the work&lt;/a&gt;. That post got a long discussion on Reddit, and a few people in that thread pushed the idea further than I had. This post is the version I use now.&lt;/p&gt;

&lt;h2&gt;
  
  
  What each step needs as proof
&lt;/h2&gt;

&lt;p&gt;The useful part of the ladder is that each step has a different kind of evidence. If you can't name the evidence, you're not at that step.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;What proves it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Intended&lt;/td&gt;
&lt;td&gt;The plan or the tool choice in the trace&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Attempted&lt;/td&gt;
&lt;td&gt;The outgoing request in your logs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Accepted&lt;/td&gt;
&lt;td&gt;The response code and id from the API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Applied&lt;/td&gt;
&lt;td&gt;A fresh read from the real system, not from the agent's memory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Correct&lt;/td&gt;
&lt;td&gt;That read compared against the original request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Effective&lt;/td&gt;
&lt;td&gt;A signal from the outside world: delivered, paid, replied&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Steps 1 to 3 are cheap. You get them for free from your logs.&lt;/p&gt;

&lt;p&gt;Step 4 is where most of the value is. It costs one extra read, and it catches most of the silent failures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: read it back from the real place
&lt;/h2&gt;

&lt;p&gt;The rule I follow: an agent is only allowed to say "done" after it has read the result back from the system it changed.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sent an email? Check the Sent folder or the provider's message status, not the id the send call returned.&lt;/li&gt;
&lt;li&gt;Updated a record? Fetch the record again after a short delay. Workflow rules love to run a second later.&lt;/li&gt;
&lt;li&gt;Booked a meeting? Read the event and check the attendee list.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Today I had 18 WhatsApp follow-ups going out. Each one was checked in the chat for its tick, sent or delivered, before it was marked finished. It's boring. That's the point. Boring checks are what make an agent trustworthy enough to leave alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5 is the sneaky one
&lt;/h2&gt;

&lt;p&gt;This one came from a comment on Reddit, and it changed how I build these checks.&lt;/p&gt;

&lt;p&gt;A read-back compares the system against what the agent &lt;em&gt;planned&lt;/em&gt; to do. If the agent picked the wrong person from the CRM, the email goes to the wrong person, the read-back matches the plan, and the action gets marked as verified.&lt;/p&gt;

&lt;p&gt;Everything passes. Everything is wrong.&lt;/p&gt;

&lt;p&gt;So the second check compares the result against the original request, not the agent's plan:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is this the contact the person named?&lt;/li&gt;
&lt;li&gt;Is this the amount they asked for?&lt;/li&gt;
&lt;li&gt;Is this the thread they were talking about?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For anything that sends, pays or deletes, I want both checks. The cost is a few lines of code. The cost of skipping it is an apology email to the wrong customer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6 is mostly out of your hands
&lt;/h2&gt;

&lt;p&gt;You can't force someone to read a message or pay an invoice. An agent can't promise step 6.&lt;/p&gt;

&lt;p&gt;But it can be honest about where it stopped. That is the real fix: stop collapsing six states into one word.&lt;/p&gt;

&lt;p&gt;Instead of "Done", the agent says what it actually knows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Accepted by the mail API" (step 3)&lt;/li&gt;
&lt;li&gt;"In your Sent folder" (step 4)&lt;/li&gt;
&lt;li&gt;"Sent to Dana from Acme, the one you mentioned" (step 5)&lt;/li&gt;
&lt;li&gt;"Delivered" or "Paid" when a webhook confirms it (step 6)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's exactly what WhatsApp does with its ticks. It never claims more than it knows.&lt;/p&gt;

&lt;h2&gt;
  
  
  A small checklist
&lt;/h2&gt;

&lt;p&gt;If you're building an agent that takes real actions, these are the questions I'd ask:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Which step does my agent actually stop at today?&lt;/li&gt;
&lt;li&gt;For every write, is there a read-back from the real system?&lt;/li&gt;
&lt;li&gt;For anything that sends, pays or deletes, is the result checked against the original request?&lt;/li&gt;
&lt;li&gt;Does the status the user sees use an honest word, or just "done"?&lt;/li&gt;
&lt;li&gt;When a read-back fails, does the agent stop and say so, or does it retry blindly and risk doing the thing twice?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of this needs a new framework. It needs the agent to stop at the right word.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;p&gt;"Done" is six states: intended, attempted, accepted, applied, correct, effective.&lt;/p&gt;

&lt;p&gt;Most agents report the third and call it the sixth. Read back from the real system, compare against what was asked, and let the agent say exactly where it stopped.&lt;/p&gt;

&lt;p&gt;Your chat app already does this. Your agent can too.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you've built agents that take actions, I'd like to hear which step yours stops at, and what broke when it didn't. I'm on &lt;a href="https://x.com/devprathapk" rel="noopener noreferrer"&gt;X as @devprathapk&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://devprathap.online/blog/six-states-of-done" rel="noopener noreferrer"&gt;devprathap.online&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>agents</category>
      <category>llm</category>
    </item>
    <item>
      <title>"200 OK" doesn't mean your AI agent did the job</title>
      <dc:creator>PRATHAP K</dc:creator>
      <pubDate>Mon, 12 Oct 2026 07:53:16 +0000</pubDate>
      <link>https://dev.to/dev-prathap/200-ok-doesnt-mean-your-ai-agent-did-the-job-575f</link>
      <guid>https://dev.to/dev-prathap/200-ok-doesnt-mean-your-ai-agent-did-the-job-575f</guid>
      <description>&lt;p&gt;Your agent says it sent the proposal. The tool call came back with an id. The trace shows a green step.&lt;/p&gt;

&lt;p&gt;The customer never got the email.&lt;/p&gt;

&lt;p&gt;I've seen versions of this enough times that I no longer trust a success response on its own. Here's why, and what I check instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Success responses mean "accepted", not "done"
&lt;/h2&gt;

&lt;p&gt;Most APIs tell you they received your request. They don't tell you the end result.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A mail API returns an id when the message is accepted. That's not the same as delivered, or even sent to the right person.&lt;/li&gt;
&lt;li&gt;A CRM update returns 200 when the request is processed. A workflow rule can overwrite your value a moment later.&lt;/li&gt;
&lt;li&gt;A calendar API creates the event and quietly drops an attendee it couldn't match.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every one of these looks like success in the logs. None of them proves the work happened.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we usually look at
&lt;/h2&gt;

&lt;p&gt;When an agent takes an action, we normally have three kinds of evidence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The trace: what the model decided and which tool it called.&lt;/li&gt;
&lt;li&gt;The tool response: what the API said back. A status code, an id, ok: true.&lt;/li&gt;
&lt;li&gt;The model's summary: "I sent the email and updated the deal."&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All three describe the request. None of them looks at the system after the action.&lt;/p&gt;

&lt;h2&gt;
  
  
  What proof looks like: read it back
&lt;/h2&gt;

&lt;p&gt;The fix is simple to describe. After the agent writes something, read it back and compare it with what you meant to do.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sent an email? Fetch the message. Is it in Sent? Is the right person in the To field? Is the subject what you intended?&lt;/li&gt;
&lt;li&gt;Updated a deal? Fetch the deal and check every field you set.&lt;/li&gt;
&lt;li&gt;Created a record? Get it by id and check the values.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I find it useful to label every action with how sure we actually are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Verified&lt;/strong&gt;: read back and it matched.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Acknowledged&lt;/strong&gt;: the API said yes, but nothing was read back. This is all a "200 OK" is worth.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contradicted&lt;/strong&gt;: a read-back ran and disagreed with what the agent claimed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two rules keep this honest. If the check couldn't run (network error, missing id), that's not a contradiction, it's just unchecked. And if the record exists but there's nothing to compare, it's still only acknowledged.&lt;/p&gt;

&lt;h2&gt;
  
  
  The case that matters most
&lt;/h2&gt;

&lt;p&gt;The contradicted case is the one worth catching. The API said yes and the real system said no: the email sitting in Drafts, the deal stage a rule reverted, the message posted to the wrong channel.&lt;/p&gt;

&lt;p&gt;Without a read-back, you find out from an unhappy customer. With one, you find out seconds later, along with the exact field that didn't match.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this check should live
&lt;/h2&gt;

&lt;p&gt;Frameworks are adding approvals and human review. Fewer are adding read-back, because it needs to know which read proves which write for each system. That knowledge is the same no matter which framework you use, so it fits best as its own small layer in front of anything that touches a real system.&lt;/p&gt;

&lt;p&gt;If you're putting agents in front of email, a CRM or customer messages, start here: for every write, decide what read would prove it worked.&lt;/p&gt;

&lt;p&gt;How are you checking that your agents actually did what they said?&lt;/p&gt;

</description>
      <category>agents</category>
      <category>llm</category>
      <category>ai</category>
      <category>programming</category>
    </item>
    <item>
      <title>A 200 OK is not proof your agent did the work</title>
      <dc:creator>PRATHAP K</dc:creator>
      <pubDate>Sat, 03 Oct 2026 15:00:09 +0000</pubDate>
      <link>https://dev.to/dev-prathap/a-200-ok-is-not-proof-your-agent-did-the-work-11bd</link>
      <guid>https://dev.to/dev-prathap/a-200-ok-is-not-proof-your-agent-did-the-work-11bd</guid>
      <description>&lt;p&gt;Your agent just told you it sent the proposal. The tool call came back with an id, the trace shows a clean green step, and the customer never got the email. That gap is small and quiet, and it is the thing underneath a loud week.&lt;/p&gt;

&lt;p&gt;People spent the last few days watching agents take real actions and finding out afterward what they had done. One widely shared Hacker News post describes an OpenAI Codex account that, from a single request, launched 826 parallel agents and ran up roughly 78,000 dollars in tokens with the records of what it touched deleted. Another describes Claude Code pulling a contract out of a Gmail thread, finding a saved signature image, placing it on the document, and preparing to send it before the owner intervened. Different tools, same hole: everyone could see what the agent said it did, and almost no one could check what it actually did.&lt;/p&gt;

&lt;p&gt;I work on the layer where agents touch real systems, and I have lived with this failure on real accounts, not in a chat demo. So I want to be precise about what we usually mean by "it worked," and why none of it is proof.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three kinds of evidence, all model-side
&lt;/h2&gt;

&lt;p&gt;An agent stack hands you three things after an action. The trace: what the model said and which tool it called. The tool response: what the vendor claimed, a status code or an id or &lt;code&gt;ok: true&lt;/code&gt;. And the model's own summary: "I sent the email and updated the deal." All three are generated or reported by the side that wanted the action to succeed. None of them looks at the world after the call.&lt;/p&gt;

&lt;p&gt;That matters because success codes lie by omission. A mail API returns an id when a message is accepted, not when it is delivered. A CRM &lt;code&gt;PATCH&lt;/code&gt; returns a 200 when the request was processed, including when a workflow rule overwrote your value a millisecond later. A calendar API creates the event and silently drops the attendee it could not resolve. Every one of these is a success. Not one is proof.&lt;/p&gt;

&lt;h2&gt;
  
  
  Proof is a read-back
&lt;/h2&gt;

&lt;p&gt;The only evidence that counts comes from the system of record itself. Send the email, then fetch it and check that it is actually in &lt;code&gt;SENT&lt;/code&gt;, that the recipient you meant is in the &lt;code&gt;To&lt;/code&gt; header, that the subject is the one you set. Create the lead, then &lt;code&gt;GET /leads/{id}&lt;/code&gt; and compare the fields you wrote. Proof is boring work: you read back what you just wrote and see whether the world agrees.&lt;/p&gt;

&lt;p&gt;This is the whole idea behind Attest, the open-source library I wrote to prove what an agent did. It never executes your action; your tool does. It decides whether the action may run, gates it behind a human when it matters, then reads back from the system of record and records the result either way. Because it never executes anything, it owes no connectors and works with any app and any framework on day one.&lt;/p&gt;

&lt;p&gt;The result is never a boolean. It is a level: &lt;code&gt;verified&lt;/code&gt; when a read-back matched, &lt;code&gt;verified-custom&lt;/code&gt; when your own check passed, &lt;code&gt;acknowledged&lt;/code&gt; when the action was accepted but nothing was read back, &lt;code&gt;attested-only&lt;/code&gt; when there was nothing checkable, and &lt;code&gt;unverified&lt;/code&gt; when a check ran and contradicted the claim. That &lt;code&gt;acknowledged&lt;/code&gt; rung is exactly what a bare 200 is worth.&lt;/p&gt;

&lt;p&gt;Here is what that looks like in the ledger. Two identical CRM writes, both reported successful by the tool, both approved by a human:&lt;/p&gt;

&lt;p&gt;sources/ATTEST/README.md&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;attest ledger
&lt;span class="gp"&gt;#&lt;/span&gt;2  2026-09-19 10:16:46  someweirdcrm.create → leads   ask  approved  unverified   a09e7641db12
&lt;span class="gp"&gt;#&lt;/span&gt;1  2026-09-19 10:16:46  someweirdcrm.create → leads   ask  approved  verified     77a6d99a60f6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second read back clean. The first did not, and the row names the field that disagreed:&lt;/p&gt;

&lt;p&gt;sources/ATTEST/README.md&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;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"unverified"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"matched"&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;"evidence"&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;"compared"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"exists"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"failed"&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="s2"&gt;"stage"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"fields"&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;"stage"&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;"want"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"qualified"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"got"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"new"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"ok"&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="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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A trace would have shown two green steps. That gap is the entire point.&lt;/p&gt;

&lt;h2&gt;
  
  
  "Could not check" is not "did not happen"
&lt;/h2&gt;

&lt;p&gt;The rule that keeps this honest is the one people get wrong. A check that could not run, a network error, a missing id, a read-only token, is not a contradiction. It degrades to &lt;code&gt;acknowledged&lt;/code&gt; with the error written into the evidence. Only a read-back that finds the record and sees a field disagree earns &lt;code&gt;unverified&lt;/code&gt;. "We could not check" and "it did not happen" are different facts, and a tool that blurs them is worse than useless, because it trains you to ignore red.&lt;/p&gt;

&lt;p&gt;You do not have to teach it every API to get there. Point it at an unknown REST service and it climbs the ladder with whatever you give it:&lt;/p&gt;

&lt;p&gt;sources/ATTEST/examples/unknown_app.py&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@at.action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.someweirdcrm.io/v2/leads&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_lead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;web&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;http_post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.someweirdcrm.io/v2/leads&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;source&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;


&lt;span class="c1"&gt;# the same tool with a one-line custom check ⇒ L2 `verified-custom`
&lt;/span&gt;&lt;span class="nd"&gt;@at.action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.someweirdcrm.io/v2/leads&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="n"&gt;verify&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;http_get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.someweirdcrm.io/v2/leads/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_lead_checked&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;http_post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.someweirdcrm.io/v2/leads&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;


&lt;span class="c1"&gt;# the same tool with an HTTP getter ⇒ convention read-back GET /v2/leads/{id}, field compare ⇒ L3 `verified`
&lt;/span&gt;&lt;span class="nd"&gt;@at_l3.action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.someweirdcrm.io/v2/leads&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_lead_l3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;http_post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.someweirdcrm.io/v2/leads&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first wrap gives you &lt;code&gt;acknowledged&lt;/code&gt;. Add a one-line &lt;code&gt;verify=&lt;/code&gt; and you get &lt;code&gt;verified-custom&lt;/code&gt;. Give it your own HTTP getter and the convention driver does the &lt;code&gt;GET&lt;/code&gt;, compares the fields, and you get &lt;code&gt;verified&lt;/code&gt;, with your credentials, in your process. A wrong guess can only make a real write read as &lt;code&gt;unverified&lt;/code&gt;; it can never invent a pass.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do this week
&lt;/h2&gt;

&lt;p&gt;You do not need to verify everything. You need to verify the actions that would cost you if they silently failed: the outbound email, the deal update, the money movement. Wrap those, read them back, and put the level where a human will see it. The overhead is small, roughly a sixth of a millisecond per action on the repo's benchmarks, and the payoff is that a reverted deal stage or an email stuck in Drafts shows up as a red row thirty seconds later instead of a customer complaint next week.&lt;/p&gt;

&lt;p&gt;The incidents this week were not caused by agents being too capable. They were caused by nobody holding a receipt. Build the receipt into the layer in front of every agent that touches a real system, and "did it actually work" stops being a guess.&lt;/p&gt;

&lt;p&gt;Attest is MIT-licensed, &lt;code&gt;pip install attestlayer&lt;/code&gt; or &lt;code&gt;npm install attestlayer&lt;/code&gt;, with 355 offline tests in the SDK and 37 in the cloud. The code and the read-back recipes are at &lt;a href="https://dev-prathap.github.io/ATTEST/" rel="noopener noreferrer"&gt;dev-prathap.github.io/ATTEST&lt;/a&gt; if you want to see exactly which read proves which write.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>verification</category>
      <category>python</category>
    </item>
  </channel>
</rss>
