AI Agents  

Building AI Tool Approval Workflows with Microsoft Foundry

Introduction

AI agents become much more useful when they can interact with real systems.

An agent can read customer information, search internal documents, create tickets, update records, call APIs, or trigger business workflows. But once an AI system can perform actions instead of simply generating text, the security model changes.

The important question is no longer only:

Can the agent call this tool?

It becomes:

Should this particular tool call be allowed right now?

That is where AI tool approval workflows become important.

A well-designed approval workflow allows an agent to operate autonomously for low-risk operations while requiring explicit human authorization for sensitive actions.

For example:

Read Customer Profile
        |
        v
Automatic Approval

while:

Delete Customer Account
        |
        v
Human Approval Required
        |
        v
Tool Execution

This article explains how to design a practical tool approval workflow around Microsoft Foundry-based agent systems, with a focus on .NET applications, policy enforcement, auditability, and production safety.

What Is an AI Tool Approval Workflow?

A tool approval workflow is a control layer between an AI agent and the tools it is allowed to invoke.

Instead of:

User
 |
 v
AI Agent
 |
 v
Tool

the architecture becomes:

User
 |
 v
AI Agent
 |
 v
Tool Request
 |
 v
Policy Engine
 |
 +---- Low Risk ------> Execute
 |
 +---- High Risk -----> Approval
                         |
                         v
                    Approved?
                     /      \
                   Yes       No
                    |         |
                    v         v
                 Execute    Reject

This architecture separates reasoning from authorization.

The model can decide that a tool is useful, but it should not automatically have the authority to execute every tool it can discover.

Why Tool Approval Matters

AI agents introduce an important distinction between two decisions:

  1. Tool selection

  2. Tool authorization

An agent may correctly identify that RefundPayment is the right tool for a task.

That does not mean the system should automatically execute it.

For example:

Agent Decision:
"RefundPayment is required."

Authorization Decision:
"Is this refund allowed without human approval?"

Keeping these decisions separate creates a stronger security boundary.

Classify Tools by Risk

The first step is to classify available tools.

A simple model can use three categories.

RiskExampleApproval
LowRead customer profileAutomatic
MediumCreate support ticketPolicy dependent
HighRefund paymentHuman approval
CriticalDelete production dataExplicit approval

A more detailed classification can consider:

  • Read vs write

  • Reversible vs irreversible

  • Internal vs external

  • Financial impact

  • Privacy impact

  • Production impact

  • Number of affected records

  • Required user permissions

For example:

GetCustomer
    -> Read
    -> Low Risk

CreateTicket
    -> Write
    -> Medium Risk

RefundPayment
    -> Financial
    -> High Risk

DeleteAccount
    -> Destructive
    -> Critical

Deny by Default

A strong approval architecture should use deny-by-default behavior.

The principle is simple:

Unknown Tool
     |
     v
     DENY

rather than:

Unknown Tool
     |
     v
     ALLOW

This becomes particularly important when an agent has access to a large tool catalog.

A new tool should not automatically inherit permission merely because it has been registered.

Separate Tool Discovery From Tool Execution

Agents often need to discover which tools are available.

That does not mean discovery should grant execution rights.

For example:

Tool Registry
     |
     v
Tool Discovery
     |
     v
Agent selects tool
     |
     v
Authorization
     |
     v
Execution

This separation prevents a common mistake where visibility and permission are treated as the same thing.

Define an Approval Policy

An approval policy should answer several questions:

  • Which tools require approval?

  • Which users can approve them?

  • When is approval required?

  • How long is approval valid?

  • Can approval be reused?

  • What happens when approval expires?

  • What happens if approval is rejected?

  • What information must the reviewer see?

A conceptual policy could look like:

{
  "tool": "RefundPayment",
  "risk": "high",
  "approvalRequired": true,
  "approverRole": "FinanceManager",
  "expiresAfterMinutes": 10
}

The exact representation can vary, but the policy should be explicit and machine-enforceable.

Model the Tool Request

Before sending a request for approval, create a structured representation of the intended action.

For example:

public sealed record ToolApprovalRequest(
    string RequestId,
    string ToolName,
    string UserId,
    string Reason,
    string Arguments,
    string RiskLevel,
    DateTimeOffset CreatedAt);

The approval service can then evaluate this request independently of the model.

Never Trust the Model's Approval Decision

The model should not be responsible for deciding whether it has permission to perform an action.

For example, avoid an architecture like:

Agent:
"I have determined this operation is safe."

System:
"Okay, execute it."

The authorization decision should happen outside the model.

A safer architecture is:

Agent
 |
 | Tool Request
 v
Application Policy Layer
 |
 v
Approval Service
 |
 v
Tool

The model can request an operation.

The application decides whether that operation is authorized.

Build an Approval State Machine

Approval should be represented as a state transition rather than a simple Boolean.

A practical state model is:

Pending
  |
  +----> Approved ----> Executed
  |
  +----> Rejected
  |
  +----> Expired

For example:

public enum ApprovalStatus
{
    Pending,
    Approved,
    Rejected,
    Expired,
    Executed
}

