Skip to content

fix: add missing ExecutorContext methods to AgentExecutor for human_input support#6352

Open
C1-BA-B1-F3 wants to merge 2 commits into
crewAIInc:mainfrom
C1-BA-B1-F3:fix/human-input-agent-executor
Open

fix: add missing ExecutorContext methods to AgentExecutor for human_input support#6352
C1-BA-B1-F3 wants to merge 2 commits into
crewAIInc:mainfrom
C1-BA-B1-F3:fix/human-input-agent-executor

Conversation

@C1-BA-B1-F3

@C1-BA-B1-F3 C1-BA-B1-F3 commented Jun 26, 2026

Copy link
Copy Markdown

Summary

Fixes #6347

Task(human_input=True) crashes with AttributeError: 'AgentExecutor' object has no attribute '_format_feedback_message' after the default executor was swapped from CrewAgentExecutor to AgentExecutor in 1.15.0.

Root Cause

AgentExecutor is cast to ExecutorContext in _handle_human_feedback(), but three protocol methods were never implemented:

Method Used By Status
_format_feedback_message(feedback) SyncHumanInputProvider._handle_regular_feedback() ❌ Missing
_invoke_loop() SyncHumanInputProvider._handle_regular_feedback() ❌ Missing
_ainvoke_loop() AsyncExecutorContext protocol ❌ Missing

Fix

Added all three missing methods to AgentExecutor in src/crewai/experimental/agent_executor.py:

  • _invoke_loop() — resets execution state and re-runs the flow via kickoff()
  • _ainvoke_loop() — async version using kickoff_async()
  • _format_feedback_message() — formats feedback using I18N_DEFAULT

Test Plan

  • Added TestAgentExecutorHumanInputProtocol test class with 5 tests verifying:
    • All three methods exist and are callable
    • _format_feedback_message() returns a proper dict with feedback content
    • All ExecutorContext protocol methods are present on AgentExecutor

Verification

# Run the new tests
pytest lib/crewai/tests/agents/test_agent_executor.py::TestAgentExecutorHumanInputProtocol -xvs

# Run existing human input integration tests (no regressions)
pytest lib/crewai/tests/test_flow_human_input_integration.py -xvs

All tests pass ✅

Summary by CodeRabbit

  • Bug Fixes

    • Improved human-feedback handling to ensure runs can be safely rerun without leaking transient execution state.
    • Added stricter validation during feedback-driven retries to prevent invalid completion results from continuing.
  • Tests

    • Expanded unit coverage to confirm transient run state resets between sequential executions.
    • Added checks for human-input executor behaviors, including feedback message formatting and required protocol method availability.

…nput support

AgentExecutor was missing three methods required by the ExecutorContext protocol: _invoke_loop, _ainvoke_loop, and _format_feedback_message. This caused Task(human_input=True) to crash with AttributeError after the default executor was swapped from CrewAgentExecutor to AgentExecutor in 1.15.0.

Fixes crewAIInc#6347

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

@corridor-security corridor-security Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary: This PR adds missing human input feedback loop methods to AgentExecutor, including sync/async re-invocation and feedback message formatting. No exploitable security vulnerabilities were identified in the added code.

Risk: Low risk. The changes do not introduce new public endpoints, authentication or authorization logic, filesystem/network access, subprocess execution, or other new attacker-facing security boundaries.

@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

AgentExecutor now resets transient execution state through a shared helper, adds sync and async human-feedback rerun helpers with feedback message formatting, and extends tests for state cleanup and protocol coverage.

Changes

Human feedback handling

Layer / File(s) Summary
Pipeline state reset
lib/crewai/src/crewai/experimental/agent_executor.py
Adds _reset_pipeline_state() and uses it in invoke() and invoke_async() startup and cleanup paths to clear transient execution fields.
Feedback rerun helpers
lib/crewai/src/crewai/experimental/agent_executor.py
Adds _invoke_loop(), _ainvoke_loop(), and _format_feedback_message() to rerun agent execution after feedback and build the LLM feedback message.
Protocol coverage tests
lib/crewai/tests/agents/test_agent_executor.py
Adds regression coverage for transient state clearing across reruns and verifies the human-input protocol methods and feedback message shape on AgentExecutor.

