Skip to content

16 min read

Confirmation cost and reversibility

On this page

A confirmation is the only control in this topic with a running cost somebody can see on a dashboard. Scope, timeouts, previews and audit records are paid once in engineering and thereafter in microseconds; an approval is paid on every call, in the response time of whoever has to click it, by a team whose reason for building the agent was speed. That asymmetry decides which controls are still in the codebase a year later, and it is a design input rather than an unfortunate fact about organisations.

Three substitutes make it workable — cheap reversibility, confirmation by the affected party, and a velocity limit with an automatic stop — and what is left after they have taken their share is the slice that genuinely needs a person, which is much smaller than the operation it sits inside.

What the telemetry says about a confirmation on everything

Take the numbers that get a control deleted. Cancelling a subscription by hand takes a support engineer a median of 2 minutes 10 seconds end to end. An agent built to take that work off the queue resolves the same requests in a median of 6 minutes 40 seconds, because every cancellation raises an approval and the approval sits in a Slack channel for a median of 4 minutes 12 seconds before anyone clicks it. The agent has made the process three times slower than the thing it replaced.

Subtracting one figure from another turns that into a diagnosis rather than a complaint. Take the approval wait out of the agent's median and what remains is a little under two and a half minutes of the agent actually working — which is within twenty seconds of what a support engineer needs to do the whole job by hand. So the agent is not slow. The agent is roughly as quick as the person it replaced, and every bit of the threefold gap is the interval during which nothing at all is happening while a message sits in a channel. That is worth establishing before anybody argues about the control, because it forecloses two wrong conclusions at once. The agent is not the problem and does not need optimising, and the confirmation step is not slow for a confirmation — four minutes to get a human's attention in a shared channel is ordinary. What is wrong is the pairing: a human-latency control has been placed in front of an operation whose whole value was that it took two minutes.

The second number in that dashboard is the one that rarely makes it into the argument. Of 5,800 approvals raised in 34 days, 5,719 were approved, and the median time between the approver opening the message and clicking approve was 2.4 seconds. Nobody was reading the preview. A control approved 98.6% of the time after 2.4 seconds of consideration has already stopped functioning as a control; what remains of it is the latency.

The reason those two figures indict the control rather than the approvers is worth spelling out, because "people should read more carefully" is the conclusion most rooms reach and it is the wrong one. A confirmation is a measurement: somebody looks at a plan and emits a verdict, and the verdict is supposed to depend on what the plan says. Across thirty-four days the verdict was the same almost every time, and it arrived after an interval too short to have read the plan in. A quantity that takes the same value on almost every input carries almost no information about the input, whatever it is nominally measuring — which is the same objection this topic has already raised against a docstring that reads the same on two different calls and a tenant check comparing a value with itself. Eighty-one refusals across five thousand eight hundred requests is the entire output of the control, and nothing in the telemetry establishes that those eighty-one were the dangerous ones rather than the ones raised while somebody happened to be paying attention.

Both numbers point at the same conclusion, and it is not "remove the confirmation". It is that a confirmation applied uniformly to a high-frequency operation converts into a rubber stamp, which is worse than nothing because it produces an audit trail asserting that a human reviewed something.

The design that is easy to specify

Take the tool as it stands. cancel_subscription is irreversible in the customer's eyes, it fires webhooks into entitlements, which decide what the account may still use, into the customer relationship management system and into the finance warehouse, and support performs it roughly 200 times a day. The parts of the safety design that this topic has already established go on quickly, and they are cheap.

Scope, in the sense of chapter 2, on scoped allowlists, binds the operation to a subscription belonging to the tenant on the session, with the surface deciding reach: from end_user_chat the agent may cancel only subscriptions belonging to the authenticated account, and from support_console it may cancel any subscription in the tenant but only with an impersonation grant carrying a ticket reference. Cancellations are also velocity-limited per run and per tenant, because the failure that scope cannot express is one legitimate cancellation repeated four hundred times.

The preview carries the resolved plan rather than a sentence: the subscription id and plan name, the effective date, the proration — the credit owed for the unused remainder of the period already paid for — or refund amount with currency, the contract end date and any early-termination fee, the seats that lose access and when their data is deleted, and the three webhooks that fire with a note on which of them is not revertible from this side. That is the chapter 1 requirement applied to this operation, and building it takes an afternoon.

The timeout is a task-budget slice with an idempotency key derived from the run and turn, plus the reconciliation step from chapter 3, on task deadlines and cancellation, because a cancellation that times out with state: unknown must never be blindly retried. The audit record carries the run, the actor, the surface, the approval if there was one, the downstream request id from the billing service and the outcome, per chapter 4. And the integration suite forces the approval race, the timeout, a cross-tenant denial and a bypass attempt, per chapter 5.

None of that is the hard part. Every one of those controls costs microseconds of latency and a few days of engineering, and none of them will ever be deleted by somebody reading a dashboard.

