AI agents

Writing Approval Prompts a Reviewer Can Decide Fast

Antonio Evans10 min read

A side-by-side card: a raw diff with stack traces on the left, and on the right an approval card with four labeled blocks (what changed, why, blast radius graph, rollback command) and a reviewer approving in seconds.
Key points

Key points

  1. 1Four fields belong on every approval request: what changed, why it changed, blast radius, and rollback, each rendered in one line plus a short detail block.
  2. 2Compute each field from data you already have (diff stats, ticket links, service dependency maps, runbook steps) so the requester writes no prose under time pressure.
  3. 3Keep the card to one screen and put the full diff behind a link, because reviewers who must scroll to find the decision lose time on navigation rather than judgment.
  4. 4Track median time-to-decision and the share of rejections that cite a missing field, then fix whichever field those two numbers point at.

Why the payload, not the gate, is where decisions stall

Approval gates are now standard in agentic systems: a workflow pauses, emits a request, and resumes when a human responds. That mechanism is well documented. Microsoft's agent framework implements it with a request port that emits an event and waits for a response before execution continues (Microsoft Agent Framework), and LangChain's deep agents expose an interrupt_on setting so specific tools pause for approve, edit, reject, or respond decisions (LangChain docs). The gate is the easy part.

The hard part is what arrives inside the request. A pause with a thin payload ("Approve tool call: update_record?") moves the work of investigation onto the reviewer, and reviewers respond by either rubber-stamping or stalling. Research on human-in-the-loop design is consistent on this: human review earns its cost only when it is risk-based, targeted at high-impact or uncertain actions, and supported by the context a person needs to judge the specific case (Databricks). The request itself is that context.

There is a security dimension too. An approval gate answers a different question than a permission scope: scope says an agent may call a category of tool, while an approval event says a human authorized this specific invocation, with these arguments, at this time, which is what audit and compliance reviews typically demand (Scalekit). That framing is useful, because it tells you what the payload must contain to have evidentiary value: the arguments, the target, and the identity behind the request.

So treat the approval card as a document with a job. Its job is to let an informed reviewer answer one question, "is this action acceptable as described," without opening a second tab. Everything below serves that.

The four fields, and the decision rule that orders them

Every request carries four fields. They are not equally important, so give them a fixed order and a fixed visual weight:

FieldQuestion it answersOne-line formDetail block
What changedWhat exactly moves?3 files, 1 migration, 1 flag flipResource list, diff link, line counts
WhyWho wants it and toward what end?Priya, INC-4821, unblock billing retryRequester, intent, ticket link
Blast radiusWho and what is affected, and how do you know?2 services, 1 primary DB, ~4k customersUpstream/downstream list, data touched, evidence source
RollbackHow do we undo it, and how long does that take?Revert commit, restore snapshot, ~12 minExact steps, owner, decision trigger

The decision rule: if a reviewer cannot approve or reject from the four fields alone, the request is not ready to send. A missing field is a defect in the request, not a question for the reviewer. Encode that as a validation check in whatever produces the request, so incomplete cards never reach a human.

Order matters for speed. Put "what changed" first because it is the cheapest field to read and often settles the question outright: a one-line config change to a staging service needs seconds, while a schema migration on a primary database needs attention regardless of how good the justification is. Put rollback last, but never omit it. Reversibility is the single best proxy for how much deliberation a change deserves; the common framing of one-way and two-way doors is useful here because it separates choices where a wrong call is expensive to unwind from those where it is cheap (Perez Technology Group).

Two fields do most of the work when they are well computed. Blast radius, because it converts "this feels risky" into a list of named services, data stores, and customer segments. Rollback, because it forces the requester to have actually thought about failure before asking someone else to accept it.

Computing and rendering "what changed" and "why"

These two fields are mostly plumbing, which is good news: they should be generated, not written.

