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 under
learn_source(default"hitl"). This happens whether the reply arrives during the run or later resumes a paused flow through an async provider. 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. Text output is rewritten as text, and a Pydantic model is rewritten as the same model unless it holds something the LLM can’t see: a secret, an excluded field, or a private attribute changed from its default. Any other output, such as a
dictor alist, is shown unchanged.
The human approves the reviewed version, which can differ from what the method returned. Downstream, read it from
self.last_human_feedback.output (or the listener’s argument). State the method set before the review still holds the original. If the flow paused for async feedback, the output is restored from JSON on resume, so a Pydantic model arrives as a dict.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 unless
learn_strict=True, which raises instead. - 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
