cd /news/ai-agents/human-confirmation-cannot-be-a-boole… · home › topics › ai-agents › article
[ARTICLE · art-149350] src=dev.to ↗ pub= topic=ai-agents verified=true sentiment=· neutral

Human Confirmation Cannot Be a Boolean: What an AI Write Has to Bind

A developer argues that human confirmation for AI-agent writes cannot be a boolean, and describes binding five elements into each approval row: the exact arguments, the destination system, the declared writable fields, the validity window, and the refusal path. The account details failure modes that arise when any of these is left unbound, such as a proxy tool's destination being re-pointed after approval while the stored record still shows the original arguments, and recommends re-resolving the destination at execution time and refusing on mismatch.

by read5 min views5 publishedOct 12, 2026

The earlier pieces were about stopping things: where the boundary sits, how risk gets tiered, how a write gets undone. This one steps back and looks at a single part.

What the system actually holds after a user clicks "yes".

It is the easiest part of the chain to build too thin.

If "yes" is a boolean, confirmed = true, there are two problems. The smaller one is that it can be reused: can the same yes approve a second thing. That is not a new problem, and marking it one-shot settles it.

The larger one is quieter: a boolean is a sentence, not a credential. The word "yes" cannot answer any of these:

which request was approved?

approved to write what?

approved to write it where?

approved to change it how much?

until when is it valid?

If it cannot answer those, then the answers are decided at the moment of execution instead, and that moment sits some distance away from the nod. Anything that changes in between goes unnoticed by the user. What we do is make the yes a row, binding five things. Each one below comes with what breaks without it.

Not "the arguments have to fall in some range", but "these are the arguments". Whatever they were is what has to run; one character off and it is refused.

That is stricter than a value domain, and the cost is worth stating: the whole set is accepted, or the whole set is refused.

It cannot express "in this approval, status may change but owner may not", because the arguments are bound as one sealed package. Expressing that needs something else, which is section 4.

The original version recorded the tool and the arguments. That looked sufficient. What would be written was locked down. What else could move?

Where it would be written.

The tool name and its arguments are both things on our side. Which external system the write actually lands in is a separate matter: a proxy tool's configuration can be re-pointed, an external service can be re-registered. The approved record sits in the database with every argument unchanged, while the place it is headed has been swapped for another.

Nothing in the record contradicts it.

The fix is to bind the destination too: record it at issue time, re-resolve it at execution time, refuse when the two disagree. Never follow it silently.

Worth remembering on its own: "approved" and "approved to where" are two different things.

Because the exact arguments can only accept or refuse the whole set, while what a confirmation usually means is per field:

this time it may change status;

but it may not change owner.

Only declaring the writable fields separately can say that.

It sits alongside the exact arguments rather than replacing them: one answers "are these the values", the other answers "may this kind of field be written at all". The convention matches the rest of the system: nothing declared means nothing constrained; anything outside the declared set is refused, and not truncated. Truncation would read as "part of it changed" when what actually happened is "a write that should have been refused was silently narrowed and allowed".

The mistake we made: the window lived only on the token. Once the token was consumed, it was gone. So looking at that row three months later could say "it was approved" and could not say what the window had been.

And that is exactly when it matters. Someone asks why a write was allowed at that time, you go to the record, and the window turns out to have depended on the configuration at issue time, which has since changed.

The fix is plain: write the window into the row at issue time, and later read the value on the row. Do not recompute it from the current configuration.

And refusal goes through the same binding as approval.

That last clause is deliberate. If "no" were a separate path with its own switch, the two would drift apart. A binding added to approval today gets forgotten on the refusal path tomorrow. Sharing one record and one entry point makes that impossible.

Which fields in your confirmation are computed once at issue time and computed again at execution time?

Anything like that can drift. Three questions to test it:

Point the same confirmation at a different target system. Does it succeed?

Change one argument. Does it succeed?

Three months later, can you say what the window or the policy revision was?

Any "yes" or "can't tell" marks a place that was left unbound.

Closing

Back to the opening shape: if "yes" is a boolean, it is a sentence, not a credential. And every promise in the chain comes to rest on this part. The audit records who approved what. Revocation undoes the write that was approved. Build this part thin and every later link is quietly covering for it.

The five we bind: exact arguments, destination, writable field domain, validity window, operator and decision moment.

Two things we have not done, stated plainly. The field domain can say whether a kind of field may be written, and cannot express a cross-field rule, such as "B may change only if A did". And the window column was added later, so rows older than it do not return that field. Their window is not knowable, and we do not invent a default for it.

One question we decided not to pursue, since it is the natural next step from here: why not issue a credential per approval?

We costed it. The confirmation is already one-shot, a credential is already short-lived and scoped to one target system, and the destination is now bound as well. Together those cover the worry. Another layer buys duplicated complexity, not a new guarantee.

So it is a trade-off, not a gap waiting to be filled. It is here because anyone who takes this seriously will ask it.

Drafted with an AI assistant. The system, the positions and the mistakes are mine.

── more in #ai-agents 4 stories · sorted by recency
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/human-confirmation-c…] indexed:0 read:5min 2026-10-12 · —