Featured image of post Microsoft Agent Framework III: Agent Harness, Planning, and Human Approval

Microsoft Agent Framework III: Agent Harness, Planning, and Human Approval

Build a resumable .NET support agent with an explicit harness, bounded planning, and human approval for sensitive actions using Microsoft Agent Framework.

Introduction

Part II gave our support agent tools, MCP, conversation sessions, and retrieval. Those capabilities answer questions, but a production agent also needs to manage work that takes several steps and may change business state.

This article adds an agent harness around the same AIAgent foundation. The harness owns a plan, records progress, pauses before a sensitive tool call, and resumes the same session after a human approves or rejects the action. Planning is explicit application state; the model may propose steps, but it does not get authority to execute them.

The sample handles a duplicate-payment request:

textCustomer Request
1
I was charged twice for ORD-1001. Please refund the duplicate charge.

The agent can inspect the order immediately. A refund changes financial state, so the harness creates an approval request instead of calling the refund operation.

Real-World Scenario: A Duplicate Charge

The support team receives this request:

textCustomer Request
1
I was charged twice for ORD-1001. Please refund the duplicate charge.

The agent must not jump directly from text to a refund. It needs to:

  1. Identify and validate the order.
  2. Check the order and payment facts.
  3. Explain why a refund is being considered.
  4. Ask an authorized employee to approve the financial action.
  5. Execute the refund once, then report the result.

The customer request and the approval decision may happen in different HTTP requests, or even on different machines. That is why this is a harness problem rather than only a prompt-writing problem.

flowchart LR C((Customer reports duplicate charge)) --> L[Validate order] L --> F[Read payment facts] F --> P[Create bounded refund plan] P --> A[Wait for human approval] A -->|Approved| R[Execute idempotent refund] A -->|Rejected| N[Record rejection] R --> O([Explain outcome]) N --> O classDef customer fill:#fef3c7,stroke:#d97706,stroke-width:2px,color:#78350f classDef process fill:#dbeafe,stroke:#2563eb,stroke-width:2px,color:#1e3a8a classDef plan fill:#cffafe,stroke:#0891b2,stroke-width:2px,color:#164e63 classDef approval fill:#fce7f3,stroke:#db2777,stroke-width:2px,color:#831843 classDef action fill:#dcfce7,stroke:#16a34a,stroke-width:2px,color:#14532d classDef result fill:#e0e7ff,stroke:#4f46e5,stroke-width:2px,color:#312e81 class C customer class L,F process class P plan class A approval class R,N action class O result

1. Agent Harness: Runtime Control Boundary

An agent harness is the runtime boundary around an agent run. It coordinates the parts that are easy to lose when an agent grows beyond one prompt:

  • Session and plan state.
  • Tool selection and execution policy.
  • Progress, failures, and resumability.
  • Context limits and persistence.
  • Approval requests for sensitive operations.
  • Logging, tracing, and audit records.

The harness does not make the model reliable by itself. It makes important decisions visible and enforceable in application code.

flowchart LR U((Customer)) --> H[Agent harness] H --> P[(Plan and session)] H --> A{{AIAgent}} A --> R[Read-only lookup] A --> X{Sensitive action?} X -->|No| T[Execute approved tool] X -->|Yes| Q[Create approval request] Q --> V{Human decision} V -->|Approve| T V -->|Reject| E[Record rejection] T --> O([Customer outcome]) E --> O classDef customer fill:#fef3c7,stroke:#d97706,stroke-width:2px,color:#78350f classDef harness fill:#dbeafe,stroke:#2563eb,stroke-width:2px,color:#1e3a8a classDef state fill:#cffafe,stroke:#0891b2,stroke-width:2px,color:#164e63 classDef agent fill:#fce7f3,stroke:#db2777,stroke-width:2px,color:#831843 classDef decision fill:#e0e7ff,stroke:#4f46e5,stroke-width:2px,color:#312e81 classDef action fill:#dcfce7,stroke:#16a34a,stroke-width:2px,color:#14532d class U customer class H harness class P state class A agent class X,V decision class R,Q,T,E action class O customer

Microsoft’s current Agent Harness guidance describes a fuller runtime with planning/execution modes, todo tracking, context handling, approvals, persistence, and telemetry. This sample keeps the boundary small so every state transition is visible. It is an application-level harness, not a replacement for the framework’s official harness package.

2. Planning: Controlled Decomposition

Why planning matters

A single model response hides whether the agent inspected the right data, skipped a required check, or attempted an unsafe action. A plan turns a vague goal into observable work:

textSupport Plan
1
2
3
4
5
1. Inspect order and payment records       completed
2. Check duplicate-charge policy             completed
3. Request approval for duplicate refund    waiting_for_approval
4. Execute refund after approval             pending
5. Tell customer what happened               pending

The plan is not a promise that every step will succeed. It is a checkpointed state machine. Each step should have a clear owner, bounded inputs, and a durable status.

Planner versus executor

Keep two responsibilities separate:

Responsibility Owner
Interpret customer goal Model and agent
Propose useful steps Planner
Validate allowed steps Harness policy
Execute business operation Application tool
Decide sensitive approval Human or policy service
Persist progress Harness store

Do not let free-form model text become executable instructions. In the sample, the planner creates a fixed plan for the refund scenario. A model can later propose a plan, but the harness must validate its step types against an allow-list before persistence.

3. Human Approval: Gate Sensitive Tools

Approval is a pause, not a prompt instruction

Adding “ask for approval” to system instructions is not an authorization boundary. The application must prevent the sensitive function from running until an approval decision exists.

