Human-in-the-Loop (HITL) for Agentic Workflows

March 17, 2026 · View on GitHub

Sample code accompanying the blog post on implementing Human-in-the-Loop constructs for AI agents in healthcare and life sciences using Strands Agents, Amazon Bedrock AgentCore Runtime, and the Model Context Protocol (MCP).

Overview

Four standalone methods for adding human oversight to sensitive agent tool calls:

MethodApproachApproval Model
1 — Hook SystemAgent framework hook intercepts tool calls before executionCentralized, blanket policy
2 — Tool ContextApproval logic embedded inside each tool with role-based accessPer-tool, fine-grained
3 — Step FunctionsAsync workflow sends email to external approver via SNSThird-party, asynchronous
4 — MCP ElicitationMCP server uses ctx.elicit() for real-time interactive approvalProtocol-native, real-time

Prerequisites

  • Python 3.11+
  • uv package manager
  • AWS account with permissions for AgentCore, Lambda, Step Functions, SNS, Cognito
  • AgentCore CLI (agentcore)

Setup

  1. Configure AWS credentials

    Create a .env file in the project root:

    AWS_PROFILE=your-profile
    AWS_REGION=us-west-2
    NOTIFICATION_EMAIL=approver@example.com
    
  2. Install dependencies

    uv sync
    
  3. Deploy infrastructure (Lambda, Step Functions, SNS, Cognito)

    cd infra && bash deploy.sh
    

    This deploys a CloudFormation stack (hitl-stack) with:

    • A Lambda function simulating a sensitive patient vitals tool
    • A Step Functions state machine for external approval workflows (Method 3)
    • An SNS topic for email notifications (Method 3)
    • A Cognito User Pool for MCP server authentication (Method 4)

Architecture

Methods 1 & 2: Hook System / Tool Context

Architecture diagram for Methods 1 and 2 showing a Healthcare Application calling into Amazon Bedrock AgentCore Runtime, where a StrandsAgent exposes patient tools with hook-based or tool-context-based approval

Method 3: Step Functions

Architecture diagram for Method 3 showing a Healthcare Application calling into Amazon Bedrock AgentCore Runtime, where a StrandsAgent delegates discharge approval to an AWS Step Functions authorization workflow

Method 4: MCP Elicitation

Architecture diagram for Method 4 showing a Healthcare Application connected via WebSockets to Amazon Bedrock AgentCore Runtime, where a StrandsAgent communicates with an MCP Server over the MCP Protocol for real-time elicitation-based approval

Deploying & Testing Each Method

Each method has its own directory with an agent.py (AgentCore entrypoint), deploy.sh, and test.py.

Method 1: Hook System

bash method1_hook/deploy.sh
uv run method1_hook/test.py

Method 2: Tool Context

bash method2_tool_context/deploy.sh
uv run method2_tool_context/test.py

Method 3: Step Functions

bash method3_remote_sfn/deploy.sh
uv run method3_remote_sfn/test.py

When the agent requests a discharge, an email is sent to the configured NOTIFICATION_EMAIL. Use the approval script to approve/deny:

uv run method3_remote_sfn/approve.py

Method 4: MCP Elicitation

Deploy the MCP server first, then the agent:

bash method4_mcp_elicitation/deploy.sh       # MCP server
bash method4_mcp_elicitation/agent_deploy.sh  # Agent (WebSocket)
uv run method4_mcp_elicitation/test.py

A local-only version (no AgentCore deployment needed) is also available:

uv run method4_mcp_elicitation/main.py

Project Structure

├── images/
│   ├── method1_2.png                # Architecture diagram for Methods 1 & 2
│   ├── method3.png                  # Architecture diagram for Method 3
│   └── method4.png                  # Architecture diagram for Method 4
├── infra/
│   ├── cloudformation.yaml          # Lambda, Step Functions, SNS, Cognito
│   └── deploy.sh
├── method1_hook/
│   ├── agent.py                     # BeforeToolCallEvent hook
│   ├── deploy.sh
│   └── test.py
├── method2_tool_context/
│   ├── agent.py                     # tool_context.interrupt() with RBAC
│   ├── deploy.sh
│   └── test.py
├── method3_remote_sfn/
│   ├── agent.py                     # Step Functions + SNS async approval
│   ├── approve.py                   # CLI script to approve/deny pending executions
│   ├── deploy.sh
│   └── test.py
├── method4_mcp_elicitation/
│   ├── server.py                    # MCP server with ctx.elicit()
│   ├── agent.py                     # WebSocket agent relaying elicitation
│   ├── main.py                      # Local version (no deployment needed)
│   ├── deploy.sh                    # MCP server deploy
│   ├── agent_deploy.sh              # Agent deploy
│   └── test.py
└── pyproject.toml

Cleanup

To remove deployed agents:

uv run agentcore delete --agent <agent_name>

To remove infrastructure:

aws cloudformation delete-stack --stack-name hitl-stack --region us-west-2