tl;dr
If you’re building automated workflows that sometimes must pause for human judgment (approvals, moderation, triage), Microsoft Agent Framework provides first‑class HITL primitives: typed RequestPorts, durable suspended execution, emitted RequestInfoEvents, and automatic routing of responses back to the correct workflow executor.

This article explains the runtime mechanics (what happens when a workflow asks a human for input), the developer experience (what you write), concrete examples (C# + JSON contracts), integration patterns with Bot Framework/contact centers, failure modes, and testing strategies.
A concise scenario
An LLM suggests a $50,000 refund to a customer. If your automation approves it automatically you risk fraud and compliance violations; if you block it you frustrate customers and increase manual work. The correct middle ground is a durable, auditable workflow that pauses, sends a precise task to a human reviewer, and reliably resumes once the reviewer responds. Microsoft Agent Framework makes that pattern a core primitive instead of an ad‑hoc add‑on.
Overview: what Agent Framework gives you for HITL
- Typed RequestPorts: define strongly typed request and response schemas so humans and automation agree on payload shapes.
- Durable suspend/resume: when an executor issues a request, the workflow branch is suspended durably until a response arrives (survives restarts).
- RequestInfoEvent emission: the framework emits a structured event the agent hub or any subscriber can consume to present tasks to humans.
- Automatic routing & correlation: responses include metadata that the framework uses to match the response to the exact waiting executor, avoiding much manual correlation code.
- Orchestration & handoff primitives: the workflow language supports approval nodes, escalations, timeouts, and integration points for agent hubs or contact centers.
Request lifecycle: step‑by‑step
Declare RequestPort(s) in the workflow
- A RequestPort is a typed channel (request type, response type). Example: RequestPort.
Executor issues a request
- Inside an executor (HandleAsync or equivalent), the code calls the framework API (e.g., context.SendRequestAsync) to send the request payload. The call returns only when the response is received or the request is cancelled/timeout occurs.
Workflow branch is suspended
- The framework persists the suspended state. The executor essentially awaits the response; the workflow instance remains durable in storage (so node restarts or transient failures won’t lose the wait).
RequestInfoEvent is emitted
- The framework publishes an event that includes request metadata, the typed payload, timestamps, TTL, and any contextual fields the workflow author supplied. External systems subscribe and surface the task to human operators.
Human reviews and posts a response
- The human operator uses an agent hub UI (built-in or custom) to inspect context and submit a response. The agent hub posts the typed response back to the Agent Framework through a Response Ingest API or SDK call.
Framework routes response back to waiting executor
- Using request id + workflow instance id (and port name/type), the framework finds the suspended executor and delivers the typed response. The executor resumes and the workflow continues.
Developer view: what you author
At a high level you:
- Define RequestPorts and response types.
- Write executors that call context.SendRequestAsync (or RequestPort.Send).
- Subscribe to RequestInfoEvents or integrate Event Grid/Service Bus to route tasks to your agent hub/UIs.
- Implement a response ingest endpoint (secured) that posts typed responses back to the framework.
Concise C# worked example (end‑to‑end sketch)
Below is a minimal, runnable‑style illustration (API names adapted to the docs’ conceptual forms). This shows the RequestPort declaration, the executor sending a request, emitting an event handled by the agent hub, and the response ingest flow.
1) Define request/response types
public record ContentModerationRequest(string Content, string UserId, string ConversationId);
public record ContentModerationResponse(bool Approved, string ReviewerId, string Notes);
2) Create a RequestPort and wire into a workflow
var contentModerationPort = RequestPort.Create(“ContentModeration”);
var workflow = new WorkflowBuilder(contentModerationPort)
.AddEdge(inputSource, contentModerationExecutor)
.AddEdge(contentModerationExecutor, continueExecutor)
.Build();
3) Executor sends request and awaits response
internal sealed class ContentModerationExecutor : Executor
{
public override async ValueTask HandleAsync(ContentItem message, IWorkflowContext context, CancellationToken ct)
{
var request = new ContentModerationRequest(message.Text, message.UserId, message.ConversationId);
// SendRequestAsync emits a RequestInfoEvent and suspends until the human response arrives
ContentModerationResponse response = await context.SendRequestAsync(contentModerationPort, request, ct);
if (response.Approved)
{
// proceed with publishing, optionally using edited content from notes
await context.ScheduleAsync(new PublishMessage(response.ReviewerId, message.Id), ct);
}
else
{
// handle rejection path
await context.ScheduleAsync(new NotifyUserDecline(message.UserId), ct);
}
}
}
Notes:
- SendRequestAsync returns a typed response object (ContentModerationResponse) when the human submits their decision.
- While awaiting, the workflow’s state is persisted so the process is durable.
Agent hub responsibilities and the RequestInfoEvent
When SendRequestAsync is called, the framework emits a RequestInfoEvent. The agent hub subscribes (Event Grid, Service Bus, or a SDK event callback) and must implement:
- Task display: show request details, provenance (workflow id, request id), attachments, and relevant context (conversation transcript, policy guidance).
- Claiming model: accept/claim or pick-from-queue semantics to avoid double-work.
- Response posting: a secure call to the Response Ingest API that returns success or a clear error (with appropriate HTTP status codes).
Sample RequestInfoEvent payload (JSON)
{
“requestId”: “req-abc123”,
“workflowInstanceId”: “wf-987”,
“portName”: “ContentModeration”,
“requestPayload”: {
“content”: “AI-generated reply…”,
“userId”: “user-42”,
“conversationId”: “conv-77”
},
“createdAt”: “2026-09-14T12:34:56Z”,
“ttlSeconds”: 3600,
“metadata”: { “priority”: “high”, “origin”: “chatbot-01” }
}
Response ingest contract (what the agent hub posts back)
POST /api/agent/responses
Headers:
Authorization: Bearer
Body:
{
“requestId”: “req-abc123”,
“workflowInstanceId”: “wf-987”,
“responsePayload”: {
“approved”: true,
“reviewerId”: “agent-007”,
“notes”: “Approved; removed PII”
},
“submittedAt”: “2026-09-14T12:40:01Z”
}
Expected responses:
- 200 OK — response accepted and routed to the executor.
- 400 Bad Request — malformed payload or schema mismatch.
- 404 Not Found — request id not found (possibly expired or already completed).
- 409 Conflict — duplicate or conflicting response (depending on your policy).
- 410 Gone — request timed out or was cancelled.
Correlation, routing, and metadata best practices
What the framework provides:
- requestId: unique per request invocation.
- workflowInstanceId: identifies the workflow instance.
- portName (and port type): identifies the RequestPort.
With those, most routing is automatic. Practical guidance: - Always include requestId in your response ingest calls. requestId is the primary, unambiguous handle for routing.
- Port name/type adds another safety layer, useful if your agent hub sees many types of requests.
- Capture and surface workflowInstanceId in the agent UI for audit and troubleshooting.
- Store and emit reviewer identity and timestamps in responses for compliance.
Handling duplicates, late responses, and concurrency
- First‑wins vs quorum: by default a single response satisfies the waiting executor. If you need multiple approvals or consensus, model it explicitly—create multiple RequestPorts or a response type that supports aggregation and implement quorum logic in the workflow.
- Duplicate submissions: agent hub should make the ingest POST idempotent. The server should accept the first valid response and return 409 or 200 for subsequent duplicates, depending on your semantics.
- Late arrivals: responses for cancelled or timed‑out requests should be rejected with 410 Gone and the agent hub should surface a clear explanation and allow re‑issuing (create a new request).
- Concurrency: if two human agents submit different responses simultaneously, the framework decides based on request id ordering; design the workflow to verify critical transitions—e.g., accept only responses signed by an approver role, or require a reconciliation step.
Timeouts, fallbacks, and alternate flows
Long human latency blocks user experience. Your workflows should:
- Configure sensible TTLs on RequestPorts (e.g., 30m for routine approvals, 24h for slower approvals, two minutes if the user is on hold!).
- Define fallback flows for timeouts: reassign to escalation queue, auto‑approve/deny under defined policies, or create a notification to a manager.
- Emit interim events (e.g., “pending too long”) to monitoring and alerting systems.
Orchestration patterns: single approval, parallel approvals, and SAGA compensation
- Single approval: the workflow issues one request and continues when it receives a single response.
- Parallel approvals (n of m or quorum): create multiple requests in parallel and wait for N responses or a consensus step. Example:
- Spawn three RequestPorts (or the same port with multiple request instances) and gather responses. Resume when at least two are approvals.
- SAGA / compensation: if the human rejects a step that has already triggered side effects, model compensating actions in the workflow (reverse a debit, revoke a provisional publish). Combine timeouts with compensation to avoid leaving inconsistent state.
Integrating with Bot Framework and contact centers
Agent Framework HITL and Bot Framework are complementary:
- Bot as trigger: a conversational bot collects context and triggers a workflow that may create a RequestPort task for human approval.
- Bot as proxy: once a human accepts, Bot Framework’s handoff patterns (bot-as-proxy) can connect a human agent into the live conversation while the workflow continues to manage business-level approvals.
- Agent hub integration: use Omnichannel for Dynamics or an agent hub service to surface tasks to Teams, Dynamics agents, or third‑party contact centers.
A concrete flow (bot + Agent Framework + Teams)
- Bot collects issue and calls a workflow API to start a warranty claim workflow.
- Workflow evaluates risk. If high, it calls SendRequestAsync on a ClaimApprovalRequestPort.
- The Agent Framework emits RequestInfoEvent and routes it to Event Grid.
- Event Grid forwards to an agent hub that posts a message in a Teams queue or Dynamics Omnichannel.
- An agent reviews in Teams, submits the approval via the agent hub (which calls the Response Ingest API).
- Agent Framework resumes the workflow and posts the outcome back to the bot (or directly to the user).
Security and compliance
- Authentication: secure the Response Ingest API with OAuth 2.0 (Azure AD Bearer tokens) or mutual TLS. Agent hub should authenticate as a service principal with least privilege.
- Authorization: enforce RBAC so only authorized roles can approve certain request types (finance team for refunds).
- PII removal: redact or mask sensitive fields in RequestInfoEvent payloads and in agent UIs. If full PII is required, limit retention and access logs.
- Audit trails: persist request/response payloads, reviewer identity, timestamps, and any changes. Use immutable storage or append‑only logs for compliance.
Observability and operational metrics
Track and alert on:
- Pending request count per port.
- Average and P95 human response time.
- SLA (Service Level Agreement) breaches (e.g., pending_response_time > SLA).
- Request churn (creates, cancels, re‑issues).
- Approval rates and reviewer throughput.
Note P95 indicates 95th percental latency between when a task is presented to a human and when they respond.
Suggested alerts:
- Pending requests > threshold for > X minutes (escalate).
- Average response time rising > 2x baseline (investigate agent hub throughput/availability).
- Rejected requests spike > typical baseline (potential model drift or policy change).
Testing and CI/CD strategies
- Unit testing executors: mock SendRequestAsync to simulate human responses and assert branching logic.
- Integration tests: test full path — executor sends request → event emitted → simulate agent hub response → workflow resumes.
- Load testing agent hub: simulate spikes of RequestInfoEvent to confirm queueing behavior and UI scaling.
- Chaos testing: simulate delayed responses, duplicate responses, and timeout conditions to verify fallback flows and compensation logic.
Common pitfalls and how to avoid them
- Missing context in the agent UI: always surface the workflowInstanceId, requestId, and relevant transcript attachments. Humans need context to make fast decisions.
- Assuming low latency: design timeouts and fallback behavior; don’t block user interfaces waiting indefinitely.
- Weak correlation design: require requestId in responses. Avoid relying on human-entered identifiers that can be mistyped.
- Lack of auditability: store reviewer identity and notes; consider signing responses or using immutable logs for compliance.
Note: The initial draft of this post was written by BlogWriter and then edited by Jesse Liberty
Illustrations by Copilot. Caution: LLMs make mistakes; this post is offered as is.
