August 3, 2026Engineering

Your Agent Will Tell You It Sent the Email

A model that calls a tool will narrate success whether or not anything happened. Four things get conflated — a tool existing, being able to use it, using it, and confirming it worked — and separating them is most of what makes an agent trustworthy.

By Akshay Aggarwal · 9 min read

Ask an agent to send an email and watch what it says when the send fails. In our experience the answer is usually "Done — I've sent that for you," delivered with the same confidence as a successful send.

This is not dishonesty and it is not a bug in any single place. A language model completes text. After a tool call and a result, the most probable continuation is a success sentence — the training data is full of tasks that worked. If the tool returned an error, or returned nothing, or timed out and got retried, "Done" is still the fluent thing to say next. The model is doing exactly what it does.

The problem is that we let the model be the one who says it.

Four things that are not the same thing#

Most agent architectures collapse these into a single yes/no, and most agent failures live in the seams between them.

  1. A tool exists. There is a function in the registry called send_email.
  2. The agent can actually use it right now. The account is connected, the token has the right scope, the user has granted the OS permission, the machine is online.
  3. It ran. The function was invoked and returned.
  4. The effect is confirmed. Something outside the agent — a message ID, a calendar event ID, a file on disk — proves the thing happened.

An agent that treats (1) as (4) will tell you it booked the meeting because it knows a booking function exists. That sounds absurd written down; it is the default behaviour of almost every agent demo.

Capability is a runtime question, not a model question#

The first fix is to stop asking the model whether it can do something. We keep a capability registry that answers, for a specific action, at this moment:

  • is the provider connected?
  • does the token carry the required scope?
  • is this action read-only, draft-only, or executable?
  • can the result be independently verified afterwards?
  • has the user opted into trusted automation for this exact routine?

It returns one of: unavailable, needs_setup, readable, draftable, executable, automatable. Deliberately not a boolean — the interesting states are in the middle. The most common real-world state is needs_setup, and the correct response to it is neither "I can't do that" nor an attempt: it is to prepare the thing and show the user the connect-account step.

These probes are heuristic and local. No model call sits on that path, because a capability check that costs a round-trip will get skipped under latency pressure, and a check that gets skipped is not a check.

Postures, not permissions#

Once capability is a real signal, the next question is what the agent should do with a request — and "run it" versus "refuse" is too coarse. We resolve every turn to one of six postures:

PostureWhen
answerhigh confidence, read-only
briefhigh confidence, synthesis into something scannable
draftirreversible action, or capability is draftable / needs_setup
clarifymedium confidence and the ambiguity actually blocks the work
reviewprepared payload waiting on approval
executeapproved, or reversible and capability says executable

The rules that pick between them are boring and that is the point: low confidence never executes; high confidence plus irreversible always drafts and asks; needs_setup always drafts, never attempts. It is a pure function of route and capability — no model call, no I/O, so it behaves identically every time.

The posture that earns its keep is clarify, with a hard cap of one question. An agent that asks three questions before acting is worse than one that guesses, and every implementation drifts toward three unless something structurally forbids it.

Approve the payload, not the conversation#

Here is the subtle failure, and the one we did not anticipate.

The user says "reply to Brian and tell him the deck is coming Friday." The agent drafts it. The user reads it and says yes. The agent then executes — and, in a naive implementation, regenerates the email body on the way to the API.

The user approved text they will never see again. The text that actually went out is a fresh sample from the model, usually similar, occasionally not.

So approval binds to a hash of the exact payload. The runtime persists the payload, not the conversation. Approval applies to that hash. Editing the draft produces a new hash and returns to review. Execution reads the stored payload and never asks the model for the text again. If the hash does not match, execution refuses.

This is the piece we would tell anyone building this to implement first. It is a hundred lines. Without it, every other guarantee in the system is decorative, because the artifact the user consented to is not the artifact that ships.

Verification gates the language, not just the log#

The last step is the one that changes what users experience: the runtime decides whether the agent is allowed to claim success.

The rule we enforce is a single sentence — never claim sent, booked, deleted or posted unless verification is verified — and it is enforced where the phrasing is produced, not in a prompt asking the model to be careful. Prompts are advice. This is a gate.

Every execution result gets classified into a verification status, written to a trace keyed by the turn, and turned into a user-facing phrase. verified earns "Sent." Everything else gets language that matches what we actually know: "I've prepared it — the send didn't confirm, here it is." Uncertainty is not a failure state to be hidden. It is information the user needs in order to decide whether to go check.

The same logic applies to notifications. We route every "there's work waiting" banner through one chokepoint whose count derives from artifacts the user can actually open, not from an integer written at production time. A notification for something that no longer exists, or was never openable, teaches people to ignore notifications — and that lesson, once learned, is permanent.

What it costs, and when not to do it#

All of this is expensive. It adds latency before an action, it adds review UI to flows that would otherwise be one sentence, and it is a meaningful share of a codebase that would be smaller and more demoable without it.

For a read-only agent — search, summarize, answer — it is over-engineering. Skip it. The cost of a wrong answer is a wrong answer, and the user is right there.

The moment an agent can send, book, delete, post, or pay, the calculus inverts. One confidently-narrated action that did not happen costs more trust than fifty correct ones build, because the user's model of the system changes from "it does things" to "it says things." That is very hard to walk back.

We also cannot claim to have finished this. Verification is only as good as the signal a provider gives back, and some give back very little. Where we cannot verify, the honest move is to say so in the sentence rather than to quietly upgrade a hope into a claim.

Jarvis is free and open source if you want to read the actual implementation rather than the description of it — the approval and verification layers are the parts we would most like other people to take and improve.

Frequently asked questions

Why not just prompt the model to be honest about failures?

Because a prompt is a suggestion competing with every other pressure on the next token, and it fails exactly when things are unusual — which is when it matters. Anything you cannot afford to have wrong belongs in the runtime, not the prompt.

What is payload hashing and why does it matter?

The runtime stores the exact bytes the user approved and hashes them. Execution reads the stored payload and refuses if the hash does not match. Without it, the agent can regenerate the text between approval and execution, so the user consents to one message and a different one is sent.

Is a capability check different from a tool being available?

Yes, and conflating them is the most common failure we have seen. A tool can exist in the registry while the account is disconnected, the token lacks scope, or the OS permission was never granted. Capability is a runtime probe of whether the action can actually happen right now.

Does this apply to read-only agents?

Mostly no. If nothing the agent does is irreversible, this machinery is overhead. It becomes essential the moment the agent can send, book, delete, post or pay.

What happens when an action cannot be verified at all?

The agent is not permitted to claim it succeeded. It reports what it attempted and what it does not know. Some providers return nothing useful, so this state is common, and dressing it up as success is how agents lose trust permanently.

Try it on your own Mac

Jarvis is free and runs on-device. Apple silicon and Intel.

Download Jarvis

Keep reading