Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@
"group": "Human-in-the-Loop",
"pages": [
"workflows/hitl/overview",
"workflows/hitl/human-review",
"workflows/hitl/step",
"workflows/hitl/output-review",
"workflows/hitl/router",
Expand Down
29 changes: 19 additions & 10 deletions workflows/hitl/condition.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
title: Condition HITL
sidebarTitle: Condition
description: Let users decide which branch to execute in conditional workflows.
keywords: [workflow, condition, HITL, confirmation, branching, decision]
keywords: [workflow, condition, HITL, confirmation, branching, decision, human review]
---

Conditions support confirmation HITL, allowing users to decide which branch to execute at runtime.

All HITL settings are configured via [`HumanReview`](/workflows/hitl/human-review). Conditions only support `requires_confirmation`. Passing unsupported fields (like `requires_output_review`) raises a `ValueError`.

## User-Controlled Branching

When `requires_confirmation=True`, the condition pauses for user decision:
Expand All @@ -17,7 +19,7 @@ When `requires_confirmation=True`, the condition pauses for user decision:
from agno.workflow import Workflow, OnReject
from agno.workflow.condition import Condition
from agno.workflow.step import Step
from agno.workflow.types import StepInput, StepOutput
from agno.workflow.types import HumanReview, StepInput, StepOutput
from agno.db.sqlite import SqliteDb

def detailed_analysis(step_input: StepInput) -> StepOutput:
Expand All @@ -35,9 +37,11 @@ workflow = Workflow(
name="analysis_depth",
steps=[Step(name="detailed", executor=detailed_analysis)],
else_steps=[Step(name="quick", executor=quick_summary)],
requires_confirmation=True,
confirmation_message="Perform detailed analysis?",
on_reject=OnReject.else_branch,
human_review=HumanReview(
requires_confirmation=True,
confirmation_message="Perform detailed analysis?",
on_reject=OnReject.else_branch,
),
),
Step(name="report", executor=generate_report),
],
Expand Down Expand Up @@ -96,9 +100,11 @@ Condition(
name="optional_processing",
steps=[Step(name="process", executor=process)],
# No else_steps defined
requires_confirmation=True,
confirmation_message="Run optional processing?",
on_reject=OnReject.else_branch, # Will skip if rejected
human_review=HumanReview(
requires_confirmation=True,
confirmation_message="Run optional processing?",
on_reject=OnReject.else_branch, # Will skip if rejected
),
)
```

Expand All @@ -112,8 +118,10 @@ Condition(
# evaluator is ignored when requires_confirmation=True
steps=[Step(name="if_branch", ...)],
else_steps=[Step(name="else_branch", ...)],
requires_confirmation=True,
confirmation_message="Proceed with if branch?",
human_review=HumanReview(
requires_confirmation=True,
confirmation_message="Proceed with if branch?",
),
)
```

Expand Down Expand Up @@ -144,6 +152,7 @@ while run_output.is_paused:

## Developer Resources

- [HumanReview Config](/workflows/hitl/human-review)
- [Workflow HITL overview](/workflows/hitl/overview)
- [Condition reference](/reference/workflows/conditional-steps)
- [Conditional workflow pattern](/workflows/workflow-patterns/conditional-workflow)
203 changes: 203 additions & 0 deletions workflows/hitl/human-review.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
---
title: HumanReview Config
sidebarTitle: HumanReview Config
description: Consolidate all human-in-the-loop settings into a single config object.
keywords: [workflow, HITL, human-review, config, confirmation, output review, iteration review, timeout]
---

`HumanReview` groups all HITL settings into a single object instead of passing them as separate parameters. All workflow primitives accept `human_review=HumanReview(...)`: Step, Loop, Router, Condition, Steps, and Parallel.

```python
from agno.workflow.step import Step
from agno.workflow.types import HumanReview
from agno.workflow import OnReject

Step(
name="draft_email",
agent=draft_agent,
human_review=HumanReview(
requires_output_review=True,
output_review_message="Review the draft before sending.",
on_reject=OnReject.retry,
max_retries=3,
timeout=300,
on_timeout=OnTimeout.approve,
),
)
```

Flat parameters still work for backward compatibility:

```python
# Equivalent to the above
Step(
name="draft_email",
agent=draft_agent,
requires_output_review=True,
output_review_message="Review the draft before sending.",
on_reject=OnReject.retry,
hitl_max_retries=3,
hitl_timeout=300,
on_timeout=OnTimeout.approve,
)
```

If both `human_review` and flat parameters are provided, `human_review` takes priority.

