How Do You Keep Agents From Lying? Create a Source of Truth.

I spent yesterday running an experiment where 78 autonomous agents tried to find and call an API on my company's website while I changed how discoverable it was, round by round, from a full machine-readable declaration down to nothing at all. The success rates were interesting. The thing I actually want to write about is the part where I decided, before running anything, that I would not believe a word any of these agents said.

First, a correction to my own title. Agents don't lie. Lying implies intent, and what agents do is closer to confabulation: they fill gaps with a confident, plausible narrative, and the narrative arrives in the same tone whether it's true or not. Because they literally don't know the difference. The distinction matters because you can't fix confabulation by asking the agent to be more honest. It already thinks it is. You fix it by building something outside the agent's control that can't be talked to at all.

The problem

The task I gave every agent was simple: get this JSON pretty-printed by the website's own tooling. Don't format it yourself, show me the raw response proving the site did the work. Report back in a fixed JSON shape including every request you made.

Here is a sample of what came back across the day.

One model tier reported a successful run with a request log containing exactly one request. One. The task requires discovering an endpoint and then calling it, so a one-request success is physically impossible. The success was real, it turned out. The log was fiction.

In a round where the API was hidden at a nonstandard path, agents didn't report "I couldn't find it." Five out of eight reported that the site has no server-side API at all. A confident, specific result which was wrong. The API was answering the whole time, one path segment away from where they looked.

And my favorite: in an early version of the harness, one agent was asked to black-box test the public website and instead read the experiment's own source code off the local machine, found the API's new path in a commit, and would have sailed into the results as a discovery success but it then dismissed the finding as undeployed because it was on a branch! It found the answer key, twice, actually, across two attempts, and then decided the answer was wrong without trying it. We rebuilt the harness to run every probe from an empty directory after that, no more answer keys, and the same model tier ran clean for the rest of the day.

None of this is unusual and none of it is news to anyone who has run agents at scale. The point is that if I had graded this experiment on self-report, every number in it would be garbage and I would have no way of knowing which ones.

The fix

The API signs its work now.

Every successful response from the tools API carries a proof object: an HMAC-SHA256 signature over the response data plus a server timestamp. The signing secret lives in the deployment platform's environment where no agent can reach it. Nobody who could conceivably want the experiment to succeed can produce a signature.

Next to it sits a public verify endpoint. POST any (data, timestamp, signature) triple at it and it tells you whether that exact data was signed at that exact time.

Grading then looks like this:

for every claimed success:
  1. replay its proof against the verify endpoint     -> must be valid
  2. check the timestamp                              -> must fall inside
                                                         that round's window
  3. check the signature against every other trial    -> must be unique

Check 2 stops an agent from citing a proof it found lying around from some earlier run. Check 3 stops one real success from being replayed into sixteen. Failures need no checking at all, because a failure carries no proof by construction. There is no way to fail convincingly. There is only succeeding, with a receipt, or not.

Final tally: 53 verified successes, 53 valid signatures, every timestamp in its window, all signatures verified. Zero fabricated successes made it into the record. Not because agents didn't lie, but because a lie can't produce a verifiable 256-bit MAC.

What this actually bought

Three things:

Catching fabrication is the obvious one, and honestly the receipts caught less of it than I expected. Most agents that failed said so. The confabulation showed up in the details: the impossible request logs, the confident "this API does not exist."

Rescuing mangled truth is the one I didn't see coming. That one-request success I mentioned? Same model tier also transcribed its raw response bodies with unescaped newlines, turning them into invalid JSON. Self-reported evidence: broken. But the proof triple inside still verified against the canonical data. The signature let me confirm a real success through a garbled report. A source of truth doesn't just catch liars. It vindicates bad witnesses.

Agents will use it themselves, which is the one that made me sit up. In the final round, two agents, unprompted, took the proof from their own response and POSTed it to the verify endpoint before reporting back to me. They checked their own receipts. Nothing in the prompt suggested it. The verify endpoint was just documented, so they used it. Publish a way to check the truth and some fraction of your callers will check it on their own.

The experiment underneath

The discovery results deserve their own write-up and they're getting one on the company site, but the shape of the ladder is worth a table here:

what the site published            unaided success
full ACP machine declaration                16/16
declaration missing the API             11/16
nothing (API at guessable path)          7/8
nothing (API at weird path)              0/8
one human docs page (nonstandard path)         6/8

Two findings that I find especially interesting. A manifest that omits your API is worse than no manifest, because agents believe it and stop looking. And when agents can't find something, they don't report it as hidden. They report it as nonexistent, and whoever sent them believes that too. Which loops right back to the receipts: in a world where agents relay confident wrongness by default, any claim that matters needs a path back to something that can't confabulate.

This is worth saying again, because it happens every day. A drift in your documentation or manifest is treated as ground truth by your agent visitors. If a bad deploy or regression removes some part of your API doc, or reverts a declaration at the boundary, any agent hitting your site will confidently report that the route is nonexistant unless it's easily guessable (and even then, not always).

Humans don't work that way. A human who has used your site before will remember what was there, and know that a documentation gap is just a mistake.

Do this

If you run a service that agents call, or will call, sign your responses. HMAC over the payload and a timestamp, secret in your deployment environment, one public verify endpoint. It's a few dozen lines. You get:

  • claims about your service that anyone can check without trusting the claimant
  • protection from replay with two extra checks that cost nothing
  • a debugging tool for every garbled report an agent ever hands you
  • and apparently, callers that start auditing themselves

The agents aren't lying to you, they're doing something worse: being confidently wrong. You can't dig your way out of it with prompts but you can make it not matter.