Deciding which cancellations stop

The objection that a confirmation on every cancellation destroys the reason for building the agent is correct as stated and does not lead where it appears to. The purpose of a human confirmation is to prevent an irreversible mistake from being made by an actor whose judgement is attacker-influenceable. Where the mistake can be made cheaply reversible instead, the confirmation is buying something you already have, and where the customer is the party harmed, the customer is a better confirming actor than a support engineer who has no context on the account.

That gives three substitutes for a human approval, each appropriate to different cases. Reversibility: cancel effective at the end of the current billing period rather than immediately, keep entitlements live until then, and offer a 72-hour undo — a mistaken cancellation becomes an email and a click rather than an incident. Confirmation by the affected party: the customer types the plan name into the chat, or clicks a link in an email sent to the address on file, which also defeats the injection case, since injected text in a document cannot produce a click in the account owner's inbox. Velocity limits with an automatic stop: more than three cancellations in a run, or more than 25 in an hour across a tenant, halts the tool and pages someone, which is the backstop for the case where every individual decision passes and the aggregate is obviously wrong.

Human approval is then reserved for the slice where none of those work, which is where the money is large, the action is genuinely irreversible, or the account is in a state that makes an automated decision unsafe.

SliceShare of the 200/dayControlWhy this one
Monthly self-serve, one seat, no commitment~62%Customer types plan name; effective at period end; 72h undoReversible for 72h and confirmed by the harmed party
Monthly, 2–10 seats, mid-period, proration under €500~24%Same, plus proration shown and an email to the account ownerMoney is bounded and refundable
Annual contract, mid-term, or early-termination fee~9%Human approval against the pinned planIrreversible commercially; a finance decision
Account in billing dispute, legal hold, or churn-save flow (a retention offer already in progress)~3%Refused; routed to a personThe agent lacks the context that decides it
Any run exceeding 3 cancellations, or tenant over 25/hour~2%Halted and pagedAggregate is wrong even if each is right

Roughly 86% of the volume never waits for a human, so the agent is faster than the manual process for the overwhelming majority of requests, and the 9% that do wait are the ones where a four-minute delay is proportionate to the amount at stake. An approval queue receiving 18 items a day instead of 200 is also a queue people actually read, which is what makes the remaining approvals mean anything.

Those percentages are worth adding up, because a tiering table is only trustworthy when it is a partition — every cancellation in exactly one row, no cancellation in none of them. The five shares sum to a hundred with nothing left over, which is the property that licenses reading the rest of the table as a budget: the eighty-six per cent that proceeds automatically is the first two rows added, the nine per cent that waits is the third, and the remaining five per cent neither proceeds nor waits, because it stops. Multiply the third row's share by the two hundred a day and you get the eighteen items the queue actually receives, which is the same arithmetic the approvers will do when they ask why the channel went quiet. The empty cell in that table is also informative: no row sends a self-serve monthly cancellation to a human, and that absence is the decision the table exists to record. If a later change adds a sixth row without taking share from an existing one, the sum stops closing and the table has stopped describing the traffic.

Flowchart: cancel_subscription proposed → in scope for tenant + surface?; in scope for tenant + surface? → no; no → deny + audit; in scope for tenant + surface? → yes; yes → run/tenant velocity limit hit?; run/tenant velocity limit hit? → yes; yes → halt + page; run/tenant velocity limit hit? → no; no → annual, ETF, dispute or hold?; annual, ETF, dispute or hold? → yes; yes → human approves pinned plan; annual, ETF, dispute or hold? → no; no → customer confirms period-end + 72h undo; human approves pinned plan → execute + audit; customer confirms period-end + 72h undo → execute + audit.

Tiering like this is a policy decision with commercial consequences, so it belongs in a versioned artifact that finance and support sign off on, not in a conditional buried in the tool handler. Give it a version identifier and record that identifier on every cancellation, because the first question after any tiering change is which rows were decided under which rules.

Arranging the answer to a question nobody has asked yet

Six weeks after launch, cancellations are up 4% and nobody can say whether that is the agent. The work that would have answered it had to happen before launch, and it is mostly one column.

Every cancellation row needs to carry its provenance at write time: whether a human or an agent performed it, which run, which policy version, which tier decided it, and whether a human approved.

sql
ALTER TABLE subscription_cancellations
  ADD COLUMN origin            text NOT NULL,   -- 'support_manual' | 'agent' | 'self_serve'
  ADD COLUMN agent_run_id      text,            -- joins to the audit log
  ADD COLUMN policy_version    text,            -- 'cancel-tiers@2026-01-14'
  ADD COLUMN decision_tier     text,            -- 'self_serve_monthly' | 'annual_approved' | …
  ADD COLUMN approved_by       text,
  ADD COLUMN experiment_arm    text;            -- 'agent' | 'holdout' | null

