An AI agent tool approval flow lets a human or policy authority review a proposed action before the agent executes it. If approval binds to a natural-language summary the model generates rather than the canonical action envelope, the action that runs can differ from the action the approver saw. This post documents a safe default for binding approval to the exact tool invocation and its trade-offs.

System description

The approval system binds every approved action to an action_hash computed over the full normalized envelope. The executor receives only an envelope ID and fetches every parameter from the trusted store, not from the model. No execution proceeds without a matching approval in the store, and each envelope is accepted for execution exactly once.

AI Tool Execution Workflow

Architecture choice

There are three common placements for the approval decision. The right choice depends on whether the agent serves a single user or a broader team, and how much human review each action class warrants.

Inline user approval

The agent pauses and presents the proposed action to the user who initiated the task. That same session receives the approval and continues execution.

Use this when:

  • The action affects only the requesting user

  • The user is present and can review the exact target and parameters

  • The agent is a personal assistant or a local developer tool

Trade-off: the requester may be the wrong approver for financial or cross-tenant actions. Timeout and replay handling are harder when the approval decision is tied to an interactive session.

Policy-backed approval queue

The agent submits the normalized envelope to a queue. A policy engine chooses the required approver and the execution window.

Use this when:

  • Actions affect shared resources or regulated workflows

  • Different action classes need different approvers

  • You need searchable evidence for audits and incident review

Trade-off: adds latency and product complexity. The queue also becomes a surface for stale pending actions and approval phishing.

Delegated automation approval

A workflow owner pre-approves a class of actions through a policy rule. The executor can run without a human when the envelope matches the policy exactly.

Use this when:

  • The action class is repetitive and low-variance

  • The policy can be expressed as exact targets and allowed parameter values

  • Failed or incorrect execution is reversible

Main risks: policy drift and approval scope that gradually expands into standing privilege.

Common middle ground: require human approval for new action shapes and high-impact targets; allow delegated approval only for repeated actions with the same normalized envelope shape.

Golden path

Start with this path:

Agent proposes tool call → normalizer creates canonical envelope → policy assigns approval tier → approver approves `action_hash` → executor fetches stored envelope by `envelope_id` → executor claims envelope before side effect → evidence log records outcome

Related patterns:

  • For the upstream control that prevents a malicious tool description from steering the agent toward deceptive approvals, see Designing a Safe Approval Flow for MCP Tool Descriptions; the hash-binding mechanism is identical, applied one stage earlier in the same pipeline

  • The interception layer that makes envelope enforcement possible across agents is the AI Agent Gateway; without a mediation layer, there is no single place to attach the envelope and enforce the hash check before tools are called

  • For the closest human-in-the-loop analog outside the AI context, see Designing a Safe Support Impersonation Flow; both share the scoped-credential and action-gate design, with the key difference being that impersonation binds to a session and this pattern binds to a single tool invocation

Minimal system context

This pattern assumes the agent cannot call tools directly. Tool calls pass through an agent gateway or execution service that normalizes the proposed action and enforces policy.

  • User (identity): the person or service that assigns the task to the agent

  • LLM agent: proposes tool calls; untrusted for authorization

  • Action normalizer (control plane): translates proposed calls into server-controlled envelopes

  • Policy engine (authorization): decides allow, deny, or the required approval tier

  • Approval UI (control plane): presents envelope fields to the approver; the decision surface

  • Envelope store (data plane): authoritative source for envelope state; must support atomic state transitions

  • Executor (control plane): claims an approved envelope and runs the side effect

  • Execution authority: limits the executor to the approved operation and target; implemented by target-side authorization, scoped tokens, assumed roles, or a credential broker

  • Target tool: performs the side effect; enforces its own authorization separately

  • Evidence log (data plane): append-only record of every envelope lifecycle event

Core design

Action envelope