What changed. Pull from the source of truth for the change itself. For code, that means the diff: files touched, lines added and removed, whether migrations, feature flags, or dependency manifests are in the set. For agent tool calls, it means the tool name plus its arguments, which frameworks already hand you; Microsoft's approval request content exposes the function call including its name and arguments so it can be shown to the user (Microsoft Learn). For infrastructure, it is the resource and the operation: rule added, snapshot restored, service scaled.

Render it as a summary line with a count, then a bullet list of resources, then a link. The link is the important design choice: keep the diff out of the card. Long diffs push rollback below the fold and turn a decision into a reading task.

Changed: 3 files, 1 migration, 0 flags
  services/billing/retry.py        +41 -12
  migrations/0042_add_retry_at.sql  +18 -0
  config/billing.yaml              +2 -2
  Diff: https://git.example/compare/a1b2c3d... (opens new tab)

Why. Three parts: who, what for, and what it unblocks. The who is an identity you can resolve to a person or service account. The what-for is one sentence of intent, and it should be specific enough to be falsifiable ("cut retry latency for failed charges" is fine; "improve reliability" is not). The unblocking is a linked ticket, incident, or experiment ID, because a link to an existing record is the cheapest possible evidence that the request is not improvised.

Most systems already hold all three. If they are spread across a tracker, a chat thread, and a CI job, that is precisely the argument for assembling the card centrally: the scattered-context problem is one of the most common reasons a decision goes sideways, and consolidating the inputs in one view is the first fix (Koder).

A worked example of the speed difference. Card A reads: "Approve deployment." Card B reads: "Deploy billing-retry v14 to prod. Why: Priya, INC-4821, failed charges retrying hourly instead of daily. Blast radius: billing-worker and payments-api, reads and writes to charges table, affects roughly 4,000 customers with pending retries; no schema lock. Rollback: flip billing.retry_v14 flag off, ~2 min, no data change; revert commit if the flag path fails." A reviewer who knows the billing system can decide Card B in well under a minute. Card A requires a conversation.

Computing and rendering blast radius

Blast radius is the field teams most often fake. "Low risk" is not a blast radius. A usable blast radius names the affected surface along four axes and cites how the estimate was produced.

Services and dependencies. Which services run the changed code, which call it, and which it calls. This comes from your service catalog or dependency graph, not from memory. If an agent is making a tool call, the equivalent is the set of records or systems the call touches at its arguments' values.

Data stores and data classes. Which tables, buckets, or indexes are read or written, and whether the operation is additive or destructive. This is where "one record in staging" and "ten thousand records in production" diverge despite sharing the same permission, a distinction static authorization cannot express and only invocation-level context can (Scalekit).

Customers. An estimated count and segment, with the method stated. "Approximately 4,000 customers with pending retries, counted from the retry queue depth at request time" is checkable. "Some customers" is not.

Reversibility class. One-way or two-way, stated explicitly. Destructive writes, schema changes with data loss, and anything that cannot be undone by aborting go in the one-way bucket and get flagged at the top of the card.

Render it compactly:

Blast radius: 2 services, 1 primary table, ~4k customers
  Runs in: billing-worker (12 pods), payments-api (6 pods)
  Calls/reads: charges (read+write), ledger (read)
  Data: updates retry_at on pending charges; no deletes, no schema lock
  Reversibility: two-way (flag off restores prior path)
  Source: service catalog + queue depth at request time

That last line matters more than it looks. Telling the reviewer where the number came from is what lets them calibrate trust: a count derived from a live queue is stronger evidence than a guess typed by the requester. It also gives you something to audit later, since records of what was known at decision time are what make post-incident review possible at all (Koder).

Where blast radius is genuinely hard to compute, do not leave the field blank and do not let it read "unknown." Say what is unknown and what would resolve it: "Downstream consumers of charges.updated are not fully enumerated; the topic has 7 subscribers, 3 confirmed by catalog." That is still a decision-grade statement.

Writing rollback so it can actually be executed

