Silent Tool Failures: Why Your AI Agent Says "Done" When Nothing Happened
Your agent said it sent the email. It didn't. There was no error, no exception, nothing to grep for, the model narrated success and the system moved on. This is the silent tool failure, the most expensive bug in agentic AI, because it looks exactly like success.
What is a silent tool failure?
A silent tool failure is when an AI agent reports that an action succeeded, but the underlying tool call never happened or did nothing. No error is raised, so nothing is logged. The failure surfaces days later as a missing record, an unsent message, or a customer who never got refunded.
Silent failures get worse as models get better, not better, a more capable model writes a more convincing story of success, which drifts further from what the system actually did.
Why "the model said it worked" is not evidence
The core mistake in agent design is treating the model's narration as proof of execution. The model is not a witness to its own tool calls; it is a text generator predicting a plausible next step. Asking a model "did you really send that email?" doesn't help, a confident model just says yes. You cannot verify an action by asking the thing that claimed it.
The fix is to stop treating the model's story as evidence and require a receipt instead.
What is a tool-call receipt?
A tool-call receipt is a verifiable record that a specific action actually executed, produced by the tool itself, not by the model's description of it. It answers a different question than schema validation:
- Schema validation asks: were the arguments well-formed?
- Receipt verification asks: did the call actually fire, and what did it return?
An agent can pass schema validation perfectly and still never make the call. Only a receipt closes that gap. The rule: no receipt, no "done." If the model claims an action and no receipt exists for it this turn, the state is not success, it is unknown.
The mechanisms of silent agent failure
Silent failures cluster into a few mechanisms:
| Mechanism | What you see | Caught today? |
|---|---|---|
| Fabricated execution | a claim with no call fired | ✓ tool_bypass |
| Empty-as-success | null or empty read as done | ✓ receipts |
| Valid-but-wrong data | clean result, wrong value | ~ optional @verify |
| Broken call mechanics | malformed or misrouted | framework's job |
| Staleness | valid when planned, not when it ran | roadmap |
The first two are the cheapest and most common, and a receipt catches them structurally, with no model in the loop.
Schema validation vs receipt verification
| Schema validation | Receipt verification | |
|---|---|---|
| Question | Were the arguments valid? | Did the action actually happen? |
| Catches | Malformed calls | Claimed-but-never-fired calls |
| Oracle | The JSON schema | The tool's own receipt |
| Silent failure caught? | No | Yes |
Schema validation is necessary and now largely commoditized by strict-mode APIs. Receipt verification is the layer above it, and where silent failures actually live.
How Cruxial implements tool-call receipts
Cruxial is an open-source (MIT) action layer for AI agents. It validates and auto-repairs a tool call's arguments, then does what schema validation can't: it resolves every side-effecting call from its receipt, never the model's word for it.
You mark a side-effecting tool with @action, tell Cruxial how to read its receipt, and add an optional domain check:
import cruxial
@cruxial.action
def send_email(to, subject, body):
return mailer.send(to=to, subject=subject, body=body) # e.g. {"message_id": "..."}
@cruxial.receipt("send_email")
def _(raw):
return cruxial.Receipt(ok=bool(raw.get("message_id")), id=raw.get("message_id"), kind="email")
result = cruxial.run(client, model="gpt-4o", messages=msgs, tools=tools, executors=ex)
result.state("send_email") # -> "posted" | "unknown" | "needs_review" | "failed"
result.render() # receipt-derived summary, never a bare "done"
Three things hold in v0.5.3:
- Every side-effecting call resolves from its receipt,
postedwhen there's proof,unknownwhen there isn't. - A claimed-but-never-called action is caught deterministically and recorded as
unknown, with no extra model call. render()derives status from receipts, never narration. What you do about anunknownis your policy, Cruxial won't blind-retry or fake a "done."
An append-only local ledger records every operation, so cruxial view --web shows exactly where your agent said "done" but the system couldn't confirm it. It runs alongside OpenAI, Azure OpenAI, Anthropic, and LiteLLM, validation adds under 1ms p99, and it fails open, if Cruxial itself errors, your tool still runs.
In Cruxial's published benchmark, the core guarantee held across two independent runs:
across 342 live tool calls
A silent pass is the one failure a verification layer has to get right. Benchmark run June 2026 on Azure gpt-4o; full method and reproduction in the repo's BENCHMARKS.md.
Try it in one line:
pip install cruxial
Then cruxial demo catches every failure category live, offline, no API key. Repo and docs: github.com/cruxial-ai/cruxial.
Frequently asked questions
What causes an AI agent to fail silently?
The model narrates a successful action that never executed, or that returned empty or invalid data, with no error raised. The system trusts the narration and moves on.
How do you detect a tool call that never happened?
Compare the model's claimed actions against the receipts produced this turn. A claimed action with no matching receipt is a fabricated execution, record it as unknown rather than accepting the model's word.
Is receipt verification the same as an audit log?
No. An audit log records what the system reported. Receipt verification determines what actually happened first, then records that, so the log reflects system state, not the model's story.
Is Cruxial open source?
Yes, Cruxial is MIT-licensed and installable with pip install cruxial.