From 37eb0a0691c8e8574402a29fc208f1be4f571177 Mon Sep 17 00:00:00 2001 From: Julien-ser <73262183+Julien-ser@users.noreply.github.com> Date: Mon, 8 Jun 2026 17:16:27 -0400 Subject: [PATCH 1/4] fix: show agent result before feedback prompt when verbose=False MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When human_input=True but verbose=False, the feedback panel said 'Provide feedback on the Final Result above' but the result was never displayed — the AgentLogsExecutionEvent is verbose-gated while the human-input gate fires unconditionally. Fix: handle_feedback / handle_feedback_async detect when the executor is non-verbose and pass the AgentFinish to _prompt_input / _prompt_input_async. Both methods now print a green 'Agent Final Answer' panel before the feedback prompt when the caller supplies the answer, so the operator always sees the result they are asked to review. Training mode is unaffected — the answer panel is skipped in that path. Fixes #6072 --- .../src/crewai/core/providers/human_input.py | 56 +++++++++++++++++-- 1 file changed, 52 insertions(+), 4 deletions(-) diff --git a/lib/crewai/src/crewai/core/providers/human_input.py b/lib/crewai/src/crewai/core/providers/human_input.py index b82e408d9d..2767aa2bd7 100644 --- a/lib/crewai/src/crewai/core/providers/human_input.py +++ b/lib/crewai/src/crewai/core/providers/human_input.py @@ -179,7 +179,13 @@ def handle_feedback( Returns: The final answer after feedback processing. """ - feedback = self._prompt_input(context.crew) + is_verbose = context.agent.verbose or bool( + context.crew and getattr(context.crew, "verbose", False) + ) + feedback = self._prompt_input( + context.crew, + answer=None if is_verbose else formatted_answer, + ) if context._is_training_mode(): return self._handle_training_feedback(formatted_answer, feedback, context) @@ -200,7 +206,13 @@ async def handle_feedback_async( Returns: The final answer after feedback processing. """ - feedback = await self._prompt_input_async(context.crew) + is_verbose = context.agent.verbose or bool( + context.crew and getattr(context.crew, "verbose", False) + ) + feedback = await self._prompt_input_async( + context.crew, + answer=None if is_verbose else formatted_answer, + ) if context._is_training_mode(): return await self._handle_training_feedback_async( @@ -316,11 +328,17 @@ async def _handle_regular_feedback_async( return answer @staticmethod - def _prompt_input(crew: Crew | None) -> str: + def _prompt_input( + crew: Crew | None, + answer: "AgentFinish | None" = None, + ) -> str: """Show rich panel and prompt for input. Args: crew: The crew instance for context. + answer: When provided, the agent result is printed before the + feedback prompt so the operator can see it even when verbose + is disabled. Returns: User input string from terminal. @@ -342,6 +360,18 @@ def _prompt_input(crew: Crew | None) -> str: ) title = "🎓 Training Feedback Required" else: + if answer is not None: + output_str = HumanInputProvider._get_output_string(answer) + result_content = Text() + result_content.append(output_str, style="green") + formatter.console.print( + Panel( + result_content, + title="✅ Agent Final Answer", + border_style="green", + padding=(1, 2), + ) + ) prompt_text = ( "Provide feedback on the Final Result above.\n\n" "• If you are happy with the result, simply hit Enter without typing anything.\n" @@ -369,11 +399,17 @@ def _prompt_input(crew: Crew | None) -> str: formatter.resume_live_updates() @staticmethod - async def _prompt_input_async(crew: Crew | None) -> str: + async def _prompt_input_async( + crew: Crew | None, + answer: "AgentFinish | None" = None, + ) -> str: """Show rich panel and prompt for input without blocking the event loop. Args: crew: The crew instance for context. + answer: When provided, the agent result is printed before the + feedback prompt so the operator can see it even when verbose + is disabled. Returns: User input string from terminal. @@ -395,6 +431,18 @@ async def _prompt_input_async(crew: Crew | None) -> str: ) title = "🎓 Training Feedback Required" else: + if answer is not None: + output_str = HumanInputProvider._get_output_string(answer) + result_content = Text() + result_content.append(output_str, style="green") + formatter.console.print( + Panel( + result_content, + title="✅ Agent Final Answer", + border_style="green", + padding=(1, 2), + ) + ) prompt_text = ( "Provide feedback on the Final Result above.\n\n" "• If you are happy with the result, simply hit Enter without typing anything.\n" From 221ecad780538e5ddd65dd18e423cf668677a1ca Mon Sep 17 00:00:00 2001 From: Julien-ser <73262183+Julien-ser@users.noreply.github.com> Date: Mon, 29 Jun 2026 10:44:04 -0400 Subject: [PATCH 2/4] fix: show updated answer before each subsequent feedback prompt when verbose=False Addresses CodeRabbit feedback from PR #6073: when iterating through multiple rounds of human feedback in non-verbose mode, only the initial answer was shown. On subsequent rounds, the user was prompted again without seeing the newly generated response. Pass show_answer=not is_verbose to _handle_regular_feedback / _handle_regular_feedback_async so that _prompt_input re-displays the current answer before each feedback prompt whenever verbose output is suppressed. Co-Authored-By: Claude Sonnet 4.6 --- .../src/crewai/core/providers/human_input.py | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/lib/crewai/src/crewai/core/providers/human_input.py b/lib/crewai/src/crewai/core/providers/human_input.py index 2767aa2bd7..c2a394b26f 100644 --- a/lib/crewai/src/crewai/core/providers/human_input.py +++ b/lib/crewai/src/crewai/core/providers/human_input.py @@ -190,7 +190,9 @@ def handle_feedback( if context._is_training_mode(): return self._handle_training_feedback(formatted_answer, feedback, context) - return self._handle_regular_feedback(formatted_answer, feedback, context) + return self._handle_regular_feedback( + formatted_answer, feedback, context, show_answer=not is_verbose + ) async def handle_feedback_async( self, @@ -220,7 +222,7 @@ async def handle_feedback_async( ) return await self._handle_regular_feedback_async( - formatted_answer, feedback, context + formatted_answer, feedback, context, show_answer=not is_verbose ) @staticmethod @@ -251,6 +253,7 @@ def _handle_regular_feedback( current_answer: AgentFinish, initial_feedback: str, context: ExecutorContext, + show_answer: bool = False, ) -> AgentFinish: """Process regular feedback with iteration loop. @@ -258,6 +261,7 @@ def _handle_regular_feedback( current_answer: The agent's current answer. initial_feedback: Initial human feedback string. context: Executor context for callbacks. + show_answer: When True, display the updated answer before each prompt. Returns: Final answer after all feedback iterations. @@ -271,7 +275,10 @@ def _handle_regular_feedback( else: context.messages.append(context._format_feedback_message(feedback)) answer = context._invoke_loop() - feedback = self._prompt_input(context.crew) + feedback = self._prompt_input( + context.crew, + answer=answer if show_answer else None, + ) return answer @@ -303,6 +310,7 @@ async def _handle_regular_feedback_async( current_answer: AgentFinish, initial_feedback: str, context: AsyncExecutorContext, + show_answer: bool = False, ) -> AgentFinish: """Process regular feedback with async iteration loop. @@ -310,6 +318,7 @@ async def _handle_regular_feedback_async( current_answer: The agent's current answer. initial_feedback: Initial human feedback string. context: Async executor context for callbacks. + show_answer: When True, display the updated answer before each prompt. Returns: Final answer after all feedback iterations. @@ -323,7 +332,10 @@ async def _handle_regular_feedback_async( else: context.messages.append(context._format_feedback_message(feedback)) answer = await context._ainvoke_loop() - feedback = await self._prompt_input_async(context.crew) + feedback = await self._prompt_input_async( + context.crew, + answer=answer if show_answer else None, + ) return answer From 1d207fd8cbfdf952bc5b29d6f30e0ca2119bc422 Mon Sep 17 00:00:00 2001 From: Julien-ser <73262183+Julien-ser@users.noreply.github.com> Date: Thu, 3 Sep 2026 00:10:59 -0400 Subject: [PATCH 3/4] fix: show the agent answer in training mode too, not just normal mode The answer panel added by this PR was rendered only in the non-training branch of _prompt_input / _prompt_input_async, so with crew._train set the operator was still asked to judge output they could not see -- the same gap this PR set out to close, just on the training path. The training prompt explicitly asks for "detailed feedback about the result quality and reasoning process", so the result has to be on screen there as much as in normal mode. Hoist the rendering above the training/normal branch in both the sync and async paths, and extract it into a _render_answer_panel helper rather than repeat the same panel construction a third and fourth time. Adds a parametrized regression test covering _train=True and _train=False; the _train=True case fails without this change. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01JRC5ACRhgrzdagKjPUbYEH --- .../src/crewai/core/providers/human_input.py | 63 +++++++++++-------- lib/crewai/tests/agents/test_agent.py | 37 +++++++++++ 2 files changed, 73 insertions(+), 27 deletions(-) diff --git a/lib/crewai/src/crewai/core/providers/human_input.py b/lib/crewai/src/crewai/core/providers/human_input.py index c2a394b26f..83ee269941 100644 --- a/lib/crewai/src/crewai/core/providers/human_input.py +++ b/lib/crewai/src/crewai/core/providers/human_input.py @@ -5,7 +5,7 @@ import asyncio from contextvars import ContextVar, Token import sys -from typing import TYPE_CHECKING, Protocol, runtime_checkable +from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable if TYPE_CHECKING: @@ -143,6 +143,33 @@ def _get_output_string(answer: AgentFinish) -> str: return answer.output return answer.output.model_dump_json() + @staticmethod + def _render_answer_panel(formatter: Any, answer: AgentFinish) -> None: + """Print the agent's current answer above the feedback prompt. + + Rendered in training mode as well as normal mode: both prompts ask the + operator to judge the result, so the result has to be visible in both. + + Args: + formatter: Event-listener formatter owning the rich console. + answer: The agent's finished answer to display. + """ + from rich.panel import Panel + from rich.text import Text + + result_content = Text() + result_content.append( + HumanInputProvider._get_output_string(answer), style="green" + ) + formatter.console.print( + Panel( + result_content, + title="✅ Agent Final Answer", + border_style="green", + padding=(1, 2), + ) + ) + class SyncHumanInputProvider(HumanInputProvider): """Default human input provider with sync and async support.""" @@ -342,7 +369,7 @@ async def _handle_regular_feedback_async( @staticmethod def _prompt_input( crew: Crew | None, - answer: "AgentFinish | None" = None, + answer: AgentFinish | None = None, ) -> str: """Show rich panel and prompt for input. @@ -364,6 +391,9 @@ def _prompt_input( formatter.pause_live_updates() try: + if answer is not None: + HumanInputProvider._render_answer_panel(formatter, answer) + if crew and getattr(crew, "_train", False): prompt_text = ( "TRAINING MODE: Provide feedback to improve the agent's performance.\n\n" @@ -372,18 +402,6 @@ def _prompt_input( ) title = "🎓 Training Feedback Required" else: - if answer is not None: - output_str = HumanInputProvider._get_output_string(answer) - result_content = Text() - result_content.append(output_str, style="green") - formatter.console.print( - Panel( - result_content, - title="✅ Agent Final Answer", - border_style="green", - padding=(1, 2), - ) - ) prompt_text = ( "Provide feedback on the Final Result above.\n\n" "• If you are happy with the result, simply hit Enter without typing anything.\n" @@ -413,7 +431,7 @@ def _prompt_input( @staticmethod async def _prompt_input_async( crew: Crew | None, - answer: "AgentFinish | None" = None, + answer: AgentFinish | None = None, ) -> str: """Show rich panel and prompt for input without blocking the event loop. @@ -435,6 +453,9 @@ async def _prompt_input_async( formatter.pause_live_updates() try: + if answer is not None: + HumanInputProvider._render_answer_panel(formatter, answer) + if crew and getattr(crew, "_train", False): prompt_text = ( "TRAINING MODE: Provide feedback to improve the agent's performance.\n\n" @@ -443,18 +464,6 @@ async def _prompt_input_async( ) title = "🎓 Training Feedback Required" else: - if answer is not None: - output_str = HumanInputProvider._get_output_string(answer) - result_content = Text() - result_content.append(output_str, style="green") - formatter.console.print( - Panel( - result_content, - title="✅ Agent Final Answer", - border_style="green", - padding=(1, 2), - ) - ) prompt_text = ( "Provide feedback on the Final Result above.\n\n" "• If you are happy with the result, simply hit Enter without typing anything.\n" diff --git a/lib/crewai/tests/agents/test_agent.py b/lib/crewai/tests/agents/test_agent.py index 98d220bd18..ffd53ee723 100644 --- a/lib/crewai/tests/agents/test_agent.py +++ b/lib/crewai/tests/agents/test_agent.py @@ -894,6 +894,43 @@ async def kickoff_side_effect(executor, *_args, **_kwargs): assert mock_kickoff.await_count == 2 +@pytest.mark.parametrize("training_mode", [False, True]) +def test_prompt_input_renders_answer_in_both_modes(training_mode): + """The agent's answer must be shown above the feedback prompt in training mode too. + + Both prompts ask the operator to judge the result ("Provide feedback on the + Final Result above" / "feedback about the result quality"), so hiding the + answer in training mode leaves them judging output they cannot see. + """ + from unittest.mock import MagicMock + + from crewai.core.providers.human_input import SyncHumanInputProvider + + crew = MagicMock() + crew._train = training_mode + formatter = MagicMock() + answer = AgentFinish(output="Hello", thought="", text="") + + with ( + patch( + "crewai.events.event_listener.event_listener.formatter", + formatter, + ), + patch("builtins.input", return_value=""), + ): + SyncHumanInputProvider._prompt_input(crew, answer=answer) + + printed = [str(call.args[0]) for call in formatter.console.print.call_args_list] + rendered = [ + call.args[0] + for call in formatter.console.print.call_args_list + if getattr(call.args[0], "title", None) == "✅ Agent Final Answer" + ] + assert rendered, ( + f"answer panel not rendered with _train={training_mode}; printed={printed}" + ) + + def test_interpolate_inputs(): agent = Agent( role="{topic} specialist", From e023a142f1d76141113004e739552bcbbfb03797 Mon Sep 17 00:00:00 2001 From: Julien-ser <73262183+Julien-ser@users.noreply.github.com> Date: Fri, 4 Sep 2026 21:38:32 -0400 Subject: [PATCH 4/4] fix: address Copilot review on #6386 (panel style, brittle title assertion) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Use bright_green for the answer text, matching the existing final-answer rendering in console_formatter.py (lines 622 and 1220). The panel border stays green as it is there, so the same "✅ Agent Final Answer" panel now looks identical in the verbose and non-verbose flows. - Coerce the Rich panel title with str() before comparing. Rich stores Panel.title as either a str or a Text depending on version and config, so the equality check could silently match nothing on some versions and leave the test passing vacuously. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Xx1JiCDAZcBC71R2HN7Eyx --- lib/crewai/src/crewai/core/providers/human_input.py | 2 +- lib/crewai/tests/agents/test_agent.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/crewai/src/crewai/core/providers/human_input.py b/lib/crewai/src/crewai/core/providers/human_input.py index 83ee269941..ae82c1c566 100644 --- a/lib/crewai/src/crewai/core/providers/human_input.py +++ b/lib/crewai/src/crewai/core/providers/human_input.py @@ -159,7 +159,7 @@ def _render_answer_panel(formatter: Any, answer: AgentFinish) -> None: result_content = Text() result_content.append( - HumanInputProvider._get_output_string(answer), style="green" + HumanInputProvider._get_output_string(answer), style="bright_green" ) formatter.console.print( Panel( diff --git a/lib/crewai/tests/agents/test_agent.py b/lib/crewai/tests/agents/test_agent.py index ffd53ee723..e1a560221f 100644 --- a/lib/crewai/tests/agents/test_agent.py +++ b/lib/crewai/tests/agents/test_agent.py @@ -924,7 +924,7 @@ def test_prompt_input_renders_answer_in_both_modes(training_mode): rendered = [ call.args[0] for call in formatter.console.print.call_args_list - if getattr(call.args[0], "title", None) == "✅ Agent Final Answer" + if str(getattr(call.args[0], "title", "")) == "✅ Agent Final Answer" ] assert rendered, ( f"answer panel not rendered with _train={training_mode}; printed={printed}"