Suggested reviewers

  • greysonlalonde
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly states the main change: adding missing ExecutorContext methods to AgentExecutor for human input support.
Linked Issues check ✅ Passed The PR adds the missing human-input methods, rerun helpers, feedback formatting, and regression tests needed to fix the crash.
Out of Scope Changes check ✅ Passed The changes are focused on human_input support and related regression coverage, with no obvious unrelated additions.
Docstring Coverage ✅ Passed Docstring coverage is 90.48% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
lib/crewai/tests/agents/test_agent_executor.py (1)

2406-2459: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Regression tests only assert method presence, not feedback-loop behavior.

These tests guard against the original AttributeError (good), but none exercise _invoke_loop/_ainvoke_loop actually re-running the flow and returning an improved AgentFinish. The state-reset concern raised on agent_executor.py (carried-over iterations) would not be caught here. Consider adding a test that runs the feedback loop with a stubbed LLM across more than one prior iteration to lock in correct rerun semantics.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/crewai/tests/agents/test_agent_executor.py` around lines 2406 - 2459, The
regression tests currently only verify that AgentExecutor exposes _invoke_loop,
_ainvoke_loop, and _format_feedback_message, but they do not validate the
feedback-loop behavior itself. Add a test that exercises the actual rerun path
in AgentExecutor by stubbing the LLM and invoking _invoke_loop and/or
_ainvoke_loop after prior iterations, then assert the flow resets state
correctly and returns the expected improved AgentFinish. Use the existing
AgentExecutor helpers and the ExecutorContext feedback methods to locate the
behavior under test.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/crewai/src/crewai/experimental/agent_executor.py`:
- Around line 3172-3199: The feedback rerun in `_invoke_loop` and
`_ainvoke_loop` is only resetting part of the executor state, so prior
iterations and tool state leak into the second run. Update both methods to
mirror the full reset used by `invoke()`, including `state.iterations`,
`state.current_answer`, `state.use_native_tools`, and
`state.pending_tool_calls`, along with the existing flags. Make sure the async
path in `_ainvoke_loop` applies the same state reset before `kickoff_async()` so
`check_max_iterations` and `initialize_reasoning` behave as if this is a fresh
execution.

---

Nitpick comments:
In `@lib/crewai/tests/agents/test_agent_executor.py`:
- Around line 2406-2459: The regression tests currently only verify that
AgentExecutor exposes _invoke_loop, _ainvoke_loop, and _format_feedback_message,
but they do not validate the feedback-loop behavior itself. Add a test that
exercises the actual rerun path in AgentExecutor by stubbing the LLM and
invoking _invoke_loop and/or _ainvoke_loop after prior iterations, then assert
the flow resets state correctly and returns the expected improved AgentFinish.
Use the existing AgentExecutor helpers and the ExecutorContext feedback methods
to locate the behavior under test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9e5debdb-775d-4e72-a2ce-2c6b2d0ce918

📥 Commits

Reviewing files that changed from the base of the PR and between f364a7d and e73de84.

📒 Files selected for processing (2)
  • lib/crewai/src/crewai/experimental/agent_executor.py
  • lib/crewai/tests/agents/test_agent_executor.py

Comment thread lib/crewai/src/crewai/experimental/agent_executor.py Outdated
@samrusani

Copy link
Copy Markdown

I independently checked the rerun reset path on this branch and the state leak is reproducible on e73de8499c72866f1073619a247faa6a0922d404.

With prior feedback-loop state seeded before _invoke_loop(), a small local harness observed this state at the start of kickoff():

