AI agents can automate many tasks, but not every decision should happen without human involvement.

A long-running agent may analyze documents, update records, approve requests, send notifications, or interact with external systems. Some of these actions can have real business consequences. In such cases, allowing the agent to make the final decision automatically can create unnecessary risk.

A better approach is to pause the workflow at important decision points and ask a human to review the proposed action.

This is commonly called human-in-the-loop.

In this article, we will see how to design human approval into a long-running agent workflow in C#, how approval state should be handled, and what developers should consider when building this pattern for production applications.

What Is Human Approval in an AI Agent?

Human approval means that the agent can perform analysis and prepare an action, but a person must approve that action before the workflow continues.

A simple workflow might look like this:

User Request
     |
     v
AI Agent
     |
     v
Analyze Information
     |
     v
Prepare Action
     |
     v
Human Approval
     |
     +---- Reject ----> End
     |
     +---- Approve ---> Continue
                         |
                         v
                    Execute Action

The important point is that the human is not necessarily involved in every step.

The agent can still perform routine work automatically. Human involvement is added only where a decision requires review.

For example, an agent could:

  1. Read a customer request.

  2. Analyze the request.

  3. Prepare a response.

  4. Determine that a refund may be required.

  5. Pause for approval.

  6. Continue only after an authorized employee approves the refund.

This gives the agent automation while keeping a human responsible for sensitive decisions.

Why Long-Running Agents Need Approval Workflows

Human approval becomes more important when an agent can run for an extended period.

Consider an employee onboarding agent:

Read Employee Information
        |
        v
Validate Documents
        |
        v
Create Account Request
        |
        v
Human Approval
        |
        v
Create Accounts
        |
        v
Send Welcome Notification

The first three steps might be completely automated.

Creating accounts, however, may require authorization.

If the agent reaches the approval step and the application process is restarted, the workflow should not forget that it was waiting for a decision.

The state needs to represent something like:

Workflow Status: WaitingForApproval
Approval Status: Pending

The workflow can then continue later without starting the entire operation again.

Representing Approval State in C#

A simple approval model can be represented with an enum:

public enum ApprovalStatus
{
    Pending,
    Approved,
    Rejected
}

The workflow state can then contain the approval information:

public sealed class ApprovalState
{
    public string WorkflowId { get; set; } = string.Empty;

    public ApprovalStatus Status { get; set; }

    public string? ApprovedBy { get; set; }

    public DateTimeOffset? DecisionTime { get; set; }

    public string? Reason { get; set; }
}

This state provides enough information to understand what happened.

For example:

WorkflowId: WF-1024
Status: Approved
ApprovedBy: employee-123
DecisionTime: 2026-09-25T10:30:00Z

In a real application, the workflow state would normally be persisted rather than kept only in application memory.

Separating Agent Decisions From Human Decisions

One of the most important design principles is to clearly separate what the agent decides from what the human approves.

For example:

Agent:
"Refund of $750 is recommended because the order
matches the refund policy."

Human:
"Approve"

System:
"Execute refund."

The agent recommends an action.

The human authorizes the action.

The application executes it.

This separation makes the workflow easier to understand and audit.

A C# model could represent the proposed action like this:

public sealed class ProposedAction
{
    public string ActionId { get; set; } = string.Empty;

    public string Description { get; set; } = string.Empty;

    public decimal Amount { get; set; }

    public string Reason { get; set; } = string.Empty;

    public ApprovalStatus ApprovalStatus { get; set; }
}

The agent can populate the proposal, while the approval process controls whether the action can actually execute.

Building the Workflow Around an Approval Point

A long-running workflow can be structured into separate steps:

public async Task ProcessRequestAsync(
    Request request,
    CancellationToken cancellationToken)
{
    var analysis = await AnalyzeRequestAsync(
        request,
        cancellationToken);

    var action = await CreateProposalAsync(
        analysis,
        cancellationToken);

    await SaveProposalAsync(
        action,
        cancellationToken);

    // Workflow pauses here until approval.

    if (action.ApprovalStatus != ApprovalStatus.Approved)
    {
        return;
    }

    await ExecuteActionAsync(
        action,
        cancellationToken);
}

The important part is not the if statement itself.

The important part is that the approval becomes part of the workflow state.

A durable workflow can stop after creating the proposal and continue after an approval event is received.

Waiting for Human Input

A common mistake is to keep the application process running while waiting for someone to approve an action.

For example:

while (!approved)
{
    await Task.Delay(TimeSpan.FromMinutes(1));
}

This approach is generally not suitable for long-running workflows.

It keeps a process alive simply to wait for an external decision.

A better design is event-driven:

Agent
  |
  v
Create Approval Request
  |
  v
Persist Workflow State
  |
  v
Wait
  |
  | Approval Event
  v
Resume Workflow
  |
  v
Execute Action

The workflow does not need to continuously poll for approval.

Instead, the approval system can generate an event when the human makes a decision.

Approval Events in C#

You can represent an approval decision as a domain event:

public sealed class ApprovalDecision
{
    public string WorkflowId { get; init; } = string.Empty;

    public ApprovalStatus Status { get; init; }

    public string ApproverId { get; init; } = string.Empty;

    public string? Reason { get; init; }
}

When the user approves the request, the application can process the event:

