Output Review

Pause after step execution to review, edit, or reject output before it flows downstream.

Output review pauses the workflow after a step executes, letting a human inspect the output before it continues to the next step. This complements pre-execution confirmation, which pauses before a step runs.

Configure it with human_review=HumanReview(...) on the primitive. Flat workflow HITL constructor fields are no longer accepted.

Supported on Step, Router, and Loop (via requires_iteration_review on HumanReview).

from agno.workflow import Workflow, OnReject
from agno.workflow.step import Step
from agno.workflow.types import HumanReview
from agno.db.sqlite import SqliteDb

workflow = Workflow(
    name="email_workflow",
    db=SqliteDb(db_file="workflow.db"),
    steps=[
        Step(
            name="draft_email",
            agent=draft_agent,
            human_review=HumanReview(
                requires_output_review=True,
                output_review_message="Review the email draft before sending",
                on_reject=OnReject.cancel,
            ),
        ),
        Step(name="send_email", agent=send_agent),
    ],
)

run_output = workflow.run("Draft an email about the Friday standup")

if run_output.is_paused:
    for req in run_output.steps_requiring_output_review:
        print(req.step_output.content)

        if user_approves():
            req.confirm()
        else:
            req.reject()

    run_output = workflow.continue_run(run_output)

The step executes, then the workflow pauses with the full output available in req.step_output. The reviewer calls confirm(), reject(), or edit() before resuming.

Parameters

Pass these fields inside HumanReview. See HumanReview Config for the full field list, defaults, and validation rules.

FieldTypeDefaultDescription
requires_output_reviewbool | Callable[[StepOutput], bool]FalsePause after execution for review. Pass a callable for conditional review
output_review_messagestrNoneMessage shown to the reviewer
on_rejectOnRejectOnReject.skipAction on rejection: skip, cancel, retry
max_retriesint3Maximum retry attempts when on_reject=OnReject.retry
timeoutintNoneSeconds before auto-resolving. Only applies on Step. See Timeout
on_timeoutOnTimeoutOnTimeout.cancelAction when timeout expires. See Timeout

Reviewer Actions

MethodEffect
req.confirm()Accept output as-is. Continues to next step
req.reject()Reject output. Behavior depends on on_reject
req.reject(feedback="...")Reject with feedback. Feedback is sent to the agent on retry
req.edit("new output")Accept with modifications. Edited output replaces original

Reject with Retry

Set on_reject=OnReject.retry to re-execute the step when a reviewer rejects. Pair with reject(feedback=...) to send the reviewer's feedback to the agent on the next attempt.

from agno.workflow import Workflow, OnReject
from agno.workflow.step import Step
from agno.workflow.types import HumanReview
from agno.db.sqlite import SqliteDb

workflow = Workflow(
    name="email_review_workflow",
    db=SqliteDb(db_file="workflow.db"),
    steps=[
        Step(
            name="draft_email",
            agent=draft_agent,
            human_review=HumanReview(
                requires_output_review=True,
                output_review_message="Review the email draft",
                on_reject=OnReject.retry,
                max_retries=3,
            ),
        ),
        Step(name="send_email", agent=send_agent),
    ],
)

run_output = workflow.run("Draft an email about the Friday standup")

while run_output.is_paused:
    for req in run_output.steps_requiring_output_review:
        print(f"Attempt {req.retry_count + 1}:")
        print(req.step_output.content)

        if user_approves():
            req.confirm()
        else:
            feedback = input("What should change? ")
            req.reject(feedback=feedback)

    run_output = workflow.continue_run(run_output)

The feedback string is injected into the agent's message as "Feedback from reviewer: ..." on the next execution. Without feedback, the step simply re-runs with the same input.

Retry Behavior

ScenarioResult
Reject with on_reject=OnReject.retryStep re-executes and retry_count increments. Previous output is removed from collected outputs
Reject with feedbackFeedback is passed to the agent on the next attempt
max_retries exhaustedWorkflow is cancelled with a max-retries message

Edit Output

Accept with modifications. The edited content replaces the original step output before it flows to the next step. Use this when the fix is minor and a full retry would be wasteful.

run_output = workflow.run("Draft an email about the Friday standup")

if run_output.is_paused:
    for req in run_output.steps_requiring_output_review:
        print(req.step_output.content)

        choice = input("[a]pprove / [e]dit / [r]eject: ").strip().lower()

        if choice == "a":
            req.confirm()
        elif choice == "e":
            edited = input("Enter corrected output: ")
            req.edit(edited)
        else:
            req.reject()

    run_output = workflow.continue_run(run_output)

edit() sets confirmed=True and stores the edited content. On resume, the edited output replaces the original in collected outputs.

Conditional Review

Pass a callable to requires_output_review inside HumanReview to evaluate at runtime whether review is needed. The predicate receives the StepOutput and returns True to pause or False to auto-approve.

from agno.workflow import Workflow, OnReject
from agno.workflow.step import Step
from agno.workflow.types import HumanReview, StepOutput
from agno.db.sqlite import SqliteDb

def needs_review(step_output: StepOutput) -> bool:
    """Only review outputs longer than 200 characters."""
    content = str(step_output.content) if step_output.content else ""
    return len(content) > 200

workflow = Workflow(
    name="conditional_review_workflow",
    db=SqliteDb(db_file="workflow.db"),
    steps=[
        Step(
            name="draft_email",
            agent=draft_agent,
            human_review=HumanReview(
                requires_output_review=needs_review,
                output_review_message="Long email detected. Review before sending",
                on_reject=OnReject.retry,
                max_retries=2,
            ),
        ),
        Step(name="send_email", agent=send_agent),
    ],
)

Common review predicates:

ConditionExample
Output lengthlen(str(output.content)) > 200
Contains sensitive keywords"password" in str(output.content).lower()
Confidence scoreoutput.content.confidence < 0.8 (with structured output)
Random samplingrandom.random() < 0.1 for 10% review rate

Full Review Loop

The complete pattern with all three reviewer actions:

from agno.workflow import Workflow, OnReject
from agno.workflow.step import Step
from agno.workflow.types import HumanReview
from agno.db.sqlite import SqliteDb

workflow = Workflow(
    name="review_workflow",
    db=SqliteDb(db_file="workflow.db"),
    steps=[
        Step(
            name="draft",
            agent=draft_agent,
            human_review=HumanReview(
                requires_output_review=True,
                output_review_message="Review the draft",
                on_reject=OnReject.retry,
                max_retries=3,
            ),
        ),
        Step(name="publish", agent=publish_agent),
    ],
)

run_output = workflow.run("Write a client email")

while run_output.is_paused:
    for req in run_output.steps_requiring_output_review:
        print(req.step_output.content)

        choice = input("[a]pprove / [r]eject / [e]dit: ")
        if choice == "a":
            req.confirm()
        elif choice == "r":
            feedback = input("What should change? ")
            req.reject(feedback=feedback)
        elif choice == "e":
            edited = input("Enter corrected output: ")
            req.edit(edited)

    run_output = workflow.continue_run(run_output)

StepRequirement Properties

When a step pauses for output review, the StepRequirement includes:

PropertyTypeDescription
step_namestrName of the paused step
step_outputStepOutputThe step's output for review
output_review_messagestrMessage from the step configuration
retry_countintNumber of retry attempts so far
rejection_feedbackstrFeedback from the last rejection
timeout_atdatetimeWhen the timeout expires (if set)

Developer Resources