The envelope is the canonical, server-created record of a proposed tool call. The approver approves action_hash; the executor runs only from the stored envelope. Minimum fields:

  • envelope_id: server-generated ULID or UUIDv7; the single-use execution token

  • actor_id: authenticated identity of the requesting principal, derived from the session, never from the request body

  • tenant_id: derived from the session token, never from the model's output

  • tool_id: stable internal identifier, distinct from any display name

  • operation: the specific method on the tool (send, delete, deploy)

  • target: normalized resource identifier

  • parameters: the full, normalized parameters object

  • parameters_hash: SHA-256 over canonical JSON (RFC 8785 JCS) of parameters; the executor re-derives and checks this at execution time

  • normalizer_version: version of the normalization logic applied to parameters

  • tool_schema_version: version of the tool's parameter schema at proposal time

  • expires_at: policy-assigned approval deadline, set at envelope creation time before the approval view is rendered; never null

  • action_hash: SHA-256 over canonical JSON of tenant_id, actor_id, tool_id, operation, target, parameters_hash, normalizer_version, tool_schema_version, and expires_at; this is what the approver approves

Normalizer

The normalizer maps the model's proposed tool call into the stable envelope schema. It resolves aliases (prod / production / PROD"production") and converts amounts to integer smallest-unit representation (cents, not dollars). Unknown parameter values are rejected at normalization time, not passed through. Use RFC 8785 JCS for deterministic hashing across runtimes and languages; ordinary JSON key ordering is not stable.

The same normalization logic runs at proposal time and at execution time. Store the current version in normalizer_version. At execution time, reject envelopes whose normalizer_version or tool_schema_version is no longer in the active allowlist; require a new envelope and re-approval.

Policy engine

The policy engine decides allow, deny, or the required approval tier based on the fully normalized envelope. It operates as a whitelist: any tool call matching no rule is denied, not forwarded to a human for improvised review. When routing to a human approver, the engine excludes envelope.actor_id from the candidate pool.

Approval UI

The UI renders directly from the server-side envelope, not from the model's chat context. Show all security-relevant fields by default: tool, operation, target, tenant, amounts, recipients, environment, and permission scope. Make the full canonical envelope available without truncation. For irreversible operations, display the "cannot be undone" label derived from the tool schema.

For high-risk actions, require the approver to type the target name or amount before submitting.

Executor

The executor receives only the envelope_id. At execution time, it fetches the envelope from the server-side store, recomputes parameters_hash from envelope.parameters, recomputes action_hash from the stored envelope fields, and compares the recomputed action_hash against the approved record's action_hash. It then checks expiry, status, active normalizer_version / tool_schema_version, and tenant/resource consistency before performing an atomic compare-and-swap to claim the envelope. That claim must precede the side effect.

Execution authority

The executor should not rely on reusable broad credentials for approval-bound actions. Limit execution authority to the approved operation and target.

For internal tools, the target service can enforce this directly by accepting envelope_id, fetching the stored envelope, and verifying tenant, operation, target, status, and expiry before executing. For external APIs or cloud operations, use a short-lived scoped token or assumed-role session tagged with envelope_id.

The required property is scoped execution authority. A credential broker is one implementation, not a required component.

Evidence log

State transitions produce append-only events. On the approval side: action.proposed, approval.required, approval.granted, approval.revoked. On the execution side: execution.claimed, execution.started, execution.succeeded, execution.failed. The log joins envelope_id, actor_id, approved_by, tool_id, target, tenant_id, and the outcome. A reconciliation job should alert on execution.claimed or execution.started events with no terminal outcome after twice the approval TTL.

Minimal API shape

POST /agent-actions
  → { envelope_id, action_hash, expires_at, approval_requirement }

GET  /agent-actions/{envelope_id}/approval
  → approval view rendered from envelope fields

POST /agent-actions/{envelope_id}/approve
  → { approved_at, action_hash, expires_at }

POST /agent-actions/{envelope_id}/revoke

POST /agent-actions/{envelope_id}/execute
  → executor-only; all parameters come from the store, not the request body

Threat model

Baseline assumptions

  • The LLM agent and its tool invocations are untrusted for authorization purposes

  • The normalizer, policy engine, envelope store, executor, and credential broker are trusted components

  • The requester may be operating under prompt injection or plan drift

  • The approver is authenticated and can provide freshness or step-up context for high-risk actions

  • Target systems still enforce their own authorization independently of the approval check

  • Standard infra controls (TLS, service authentication, secret management, database access controls) are assumed to be in place. This model focuses on the proposal-to-execution binding

A note on risk