This provides better auditability than storing only:

approved = true

Use Approval IDs

Every approval request should have a unique identifier.

var request = new ToolApprovalRequest(
    Guid.NewGuid().ToString("N"),
    "RefundPayment",
    userId,
    "Refund requested by customer",
    arguments,
    "High",
    DateTimeOffset.UtcNow);

The ID should follow the operation through the entire workflow.

For example:

Approval ID
   |
   +--> Agent Request
   |
   +--> Approval UI
   |
   +--> Authorization Decision
   |
   +--> Tool Execution
   |
   +--> Audit Log

This makes troubleshooting and auditing considerably easier.

What Should the Approver See?

A common mistake is asking a human to approve something without enough context.

Avoid:

"Approve tool call?"

Instead, provide meaningful information:

Tool:
RefundPayment

Customer:
Customer-4821

Amount:
₹8,500

Reason:
Duplicate payment

Requested By:
Support Agent

Risk:
High

Requested At:
12:35 PM

The approver should be able to understand the consequence of the operation before approving it.

Approval Should Be Specific

An approval should authorize a specific operation, not provide unlimited access.

Bad model:

User approved RefundPayment.

Better:

User approved:
RefundPayment
Customer: 4821
Amount: ₹8,500
RequestId: abc123

This prevents an approval from being reused for a different action.

Prevent Approval Replay

Suppose a user approves:

RefundPayment(Customer=4821, Amount=8500)

The agent should not be able to reuse that approval for:

RefundPayment(Customer=9127, Amount=8500)

or:

RefundPayment(Customer=4821, Amount=85000)

The authorization should be bound to the specific request.

A useful technique is to generate a request hash.

public static string CreateApprovalFingerprint(
    string toolName,
    string arguments)
{
    using var sha256 = System.Security.Cryptography.SHA256.Create();

    var input = $"{toolName}:{arguments}";
    var bytes = System.Text.Encoding.UTF8.GetBytes(input);

    return Convert.ToHexString(
        sha256.ComputeHash(bytes));
}

The fingerprint can then be associated with the approval.

Approval Expiration

Approvals should not remain valid indefinitely.

For example:

Requested
   |
   v
Pending
   |
   v
Approved
   |
   +---- 10 minutes ----> Expired

The expiration period should depend on risk.

A high-risk operation might require approval immediately before execution.

Handle Human Rejection Explicitly

When approval is rejected, the agent should receive a controlled result.

For example:

{
  "status": "rejected",
  "reason": "Approval denied",
  "retryAllowed": false
}

Do not simply return a generic tool failure.

Otherwise, the agent may interpret the rejection as a transient failure and repeatedly attempt the operation.

Prevent Approval Loops

Consider:

Agent
 |
 v
Tool Request
 |
 v
Approval Required
 |
 v
Rejected
 |
 v
Agent retries
 |
 v
Approval Required

This can become an unwanted loop.

The policy layer should track repeated attempts.

For example:

Attempt 1 -> Rejected
Attempt 2 -> Rejected
Attempt 3 -> Blocked

The application can then stop the workflow or escalate it to a human.

Keep Approval Outside the Agent Context

Approval state should be maintained by the application or an authoritative backend.

Do not rely solely on conversation history.

For example, this is weak:

User:
"Yes, approve it."

Agent:
"Approval received."

The system should verify:

Who approved?
What was approved?
When?
For which request?
Was the approval still valid?
Was the approval authorized?

This information should come from a trusted system rather than model-generated conversation state.

Example .NET Approval Service

A simplified service could look like this:

public interface IToolApprovalService
{
    Task<ToolApprovalResult> RequestAsync(
        ToolApprovalRequest request);

    Task<bool> IsApprovedAsync(
        string requestId);
}

The tool execution layer can then enforce the result:

if (policy.RequiresApproval(toolName))
{
    var approved = await approvalService
        .IsApprovedAsync(requestId);

    if (!approved)
    {
        return ToolResult.Denied(
            "Human approval is required.");
    }
}

The important design principle is that the tool itself should not trust the agent to enforce the policy.

Add a Policy Enforcement Layer

A production architecture should centralize authorization.

                  +------------------+
                  |     AI Agent     |
                  +--------+---------+
                           |
                           v
                  +------------------+
                  | Tool Request     |
                  +--------+---------+
                           |
                           v
                  +------------------+
                  | Policy Engine    |
                  +--------+---------+
                           |
              +------------+------------+
              |                         |
              v                         v
        Auto Approved             Approval Required
              |                         |
              |                         v
              |                  Human Approval
              |                         |
              +------------+------------+
                           |
                           v
                    Tool Execution

This architecture keeps authorization decisions centralized and auditable.

Add User and Role Context

Approval should consider the identity of the requesting user.

For example:

User A
  |
  +--> ReadCustomer
  +--> CreateTicket

User B
  |
  +--> ReadCustomer
  +--> CreateTicket
  +--> RefundPayment

The agent should not be able to elevate privileges simply by selecting a more powerful tool.

Authorization should be based on trusted identity and application policy.

Tool Arguments Need Validation

Approval does not eliminate input validation.

