Overview
The
@human_feedback decorator requires CrewAI version 1.8.0 or higher. Make sure to update your installation before using this feature.@human_feedback decorator enables human-in-the-loop (HITL) workflows directly within CrewAI Flows. It allows you to pause flow execution, present output to a human for review, collect their feedback, and optionally route to different listeners based on the feedback outcome.
This is particularly valuable for:
- Quality assurance: Review AI-generated content before it’s used downstream
- Decision gates: Let humans make critical decisions in automated workflows
- Approval workflows: Implement approve/reject/revise patterns
- Interactive refinement: Collect feedback to improve outputs iteratively
Quick Start
Here’s the simplest way to add human feedback to a flow:Code
- Execute
generate_contentand return the string - Display the output to the user with the request message
- Wait for the user to type feedback (or press Enter to skip)
- Pass a
HumanFeedbackResultobject toprocess_feedback
The @human_feedback Decorator
Parameters
Basic Usage (No Routing)
When you don’t specifyemit, the decorator simply collects feedback and passes a HumanFeedbackResult to the next listener:
Code
Routing with emit
When you specifyemit, the decorator becomes a router. The human’s free-form feedback is interpreted by an LLM and collapsed into one of the specified outcomes:
Code
"needs_revision", which triggers review_content again via or_() — creating a revision loop. The loop continues until the outcome is "approved" or "rejected".
HumanFeedbackResult
TheHumanFeedbackResult dataclass contains all information about a human feedback interaction:
Code
Accessing in Listeners
When a listener is triggered by a@human_feedback method with emit, it receives the HumanFeedbackResult:
Code
Accessing Feedback History
TheFlow class provides two attributes for accessing human feedback:
last_human_feedback
Returns the most recentHumanFeedbackResult:
Code
human_feedback_history
A list of allHumanFeedbackResult objects collected during the flow:
Code
Complete Example: Content Approval Workflow
Here’s a full example implementing a content review and approval workflow with a revision loop:@listen(or_("generate_draft", "needs_revision")) — the review method listens to both the initial trigger and its own revision outcome, creating a self-loop that repeats until the human approves or rejects.
Combining with Other Decorators
The@human_feedback decorator works with @start(), @listen(), and or_(). Both decorator orderings work — the framework propagates attributes in both directions — but the recommended patterns are:
Code
Self-loop pattern
To create a revision loop, the review method must listen to both an upstream trigger and its own revision outcome usingor_():
Code
"revise", the flow routes back to review (because it listens to "revise" via or_()). When the outcome is "approved", the flow continues to publish. This works because the flow engine exempts routers from the “fire once” rule, allowing them to re-execute on each loop iteration.
Chained routers
A listener triggered by one router’s outcome can itself be a router:Code
Limitations
@start()methods run once: A@start()method cannot self-loop. If you need a revision cycle, use a separate@start()method as the entry point and put the@human_feedbackon a@listen()method.- No
@start()+@listen()on the same method: This is a Flow framework constraint. A method is either a start point or a listener, not both.
Best Practices
1. Write Clear Request Messages
Themessage parameter is what the human sees. Make it actionable:
Code
2. Choose Meaningful Outcomes
When usingemit, pick outcomes that map naturally to human responses:
Code
3. Always Provide a Default Outcome
Usedefault_outcome to handle cases where users press Enter without typing:
Code
4. Use Feedback History for Audit Trails
Accesshuman_feedback_history to create audit logs:
Code
5. Handle Both Routed and Non-Routed Feedback
When designing flows, consider whether you need routing:Async Human Feedback (Non-Blocking)
By default,@human_feedback blocks execution waiting for console input. For production applications, you may need async/non-blocking feedback that integrates with external systems like Slack, email, webhooks, or APIs.
The Provider Abstraction
Use theprovider parameter to specify a custom feedback collection strategy:
Code
Handling Paused Flows
When using an async provider,kickoff() returns a HumanFeedbackPending object instead of raising an exception:
Code
Resuming a Paused Flow
When feedback arrives (e.g., via webhook), resume the flow:Code
Key Types
PendingFeedbackContext
The context contains everything needed to resume:Code
Complete Async Flow Example
Code
Best Practices for Async Feedback
- Check the return type:
kickoff()returnsHumanFeedbackPendingwhen paused—no try/except needed - Use the right resume method: Use
resume()in sync code,await resume_async()in async code - Store callback info: Use
callback_infoto store webhook URLs, ticket IDs, etc. - Implement idempotency: Your resume handler should be idempotent for safety
- Automatic persistence: State is automatically saved when
HumanFeedbackPendingis raised and usesSQLiteFlowPersistenceby default - Custom persistence: Pass a custom persistence instance to
from_pending()if needed
Learning from Feedback
Thelearn=True parameter enables a feedback loop between human reviewers and the memory system. When enabled, the system progressively improves its outputs by learning from past human corrections.
How It Works
- After feedback: The LLM extracts generalizable lessons from the output + feedback and stores them in memory with
source="hitl". If the feedback is just approval (e.g. “looks good”), nothing is stored. - Before next review: Past HITL lessons are recalled from memory and applied by the LLM to improve the output before the human sees it.
Example
Code
Configuration
Key Design Decisions
- Same LLM for everything: The
llmparameter on the decorator is shared by outcome collapsing, lesson distillation, and pre-review. No need to configure multiple models. - Structured output: Both distillation and pre-review use function calling with Pydantic models when the LLM supports it, falling back to text parsing otherwise.
- Non-blocking storage: Lessons are stored via
remember_many()which runs in a background thread — the flow continues immediately. - Graceful degradation: If the LLM fails during distillation, nothing is stored. If it fails during pre-review, the raw output is shown. Neither failure blocks the flow.
- No scope/categories needed: When storing lessons, only
sourceis passed. The encoding pipeline infers scope, categories, and importance automatically.
learn=True requires the Flow to have memory available. Flows get memory automatically by default, but if you’ve disabled it with _skip_auto_memory, HITL learning will be silently skipped.Related Documentation
- Flows Overview - Learn about CrewAI Flows
- Flow State Management - Managing state in flows
- Flow Persistence - Persisting flow state
- Routing with @router - More about conditional routing
- Human Input on Execution - Task-level human input
- Memory - The unified memory system used by HITL learning
