Demonstrates how to pause an agent mid-turn and wait for a human decision before proceeding. The send_email tool suspends itself inside a running activity by issuing a [WorkflowUpdate], and the workflow blocks on WaitConditionAsync until an external caller submits an approval or rejection.
This sample demonstrates:
TemporalAgentContext.Current.RequestApprovalAsync()suspending a tool inside an activityITemporalAgentClient.GetPendingApprovalAsync()polling for pending approvals from outside the workflowITemporalAgentClient.SubmitApprovalAsync()unblocking the workflow with a decisionActivityTimeoutset to 24 hours to accommodate human review time
User input
│
▼
proxy.RunAsync(messages, session) ← [WorkflowUpdate] to AgentWorkflow
│
├─ AgentActivities.RunDurableAgentStepAsync() ← LLM call (returns FunctionCallContent)
│
└─ AgentActivities.InvokeAgentToolAsync() ← activity per tool (24h timeout)
│
└─ send_email tool invoked
│
└─ RequestApprovalAsync() ← [WorkflowUpdate]: sends DurableApprovalRequest
│ workflow blocks on WaitConditionAsync
│
┌──────┴──────────────────────────────────────┐
│ Human review (console in this sample) │
│ client.GetPendingApprovalAsync(sessionId) │ ← [WorkflowQuery]
│ client.SubmitApprovalAsync(sessionId, ...) │ ← [WorkflowUpdate]
└──────┬──────────────────────────────────────┘
│
WaitConditionAsync satisfied → tool resumes
│
email sent (or rejected) → result returns to workflow,
next RunDurableAgentStepAsync iteration runs
- Suspension without polling. The workflow blocks on
WaitConditionAsync— no spin-wait, no timer. The worker thread is released and other workflows continue normally while waiting. GetPendingApprovalAsyncis a[WorkflowQuery]. Queries never block the workflow and are safe to call as frequently as needed. This sample polls every second from outside the workflow while the agent task is in-flight.SubmitApprovalAsyncis a[WorkflowUpdate]. Strongly consistent: it validates theRequestIdmatches the pending request before unblocking, preventing stale or duplicate decisions.ActivityTimeoutmust exceedApprovalTimeout.ActivityTimeout = TimeSpan.FromHours(24)gives Temporal the outer bound for how long the tool activity may run;ApprovalTimeout = TimeSpan.FromHours(23)is the inner bound — how long the workflow will wait for a human decision before timing out the approval. IfApprovalTimeout >= ActivityTimeout, the activity can expire while the workflow still holds an open approval request, blocking all subsequent turns indefinitely. A heartbeat timeout of 5 minutes ensures the worker is still alive. All three are set inAddTemporalAgents().send_emailis registered withopts.NoRetry(). The tool delivers an email after the human approves. WithoutNoRetry(), a transient failure immediately after delivery (before the activity reports success) would cause Temporal to retry the activity — re-entering the approval gate, issuing a second approval request, and potentially sending the email a second time. Write-style tools that produce side effects must setMaximumAttempts = 1.
- .NET 10 SDK or later
- A local Temporal server:
temporal server start-dev - An OpenAI-compatible API key
- This sample waits for you to type
approveorrejectat the console — do not run it with piped stdin
dotnet user-secrets set "OPENAI_API_KEY" "sk-..." --project samples/MAF/HumanInTheLoop
dotnet user-secrets set "OPENAI_API_BASE_URL" "https://api.openai.com/v1" --project samples/MAF/HumanInTheLoopdotnet run --project samples/MAF/HumanInTheLoop/HumanInTheLoop.csprojEmail Assistant — HITL Approval Sample
Ask the assistant to send an email.
When it tries, you will be prompted to approve or reject before it is delivered.
Type 'quit' to exit.
You: Send an email to alice@example.com saying the meeting is at 3pm
Assistant: (thinking...)
╔══════════════════════════════════════════════╗
║ APPROVAL REQUIRED ║
╠══════════════════════════════════════════════╣
║ Send email to alice@example.com ║
║ Subject: Meeting at 3pm ║
╚══════════════════════════════════════════════╝
Decision [approve/reject]: approve
Approved — agent is resuming...
[EMAIL SENT] To: alice@example.com
Subject: Meeting at 3pm
Assistant: The email has been sent to alice@example.com.