Pause workflow execution for user confirmation, input, review, or decisions at the step level.
Human-in-the-Loop (HITL) in Workflows enables you to pause execution at any step to collect user confirmation, input, or decisions. The workflow state is persisted, allowing you to resume execution after the user responds.
User input is currently supported for Step (to collect parameters) and Router (to select routes). Other primitives (Condition, Loop, Steps) support confirmation only.
Agent tool-level HITL (e.g., @tool(requires_confirmation=True)) is not propagated to the workflow. If an agent inside a step has tool-level HITL, the workflow will continue but the paused tool may not execute. Use workflow-level HITL instead.
from agno.workflow import Workflow, OnRejectfrom agno.workflow.step import Stepfrom agno.workflow.types import HumanReviewfrom agno.db.sqlite import SqliteDbworkflow = Workflow( name="data_pipeline", db=SqliteDb(db_file="workflow.db"), # Required for HITL steps=[ Step(name="fetch_data", agent=fetch_agent), Step( name="process_data", agent=process_agent, human_review=HumanReview( requires_confirmation=True, confirmation_message="Process sensitive data?", on_reject=OnReject.skip, ), ), Step(name="save_results", agent=save_agent), ],)run_output = workflow.run("Process user data")if run_output.is_paused: for req in run_output.steps_requiring_confirmation: req.confirm() # or req.reject() run_output = workflow.continue_run(run_output)
All HITL settings are grouped into a single HumanReview object:
from agno.workflow.types import HumanReviewStep( name="review_step", agent=review_agent, human_review=HumanReview( requires_output_review=True, output_review_message="Check the output.", on_reject=OnReject.retry, max_retries=3, timeout=300, ),)
Flat parameters (requires_confirmation=True, confirmation_message="...") still work for backward compatibility. See the HumanReview Config page for all fields and validation rules.
for req in run_output.steps_requiring_user_input: print(req.user_input_message) for field in req.user_input_schema: value = get_user_value(field.name, field.field_type) req.set_user_input(**{field.name: value})
User input is available in the custom function step via step_input.additional_data["user_input"].
When rejected with on_reject=OnReject.retry, the step re-executes up to max_retries times. Pair with reject(feedback=...) to send feedback to the agent.
for req in run_output.steps_requiring_route: print(req.available_choices) # ["quick_analysis", "deep_analysis"] req.select("deep_analysis") # or req.select_multiple(["quick_analysis", "deep_analysis"])
Set a timeout for HITL pauses. If the user does not respond in time, on_timeout determines the action. See the dedicated Timeout page for full details.
Mark custom function steps with HITL configuration using the @pause decorator:
from agno.workflow.decorators import pausefrom agno.workflow.types import UserInputField@pause( requires_user_input=True, user_input_message="Enter parameters:", user_input_schema=[ UserInputField(name="threshold", field_type="float", required=True), ],)def process_data(step_input: StepInput) -> StepOutput: threshold = step_input.additional_data["user_input"]["threshold"] return StepOutput(content=f"Processed with threshold {threshold}")# The decorator config is auto-detected when used in a custom function stepStep(name="process", executor=process_data)