4.1.4. Human-in-the-Loop Workflows
💡 First Principle: Autonomous AI agents should not make irreversible high-stakes decisions without human oversight — not because AI is unreliable, but because the cost of an undetected error in a high-consequence action (deleting records, sending external communications, making financial transactions) exceeds the latency cost of a human review step.
Human-in-the-loop patterns by risk level:
| Risk Level | Example Decision | Pattern | Implementation |
|---|---|---|---|
| Low | Classify support ticket priority | Fully automated | Agent acts without review |
| Medium | Draft customer-facing email response | Soft review | Agent proposes; human approves within SLA |
| High | Process refund over $10,000 | Hard gate | Agent cannot proceed without explicit approval |
| Critical | Modify production database schema | Block until reviewed | Step Functions wait state + SNS notification |
At high volume, route rather than review everything: send a decision to a reviewer only when its risk tier is medium or high, or when the model's confidence is low, and let high-confidence, low-risk decisions proceed automatically. Random sampling of outputs is quality monitoring, not a review gate.
Implementation with Step Functions waitForTaskToken:
# Step Functions state — agent pauses here until human approves
{
"Wait for Human Approval": {
"Type": "Task",
"Resource": "arn:aws:states:::sqs:sendMessage.waitForTaskToken",
"Parameters": {
"QueueUrl": "https://sqs.us-east-1.amazonaws.com/123456/approval-queue",
"MessageBody": {
"TaskToken.quot;: "$.Task.Token",
"AgentProposal.quot;: "$.proposed_action",
"Timestamp.quot;: "$.State.EnteredTime"
}
},
"HeartbeatSeconds": 3600, # Timeout if no response in 1 hour
"Next": "Execute Approved Action"
}
}
# Human reviewer's approval triggers Step Functions to resume
def approve_action(task_token, approved: bool, reviewer_id: str):
sf_client = boto3.client('stepfunctions')
if approved:
sf_client.send_task_success(
taskToken=task_token,
output=json.dumps({'approved': True, 'reviewer': reviewer_id})
)
else:
sf_client.send_task_failure(
taskToken=task_token,
error='HumanRejected',
cause=f'Reviewer {reviewer_id} rejected the proposed action'
)
⚠️ Exam Trap: Long human reviews (hours to days) belong in a Standard Workflow with .waitForTaskToken. Standard executions can run for up to one year and are billed per state transition, so a paused execution accrues no charge while it waits. Express Workflows are capped at five minutes and do not support .waitForTaskToken, so they cannot hold a 24–48 hour approval. Set HeartbeatSeconds or TimeoutSeconds so an unanswered request fails instead of waiting indefinitely.
Reflection Question: Your AI procurement agent can autonomously submit purchase orders. Stakeholders want orders under $1,000 to be fully automated, orders between $1,000–$10,000 to require manager approval within 4 hours, and orders over $10,000 to require CFO approval. Design the Step Functions workflow branching logic.