{
    "iterations": 3,
    "current_answer": "AgentFinish",
    "is_finished": False,
    "use_native_tools": True,
    "pending_tool_calls": [{"id": "stale"}],
}

The async path behaves the same before kickoff_async().

That matters because check_max_iterations() reads state.iterations, while initialize_reasoning() only re-checks native tool support when iterations == 0. A human-feedback retry can therefore inherit the previous attempt's iteration/tool state instead of behaving like a fresh retry that preserves the feedback message.

I also tried the minimal local reset before kickoff() / kickoff_async():

self.state.iterations = 0
self.state.current_answer = None
self.state.is_finished = False
self.state.use_native_tools = False
self.state.pending_tool_calls = []

After that temporary patch, the sync and async harnesses both observed clean state at rerun start, and the focused PR tests still passed:

uv run pytest lib/crewai/tests/agents/test_agent_executor.py::TestAgentExecutorHumanInputProtocol -q
5 passed

I would add a regression test that seeds stale iterations, current_answer, use_native_tools, and pending_tool_calls, then asserts the rerun helper resets those before entering the kickoff path. The current tests cover the missing methods/message shape, but not this behavioral rerun case.

@C1-BA-B1-F3

Copy link
Copy Markdown
Author

@samrusani Great catch! You're absolutely right — the state leak across feedback-loop retries is a real issue. The iterations, current_answer, use_native_tools, and pending_tool_calls fields need to be reset before re-entering kickoff()/kickoff_async().

I'll add the minimal reset you suggested and include a regression test that seeds stale state and asserts clean reset on rerun. Thanks for the detailed reproduction and the patch suggestion!

@C1-BA-B1-F3

Copy link
Copy Markdown
Author

Implemented the state reset follow-up in 0ff7f7f84.

What changed:

  • Added a shared _reset_pipeline_state() helper.
  • Reset iterations, current_answer, use_native_tools, and pending_tool_calls around invoke(), invoke_async(), _invoke_loop(), and _ainvoke_loop().
  • Added a regression test that seeds stale feedback-loop state and verifies sequential _invoke_loop() runs start clean and leave transient state cleared.

Validation run:

  • uv run pytest lib/crewai/tests/agents/test_agent_executor.py::TestFlowInvoke::test_invoke_loop_clears_transient_state_between_sequential_runs
  • uv run pytest lib/crewai/tests/agents/test_agent_executor.py::TestFlowInvoke lib/crewai/tests/agents/test_agent_executor.py::TestExecutorStateReset
  • uv run ruff check lib/crewai/src/crewai/experimental/agent_executor.py lib/crewai/tests/agents/test_agent_executor.py
  • git diff --check

The full test_agent_executor.py still has unrelated local environment failures from missing optional extras (anthropic, socksio), but the focused regression and related executor suites pass.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
lib/crewai/tests/agents/test_agent_executor.py (1)

961-1016: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add the async mirror for the state-reset regression.

_ainvoke_loop() has separate control flow and was changed alongside _invoke_loop(), but this regression only exercises the sync rerun path. A mirrored async test would prevent the async human-feedback path from drifting.

🧪 Suggested async companion test
     def test_invoke_loop_clears_transient_state_between_sequential_runs(
         self, mock_dependencies
     ):
         """Feedback reruns must not inherit pipeline state from prior runs."""
         executor = _build_executor(**mock_dependencies)
@@
         assert executor.state.current_answer is None
         assert executor.state.use_native_tools is False
         assert executor.state.pending_tool_calls == []