With origin alone, the 4% question stops being a mystery and becomes arithmetic: cancellations rose from 200 to 208 a day, of which the agent performed some number, and you can see immediately whether the increase sits inside the agent's slice or outside it. That is a descriptive answer rather than a causal one, and in most rooms it is the answer people actually wanted. If the extra eight a day are all in support_manual rows for enterprise accounts, the agent is not the story.

The causal question is harder, and it is worth knowing the size of it before promising anyone an answer. Detecting a 4% relative change in a count with 80% power, meaning an 80% chance of seeing the change if it is genuinely there, at the conventional significance threshold needs on the order of 2(1.96 + 0.84)² / 0.04² ≈ 9,800 events per arm. At 200 cancellations a day split evenly between an agent arm and a holdout, that is 98 days. A 10% holdout — a slice of traffic deliberately kept on the manual path so there is something to compare against, and the size teams reach for by default because it feels safe — gives 20 events a day in the control arm and would need well over a year. Six weeks at a 50/50 split yields about 4,200 per arm, which is enough to detect a change of around 6% and not enough to detect 4%. The honest statement to make at launch is that this experiment can rule out a large regression quickly and cannot resolve a small one before autumn, and that is a much better position than discovering it during the postmortem.

Each piece of that expression is doing something nameable, and the one that decides everything is the square. The two constants in the numerator are the conventional thresholds turned into standard-deviation distances — one for how confident you insist on being that a difference you see is real, the other for how likely you insist it is that a real difference gets seen — and they are fixed by convention rather than by your system. The factor of two is there because you are comparing two arms and each contributes its own noise. The effect you want to detect sits in the denominator and is squared, and that is the term worth internalising: the count you need goes as one over the effect squared, so wanting to catch a change half as large costs four times the events. That single fact explains every other number in the paragraph without further calculation. A ten per cent holdout gives a fifth as many events per day in the control arm as an even split, so the same target takes five times as long. And six weeks at an even split delivers under half the required count, so the effect you can actually resolve is larger than four per cent by the square root of that shortfall — about half as much again, which is where the six comes from. The honest statement at launch is not a hedge. It is the output of an arithmetic anybody at the table can redo.

There is a cheaper instrument that often does more than the experiment. If the agent records a structured reason code and the customer's own words on every cancellation, and support does the same, a 4% rise decomposes by reason within an hour: a jump in "price" tells one story, a jump in "could not get help" tells another, and a jump in "cancelled by mistake" points straight at the tool. Sample size requirements collapse when you are comparing composition rather than detecting a shift in a single rate.

They collapse for the same reason the experiment was expensive, which is the square in the denominator read backwards. A four per cent move in the total is a four per cent effect and needs the events the calculation above demands. The same extra eight cancellations a day landing entirely inside one reason code is not a four per cent effect on that code, because the code is a small share of the total to begin with and a small denominator turns the same numerator into a large proportion. Since the required count falls with the square of the effect, an effect several times larger needs a small fraction of the events — which is how an hour of data answers a question the randomised comparison cannot settle before autumn. The price of the cheaper instrument is that a reason code is a claim about why, written by the agent or typed by a customer, and it can be wrong in ways a count of cancellations cannot. Running both and saying which is which is the honest position: one is precise about a quantity that may not be the one you care about, and the other is fast and approximate about the one you do.

Irreversible operations and metrics on the control itself

None of this is a case for fewer controls. Scope, timeouts, previews and the audit log are all cheap enough that arguing about their cost is a distraction, and the tiering above only exists because they are in place — it is the audit trail and the velocity limit that make it defensible to let 86% of cancellations proceed without a person. Removing a control because it is inconvenient and replacing a control with a cheaper one that achieves the same thing look similar in a pull request and are opposite decisions.

It is also not an argument that reversibility is always available. Some operations are irreversible in a way no undo window fixes: a message sent to a customer, a payout initiated, a record deleted from a system with a legal retention obligation, a partner feed that propagates in four hours. For those, the human stays, and the correct response to "this is slow" is to reduce how often the agent reaches that operation rather than to lower the bar in front of it.

The last thing worth instrumenting is the control itself. Approval dwell time, approval rate and override rate are metrics about whether the human step is doing anything, and the numbers from the opening — 98.6% approved, 2.4 seconds of consideration — were available on day 10 and read by nobody. A quarterly review that looks at those three numbers per tool, alongside the denial counts from chapter 2 and the reconstruction exercise from chapter 4, is what keeps a design from decaying into a set of steps that everyone routes around.

When you add the next write tool, the design review has two questions that come before the tool schema. What is the cheapest reversibility available for this operation, and which slice of it genuinely stops for a person. Answer those, put the resulting tiers in a versioned policy with the provenance columns already in the table, and the controls you ship will still be there in a year — which is the only property that makes any of them worth building.

Tool execution safety — read the current version at https://daviddominguez.dev/learn/en/tool-execution-safety/confirmation-cost-and-reversibility

Back to top