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:
Tool selection
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.
| Risk | Example | Approval |
|---|
| Low | Read customer profile | Automatic |
| Medium | Create support ticket | Policy dependent |
| High | Refund payment | Human approval |
| Critical | Delete production data | Explicit 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:
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
Separate tool selection from tool authorization.
Use deny-by-default policies for sensitive operations.
Classify tools by risk.
Require explicit approval for high-impact actions.
Bind approval to a specific request ID.
Validate the tool arguments at execution time.
Add approval expiration.
Prevent approval replay.
Keep authorization state outside the model context.
Log every approval and execution transition.
Sanitize sensitive data in audit logs.
Use idempotency for side-effecting operations.
Prevent repeated approval/retry loops.
Keep policy enforcement centralized.
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.