Skip to main content
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. Supported on Step, Router, and Loop (via requires_iteration_review).
from agno.workflow import Workflow, OnReject
from agno.workflow.step import Step
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,
            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

ParameterTypeDefaultDescription
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, else_branch
hitl_max_retriesint0Maximum retry attempts when on_reject=OnReject.retry. 0 = unlimited
hitl_timeoutintNoneSeconds before auto-resolving. See Timeout
on_timeoutOnTimeoutNoneAction 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.
workflow = Workflow(
    name="email_review_workflow",
    db=SqliteDb(db_file="workflow.db"),
    steps=[
        Step(
            name="draft_email",
            agent=draft_agent,
            requires_output_review=True,
            output_review_message="Review the email draft",
            on_reject=OnReject.retry,
            hitl_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. Previous output is removed from collected outputs
Reject with feedbackFeedback is passed to the agent. retry_count increments
hitl_max_retries reachedStep is skipped (treated as final rejection)
hitl_max_retries=0Unlimited retries (default)

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 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.types import StepOutput

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,
            requires_output_review=needs_review,
            output_review_message="Long email detected. Review before sending",
            on_reject=OnReject.retry,
            hitl_max_retries=2,
        ),
        Step(name="send_email", agent=send_agent),
    ],
)
This avoids the all-or-nothing problem: review every output (expensive) or review none (risky). Common predicates:
ConditionExample
Output lengthlen(str(output.content)) > 200
Contains sensitive keywords"password" in str(output.content).lower()
Confidence scoreoutput.metrics.get("confidence", 1.0) < 0.8
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.db.sqlite import SqliteDb

workflow = Workflow(
    name="review_workflow",
    db=SqliteDb(db_file="workflow.db"),
    steps=[
        Step(
            name="draft",
            agent=draft_agent,
            requires_output_review=True,
            output_review_message="Review the draft",
            on_reject=OnReject.retry,
            hitl_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