<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Planning on dotnetuniversity</title>
        <link>https://dotnetuniversity.com/tags/planning/</link>
        <description>Recent content in Planning on dotnetuniversity</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>en-us</language>
        <lastBuildDate>Sat, 22 Aug 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://dotnetuniversity.com/tags/planning/index.xml" rel="self" type="application/rss+xml" /><item>
        <title>Microsoft Agent Framework III: Agent Harness, Planning, and Human Approval</title>
        <link>https://dotnetuniversity.com/microsoft-agent-framework-iii-agent-harness-planning-and-human-approval/</link>
        <pubDate>Sat, 22 Aug 2026 00:00:00 +0000</pubDate>
        
        <guid>https://dotnetuniversity.com/microsoft-agent-framework-iii-agent-harness-planning-and-human-approval/</guid>
        <description>&lt;img src="https://dotnetuniversity.com/microsoft-agent-framework-iii-agent-harness-planning-and-human-approval/eesofuffzich-2AsfYaYTbpI-unsplash.jpg" alt="Featured image of post Microsoft Agent Framework III: Agent Harness, Planning, and Human Approval" /&gt;&lt;h1 id=&#34;introduction&#34;&gt;Introduction
&lt;/h1&gt;&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;This article adds an &lt;strong&gt;agent harness&lt;/strong&gt; around the same &lt;code&gt;AIAgent&lt;/code&gt; 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.&lt;/p&gt;
&lt;p&gt;The sample handles a duplicate-payment request:&lt;/p&gt;
&lt;div class=&#34;copilot-prompt text-code-block&#34;&gt;&lt;div class=&#34;copilot-prompt__header&#34;&gt;&lt;span class=&#34;text-code-block__language&#34;&gt;text&lt;/span&gt;&lt;span class=&#34;text-code-block__title&#34;&gt;Customer Request&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;copilot-prompt__body&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;I was charged twice for ORD-1001. Please refund the duplicate charge.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h1 id=&#34;real-world-scenario-a-duplicate-charge&#34;&gt;Real-World Scenario: A Duplicate Charge
&lt;/h1&gt;&lt;p&gt;The support team receives this request:&lt;/p&gt;
&lt;div class=&#34;copilot-prompt text-code-block&#34;&gt;&lt;div class=&#34;copilot-prompt__header&#34;&gt;&lt;span class=&#34;text-code-block__language&#34;&gt;text&lt;/span&gt;&lt;span class=&#34;text-code-block__title&#34;&gt;Customer Request&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;copilot-prompt__body&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;I was charged twice for ORD-1001. Please refund the duplicate charge.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The agent must not jump directly from text to a refund. It needs to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Identify and validate the order.&lt;/li&gt;
&lt;li&gt;Check the order and payment facts.&lt;/li&gt;
&lt;li&gt;Explain why a refund is being considered.&lt;/li&gt;
&lt;li&gt;Ask an authorized employee to approve the financial action.&lt;/li&gt;
&lt;li&gt;Execute the refund once, then report the result.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;div class=&#34;mermaid&#34;&gt;flowchart LR
  C((Customer reports duplicate charge)) --&gt; L[Validate order]
  L --&gt; F[Read payment facts]
  F --&gt; P[Create bounded refund plan]
  P --&gt; A[Wait for human approval]
  A --&gt;|Approved| R[Execute idempotent refund]
  A --&gt;|Rejected| N[Record rejection]
  R --&gt; O([Explain outcome])
  N --&gt; 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&lt;/div&gt;
&lt;h1 id=&#34;1-agent-harness-runtime-control-boundary&#34;&gt;1. Agent Harness: Runtime Control Boundary
&lt;/h1&gt;&lt;p&gt;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:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Session and plan state.&lt;/li&gt;
&lt;li&gt;Tool selection and execution policy.&lt;/li&gt;
&lt;li&gt;Progress, failures, and resumability.&lt;/li&gt;
&lt;li&gt;Context limits and persistence.&lt;/li&gt;
&lt;li&gt;Approval requests for sensitive operations.&lt;/li&gt;
&lt;li&gt;Logging, tracing, and audit records.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The harness does not make the model reliable by itself. It makes important decisions visible and enforceable in application code.&lt;/p&gt;
&lt;div class=&#34;mermaid&#34;&gt;flowchart LR
  U((Customer)) --&gt; H[Agent harness]
  H --&gt; P[(Plan and session)]
  H --&gt; A{{AIAgent}}
  A --&gt; R[Read-only lookup]
  A --&gt; X{Sensitive action?}
  X --&gt;|No| T[Execute approved tool]
  X --&gt;|Yes| Q[Create approval request]
  Q --&gt; V{Human decision}
  V --&gt;|Approve| T
  V --&gt;|Reject| E[Record rejection]
  T --&gt; O([Customer outcome])
  E --&gt; 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&lt;/div&gt;
