HITL Configuration Guide
This guide covers all HITL configuration options and patterns for building robust approval workflows.
HITLConfig Overview
Section titled “HITLConfig Overview”The HITLConfig class is your main interface for configuring HITL behavior:
from langcrew.hitl import HITLConfig
config = HITLConfig( # Tool-level interrupts interrupt_before_tools=["tool1", "tool2"], # Tools requiring pre-approval interrupt_after_tools=["tool3"], # Tools requiring post-review
# Task-level interrupts (Task Mode only) interrupt_before_tasks=["task1"], interrupt_after_tasks=["task2"],
# Agent-level interrupts (Agent Mode only) interrupt_before_agents=["agent1"], interrupt_after_agents=["agent2"],
# Node-level interrupts (LangGraph native) interrupt_before_nodes=["node1"], interrupt_after_nodes=["node2"])Configuration Patterns
Section titled “Configuration Patterns”Specified Tools
Section titled “Specified Tools”Target specific tools for interrupts:
# Approve parameters before executionhitl_config = HITLConfig( interrupt_before_tools=[ "web_search", # Review search queries "file_write", # Approve file operations "send_email", # Confirm email sending "database_query", # Validate database access "external_api" # Review API calls ])# Review results after executionhitl_config = HITLConfig( interrupt_after_tools=[ "data_analysis", # Review analysis results "content_generator", # Check generated content "report_writer", # Validate reports "image_processor" # Review processed images ])
# ⚠️ IMPORTANT: After interrupts only work within single execution session# They will NOT trigger after workflow restarts or checkpointed state recoverySession-Level Limitations
Section titled “Session-Level Limitations”Understanding interrupt_after_tools Behavior
Section titled “Understanding interrupt_after_tools Behavior”interrupt_after_tools has an important limitation that affects production deployments:
# This configuration works ONLY within a single execution sessionhitl_config = HITLConfig( interrupt_after_tools=["data_analysis", "report_generator"])
# Scenario 1: Single session execution ✅# 1. Tool executes → 2. User reviews result → 3. Workflow continues
# Scenario 2: Workflow restart ❌# 1. Tool executes → 2. interrupt_after triggered → 3. System restarts → 4. Tool executes again (duplicate)Why this happens:
interrupt_after_toolsoccurs during tool node execution, before the next checkpoint is saved- When system restarts, it cannot resume from the interrupt point and must restart from the last checkpoint
- The tool node will re-execute from the beginning, causing the tool to run again
- This can lead to duplicate tool executions and potential side effects
Solutions for Cross-Restart Review
Section titled “Solutions for Cross-Restart Review”# More reliable for critical operationshitl_config = HITLConfig( interrupt_before_tools=["critical_operation"], # Always triggers # interrupt_after_tools=["critical_operation"] # May be skipped after restart)from langchain_core.tools import tool
@tooldef reentrant_analysis_tool(data: str, review_count: int = 0) -> str: """Analysis tool that supports multiple review cycles""" if review_count == 0: # First execution - do analysis result = perform_analysis(data) return f"Analysis complete (review #{review_count + 1}): {result}" else: # Re-execution after restart - allow re-review return f"Re-reviewing analysis (review #{review_count + 1}): {cached_result}"
# Configure for before-interrupt to ensure review happenshitl_config = HITLConfig( interrupt_before_tools=["reentrant_analysis_tool"])# Use workflow-level interrupts instead of tool-levelhitl_config = HITLConfig( interrupt_after_nodes=["analysis_node"], # Works across restarts # interrupt_after_tools=["analysis_tool"] # Session-limited)Agent-Level Configuration
Section titled “Agent-Level Configuration”Individual Agent Setup
Section titled “Individual Agent Setup”from langcrew import Agentfrom langcrew.hitl import HITLConfig
# High-privilege agent with minimal interruptsadmin_agent = Agent( role="System Administrator", tools=[SystemTool(), FileManagerTool()], hitl=HITLConfig( interrupt_before_tools=["system_shutdown"] # Only critical operations ))
# Regular agent with standard approvalsworker_agent = Agent( role="Data Worker", tools=[DataProcessorTool(), FileWriteTool()], hitl=HITLConfig( interrupt_before_tools=["file_write", "data_export"] ))
# Public-facing agent with maximum oversightpublic_agent = Agent( role="Public Assistant", tools=[WebSearchTool(), CalculatorTool(), FileWriteTool()], hitl=HITLConfig( interrupt_before_tools=["web_search", "file_write"] # Approve all risky operations ))Context-Aware Configuration
Section titled “Context-Aware Configuration”def create_context_aware_hitl(user_role, data_sensitivity): """Create HITL config based on context"""
if user_role == "admin": return HITLConfig( interrupt_before_tools=["system_operation"] ) elif data_sensitivity == "high": return HITLConfig( interrupt_before_tools=["file_write", "database_write", "external_api"] ) else: return HITLConfig( interrupt_before_tools=["file_write", "external_api"] )
# Use in agent creationagent = Agent( role="Data Processor", hitl=create_context_aware_hitl( user_role=current_user.role, data_sensitivity=data_classification ))Crew-Level Configuration
Section titled “Crew-Level Configuration”Unified Policy
Section titled “Unified Policy”Apply HITL policy across multiple agents:
from langcrew import Crew
# Individual agents without HITL configresearcher = Agent(role="Researcher", tools=[WebSearchTool()])writer = Agent(role="Writer", tools=[FileWriteTool()])reviewer = Agent(role="Reviewer", tools=[EmailTool()])
# Crew-level unified policycrew = Crew( agents=[researcher, writer, reviewer], hitl=HITLConfig( interrupt_before_tools=["web_search", "file_write", "send_email"] ))Mixed Configuration
Section titled “Mixed Configuration”Combine agent-level and crew-level HITL:
# Agent with specific HITL configcritical_agent = Agent( role="Critical Operations", tools=[DatabaseTool()], hitl=HITLConfig( interrupt_before_tools=["database_write", "database_delete"] # Agent-specific policy ))
# Agent without HITL config (will inherit from crew)regular_agent = Agent( role="Regular Worker", tools=[FileWriteTool()])
# Crew with fallback policycrew = Crew( agents=[critical_agent, regular_agent], hitl=HITLConfig( interrupt_before_tools=["file_write"] # Applies to regular_agent only ))Node-Level Interrupts
Section titled “Node-Level Interrupts”Integrate with LangGraph’s native interrupt system:
from langcrew.hitl import HITLConfig
# Combine tool and node interruptshitl_config = HITLConfig( # Tool-level interrupts (LangCrew HITL system) interrupt_before_tools=["critical_operation"], interrupt_after_tools=["data_export"],
# Node-level interrupts (LangGraph native) interrupt_before_nodes=["decision_node"], interrupt_after_nodes=["validation_node"])
agent = Agent( role="Comprehensive Agent", tools=[CriticalTool(), DataExportTool()], hitl=hitl_config)Response Processing
Section titled “Response Processing”Simple Responses
Section titled “Simple Responses”HITL accepts natural language responses:
# These all mean "approve":responses = [ "yes", "approve", "ok", "confirm", "accept", "agreed", # English "批准", "同意", "确认", "通过", "好的", "可以", # Chinese True, # Boolean {"approved": True} # Structured]
# These all mean "deny":responses = [ "no", "deny", "reject", "refuse", "cancel", "disagree", # English "拒绝", "不同意", "不通过", "取消", "否", "不要", # Chinese False, # Boolean {"approved": False} # Structured]Advanced Responses
Section titled “Advanced Responses”Parameter Modification (Before Interrupt)
Section titled “Parameter Modification (Before Interrupt)”# User can modify tool parametersuser_response = { "approved": True, "modified_args": { "query": "enhanced search query with more context", "max_results": 10, "language": "en", "safe_search": True }}Result Enhancement (After Interrupt)
Section titled “Result Enhancement (After Interrupt)”# User can modify tool resultsuser_response = { "approved": True, "modified_result": """ Original analysis enhanced with: - Additional context from domain expertise - Corrected data points based on recent updates - User insights and recommendations """}Denial with Reason
Section titled “Denial with Reason”# Provide detailed rejection reasonuser_response = { "approved": False, "reason": "Query parameters are too broad and may return sensitive data"}Environment-Based Configuration
Section titled “Environment-Based Configuration”Development vs Production
Section titled “Development vs Production”import os
def get_hitl_config(): """Get environment-appropriate HITL configuration"""
env = os.getenv("ENVIRONMENT", "development")
if env == "development": # Minimal interrupts for faster development return None # No HITL for development
elif env == "staging": # Critical operations only return HITLConfig( interrupt_before_tools=["database_write", "external_api", "file_delete"] )
elif env == "production": # Comprehensive oversight - specify all sensitive tools return HITLConfig( interrupt_before_tools=[ "database_write", "database_delete", "file_write", "file_delete", "external_api", "send_email" ] )
else: # Default to safe configuration return HITLConfig( interrupt_before_tools=["file_write", "database_query", "external_api"] )
# Use in agent creationagent = Agent( role="Environment-Aware Agent", hitl=get_hitl_config())Best Practices
Section titled “Best Practices”1. Security-First Approach
Section titled “1. Security-First Approach”# Start by specifying sensitive operationsinitial_config = HITLConfig( interrupt_before_tools=[ "file_write", "file_delete", "database_write", "database_delete", "external_api", "send_email" ])
# Expand as needed based on your application's requirementsproduction_config = HITLConfig( interrupt_before_tools=[ "file_write", "file_delete", "database_write", "database_delete", "external_api", "send_email", "system_command", "user_data_access" # Add more as identified ])2. Role-Based Configuration
Section titled “2. Role-Based Configuration”def get_role_based_hitl(user_role): """HITL configuration based on user role"""
role_configs = { "admin": HITLConfig( interrupt_before_tools=["system_shutdown", "user_delete"] ), "manager": HITLConfig( interrupt_before_tools=["data_export", "report_send", "file_delete"] ), "analyst": HITLConfig( interrupt_before_tools=["database_write", "external_api"] ), "viewer": HITLConfig( interrupt_before_tools=["file_write", "database_write", "external_api"] ) }
return role_configs.get(user_role, role_configs["viewer"])3. Tool Classification
Section titled “3. Tool Classification”# Classify tools by risk levelSAFE_TOOLS = ["calculator", "date_time", "text_formatter", "unit_converter"]MODERATE_TOOLS = ["web_search", "file_read", "data_query"]DANGEROUS_TOOLS = ["file_write", "file_delete", "database_write", "send_email"]
# Low risk: only dangerous tools need approvallow_risk_config = HITLConfig( interrupt_before_tools=DANGEROUS_TOOLS)
# High risk: dangerous and moderate tools need approvalhigh_risk_config = HITLConfig( interrupt_before_tools=DANGEROUS_TOOLS + MODERATE_TOOLS)
# Maximum security: explicitly list all tools that need approvalmaximum_security_config = HITLConfig( interrupt_before_tools=DANGEROUS_TOOLS + MODERATE_TOOLS + ["advanced_operation"])Troubleshooting
Section titled “Troubleshooting”Common Issues
Section titled “Common Issues”1. Interrupts Not Triggering
Section titled “1. Interrupts Not Triggering”# ❌ Problem: Tool name mismatchhitl_config = HITLConfig(interrupt_before_tools=["file_writer"])agent = Agent(tools=[FileWriteTool()]) # Actual tool name: "file_write"
# ✅ Solution: Use exact tool nameshitl_config = HITLConfig(interrupt_before_tools=["file_write"])
# Debug: Print tool namesfor tool in agent.tools: print(f"Tool name: {tool.name}")2. After-Interrupts Not Working
Section titled “2. After-Interrupts Not Working”# ❌ Problem: Using after-interrupts across sessionsresult1 = crew.kickoff(config={"configurable": {"thread_id": "session1"}}) # Tool executes, triggers after-interrupt# System restarts...result2 = crew.kickoff(config={"configurable": {"thread_id": "session1"}}) # After-interrupt won't trigger again
# ✅ Solution: Use before-interrupts for persistent approvalshitl_config = HITLConfig( interrupt_before_tools=["critical_tool"] # Always triggers)3. Response Parsing Errors
Section titled “3. Response Parsing Errors”# ❌ Problem: Malformed responseuser_response = "approve with modifications: query=new_query"
# ✅ Solution: Use structured responseuser_response = { "approved": True, "modified_args": {"query": "new_query"}}Debug Mode
Section titled “Debug Mode”Enable detailed logging for troubleshooting:
import logging
# Enable HITL debug logginglogging.getLogger("langcrew.hitl").setLevel(logging.DEBUG)
# Create agent and runagent = Agent(hitl=hitl_config, verbose=True)result = crew.kickoff(inputs=data)