Development

What the Approval Screen Shows: Payload, Reasoning and Expiry

Antonio Evans10 min read

A split-screen approval panel with a JSON payload on the left showing one changed field highlighted and a countdown timer on the right above Approve and Reject buttons.
Key points

Key points

  1. 1An approval screen must show the exact tool name and full arguments, the agent's stated reason, and a visible expiry, or the reviewer is guessing.
  2. 2Arguments belong on screen as structured, diffable text, not a collapsed JSON blob the reviewer has to expand.
  3. 3The approval is a runtime authorization event, not a one-time scope grant, so it needs to be tied to one invocation, one set of arguments, and one moment.
  4. 4An approval that stays open too long is a security hole, so expiry needs both a countdown and an absolute timestamp with a revalidation step on resume.

Why an OAuth scope is not an approval

The most common mistake in agent design is treating a valid credential as permission to act. OAuth scope answers a grant-time question: can this agent call this category of tool at all? It cannot answer the invocation-level question: should this specific call, with these arguments, run right now? That distinction is the entire reason an approval screen exists. As one engineering write-up on approval gates puts it, scope covers the action, while an approval gate covers "this specific invocation execute, with these arguments" at execution time (Scalekit on human-in-the-loop tool calling).

That matters because the Model Context Protocol explicitly frames tools as model-controlled, with "a human in the loop to grant approval" as the intended safety valve (MCP tools overview). If the human is part of the design, the screen they look at is a security control, not a courtesy dialog.

Three things make a call consequential enough to pause for:

  • Irreversibility. A delete, a merge, a payment. Revoking the token afterward is forensic, not protective.
  • Audit obligation. Regulated environments need a record that a named human authorized this operation at this timestamp.
  • Dynamic risk context. A single-field update and a bulk update of thousands of records share the same scope but not the same risk.

Every one of those concerns is answered by what the reviewer sees at the moment of the decision. That is why the screen has to carry the arguments and the reasoning, not just a tool name and a yes button.

The three elements, and what "visible" means for each

The short answer is that the reviewer needs the exact arguments, the agent's stated reason, and an expiry they can see. But "visible" is doing a lot of work in that sentence. Here is what each element should actually look like.

ElementWeak versionWhat it should be
ArgumentsCollapsed JSON blob, or a summary stringStructured key-by-key view, full values, diffable against the prior call
ReasoningNothing, or the whole chain of thoughtShort verbatim quote from the agent, tied to this call
ExpiryNothing, or "expires soon"Countdown plus absolute timestamp with time zone, and revalidation on resume
Tool identityJust a function nameNamespaced tool name with its source server
Side effectUnstatedExplicit statement of whether the call is read-only or destructive, and what it touches

The tool identity row is easy to overlook. When a host connects to multiple MCP servers, two servers can expose a tool with the same name, and clients fail on duplicate function names unless tools are namespaced by server (LiteLLM issue on namespaced MCP tool names). If the reviewer sees ask_question and cannot tell which server it came from, they cannot judge what the call will do. Show deepwiki/ask_question or the equivalent prefix in your own registry.

The side-effect row is where tool annotations help. MCP defines four boolean hints: read-only, destructive, idempotent, and open-world, and the defaults are deliberately pessimistic, so a tool with no annotations is assumed non-read-only, potentially destructive, non-idempotent, and open-world (MCP blog on tool annotations). Those hints are untrusted unless the server is trusted, so treat them as one input to the display, not the whole verdict. A useful pattern: render the hint as a badge, and render the actual arguments beside it, so the reviewer compares what the server claims against what the call will really do.

Showing arguments so they can be checked, not just seen

A collapsed JSON blob is the worst of both worlds. It looks like transparency and delivers none, because the reviewer either expands it to a wall of text or approves without reading. Good argument display has four properties.

Structured by field, not by raw serialization. Walk the tool's input schema and render one row per property, with the property name, the value, and the type. The schema is already there: MCP tool definitions carry a name, description, and JSON Schema input schema, so you have the property list for free (MCP tools overview).

Every value shown in full, with large values summarizable but expandable. A bulk update that targets ten thousand records should show a count and a sample, not nothing. Show the count prominently and link to the full list.

A diff against the last call to the same tool. If the agent called salesforce_updateRecord twice in one run, the second call's review should highlight the changed fields. Production reviewers get fast at this: the diff is the part that carries the risk.

A raw request view behind a toggle. Some reviewers want the exact bytes that will go on the wire, because the structured view is a rendering. Make it one click away, never the default and never hidden.

A small worked example makes the diff idea concrete. Suppose the agent wants to update an opportunity:

{
  "objectType": "Opportunity",
  "recordId": "0064x00000AbCdE",
  "fields": {
    "StageName": "Closed Won",
    "Amount": 185000
  }
}

