Don't trust "Done." — forcing AI agents to re-fetch reality before they report completion A developer has identified a critical flaw in AI agents that report task completion without verifying the actual state of the world, calling it the 'nastiest species of hallucination.' They propose a 'completion contract'—a simple rule requiring agents to re-fetch and confirm the post-action state before reporting 'done.' The developer notes that existing guardrail tools validate output text but fail to catch this type of hallucination, and they have implemented the contract across their skills. I hand a lot of my client work to AI agents. Production deploys, report generation, bulk data inserts. Every procedure that works gets turned into a skill, and by now a few dozen skills run my day-to-day. The one that broke me was a bulk insert. At the end, the agent said: "Inserted N rows. Done." A completion report like any other. I almost took it. I opened the admin panel just in case — not one row had been added. The insert command had failed partway through, the error had been swallowed, and the agent had confidently reported completion anyway. That was a cold moment. What if it had been a number in a report? A deliverable going to a client? How many "done" reports had I already waved through that were never actually done? This is the nastiest species of hallucination. It isn't a plausible-sounding sentence. It's the fabrication of the fact that the work was completed at all. And humans don't interrogate a confident "Done." Chase the cause and it isn't that the model is dishonest. It's that acting and checking are the same step. One turn of an LLM agent looks roughly like this: Step 2 is the problem. When the tool's return value is empty, ambiguous, or a timeout, the model writes the most likely continuation — "it probably worked." In the training data, the most natural thing to follow "ran the insert" is "done", so of course that's what comes out. The agent is claiming completion without ever looking at the world it just changed. Having never looked, it can't notice the failure either. The countermeasure is simple. An operation with side effects must re-read the state of the world afterward and confirm it, before anything is allowed to say "done." I call this the completion contract and made it a shared rule across all my skills. It's three lines: Put differently: the burden of proof moves to the agent. Not "it should have worked" but "I re-read it just now with my own eyes and it is there." Here's the difference. Before I think I did it Inserting the data tool db insert ... ← empty return value, but not conclusively an error Inserted N rows. Done. ← claimed without looking After completion contract Inserting the data tool db insert ... ← empty return value The insert returned nothing. Verifying that the rows exist. tool db count where=... ← re-fetch 0 matching rows. The insert did not land failed . Want me to check the error log and retry? The second agent can't lie. The moment you insert a step that goes and looks at the world afterward, "I think I did it" stops being physically possible. You might think a guardrail library already handles it. I looked — guardrails-ai, deepeval, promptfoo, a few others. They're good tools. They just guard a different place. What they validate is essentially the text the LLM produced : is the format right, is it harmful, is it factually consistent, is it close to the expected answer. They grade the content of the output. But the "I think I did it" hallucination is flawless as text. "Inserted N rows. Done." is grammatical and internally consistent. No amount of grading the text catches it — because what's wrong isn't the text, it's the state of the world after the action. There's a gap here. There are piles of tools that verify output, but as far as I could find, nothing that re-fetches the world after an action and reconciles the report against reality. We've reached the point where agents actually rewrite the world, and verification stopped at "what did the agent say." The most important part first: this contract works right now, with no dependency. Drop the three rules into your agent's system prompt, CLAUDE.md , or AGENTS.md as a single paragraph: Completion contract An operation with side effects create, update, delete, upload, insert may not be reported as complete until a separate command has re-fetched the resulting state and the raw result has been shown. Empty output, errors, and timeouts are reported as "empty" or "failed" as-is — never filled in with an imagined id, path, or count. That alone visibly reduced false completions in my setup. Zero cost, zero dependencies. If you only try one thing, try this. Here's what I learned running it: discipline written into a prompt gets quietly broken on a busy turn. As context grows, the model steps over that paragraph "by accident" and starts saying "Done." again. Like a human promising to be careful, a declaration gets broken. At some point you want the discipline enforced, not just written — the same way you replace a verbal note in code review with a linter that mechanically fails CI. I wanted "did you actually re-fetch before saying done?" backed by machinery instead of good intentions. genchi I published a small piece that backs the completion contract with machinery rather than goodwill: genchi 現地現物 — go and see the actual thing . npm i @hyuga/genchi It does exactly one thing: run a probe that re-fetches real state, and issue a verdict from nothing else. js import { gate, expect } from '@hyuga/genchi'; await db.insert rows ; // the side effect await gate { action: 'insert 45 rows', probe: = db.count { where: { batch: 123 } } , // ← re-reads real state, not the action's return value expect: expect.count 45 , } ; // reaching this line means the 45 rows are really there. Otherwise it threw GenchiIncomplete. The crux is that verify / gate accept nothing but a probe. The evidence has to come from calling something at the moment completion is asserted, not from a value handed in beside the claim. Empty results, errors, and timeouts aren't swallowed; they're reported as failures rather than imagined into successes. A returned count of 0 nothing landed counts as incomplete too. One thing that requirement does not buy, and I claimed it did. Until 0.3.0 the README said this made "I think I did it" structurally unwritable . It is one line: js const result = await doTheInsert ; // suppose nothing landed await verify { action: 'insert 45 rows', probe: = result.inserted, // the action's own return value expect: expect.count 45 } ; // → ok: true A probe is a function, and nothing in JavaScript can force a function to do I/O. Worse, the CLI printed re-fetched: 45 for a --probe "echo 45" that re-fetched nothing — a tool whose entire subject is "don't report what you didn't check", asserting in its own output a thing it had not checked. Both are corrected in 0.3.0: the wording is now the probe returned , and --help states the limit without being asked. What requiring a probe actually buys is a place to put the re-read — an expression somebody wrote on purpose — plus refusals that don't get quietly swallowed. That is worth having. It is less than I wrote down, and the difference is the sort of thing you only find by attacking your own package instead of re-reading it. There's a CLI for agents that don't write JS. Hand it a re-fetch command: genchi verify --probe "psql -tAc 'select count from t where batch=123'" --count 45 exit 0=verified / 1=empty or mismatched / 3=probe failed. Raw probe output is always emitted as evidence. With Claude Code, a Stop hook can block a turn that still has unverified completion contracts on it adapters/claude-code . No LLM and no API key at runtime — it's a zero-dependency static piece. Let me be straight. As far as I could find, "re-fetch the world after the action and verify" was a genuine gap. But I don't think that many people have yet been burned specifically by fabricated completion while letting agents rewrite real things. The demand may be a little ahead of its time. I still built it framework-agnostic on purpose — the Claude Code hook is pushed out to a thin adapter and the core works from any agent. I have a linter carrylint whose whole message is "don't bake your environment in", so it would be incoherent for my own tool to be Claude-Code-only. Unlike my static linters reflint for reference integrity, skills-lint for skill collisions, carrylint for runtime portability , this is the first piece of mine that goes and looks at the world at runtime. If you've ever had that cold moment over something that was reported done and wasn't, it should land. npm i @hyuga/genchi .Go doubt one more "Done."