This table is not a checklist. Focus on preventing the highest-impact failures first. Detection and response are acceptable where prevention is impractical.

Phase 1: Proposal and normalization

Focus: Preventing the proposed action from diverging into a different effective action through input manipulation or cross-tenant substitution

Asset

Threat

Baseline Controls

Mitigation Options

Risk

Envelope record

Plan drift: Agent presents an action for approval at T1, re-reasons between approval and execution due to prompt injection or new retrieval context, and submits mutated arguments at T3

Envelope created at proposal time

1. Executor fetches all parameters from the stored envelope; ignores any arguments supplied by the model at execution time

2. Short envelope expiry (5–30 min) bounds the drift window

3. Pre-execution state check on the target resource for high-impact operations

High

Normalization layer

Alias bypass: Agent proposes deploy(env="prod"); policy denies "production" but not "prod", and the alias passes through

Tool schema validation

1. Alias registry maps all known equivalents to canonical form before the envelope is created

2. Policy engine operates on the post-normalization canonical form, never on raw model output

3. Unknown parameter values are rejected at normalization time, before the envelope is created

Medium

Approved parameters

Hidden parameter injection: Model includes optional flags (drain_timeout=0, restart_dependents=true) valid per the tool schema but absent from a truncated approval display

Envelope contains full parameters object

1. Approval UI renders every field from the envelope's parameters object, not a model-generated summary

2. Tool schema marks parameters requiring explicit acknowledgment before they can be included in an envelope

3. Policy engine evaluates the full parameters object, including optional fields

Medium

Tenant namespace

Cross-tenant substitution: Agent proposes an action targeting a resource in another tenant's namespace; if the executor derives the target from model output, it runs on the wrong tenant

Auth context available per request

1. Envelope service derives tenant_id from the session token, never from the request body or model output

2. Policy engine verifies the proposed target is within the resource registry for envelope.tenant_id before storing the envelope

3. Executor re-verifies tenant match from the stored envelope before issuing scoped credentials

High

Phase 2: Approval decision

Focus: Ensuring approval is granted by the right principal with deliberate intent, and that the approval record cannot be silently bypassed

Asset

Threat

Baseline Controls

Mitigation Options

Risk

Approval decision

Self-approval: Requester and approver are the same principal, defeating independent review

Approval routing exists

1. Hard constraint: approval_record.approver_id != envelope.actor_id, enforced at the store layer, not only in the UI

2. Policy engine excludes the requester from the candidate approver pool before dispatching the request

3. Alert when the eligible approver pool for a required action has fewer than two candidates

Medium

Approver session

Stale session: High-risk approval granted from an hours-old browser session, without deliberate re-authentication

Session authentication required

1. Require WebAuthn or step-up MFA bound to the specific envelope_id for high-risk operations

2. Maximum session age for approval: reject if the session is older than the policy window (typically 10–30 min for write actions)

3. Approval UI shows elapsed time since last authentication when the freshness limit is approaching

Medium

Approval view

Approval phishing: Agent frames a destructive action as benign in conversational context; approver consents based on the framing rather than the envelope fields

Approval UI exists

1. Approval UI renders exclusively from envelope fields; the agent's conversational context is not displayed on the approval screen

2. For destructive operations, require the approver to type the target or quantity before submitting

3. "Cannot be undone" label derived from the tool schema, not from the model's description of the action

Medium

Phase 3: Execution and evidence

Focus: Ensuring the executor accepts the approved envelope once and emits complete evidence regardless of outcome

Asset

Threat

Baseline Controls

Mitigation Options

Risk

Envelope state

Approval replay: A previously-consumed or expired envelope is presented again to trigger a second execution

Single-use flag on envelope

1. Atomic compare-and-swap: UPDATE SET status='consumed' WHERE status='approved'; abort if zero rows are updated

2. Mark the envelope consumed before the side effect, not after

3. Reject execution requests for envelopes in consumed or expired status regardless of the request source

High

Claim transition

Concurrent consume race: Two executor instances receive the same envelope_id simultaneously and both attempt to transition the envelope to consumed, risking duplicate side effects

Compare-and-swap on consume

1. Treat zero rows updated by the compare-and-swap as an immediate rejection