Rollback is the field reviewers trust least, because it is the one most often written from optimism. The fix is to require it before approval rather than after the outage: the exact steps, the saved revision or snapshot to restore, who decides to roll back, and the trigger that makes the decision. Audit and compliance reviews routinely look for a tested rollback plan and proof of verification, and programs tend to fail on those two elements rather than on the requester, reason, and approver fields (fwchange).

A rollback block has four parts:

  1. Mechanism. Flag off, revert commit, redeploy previous revision, restore snapshot, or issue a compensating write. Name the specific artifact, ideally by identifier.
  2. Steps. Numbered, imperative, short enough to follow under stress. If the steps live in a runbook, link it and paste the first three.
  3. Expected time. A number, with the method: measured in staging, or estimated from the last execution.
  4. Trigger and owner. The condition that starts a rollback (error rate above a stated threshold for a stated window, a support ticket volume, a failed health check) and the role authorized to call it.
Rollback: flag off (billing.retry_v14 = false)
  1. Set flag false via admin console (or: quanta-flags disable retry_v14)
  2. Confirm retry queue drains at prior hourly rate for 10 min
  3. If flag path fails: git revert a1b2c3d && deploy billing-worker
  Expected time: ~2 min flag path, ~14 min revert path (measured in staging)
  Trigger: charge_failure_rate > 3% for 10 min, or 5+ related tickets in 1h
  Owner: on-call for billing

Two rules keep this honest. First, the rollback must match the change: a flag flip is not a rollback for a schema migration, and a snapshot restore is not a rollback for data that has since been written by other paths. Second, if no rollback exists, say so at the top of the card and treat that as the reviewer's main consideration. "No rollback; forward-fix only; impact limited to shadow table" is a legitimate statement. Silence is not.

For agent actions, the same structure applies with different mechanics: revoking a side effect usually means compensating writes or restoring from a pre-action snapshot, and the card should say which, plus how long the pause window lasts, because credentials or state valid at pause time are not guaranteed valid at resume time (Scalekit).

Two failure modes the card should prevent

Rubber-stamping. Reviewers approve without reading because the payload gives them nothing to read. The countermeasure is not more warnings; it is removing the ambiguity that makes a fast decision feel unsafe. When blast radius and rollback are concrete, approval becomes a checkable act rather than an act of faith. The Zapier guide to human-in-the-loop patterns makes the same point from the workflow side: checkpoints belong where decisions carry risk or nuance, and what reviewers catch should feed back into tightening the workflow's thresholds over time (Zapier).

Stalling. Reviewers wait for more information because the card raised a question it did not answer. This is the more expensive failure, because it consumes the reviewer's attention and the requester's time simultaneously. The four-field validation rule above is the direct remedy: an incomplete card is bounced back to the requester automatically, before a human sees it.

There is a third, quieter risk worth naming: approval fatigue. If every low-risk change produces a full card, reviewers start skimming. Route by risk, and let low-risk cards show a compressed form of the same four fields. Consistency of structure matters more than uniformity of length, because a reviewer who knows where to look reads faster regardless of size.

Measuring time-to-decision and tuning the card

You cannot improve the payload without measuring it. Two metrics are enough to start, and both are cheap to compute.

Median time-to-decision. Time from request emitted to approve or reject. Measure the median rather than the mean, since a few long-stalled requests skew averages badly. Segment by change class, because a two-way flag flip and a one-way migration should not share a target. Approval time is a specific, trackable workflow metric in its own right: it is the time an item spends waiting for a decision, and it is often the clearest signal of where work actually slows down (Atlassian Community).

Rejection reason mix. Make the reject action require a reason, chosen from a short fixed list plus free text. The list should mirror your fields:

  • Blast radius missing or wrong
  • Rollback missing, untested, or not applicable to this change
  • Why unclear or ticket not linked
  • What changed unclear (diff too large, wrong resources listed)
  • Policy or timing (change freeze, wrong window)

Then compute the share of rejections citing each reason. That distribution is a work queue for your card design: whichever field dominates is the field to automate or tighten next.