sequenceDiagram participant C as Client participant H as Harness participant A as AIAgent participant F as Refund tool participant P as Approver rect rgb(254, 243, 199) C->>H: Request refund end rect rgb(219, 234, 254) H->>A: Run with plan and tools A->>H: Proposes refund H->>H: Persist waiting_for_approval end rect rgb(252, 231, 243) H-->>C: Approval required P->>H: Approve request end rect rgb(220, 252, 231) H->>F: Execute refund F-->>H: Refund result H-->>C: Resume with outcome end

Approval records should include the request ID, session ID, tool name, validated arguments, requester, approver, decision, timestamp, and reason. Never accept the tool arguments again from an untrusted approval callback without comparing them with the persisted request.

Which tools require approval?

Usually require approval for:

  • Refunds, payments, transfers, and cancellations.
  • Sending external messages or publishing content.
  • Account, permission, or identity changes.
  • Deleting or exporting customer data.
  • Any operation whose impact is difficult to reverse.

Read-only order lookup and policy retrieval can run automatically when authentication, authorization, input validation, and rate limits are still enforced.

Build the Approval Flow

The sample uses a small set of records and an in-memory store:

csharp
1
2
3
4
public enum PlanStatus { Pending, Running, WaitingForApproval, Completed, Rejected }

public sealed record ApprovalRequest(
    string Id, string SessionId, string ToolName, string Arguments, string Status);

The harness controls transitions and calls the side-effecting operation only after it has checked the persisted decision:

csharp
1
2
3
4
5
6
7
8
9
if (approval.Status == "pending")
  return Results.Accepted(approval);

if (decision.Approved)
{
    plan.Status = PlanStatus.Running;
  plan.Result = RefundOrder(plan.OrderId);
  plan.Status = PlanStatus.Completed;
}

The important detail is ownership. The endpoint does not call RefundOrder directly. It asks the harness to run or resume a plan, and the harness is the only path that can invoke the side-effecting tool.

4. Context and Resumability

An AgentSession carries conversation context between agent runs. A harness adds operational state around that conversation: plan steps, approval requests, tool results, and failure checkpoints.

Persist both when a workflow can outlive an HTTP request. The sample uses an in-memory store for clarity, so restarting the process loses pending approvals. A production implementation should use a durable store, tenant and user authorization, optimistic concurrency, and an expiration policy for stale approvals.

Long conversations also need compaction. Keep system instructions and recent decisions, summarize old turns, and retain references to source records rather than copying unlimited tool output into every prompt. Context compaction reduces tokens; it does not replace audit storage.

5. Middleware and Failure Boundaries

Use middleware or an equivalent pipeline for cross-cutting checks:

  1. Validate session ownership and request size.
  2. Add correlation and trace information.
  3. Enforce tool allow-lists and timeouts.
  4. Convert transient failures into a resumable failed step.
  5. Record approval and tool events.

Do not assume every failed model or tool call should be retried. Read-only lookups may use bounded exponential backoff. Refunds need idempotency keys and reconciliation, not blind retries.

The Sample Application

The Part III sample is a small ASP.NET Core API built on the same Microsoft.Agents.AI package used in Parts I and II:

  • SupportAgent wraps AIAgent for optional explanations.
  • AgentHarness owns plan, approval, and idempotent refund state.
  • AgentHarness.Api exposes run, approval, and plan endpoints.

Run the Aspire host to start the API and the Redis redis:7.4 approval-store container:

powershell
1
2
$env:ASPIRE_ALLOW_UNSECURED_TRANSPORT = "true"
dotnet run --project src/Aspire/AgentHarness.AppHost

Start a plan:

bash
1
2
3
curl -X POST http://localhost:5000/api/agent/runs \
  -H "Content-Type: application/json" \
  -d '{"sessionId":"case-1001","orderId":"ORD-1001","prompt":"I was charged twice for ORD-1001. Please refund the duplicate charge."}'

The response is 202 Accepted with an approval request. Approve it using the returned request ID:

bash
1
2
3
curl -X POST http://localhost:5000/api/approvals/<approval-id> \
  -H "Content-Type: application/json" \
  -d '{"approved":true,"reason":"Verified duplicate charge"}'

Rejecting the request records a terminal rejection and never invokes the refund tool:

bash
1
2
3
curl -X POST http://localhost:5000/api/approvals/<approval-id> \
  -H "Content-Type: application/json" \
  -d '{"approved":false,"reason":"Customer verification missing"}'

The sample includes unit and API integration layers matching Parts I and II:

powershell
1
2
dotnet run --project tests/AgentHarness.UnitTests/AgentHarness.UnitTests.csproj
dotnet run --project tests/AgentHarness.IntegrationTests/AgentHarness.IntegrationTests.csproj

Production Checklist

  • Persist sessions, plans, and approval records durably.
  • Authorize every read and write by tenant, user, and resource.
  • Validate model-produced arguments with typed schemas.
  • Use idempotency keys for side effects and reconcile uncertain results.
  • Give approval requests an expiry and handle duplicate callbacks.
  • Trace model calls, tool calls, plan transitions, and approval decisions.
  • Redact payment and personal data from logs.
  • Bound plan length, tool calls, tokens, latency, and cost.
  • Test rejected, expired, duplicated, timed-out, and partially completed runs.

Conclusion

An agent harness gives multi-step work a durable control boundary. Planning makes work observable, sessions and checkpoints make it resumable, and approval gates keep sensitive tools behind an explicit human decision.

The model remains useful for interpretation and proposing the next step. The harness owns policy, state transitions, and authority. That division lets the support agent become more capable without turning a generated response into an uncontrolled business operation.

Run the Complete Example

The complete sample is available here:

Reference

Built with Hugo
Theme Stack designed by Jimmy