+
+    `@pytest.mark.asyncio`
+    async def test_ainvoke_loop_clears_transient_state_between_sequential_runs(
+        self, mock_dependencies
+    ):
+        """Async feedback reruns must not inherit pipeline state from prior runs."""
+        executor = _build_executor(**mock_dependencies)
+        start_states = []
+        outputs = ["First result", "Second result"]
+
+        async def mock_kickoff_side_effect():
+            start_states.append(
+                (
+                    executor.state.iterations,
+                    executor.state.current_answer,
+                    executor.state.use_native_tools,
+                    list(executor.state.pending_tool_calls),
+                )
+            )
+            executor.state.iterations = 3
+            executor.state.use_native_tools = True
+            executor.state.pending_tool_calls.append("tool_call")
+            executor.state.current_answer = AgentFinish(
+                thought="final thinking",
+                output=outputs.pop(0),
+                text="complete",
+            )
+
+        executor.state.iterations = 9
+        executor.state.current_answer = AgentAction(
+            thought="stale", tool="search", tool_input="query", text="action"
+        )
+        executor.state.use_native_tools = True
+        executor.state.pending_tool_calls.append("stale_tool_call")
+
+        with patch.object(
+            AgentExecutor, "kickoff_async", side_effect=mock_kickoff_side_effect
+        ):
+            first_result = await executor._ainvoke_loop()
+            assert first_result.output == "First result"
+            assert executor.state.iterations == 0
+            assert executor.state.current_answer is None
+            assert executor.state.use_native_tools is False
+            assert executor.state.pending_tool_calls == []
+
+            second_result = await executor._ainvoke_loop()
+
+        assert second_result.output == "Second result"
+        assert start_states == [
+            (0, None, False, []),
+            (0, None, False, []),
+        ]
+        assert executor.state.iterations == 0
+        assert executor.state.current_answer is None
+        assert executor.state.use_native_tools is False
+        assert executor.state.pending_tool_calls == []
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/crewai/tests/agents/test_agent_executor.py` around lines 961 - 1016, Add
an async regression test mirroring
test_invoke_loop_clears_transient_state_between_sequential_runs so
_ainvoke_loop() is covered too. Use the same AgentExecutor state-reset setup and
assertions, but drive the async path with the async kickoff method or equivalent
patch on AgentExecutor._ainvoke_loop() to verify iterations, current_answer,
use_native_tools, and pending_tool_calls are cleared between sequential feedback
reruns.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@lib/crewai/tests/agents/test_agent_executor.py`:
- Around line 961-1016: Add an async regression test mirroring
test_invoke_loop_clears_transient_state_between_sequential_runs so
_ainvoke_loop() is covered too. Use the same AgentExecutor state-reset setup and
assertions, but drive the async path with the async kickoff method or equivalent
patch on AgentExecutor._ainvoke_loop() to verify iterations, current_answer,
use_native_tools, and pending_tool_calls are cleared between sequential feedback
reruns.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2aa658ce-4206-445b-b29d-1da678adfb31

📥 Commits

Reviewing files that changed from the base of the PR and between e73de84 and 0ff7f7f.

📒 Files selected for processing (2)
  • lib/crewai/src/crewai/experimental/agent_executor.py
  • lib/crewai/tests/agents/test_agent_executor.py

@samrusani

Copy link
Copy Markdown

Rechecked current head 0ff7f7f8423dd5b82fcf46035d5614de6f42469a.

The runtime fix looks correct now: _ainvoke_loop() calls _reset_pipeline_state() before and after kickoff_async(), and that helper clears iterations, current_answer, use_native_tools, and pending_tool_calls.

I also tried the async companion regression locally by mirroring the existing sync test: seed stale state, patch AgentExecutor.kickoff_async, call _ainvoke_loop() twice, and assert both async reruns enter with clean state and leave transient state cleared.

Focused validation with that temporary async test added:

uv run pytest lib/crewai/tests/agents/test_agent_executor.py::TestFlowInvoke lib/crewai/tests/agents/test_agent_executor.py::TestExecutorStateReset -q
7 passed in 3.46s

So this looks like a coverage-only follow-up rather than a remaining runtime bug, but I agree the async mirror is worth adding because _ainvoke_loop() has a separate async path and was part of the original leak.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Task(human_input=True) crashes with AttributeError after default executor swap from CrewAgentExecutor to AgentExecutor in 1.15.0

2 participants