A worked sizing example. Suppose a team sends 40 approval requests a week, median time-to-decision is 3.5 hours, and 30 percent of rejections cite a missing rollback. Two interventions follow directly. First, make rollback a required, structured block with a mechanism picker, so it cannot be omitted. Second, add the rollback field to the validation gate so requests without one never reach a reviewer. If median time-to-decision falls to 40 minutes and rollback-cited rejections drop to 5 percent, the card is doing its job and the next iteration targets whichever reason is now on top.

Track two guardrails alongside. Approval rate that trends toward 100 percent with fast decisions is a sign of rubber-stamping, not efficiency: sample approved cards periodically and check whether the rollback field was accurate. And track resend rate, the share of requests sent back for more information, because a rising resend rate means the validation gate is too loose or the fields are hard to fill in.

Where the volume of requests is high and the fields are semi-structured, assembling and summarizing the card is itself a natural place for a model: drafting the one-line summary, normalizing resource names, and flagging when the blast radius mentions a service missing from the dependency list. That is a job for a single API across text models rather than a separate integration per provider, which is what Quanta Inference provides through an OpenAI-compatible endpoint.

FAQ

FAQ

What exactly counts as "what changed" for a non-code action, like a data update?

Report the resource, the operation, and the scale: table name, read/update/delete, and how many rows. Add the arguments that determine scope, such as a filter or an ID range, because those arguments are what distinguish a one-record fix from a bulk write under the same permission (Scalekit). A reviewer should be able to tell from the card alone whether this touches one record or ten thousand.

How do I get requesters to fill in blast radius when they genuinely do not know?

Split the field into known and unknown. Require the known part (services in the catalog that reference the resource) and require one sentence on what is unknown and what would resolve it. A stated unknown with a resolution path is decision-grade; a blank field is not. This also gives reviewers a legitimate reason to approve with residual risk noted, which is often the correct outcome.

Should the full diff ever appear inside the approval request?

Only when it is tiny, roughly a handful of lines, and only below the four fields. Otherwise link it. Large diffs push rollback below the fold, which is the field reviewers most need visible when deciding under time pressure. The one-screen rule exists because navigation cost, not reading cost, is what makes a decision feel slow.

How many decisions should one approval request cover?

One. If a request bundles several independent changes, a reviewer cannot approve part and reject part, so they either take on risk they did not examine or bounce the whole thing. Splitting by reversibility class is the useful cut: group the two-way changes together and give one-way changes their own request, even if they ship in the same window.

What is a reasonable target for median time-to-decision?

Set it per change class from your own baseline rather than borrowing a number. Measure for two weeks, find the median for two-way and one-way classes separately, then aim to cut the two-way median first, since that is where card improvements show up fastest. Revisit the target when the rejection reason mix changes, because a shift in reasons usually means the payload, not the reviewers, needs work.

Sources

Sources

  1. Microsoft Agent Framework Workflows - Human-in-the-loop (HITL)
  2. Human-in-the-loop - Docs by LangChain
  3. What is Human-in-the-Loop (HITL)? | Databricks Blog
  4. Human-in-the-Loop Tool Calling: Approval Gates for AI Agents
  5. Make More IT Decisions Reversible | PTG
  6. Using function tools with human in the loop approvals - Microsoft Learn
  7. How to Build a Web App for Feature Rollback Decisions
  8. Firewall Change Management: Process, Tools & Best ...
  9. Human-in-the-loop in AI workflows: Meaning and patterns - Zapier
  10. How to Measure Approval Time in Jira
About the author

About the author

Antonio Evans
Antonio Evans

Antonio Evans is the founder of Quanta Intelligence and a fractional CTO who builds HIPAA-ready AI systems for health and life-science companies. Before that he was technical co-founder of FriendsEAT for twenty years, CEO of Socialdraft, and a data engineer at IBM. Antonio lives with his dog Santino in Lisbon, Portugal.

All articles