The reviewer's job is to decide whether Closed Won on this record, at this amount, is right. If the screen shows only "update Salesforce record," they cannot. If it shows the two changed fields against the prior values (StageName: Negotiation → Closed Won), the decision takes seconds. That is the standard an approval gate should meet: a ten-to-thirty second evidence pack that makes the decision obvious (StackAI on designing approval workflows).

One more argument-design rule: show the arguments the tool will actually receive, not the arguments the model emitted. Any middleware between the agent and the tool (auth injection, schema coercion, default filling) changes the payload. If your client injects credentials or fills defaults, the approval screen must reflect the post-injection payload. That is why the approval should sit at the boundary of execution, after validation, not inside the model's context.

Displaying the agent's reasoning as a short quote

Reasoning is where most approval screens either show nothing or show too much. Showing nothing makes the reviewer re-derive intent from arguments alone, which is slow. Showing a full chain of thought is worse: it is long, it contains dead ends, and it invites the reviewer to audit the model's thinking rather than the proposed action.

The workable middle is a short verbatim quote from the agent, tied to the specific tool call, ideally capped at two or three sentences and rendered as a quoted block with attribution to the agent. Something like: "The customer asked for the duplicate charge to be refunded and confirmed the order number; I am refunding the second charge only."

Two implementation notes. First, capture the reason from the model turn that produced the tool call, not from a post-hoc summary, and store it with the approval record so the audit log contains what the reviewer saw. Second, if your client supports progressive tool discovery, the reasoning may arrive in the same turn as the tool choice, which keeps the mapping from reason to call clean (MCP client best practices).

The reasoning quote also gives you a cheap sanity check. If the stated reason does not match the arguments, that mismatch is itself the signal the reviewer needs. A reason that says "refund the second charge only" beside arguments that refund both charges is exactly the kind of hallucinated step an approval gate exists to catch.

Expiry: the part most implementations skip

An approval that sits open forever is a latent authorization grant. The agent paused, the world moved, and hours later someone clicks approve on a request whose arguments no longer describe reality. This is the failure mode that approval-gate write-ups call out repeatedly: state can change while the workflow is paused, so an approval can become stale, and the strongest systems revalidate current state before resuming (Promptessor on human-in-the-loop agents).

Practically, that means the screen should show expiry in two forms at once:

  • A countdown ("expires in 14 minutes") for immediate urgency.
  • An absolute timestamp with time zone ("expires at 2026-10-14 16:42 UTC") so the time is meaningful after the page sits open for an hour and the countdown has been re-rendered.

Show the two together because each fails alone. A countdown alone is ambiguous if the tab slept. An absolute time alone does not tell the reviewer whether they have time to think.

On the backend, expiry has to be enforced where the execution happens. The durable-approval pattern in Cloudflare's agent framework makes this explicit: a workflow can pause for approval with a timeout measured in days, and the pause is backed by durable state rather than a holding process (Cloudflare human-in-the-loop patterns). That is the right shape: the timer belongs to the stored request, not to the connection the reviewer happens to hold.

When an approval expires, do three things, all visible on the screen. Mark the request expired rather than letting a late click succeed. Tell the agent to re-plan rather than silently dropping the work. And if the reviewer arrives after expiry, offer one button that re-runs the proposal so the agent reissues the call with fresh state and a fresh window. That button is the difference between a safe expiry and an expiry that trains reviewers to click faster next time.

Handling the pause and resume without breaking the guarantee

The approval screen is the visible part of a longer chain: the agent proposes, the host stores the request, the human decides, and something resumes the agent. Two details of that chain affect what the screen must say.

The pause must survive restarts. Synchronous tool calling blocks on an HTTP request, and load balancers and serverless runtimes time out in seconds to minutes, so a reviewer in a meeting kills the connection and the approval is lost (Truto on HITL approval workflows). The durable pattern stores the request and lets the agent reference it later. This is why the screen should show a stable request ID: it lets support trace the decision even if the original session is gone.

The approval must bind to the arguments that were shown. On resume, verify that the stored request has not been mutated by anything between the pause and the execute. Prefer a hash of the arguments captured at proposal time and checked again at execution. The screen can then honestly say "the call you approved is the call that will run."

Tooling is converging on this pattern. Approval frameworks model it as a distinct status: a call either runs normally, is auto-approved, is auto-denied with a reason, or enters a user-approval state that emits a request and waits (AI SDK tool approvals). If you build your own gate, copy that state machine rather than inventing one. The four states map cleanly onto the screen: the request exists only in the user-approval state, and the other three never show the reviewer anything.