&lt;p&gt;Microsoft&amp;rsquo;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&amp;rsquo;s official harness package.&lt;/p&gt;
&lt;h1 id=&#34;2-planning-controlled-decomposition&#34;&gt;2. Planning: Controlled Decomposition
&lt;/h1&gt;&lt;h2 id=&#34;why-planning-matters&#34;&gt;Why planning matters
&lt;/h2&gt;&lt;p&gt;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:&lt;/p&gt;
&lt;div class=&#34;copilot-prompt text-code-block&#34;&gt;&lt;div class=&#34;copilot-prompt__header&#34;&gt;&lt;span class=&#34;text-code-block__language&#34;&gt;text&lt;/span&gt;&lt;span class=&#34;text-code-block__title&#34;&gt;Support Plan&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;copilot-prompt__body&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;1
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;2
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;3
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;4
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;1. Inspect order and payment records       completed
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;2. Check duplicate-charge policy             completed
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;3. Request approval for duplicate refund    waiting_for_approval
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;4. Execute refund after approval             pending
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;5. Tell customer what happened               pending&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2 id=&#34;planner-versus-executor&#34;&gt;Planner versus executor
&lt;/h2&gt;&lt;p&gt;Keep two responsibilities separate:&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;Responsibility&lt;/th&gt;
          &lt;th&gt;Owner&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;Interpret customer goal&lt;/td&gt;
          &lt;td&gt;Model and agent&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Propose useful steps&lt;/td&gt;
          &lt;td&gt;Planner&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Validate allowed steps&lt;/td&gt;
          &lt;td&gt;Harness policy&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Execute business operation&lt;/td&gt;
          &lt;td&gt;Application tool&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Decide sensitive approval&lt;/td&gt;
          &lt;td&gt;Human or policy service&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Persist progress&lt;/td&gt;
          &lt;td&gt;Harness store&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h1 id=&#34;3-human-approval-gate-sensitive-tools&#34;&gt;3. Human Approval: Gate Sensitive Tools
&lt;/h1&gt;&lt;h2 id=&#34;approval-is-a-pause-not-a-prompt-instruction&#34;&gt;Approval is a pause, not a prompt instruction
&lt;/h2&gt;&lt;p&gt;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.&lt;/p&gt;
&lt;div class=&#34;mermaid&#34;&gt;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-&gt;&gt;H: Request refund
  end
  rect rgb(219, 234, 254)
  H-&gt;&gt;A: Run with plan and tools
  A-&gt;&gt;H: Proposes refund
  H-&gt;&gt;H: Persist waiting_for_approval
  end
  rect rgb(252, 231, 243)
  H--&gt;&gt;C: Approval required
  P-&gt;&gt;H: Approve request
  end
  rect rgb(220, 252, 231)
  H-&gt;&gt;F: Execute refund
  F--&gt;&gt;H: Refund result
  H--&gt;&gt;C: Resume with outcome
  end&lt;/div&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2 id=&#34;which-tools-require-approval&#34;&gt;Which tools require approval?