2. Reject execution requests for envelopes already in consumed status regardless of the caller's identity

3. Reconcile execution.claimed or execution.started events with no terminal outcome after twice the envelope TTL; require manual recovery before retrying non-idempotent actions

Medium

Execution authority

Authority overreach: Executor has reusable broad credentials and can perform actions beyond the approved envelope

Service authentication

1. Target service fetches envelope_id and verifies tenant, operation, target, status, and expiry before executing

2. For systems that cannot enforce envelope checks, issue a short-lived scoped token or assumed-role session bounded to the approved operation and target

3. Include envelope_id in target-side logs for attribution

Medium

Evidence log

Missing outcome: Execution fails or partially succeeds but no terminal evidence event is emitted, leaving the audit record ambiguous

Log infrastructure present

1. Emit execution.failed with an error code for every failure, including timeouts; never swallow the error silently

2. Alert on execution.claimed or execution.started events with no terminal outcome event after twice the envelope TTL

3. For partial executions, emit execution.partial with a description of what completed and what did not

Low

If you use delegated automation approval

When a policy rule replaces the human approver, the threat profile shifts in a few ways:

  • Policy drift is the dominant risk: a rule that began as narrow gradually expands through incremental edits to cover a broader target set. Treat policy rule changes like production code: require review and audit before deployment

  • Alias bypass and hidden parameter injection carry more weight because no human reviews the envelope before execution. The normalization layer bears the full burden of keeping the effective action within the approved class

  • Automation approvals warrant shorter TTLs and tighter target constraints than human approvals

FAQs

Should every agent action require human approval?

No. Approval is slow and easy to rubber-stamp when it covers too many actions. Reserve it for irreversible operations and those that cross a trust boundary. Low-impact read operations are better covered by normal authorization and logging rather than an approval queue.

Is a chat confirmation enough?

Usually not. A chat "yes, do it" can work as a user experience, but it needs to produce a structured approval record behind the scenes. The executor requires a server-side approval record with a bound action_hash and a consumed flag. A transcript line cannot carry those guarantees on its own.

Verification checklist

  • Envelope and normalization

    • Every sensitive action converts to a canonical envelope before approval is requested

    • The approval UI renders tool_id, operation, target, tenant_id, and every parameter from the stored envelope, not from a model-generated summary

    • The same proposed action produces the same parameters_hash and action_hash across app nodes and runtimes

    • Changing tool_id, operation, target, tenant_id, any parameter, normalizer_version, tool_schema_version, or expires_at produces a different action_hash

    • If normalizer_version or tool_schema_version is no longer active at execution time, execution is rejected and re-approval is required

    • Any tool call matching no policy rule is denied, not forwarded to a human for improvised approval

  • Policy and approver selection

    • approver_id != actor_id is enforced at the store layer, not only in UI routing

    • High-risk approvals require a fresh WebAuthn or step-up MFA context

    • Policy engine excludes the requester from the candidate approver pool before dispatching

    • Approval routing operates on the normalized envelope, not on raw model output

  • Approval lifecycle

    • Approvals expire on a short TTL by action class; the executor rejects expired envelopes

    • Pending and approved envelopes can be revoked before execution; revocation takes effect immediately

    • A revoked envelope fails the status check regardless of what the caller asserts

    • Approval store uses append-only state transitions: revocations are new records, not mutations to existing ones

  • Execution

    • Executor fetches the envelope from the server-side store by envelope_id; it does not accept tool arguments from the model at execution time

    • Executor recomputes parameters_hash and action_hash immediately before execution; any mismatch is a security event, not a normal error

    • Executor marks the envelope consumed before the first non-idempotent side effect

    • A second execution attempt on a consumed envelope is rejected

    • Concurrent execution requests for the same envelope_id result in exactly one accepted attempt; the others are rejected by the compare-and-swap

  • Evidence

    • Evidence log joins envelope_id, actor_id, approved_by, and the outcome event

    • Reconciliation alerts on execution.claimed or execution.started events with no terminal outcome after twice the envelope TTL

Implementation & Review

The full threat model matrix, architectural diagrams, and a printable verification checklist for this pattern are available in the Secure Patterns repository. Use these artifacts to guide your design reviews and internal audits.

Keep Reading