## All Fields

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `requires_confirmation` | `bool` | `False` | Pause for user confirmation before execution |
| `confirmation_message` | `str` | `None` | Message shown during confirmation |
| `requires_user_input` | `bool` | `False` | Pause to collect user input before execution |
| `user_input_message` | `str` | `None` | Message shown when collecting input |
| `user_input_schema` | `List[UserInputField]` | `None` | Schema defining expected input fields |
| `requires_output_review` | `bool` | `False` | Pause after execution for output review |
| `output_review_message` | `str` | `None` | Message shown during output review |
| `requires_iteration_review` | `bool` | `False` | Pause after each loop iteration for review |
| `iteration_review_message` | `str` | `None` | Message shown during iteration review |
| `on_reject` | `OnReject` | `OnReject.skip` | Action when user rejects |
| `on_error` | `OnError` | `None` | Action when step errors |
| `max_retries` | `int` | `3` | Max retries on rejection (when `on_reject=OnReject.retry`) |
| `timeout` | `int` | `None` | Seconds to wait for user response |
| `on_timeout` | `OnTimeout` | `None` | Action when timeout expires |

## Supported Fields by Component

Not every field works on every component. Passing an unsupported field raises a `ValueError` at construction time.

| Field | Step | Loop | Router | Condition | Steps | Parallel |
|-------|------|------|--------|-----------|-------|----------|
| `requires_confirmation` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| `requires_user_input` | ✓ | - | ✓ | - | - | - |
| `requires_output_review` | ✓ | - | ✓ | - | - | - |
| `requires_iteration_review` | - | ✓ | - | - | - | - |
| `on_reject` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| `on_error` | ✓ | - | - | - | - | - |
| `max_retries` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| `timeout` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| `on_timeout` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |

Condition, Steps, and Parallel only support `requires_confirmation`. Passing unsupported fields raises a clear error:

```python
from agno.workflow.condition import Condition
from agno.workflow.types import HumanReview

# This raises ValueError: requires_output_review is not supported on Condition.
# Supported: requires_confirmation.
Condition(
name="my_condition",
steps=[...],
human_review=HumanReview(requires_output_review=True),
)
```

## Validation Rules

- `requires_output_review` and `requires_iteration_review` cannot both be `True` in the same config.
- Each component validates at construction time. You get a clear error if a field is unsupported.

## HITL Modes

### Pre-execution: Confirmation

Pause before a step runs. The user approves or rejects.

```python
HumanReview(
requires_confirmation=True,
confirmation_message="Delete 1000 records?",
on_reject=OnReject.cancel,
)
```

### Pre-execution: User Input

Collect parameters from the user before execution.

```python
from agno.workflow.types import HumanReview, UserInputField

HumanReview(
requires_user_input=True,
user_input_message="Configure report settings:",
user_input_schema=[
UserInputField(name="format", field_type="str", required=True),
UserInputField(name="include_charts", field_type="bool", required=False),
],
)
```

### Post-execution: Output Review

Pause after a step completes so the user can review the output. Supported on Step and Router.

```python
HumanReview(
requires_output_review=True,
output_review_message="Review the generated report.",
on_reject=OnReject.retry,
max_retries=3,
)
```

### Per-iteration: Iteration Review

Pause after each loop iteration for review. Supported on Loop only.

```python
HumanReview(
requires_iteration_review=True,
iteration_review_message="Review this iteration's output.",
on_reject=OnReject.retry,
)
```

## Timeout

Set a timeout for how long to wait for a user response. If the timeout expires, `on_timeout` determines what happens.

```python
from agno.workflow import OnTimeout

HumanReview(
requires_confirmation=True,
confirmation_message="Approve deployment?",
timeout=300, # 5 minutes
on_timeout=OnTimeout.approve, # Auto-approve after timeout
)
```

| `OnTimeout` Value | Behavior |
|-------------------|----------|
| `OnTimeout.approve` | Automatically approve and continue |
| `OnTimeout.reject` | Automatically reject |
| `OnTimeout.cancel` | Cancel the workflow |

## Serialization

`HumanReview` serializes to a single `"human_review"` key in the workflow state. Deserialization reads `"human_review"` first, then falls back to flat keys for backward compatibility with existing persisted data.

```python
config = HumanReview(
requires_confirmation=True,
confirmation_message="Proceed?",
timeout=60,
)

data = config.to_dict()
# {"requires_confirmation": True, "confirmation_message": "Proceed?", "timeout": 60, ...}

restored = HumanReview.from_dict(data)
```

## Developer Resources

- [Step HITL](/workflows/hitl/step)
- [Loop HITL](/workflows/hitl/loop)
- [Router HITL](/workflows/hitl/router)
- [Condition HITL](/workflows/hitl/condition)
- [Steps HITL](/workflows/hitl/steps)
- [Workflow HITL overview](/workflows/hitl/overview)
Loading
Loading