Authorization flows also intersect here. MCP authorization follows OAuth 2.1 conventions, with scopes like mcp:tools agreed at connection time (MCP authorization tutorial). Those scopes decide whether the request can be shown at all; they do not decide whether the specific invocation should run. Keep the two layers separate in your design and in your audit log: the scope grant is one record, the approval event is another.

A checklist for the approval screen

Treat the screen as a specification with pass or fail conditions. The following list is what a reviewer needs at a glance, in roughly the order they need it.

  1. Tool identity: server-qualified tool name, plus a one-line description drawn from the tool definition.
  2. Side-effect badge: read-only or destructive, plus whether the call touches an external system, sourced from annotations where available and labeled as a hint.
  3. Arguments: structured field-by-field view of the full payload that will be executed, with a diff against the prior call to the same tool and a raw view toggle.
  4. Reason: a short verbatim quote from the agent, timestamped, stored with the request.
  5. Blast radius: which records, users, recipients, or environment the call touches, with counts for bulk operations.
  6. Expiry: countdown and absolute timestamp with time zone, plus a clear expired state.
  7. Decision set: approve, reject with reason, and ideally modify or escalate, because approve-or-reject alone is often too narrow.
  8. Audit fields: reviewer identity, request ID, argument hash, and the moment of decision, all written to a durable log.

Two of these are frequently missing and both matter more than they look. Blast radius is the field that turns a decision from "is this action OK" into "is this action at this scale OK." And the decision set matters because reviewers who can only reject will reject the whole workflow; letting them edit a value or escalate to a second approver keeps the work moving without lowering the bar.

The risk-tiering rule ties the whole checklist back to cost. Reserve the synchronous screen for calls that are irreversible, costly, regulated, or wide in blast radius; let reads and reversible internal writes run with logging instead (CreateOS on where approval gates matter). Each approval that fires should be one the reviewer would be glad to see, which keeps the payload-reading habit intact when the important one arrives.

If you are wiring these gates across several model providers, one place to start is a single endpoint for the models behind the agent: Quanta Inference puts text, image, audio and embedding models behind one OpenAI-compatible API key, so the approval layer does not change when the model does (https://quantaintelligence.ai/inference).

FAQ

FAQ

What has to be on the approval screen besides the arguments and the reason?

Tool identity and side-effect class, the blast radius of the call, the decision options beyond approve and reject, and the audit fields that record what the reviewer saw. Without a server-qualified tool name, two same-named tools from different servers are indistinguishable, which is a known source of connection failures when names collide (LiteLLM issue on namespaced tool names). Without blast radius, the reviewer cannot separate a one-field edit from a bulk write.

Why show both a countdown and an absolute timestamp for expiry?

A countdown communicates urgency but is meaningless after the tab has slept, and an absolute time is precise but does not tell the reviewer how long they have to think. Showing both covers each failure. More importantly, expiry must be enforced on the stored request, not the browser, because pauses can last days and the durable pattern exists for exactly that wait (Cloudflare human-in-the-loop patterns).

Should the screen show the model's full chain of thought?

No. Show a short verbatim quote from the agent tied to this specific call, capped at two or three sentences. A long trace invites the reviewer to audit the model's thinking instead of the proposed action, and it makes a thirty-second decision take minutes. Store the quote with the approval record so the audit log contains exactly what the reviewer saw when they clicked.

How do I guarantee the approved call is the call that executes?

Capture the arguments at proposal time, hash them, and store the hash alongside the request. At execution, re-derive the hash from the payload about to be sent and compare. If they differ, send the request back for a fresh decision rather than executing. This also catches middleware that injects credentials or fills defaults after the reviewer saw the payload.

What happens when an approval expires while the reviewer is away?

Mark the request expired so a late click cannot succeed, and tell the agent to re-plan instead of silently dropping the work. When the reviewer returns, offer a single re-propose action that reissues the call with fresh state and a new window. Treating expiry as a revalidation trigger, not a dead end, is what keeps the gate trusted and the workflow moving (Promptessor on human-in-the-loop agents).

Sources

Sources

  1. Human-in-the-Loop Tool Calling: Approval Gates for AI ...
  2. Tools
  3. Namespaced MCP Tool Names #12271 - BerriAI/litellm
  4. Tool Annotations as Risk Vocabulary: What Hints Can and ...
  5. Human-in-the-Loop AI Agents: How to Design Approval ...
  6. Client Best Practices
  7. Human-in-the-Loop AI Agents: Approval Workflows & Best ...
  8. Human-in-the-loop patterns · Cloudflare Agents docs
  9. Implementing Human-in-the-Loop Approval Workflows for ...
  10. Tool Approvals
  11. Understanding Authorization in MCP
  12. Human-in-the-Loop AI Agents: When Approval Gates Matter
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