Suppose the approved operation is:

RefundPayment
Amount = 5,000

The actual execution must verify that the arguments still match the approved request.

Approved Amount: 5,000
Actual Amount:   50,000
                     |
                     v
                   DENY

This is why request fingerprinting and immutable approval records are useful.

Log Every Important Event

A production approval workflow should generate an audit trail.

Useful events include:

ToolRequested
ApprovalCreated
ApprovalViewed
ApprovalApproved
ApprovalRejected
ApprovalExpired
ToolExecutionStarted
ToolExecutionCompleted
ToolExecutionFailed

A basic audit record could be:

public sealed record ToolAuditEvent(
    string RequestId,
    string ToolName,
    string EventType,
    string UserId,
    DateTimeOffset Timestamp,
    string Details);

Correlation IDs should be carried across agent, approval, and tool execution services.

Protect Sensitive Arguments

Tool arguments may contain sensitive information.

Avoid storing raw arguments in logs when they contain:

  • Access tokens

  • Passwords

  • Payment details

  • Personal information

  • Internal secrets

Instead, log sanitized metadata.

For example:

Tool:
RefundPayment

Customer:
Customer-4821

Amount:
₹8,500

SensitiveArguments:
[REDACTED]

Auditability and data minimization should work together.

Separate Approval From Execution

One of the strongest design patterns is to treat approval as authorization and execution as a separate operation.

Request
  |
  v
Approval
  |
  v
Authorization Token
  |
  v
Execution

This makes it easier to:

  • Retry execution safely

  • Expire approvals

  • Audit decisions

  • Prevent unauthorized execution

  • Separate human workflow from tool infrastructure

Handle Tool Failures After Approval

Approval does not guarantee successful execution.

For example:

Approved
   |
   v
Tool Execution
   |
   +---- Success
   |
   +---- Timeout
   |
   +---- Validation Error
   |
   +---- External API Failure

The system should distinguish:

Authorization Failure

from:

Execution Failure

This distinction is important for both security and troubleshooting.

Idempotency Matters

Consider a payment-related tool.

The agent gets approval and calls the tool.

The request times out.

The agent retries.

Without idempotency, the operation might happen twice.

A safer approach is to associate the operation with an idempotency key:

Approval ID = abc123
Idempotency Key = abc123

The downstream system can reject duplicate execution attempts.

Common Mistakes

Letting the Model Approve Its Own Tool Call

The model should request an operation, not authorize itself.

Using a Global Approval Flag

An approval such as approved=true is too broad.

Giving the Agent Permanent Permission

High-risk tools should generally use explicit, scoped authorization.

Allowing Approval Reuse

An approval should be tied to a specific request and arguments.

Ignoring Expiration

Old approvals should not remain valid indefinitely.

Logging Sensitive Arguments

Audit logs should contain enough information for investigation without becoming another source of sensitive-data exposure.

Treating Rejection as a Tool Failure

A rejected request should be represented as an authorization decision, not an infrastructure error.

Executing After Arguments Change

The system must verify that the actual execution request matches what was approved.

Best Practices

  1. Separate tool selection from tool authorization.

  2. Use deny-by-default policies for sensitive operations.

  3. Classify tools by risk.

  4. Require explicit approval for high-impact actions.

  5. Bind approval to a specific request ID.

  6. Validate the tool arguments at execution time.

  7. Add approval expiration.

  8. Prevent approval replay.

  9. Keep authorization state outside the model context.

  10. Log every approval and execution transition.

  11. Sanitize sensitive data in audit logs.

  12. Use idempotency for side-effecting operations.

  13. Prevent repeated approval/retry loops.

  14. Keep policy enforcement centralized.

  15. Make the approval decision independently auditable.

Frequently Asked Questions

Should every AI tool require human approval?

No. Requiring approval for every read-only or low-risk operation can make an agent unnecessarily slow and difficult to use. Risk-based authorization is more practical.

Can the AI agent decide when approval is required?

The agent can identify that a tool needs to be called, but the authoritative approval decision should come from an application-controlled policy layer.

Should approval be permanent?

Generally, no for high-risk operations. Scoped and time-limited approvals reduce the risk of accidental or unauthorized reuse.

What happens if the approved tool arguments change?

The execution should be rejected unless the changed request goes through the appropriate authorization process again.

Why is an approval ID important?

It provides a stable correlation point connecting the original request, human decision, authorization, execution, and audit records.

Is approval enough to secure AI agents?

No. Approval is one layer. A production agent also needs authentication, authorization, input validation, tool isolation, secret management, monitoring, audit logging, and least-privilege access.

Conclusion

AI agents become significantly more powerful when they can interact with real systems, but that capability also introduces a new authorization problem.

A robust tool approval workflow creates a clear boundary between what an agent wants to do and what the application allows it to do.

The strongest design is risk-based: allow low-risk operations automatically, require human approval for sensitive actions, bind approvals to specific requests and arguments, enforce expiration and idempotency, and maintain an authoritative audit trail.

With this approach, AI agents can remain autonomous where automation is appropriate while keeping humans firmly in control of high-impact operations.