public async Task HandleApprovalAsync(
    ApprovalDecision decision,
    CancellationToken cancellationToken)
{
    var workflow = await workflowStore.GetAsync(
        decision.WorkflowId,
        cancellationToken);

    if (workflow is null)
    {
        throw new InvalidOperationException(
            "Workflow was not found.");
    }

    workflow.ApprovalStatus = decision.Status;
    workflow.ApproverId = decision.ApproverId;
    workflow.ApprovalReason = decision.Reason;

    await workflowStore.SaveAsync(
        workflow,
        cancellationToken);
}

The workflow can then resume from the persisted state.

Approval Is Not the Same as Authentication

Another important distinction is between authentication and authorization.

Authentication answers:

Who is this person?

Authorization answers:

Is this person allowed to approve this action?

For example, simply knowing that a user is logged in does not mean that the user should be allowed to approve a $50,000 transaction.

Approval workflows should therefore integrate with the application's authorization rules.

Conceptually:

User Identity
     |
     v
Authentication
     |
     v
Authorization
     |
     v
Approval Permission
     |
     v
Approve / Reject

The exact authorization rules depend on the application and business requirements.

Handling Approval Expiration

An approval request should not necessarily remain valid forever.

For example:

public sealed class ApprovalRequest
{
    public string Id { get; init; } = string.Empty;

    public string WorkflowId { get; init; } = string.Empty;

    public DateTimeOffset CreatedAt { get; init; }

    public DateTimeOffset ExpiresAt { get; init; }

    public ApprovalStatus Status { get; set; }
}

The workflow can check whether the request has expired:

if (DateTimeOffset.UtcNow >= approval.ExpiresAt)
{
    approval.Status = ApprovalStatus.Rejected;
}

This is useful when the underlying information can become outdated.

For example, an approval based on a price, inventory level, or security assessment may no longer be valid after a certain period.

Handling Rejection

Rejection should be treated as a valid workflow outcome, not necessarily as an application error.

For example:

Agent Analysis
      |
      v
Approval Request
      |
      +---- Approved ----> Execute
      |
      +---- Rejected ----> Record Decision
                              |
                              v
                           Complete

The application should store the reason when appropriate:

if (decision.Status == ApprovalStatus.Rejected)
{
    workflow.Status = "Rejected";
    workflow.RejectionReason = decision.Reason;
}

This helps users understand why an action did not happen and provides useful information for auditing.

Comparing Automatic and Human-Assisted Agents

Area

Fully Automatic Agent

Human-in-the-Loop Agent

Routine decisions

Fully automated

Can remain automated

Sensitive decisions

Agent decides

Human can approve

Long-running workflow

Requires state management

Requires state plus approval state

Risk control

Depends heavily on agent behavior

Adds a human checkpoint

Auditability

Requires detailed logging

Approval decisions can be recorded

User involvement

Minimal

Required at defined points

Workflow complexity

Lower initially

Higher, but more controlled

Human approval does not mean every action should require manual intervention.

The goal is to identify the points where human judgment adds meaningful value.

Common Mistakes

Keeping the Process Running While Waiting

Do not keep a worker alive indefinitely while waiting for approval.

Persist the state and resume when the decision arrives.

Allowing Approval Without Authorization

Do not assume every authenticated user can approve every action.

Apply appropriate authorization rules.

Losing the Approval Context

The approver should be able to understand what they are approving.

Store the relevant proposal, reasoning, affected resource, and other required context.

Executing an Action Twice

A workflow may receive duplicate approval events.

The execution step should therefore be designed to handle duplicate messages safely.

Treating Rejection as an Exception

A rejection is often a normal business outcome.

Model it explicitly rather than treating every rejection as a technical failure.

Best Practices for Production

Persist the Approval State

Store:

Use Idempotency

Approval events can potentially be delivered more than once.

Use a unique approval or workflow identifier to prevent duplicate execution.

Keep an Audit Trail

Record important approval events:

Approval Created
Approval Viewed
Approval Approved
Approval Rejected
Workflow Resumed
Action Executed

The exact audit requirements depend on the application.

Show Clear Approval Information

A human should not have to inspect raw agent output to understand the decision.

Present the important information clearly:

Action:
Issue customer refund

Amount:
$750

Reason:
Request meets refund policy

Status:
Pending Approval

Keep Human Decisions Explicit

Do not let the agent interpret an ambiguous response as approval.

Use explicit states such as:

Approved
Rejected
Pending
Expired

Protect Sensitive Information

Approval interfaces may expose customer, financial, operational, or other sensitive information.

Only expose the information required for the approval decision.

Advantages and Disadvantages

Advantages

Disadvantages

Troubleshooting Human Approval Workflows

When an agent does not resume after approval, check the following:

  1. Verify that the approval event contains the correct workflow ID.

  2. Check whether the approval state was persisted.

  3. Confirm that the approver had the required permission.

  4. Check whether the approval request expired.

  5. Verify that duplicate-event handling is working.

  6. Check whether the workflow was actually resumed after the decision.

  7. Review logs for the approval request and workflow identifiers.

  8. Confirm that the execution step did not already run.

These checks help separate workflow problems from authorization, persistence, or messaging problems.

Summary

Human approval provides a practical way to combine AI automation with human control in long-running C# agent workflows.

The agent can analyze information and prepare an action, while a human reviews sensitive decisions before the workflow continues. The approval itself should become part of the durable workflow state rather than being treated as a temporary application variable.

For production systems, focus on persistent state, explicit approval statuses, authorization, idempotency, expiration, audit logging, and event-driven workflow resumption.

The most useful design is usually not a completely autonomous agent or a workflow that requires human approval at every step. Instead, let the agent handle routine work and introduce human approval at the points where business rules, risk, or human judgment require it.