&lt;/h2&gt;&lt;p&gt;Usually require approval for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Refunds, payments, transfers, and cancellations.&lt;/li&gt;
&lt;li&gt;Sending external messages or publishing content.&lt;/li&gt;
&lt;li&gt;Account, permission, or identity changes.&lt;/li&gt;
&lt;li&gt;Deleting or exporting customer data.&lt;/li&gt;
&lt;li&gt;Any operation whose impact is difficult to reverse.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Read-only order lookup and policy retrieval can run automatically when authentication, authorization, input validation, and rate limits are still enforced.&lt;/p&gt;
&lt;h1 id=&#34;build-the-approval-flow&#34;&gt;Build the Approval Flow
&lt;/h1&gt;&lt;p&gt;The sample uses a small set of records and an in-memory store:&lt;/p&gt;
&lt;div class=&#34;copilot-prompt text-code-block&#34;&gt;&lt;div class=&#34;copilot-prompt__header&#34;&gt;&lt;span class=&#34;text-code-block__language&#34;&gt;csharp&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;copilot-prompt__body&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;1
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;2
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;3
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f38ba8&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#f38ba8&#34;&gt;enum&lt;/span&gt; PlanStatus { Pending, Running, WaitingForApproval, Completed, Rejected }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f38ba8&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#f38ba8&#34;&gt;sealed&lt;/span&gt; &lt;span style=&#34;color:#cba6f7&#34;&gt;record&lt;/span&gt; &lt;span style=&#34;color:#f9e2af&#34;&gt;ApprovalRequest&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f38ba8&#34;&gt;string&lt;/span&gt; Id, &lt;span style=&#34;color:#f38ba8&#34;&gt;string&lt;/span&gt; SessionId, &lt;span style=&#34;color:#f38ba8&#34;&gt;string&lt;/span&gt; ToolName, &lt;span style=&#34;color:#f38ba8&#34;&gt;string&lt;/span&gt; Arguments, &lt;span style=&#34;color:#f38ba8&#34;&gt;string&lt;/span&gt; Status);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The harness controls transitions and calls the side-effecting operation only after it has checked the persisted decision:&lt;/p&gt;
&lt;div class=&#34;copilot-prompt text-code-block&#34;&gt;&lt;div class=&#34;copilot-prompt__header&#34;&gt;&lt;span class=&#34;text-code-block__language&#34;&gt;csharp&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;copilot-prompt__body&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;1
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;2
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;3
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;4
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;5
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;6
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;7
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;8
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#cba6f7&#34;&gt;if&lt;/span&gt; (approval.Status == &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;pending&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#cba6f7&#34;&gt;return&lt;/span&gt; Results.Accepted(approval);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#cba6f7&#34;&gt;if&lt;/span&gt; (decision.Approved)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    plan.Status = PlanStatus.Running;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  plan.Result = RefundOrder(plan.OrderId);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  plan.Status = PlanStatus.Completed;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The important detail is ownership. The endpoint does not call &lt;code&gt;RefundOrder&lt;/code&gt; 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.&lt;/p&gt;
&lt;h1 id=&#34;4-context-and-resumability&#34;&gt;4. Context and Resumability
&lt;/h1&gt;&lt;p&gt;An &lt;code&gt;AgentSession&lt;/code&gt; carries conversation context between agent runs. A harness adds operational state around that conversation: plan steps, approval requests, tool results, and failure checkpoints.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h1 id=&#34;5-middleware-and-failure-boundaries&#34;&gt;5. Middleware and Failure Boundaries
&lt;/h1&gt;&lt;p&gt;Use middleware or an equivalent pipeline for cross-cutting checks:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Validate session ownership and request size.&lt;/li&gt;
&lt;li&gt;Add correlation and trace information.&lt;/li&gt;
&lt;li&gt;Enforce tool allow-lists and timeouts.&lt;/li&gt;
&lt;li&gt;Convert transient failures into a resumable failed step.&lt;/li&gt;
&lt;li&gt;Record approval and tool events.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h1 id=&#34;the-sample-application&#34;&gt;The Sample Application
&lt;/h1&gt;&lt;p&gt;The Part III sample is a small ASP.NET Core API built on the same &lt;code&gt;Microsoft.Agents.AI&lt;/code&gt; package used in Parts I and II:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;SupportAgent&lt;/code&gt; wraps &lt;code&gt;AIAgent&lt;/code&gt; for optional explanations.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AgentHarness&lt;/code&gt; owns plan, approval, and idempotent refund state.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AgentHarness.Api&lt;/code&gt; exposes run, approval, and plan endpoints.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Run the Aspire host to start the API and the Redis &lt;code&gt;redis:7.4&lt;/code&gt; approval-store container:&lt;/p&gt;
&lt;div class=&#34;copilot-prompt text-code-block&#34;&gt;&lt;div class=&#34;copilot-prompt__header&#34;&gt;&lt;span class=&#34;text-code-block__language&#34;&gt;powershell&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;copilot-prompt__body&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;1
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f5e0dc&#34;&gt;$env:ASPIRE_ALLOW_UNSECURED_TRANSPORT&lt;/span&gt; = &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;true&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;dotnet run --project src/Aspire/AgentHarness.AppHost&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Start a plan:&lt;/p&gt;
&lt;div class=&#34;copilot-prompt text-code-block&#34;&gt;&lt;div class=&#34;copilot-prompt__header&#34;&gt;&lt;span class=&#34;text-code-block__language&#34;&gt;bash&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;copilot-prompt__body&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;1
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;2
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;curl -X POST http://localhost:5000/api/agent/runs &lt;span style=&#34;color:#89b4fa&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#89b4fa&#34;&gt;&lt;/span&gt;  -H &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;Content-Type: application/json&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#89b4fa&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#89b4fa&#34;&gt;&lt;/span&gt;  -d &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#39;{&amp;#34;sessionId&amp;#34;:&amp;#34;case-1001&amp;#34;,&amp;#34;orderId&amp;#34;:&amp;#34;ORD-1001&amp;#34;,&amp;#34;prompt&amp;#34;:&amp;#34;I was charged twice for ORD-1001. Please refund the duplicate charge.&amp;#34;}&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The response is &lt;code&gt;202 Accepted&lt;/code&gt; with an approval request. Approve it using the returned request ID:&lt;/p&gt;
&lt;div class=&#34;copilot-prompt text-code-block&#34;&gt;&lt;div class=&#34;copilot-prompt__header&#34;&gt;&lt;span class=&#34;text-code-block__language&#34;&gt;bash&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;copilot-prompt__body&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;1
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;2
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;curl -X POST http://localhost:5000/api/approvals/&amp;lt;approval-id&amp;gt; &lt;span style=&#34;color:#89b4fa&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#89b4fa&#34;&gt;&lt;/span&gt;  -H &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;Content-Type: application/json&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#89b4fa&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#89b4fa&#34;&gt;&lt;/span&gt;  -d &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#39;{&amp;#34;approved&amp;#34;:true,&amp;#34;reason&amp;#34;:&amp;#34;Verified duplicate charge&amp;#34;}&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Rejecting the request records a terminal rejection and never invokes the refund tool:&lt;/p&gt;
&lt;div class=&#34;copilot-prompt text-code-block&#34;&gt;&lt;div class=&#34;copilot-prompt__header&#34;&gt;&lt;span class=&#34;text-code-block__language&#34;&gt;bash&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;copilot-prompt__body&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;1
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;2
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;curl -X POST http://localhost:5000/api/approvals/&amp;lt;approval-id&amp;gt; &lt;span style=&#34;color:#89b4fa&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#89b4fa&#34;&gt;&lt;/span&gt;  -H &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;Content-Type: application/json&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#89b4fa&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#89b4fa&#34;&gt;&lt;/span&gt;  -d &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#39;{&amp;#34;approved&amp;#34;:false,&amp;#34;reason&amp;#34;:&amp;#34;Customer verification missing&amp;#34;}&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The sample includes unit and API integration layers matching Parts I and II:&lt;/p&gt;
&lt;div class=&#34;copilot-prompt text-code-block&#34;&gt;&lt;div class=&#34;copilot-prompt__header&#34;&gt;&lt;span class=&#34;text-code-block__language&#34;&gt;powershell&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;copilot-prompt__body&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;1
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;dotnet run --project tests/AgentHarness.UnitTests/AgentHarness.&lt;span style=&#34;color:#fab387&#34;&gt;UnitTests&lt;/span&gt;.&lt;span style=&#34;color:#fab387&#34;&gt;csproj&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;dotnet run --project tests/AgentHarness.IntegrationTests/AgentHarness.&lt;span style=&#34;color:#fab387&#34;&gt;IntegrationTests&lt;/span&gt;.csproj&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&#34;production-checklist&#34;&gt;Production Checklist
&lt;/h1&gt;&lt;ul&gt;
&lt;li&gt;Persist sessions, plans, and approval records durably.&lt;/li&gt;
&lt;li&gt;Authorize every read and write by tenant, user, and resource.&lt;/li&gt;
&lt;li&gt;Validate model-produced arguments with typed schemas.&lt;/li&gt;
&lt;li&gt;Use idempotency keys for side effects and reconcile uncertain results.&lt;/li&gt;
&lt;li&gt;Give approval requests an expiry and handle duplicate callbacks.&lt;/li&gt;
&lt;li&gt;Trace model calls, tool calls, plan transitions, and approval decisions.&lt;/li&gt;
&lt;li&gt;Redact payment and personal data from logs.&lt;/li&gt;
&lt;li&gt;Bound plan length, tool calls, tokens, latency, and cost.&lt;/li&gt;
&lt;li&gt;Test rejected, expired, duplicated, timed-out, and partially completed runs.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&#34;conclusion&#34;&gt;Conclusion
&lt;/h1&gt;&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2 id=&#34;run-the-complete-example&#34;&gt;Run the Complete Example
&lt;/h2&gt;&lt;p&gt;The complete sample is available here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://github.com/meysamhadeli/blog-samples/tree/main/src/microsoft-agent-framework-agent-harness-planning-approval-sample&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://github.com/meysamhadeli/blog-samples/tree/main/src/microsoft-agent-framework-agent-harness-planning-approval-sample&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;reference&#34;&gt;Reference
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://learn.microsoft.com/en-us/agent-framework/get-started/harness&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Agent Harness&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://learn.microsoft.com/en-us/agent-framework/agents/tools/tool-approval&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Tool approval&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://learn.microsoft.com/en-us/agent-framework/concepts/agents/middleware&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Middleware&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://learn.microsoft.com/en-us/agent-framework/journey/adding-middleware&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Adding middleware&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://learn.microsoft.com/en-us/agent-framework/concepts/agents/conversations/compaction&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Conversation compaction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://learn.microsoft.com/en-us/agent-framework/concepts/workflows/executors&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Workflows and executors&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://learn.microsoft.com/en-us/agent-framework/overview&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Microsoft Agent Framework overview&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        </item>
        
    </channel>
</rss>
