crewai - ✅(Solved) Fix [BUG] Two parallel agent executors (CrewAgentExecutor vs experimental AgentExecutor) cover the same responsibility — pick a winner [2 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
crewAIInc/crewAI#5736Fetched 2026-05-07 03:39:50
View on GitHub
Comments
0
Participants
1
Timeline
3
Reactions
0
Participants
Timeline (top)
cross-referenced ×2labeled ×1

The repository contains two parallel agent-executor implementations that share the same responsibility (LLM loop + tool dispatch + memory + hooks for an Agent):

FileLinesClassexecutor_type
lib/crewai/src/crewai/agents/crew_agent_executor.py1,615CrewAgentExecutor(BaseAgentExecutor)"crew"
lib/crewai/src/crewai/experimental/agent_executor.py2,963AgentExecutor(Flow[AgentExecutorState], BaseAgentExecutor)"experimental"

Both subclass BaseAgentExecutor, both expose invoke(...), both are registered in _EXECUTOR_TYPE_REGISTRY, both wire the same before/after LLM and tool hooks, and both reuse the helpers in crewai.utilities.agent_utils. Despite the experimental/ namespace, the second implementation is wired into the production hot path:

  • It is imported eagerly at module import time in crewai/__init__.py:75.
  • It is instantiated unconditionally inside Agent.kickoff() at agent/core.py:1402.
  • It is the implicit "current" implementation in crewai.experimental.__init__ — the legacy alias CrewAgentExecutorFlow = AgentExecutor lives at experimental/agent_executor.py:2963, indicating this code already absorbed an earlier rename.

Meanwhile, Agent.execute_task() keeps calling self.executor_class(...) (agent/core.py:1038), and executor_class defaults to CrewAgentExecutor (agent/core.py:323-330). So a single Agent instance can flow through two different executors depending on entry point — and behavior diverges, e.g.:

# agent/core.py:515
if self.executor_class is not AgentExecutor:
    handle_reasoning(self, task)

…meaning the reasoning step is silently skipped when the experimental executor is in play.

The user-visible cost:

  • Bug fixes and feature work must be landed in both files or one path silently drifts.
  • Hooks, events, and tracing have two near-duplicate emission sites; two test files exist (tests/agents/test_agent_executor.py ~2,046 lines targets experimental.AgentExecutor; tests/agents/test_async_agent_executor.py ~454 lines targets CrewAgentExecutor).
  • The experimental name is a misnomer: end users have no realistic way to opt out of AgentExecutor, since Agent.kickoff() hard-codes it.
  • 27 files in the repo reference one or both classes, so any future refactor multiplies in cost the longer the split persists.

This is a real architectural defect, not a doc/naming nit — please pick a winner.

Root Cause

The repository contains two parallel agent-executor implementations that share the same responsibility (LLM loop + tool dispatch + memory + hooks for an Agent):

FileLinesClassexecutor_type
lib/crewai/src/crewai/agents/crew_agent_executor.py1,615CrewAgentExecutor(BaseAgentExecutor)"crew"
lib/crewai/src/crewai/experimental/agent_executor.py2,963AgentExecutor(Flow[AgentExecutorState], BaseAgentExecutor)"experimental"

Both subclass BaseAgentExecutor, both expose invoke(...), both are registered in _EXECUTOR_TYPE_REGISTRY, both wire the same before/after LLM and tool hooks, and both reuse the helpers in crewai.utilities.agent_utils. Despite the experimental/ namespace, the second implementation is wired into the production hot path:

  • It is imported eagerly at module import time in crewai/__init__.py:75.
  • It is instantiated unconditionally inside Agent.kickoff() at agent/core.py:1402.
  • It is the implicit "current" implementation in crewai.experimental.__init__ — the legacy alias CrewAgentExecutorFlow = AgentExecutor lives at experimental/agent_executor.py:2963, indicating this code already absorbed an earlier rename.

Meanwhile, Agent.execute_task() keeps calling self.executor_class(...) (agent/core.py:1038), and executor_class defaults to CrewAgentExecutor (agent/core.py:323-330). So a single Agent instance can flow through two different executors depending on entry point — and behavior diverges, e.g.:

# agent/core.py:515
if self.executor_class is not AgentExecutor:
    handle_reasoning(self, task)

…meaning the reasoning step is silently skipped when the experimental executor is in play.

The user-visible cost:

  • Bug fixes and feature work must be landed in both files or one path silently drifts.
  • Hooks, events, and tracing have two near-duplicate emission sites; two test files exist (tests/agents/test_agent_executor.py ~2,046 lines targets experimental.AgentExecutor; tests/agents/test_async_agent_executor.py ~454 lines targets CrewAgentExecutor).
  • The experimental name is a misnomer: end users have no realistic way to opt out of AgentExecutor, since Agent.kickoff() hard-codes it.
  • 27 files in the repo reference one or both classes, so any future refactor multiplies in cost the longer the split persists.

This is a real architectural defect, not a doc/naming nit — please pick a winner.

Fix Action

Fix / Workaround

The repository contains two parallel agent-executor implementations that share the same responsibility (LLM loop + tool dispatch + memory + hooks for an Agent):

There should be one agent executor implementation in crewai.agents that is the source of truth for the LLM loop, tool dispatch, hooks, memory, and Flow-based plan-and-execute orchestration. Whichever path the team chooses to keep, the other should become a thin re-export (during a deprecation window) and then be deleted, with a single test suite covering the unified executor.

PR fix notes

PR #5737: refactor(agents): default Agent.executor_class to AgentExecutor

Description (problem / solution / changelog)

Closes #5736.

Summary

Two parallel agent executors currently coexist:

  • crewai.agents.crew_agent_executor.CrewAgentExecutor (1,615 lines, the historical default for Agent.execute_task())
  • crewai.experimental.agent_executor.AgentExecutor (2,963 lines, a Flow[AgentExecutorState], used unconditionally by Agent.kickoff() and exported from crewai/__init__.py)

Both subclass BaseAgentExecutor, expose invoke(), register the same hooks, and reuse the helpers in crewai.utilities.agent_utils. A single Agent instance can flow through either, depending on entry point — and _is_resuming_agent_executor() only works when agent_executor is an AgentExecutor, so checkpoint resume is silently broken under the current default.

This PR consolidates the runtime default without removing the legacy class:

  • Agent.executor_class now defaults to AgentExecutor. Agent.kickoff() and Agent.execute_task() go through the same executor by default.
  • CrewAgentExecutor.__init__ emits a DeprecationWarning pointing to #5736.
  • Stale "instance of the CrewAgentExecutor class" docstrings updated.

Out of scope (follow-ups)

  • Moving AgentExecutor out of crewai.experimental.* into crewai.agents.*. Touches ~27 files of imports and is mechanical — separate PR.
  • Deleting CrewAgentExecutor. Needs at least one minor release of deprecation runway.
  • Merging tests/agents/test_agent_executor.py and tests/agents/test_async_agent_executor.py along sync/async lines instead of legacy/experimental.
  • Reasoning consolidation: handle_reasoning (crewai/agent/utils.py) and AgentExecutor.generate_plan() are intentional duals — both still work, the legacy helper is gated by if self.executor_class is not AgentExecutor (agent/core.py:515). Left alone here; they can converge after the legacy class is removed.

Files changed

FileChange
lib/crewai/src/crewai/agent/core.pyDefault executor_class flipped; field description + Agent docstring + create_agent_executor docstring updated
lib/crewai/src/crewai/agents/agent_builder/base_agent.pyStale agent_executor description corrected
lib/crewai/src/crewai/agents/crew_agent_executor.pyDeprecationWarning added in __init__; import warnings added

Diff size: +15 / -7. No public API removed; existing user code that explicitly opts into executor_class=CrewAgentExecutor keeps working (with a warning).

Test plan

I was unable to run the test suite locally (no resolved venv on the dev machine). Reviewers should confirm:

  • uv run pytest lib/crewai/tests/agents/ -x -q
  • uv run pytest lib/crewai/tests/agents/test_async_agent_executor.py -x -q -W "default::DeprecationWarning" — this file directly instantiates CrewAgentExecutor and will now emit warnings; confirm none are promoted to errors elsewhere
  • uv run pytest lib/crewai/tests/agents/test_agent.py -x -q
  • uv run mypy lib/crewai/src/crewai/agent/core.py lib/crewai/src/crewai/agents/
  • Spot-check that any test asserting executor_class == CrewAgentExecutor is either removed or updated (none found in a grep, but worth double-checking)

Risks I'm aware of:

  • Downstream code that subclasses CrewAgentExecutor will now see a warning at instantiation. If their CI treats DeprecationWarning as an error, they'll need filterwarnings. The repo itself has no such global filter, so internal tests should be unaffected.
  • Behavior change: Agent.execute_task() now runs through the Flow-based executor by default. The Flow path stores the plan in state.plan instead of mutating task.description (an intentional improvement noted in the source), but consumers that read task.description post-execution to recover the plan text will need to read agent_executor._state.plan instead.

Changed files

  • lib/crewai/src/crewai/agent/core.py (modified, +5/-5)
  • lib/crewai/src/crewai/agents/agent_builder/base_agent.py (modified, +2/-2)
  • lib/crewai/src/crewai/agents/crew_agent_executor.py (modified, +8/-0)

PR #5738: Fix issue #5736: Agent.kickoff() must honor executor_class

Description (problem / solution / changelog)

Summary

Fixes #5736.

The two parallel agent-executor implementations — CrewAgentExecutor (legacy, default for Agent.executor_class) and the experimental AgentExecutor — had drifted enough that the Agent class silently routed kickoff() and execute_task() to different executors:

  • Agent.execute_task() instantiated self.executor_class (default CrewAgentExecutor).
  • Agent.kickoff() hard-coded AgentExecutor and ignored executor_class entirely.
  • The reasoning-gate guard if self.executor_class is not AgentExecutor: handle_reasoning(...) coupled feature behavior to literal class identity, so any subclass of either executor that opted into or out of internal planning was handled incorrectly.

This PR replaces both class-identity checks with two ClassVar capability flags on BaseAgentExecutor:

  • supports_internal_planning — whether the executor runs its own planning/replanning loop (and therefore should not have the external handle_reasoning step run before task execution).
  • supports_kickoff — whether the executor implements the Flow-based Agent.kickoff() entry point.

AgentExecutor sets both to True; CrewAgentExecutor leaves both False (defaults from the base class). Agent now consults these flags:

  • The reasoning gate is now if not getattr(self.executor_class, "supports_internal_planning", False): handle_reasoning(...).
  • Agent.kickoff() routes through a new _resolve_kickoff_executor_class() helper. When the configured executor_class advertises supports_kickoff = True, that class is used verbatim. Otherwise — to preserve backwards compatibility for existing default-configured agents — the helper emits a DeprecationWarning and falls back to AgentExecutor. The warning message tells users how to silence it (executor_class=AgentExecutor).

A new test module lib/crewai/tests/agents/test_executor_consistency.py (12 tests) pins down the contract:

  • Capability flags are correctly set on each executor class.
  • Agent.kickoff() honors a kickoff-capable executor_class (including subclasses) without warning.
  • Agent.kickoff() warns and falls back when executor_class does not opt into kickoff (default CrewAgentExecutor).
  • Agent.execute_task() instantiates self.executor_class (regression guard for the existing path).
  • The reasoning gate is driven by the capability flag, not class identity — so a CrewAgentExecutor subclass that opts into internal planning correctly skips handle_reasoning, and an AgentExecutor subclass that opts out correctly runs it.

Review & Testing Checklist for Human

  • Confirm the deprecation-warning text is acceptable; in particular, confirm the wording ("The silent fallback will be removed in a future release.") aligns with the team's deprecation policy and target release.
  • Decide whether Agent.kickoff() should eventually become a hard error when executor_class doesn't advertise supports_kickoff, or whether the deprecation-then-fallback approach is the long-term plan.
  • Sanity-check that no downstream user-facing entry points other than kickoff() / execute_task() rely on the old self.executor_class is not AgentExecutor class-identity check (a repo-wide grep was clean, but a second pair of eyes is worth it).
  • Run the recorded VCR test that exercises Agent.kickoff() with planning enabled to confirm the deprecation warning surfaces in real usage and that planning still produces a sensible plan when callers explicitly opt into executor_class=AgentExecutor.

Notes

  • No changes to public AgentExecutor / CrewAgentExecutor constructor signatures or imports.
  • The new _resolve_kickoff_executor_class() is private; the public surface stays the same.
  • Lint (ruff check, ruff format --check) and mypy --strict pass on all touched source files. The full lib/crewai/tests/agents/ suite (108 tests across both executor paths) passes locally.

Link to Devin session: https://app.devin.ai/sessions/6be040b5f66d4cc797d36b5a7e144d59

Changed files

  • lib/crewai/src/crewai/agent/core.py (modified, +37/-2)
  • lib/crewai/src/crewai/agents/agent_builder/base_agent_executor.py (modified, +20/-1)
  • lib/crewai/src/crewai/experimental/agent_executor.py (modified, +5/-1)
  • lib/crewai/tests/agents/test_executor_consistency.py (added, +349/-0)

Code Example

# agent/core.py:515
if self.executor_class is not AgentExecutor:
    handle_reasoning(self, task)

---

rg "CrewAgentExecutor|AgentExecutor" lib/crewai

---

# lib/crewai/src/crewai/agent/core.py:323-330
executor_class: Annotated[
    type[CrewAgentExecutor] | type[AgentExecutor],
    BeforeValidator(_validate_executor_class),
    PlainSerializer(_serialize_executor_class, return_type=str, when_used="json"),
] = Field(
    default=CrewAgentExecutor,        # ← default says legacy
    description="Class to use for the agent executor. Defaults to CrewAgentExecutor, can optionally use AgentExecutor.",
)

# lib/crewai/src/crewai/agent/core.py:1402  (inside Agent.kickoff)
executor = AgentExecutor(             # ← but kickoff hard-codes experimental
    llm=cast(BaseLLM, self.llm),
    agent=self,
    ...
)

# lib/crewai/src/crewai/agent/core.py:515
if self.executor_class is not AgentExecutor:
    handle_reasoning(self, task)      # ← behavior diverges between executors

---

# lib/crewai/src/crewai/agents/agent_builder/base_agent.py:106-110
_EXECUTOR_TYPE_REGISTRY: dict[str, str] = {
    "base":         "crewai.agents.agent_builder.base_agent_executor.BaseAgentExecutor",
    "crew":         "crewai.agents.crew_agent_executor.CrewAgentExecutor",
    "experimental": "crewai.experimental.agent_executor.AgentExecutor",
}

---

# lib/crewai/src/crewai/experimental/agent_executor.py:2963
CrewAgentExecutorFlow = AgentExecutor  # Deprecated alias for AgentExecutor

---

$ wc -l lib/crewai/src/crewai/agents/crew_agent_executor.py \
        lib/crewai/src/crewai/experimental/agent_executor.py
  1615 lib/crewai/src/crewai/agents/crew_agent_executor.py
  2963 lib/crewai/src/crewai/experimental/agent_executor.py
  4578 total

$ rg -l "CrewAgentExecutor|AgentExecutor" lib/crewai | wc -l
27

---

lib/crewai/src/crewai/agents/crew_agent_executor.py:94:
    class CrewAgentExecutor(BaseAgentExecutor):

lib/crewai/src/crewai/experimental/agent_executor.py:157:
    class AgentExecutor(Flow[AgentExecutorState], BaseAgentExecutor):

---

lib/crewai/src/crewai/agents/crew_agent_executor.py:101:
    executor_type: Literal["crew"] = "crew"

lib/crewai/src/crewai/experimental/agent_executor.py:174:
    executor_type: Literal["experimental"] = "experimental"

---

lib/crewai/src/crewai/__init__.py:75:
    from crewai.experimental.agent_executor import AgentExecutor as _AgentExecutor

lib/crewai/src/crewai/agent/core.py:77:
    from crewai.experimental.agent_executor import AgentExecutor

lib/crewai/src/crewai/utilities/agent_utils.py:47:
    from crewai.experimental.agent_executor import AgentExecutor

lib/crewai/src/crewai/hooks/llm_hooks.py:18:
    from crewai.experimental.agent_executor import AgentExecutor
RAW_BUFFERClick to expand / collapse

Description

The repository contains two parallel agent-executor implementations that share the same responsibility (LLM loop + tool dispatch + memory + hooks for an Agent):

FileLinesClassexecutor_type
lib/crewai/src/crewai/agents/crew_agent_executor.py1,615CrewAgentExecutor(BaseAgentExecutor)"crew"
lib/crewai/src/crewai/experimental/agent_executor.py2,963AgentExecutor(Flow[AgentExecutorState], BaseAgentExecutor)"experimental"

Both subclass BaseAgentExecutor, both expose invoke(...), both are registered in _EXECUTOR_TYPE_REGISTRY, both wire the same before/after LLM and tool hooks, and both reuse the helpers in crewai.utilities.agent_utils. Despite the experimental/ namespace, the second implementation is wired into the production hot path:

  • It is imported eagerly at module import time in crewai/__init__.py:75.
  • It is instantiated unconditionally inside Agent.kickoff() at agent/core.py:1402.
  • It is the implicit "current" implementation in crewai.experimental.__init__ — the legacy alias CrewAgentExecutorFlow = AgentExecutor lives at experimental/agent_executor.py:2963, indicating this code already absorbed an earlier rename.

Meanwhile, Agent.execute_task() keeps calling self.executor_class(...) (agent/core.py:1038), and executor_class defaults to CrewAgentExecutor (agent/core.py:323-330). So a single Agent instance can flow through two different executors depending on entry point — and behavior diverges, e.g.:

# agent/core.py:515
if self.executor_class is not AgentExecutor:
    handle_reasoning(self, task)

…meaning the reasoning step is silently skipped when the experimental executor is in play.

The user-visible cost:

  • Bug fixes and feature work must be landed in both files or one path silently drifts.
  • Hooks, events, and tracing have two near-duplicate emission sites; two test files exist (tests/agents/test_agent_executor.py ~2,046 lines targets experimental.AgentExecutor; tests/agents/test_async_agent_executor.py ~454 lines targets CrewAgentExecutor).
  • The experimental name is a misnomer: end users have no realistic way to opt out of AgentExecutor, since Agent.kickoff() hard-codes it.
  • 27 files in the repo reference one or both classes, so any future refactor multiplies in cost the longer the split persists.

This is a real architectural defect, not a doc/naming nit — please pick a winner.

Steps to Reproduce

  1. git clone the repo at current main (commit d165bcb6, version 1.14.5a3).
  2. Open lib/crewai/src/crewai/agents/crew_agent_executor.py and lib/crewai/src/crewai/experimental/agent_executor.py.
  3. Search the import graph:
    rg "CrewAgentExecutor|AgentExecutor" lib/crewai
    Observe: 27 files reference these classes.
  4. Read lib/crewai/src/crewai/agent/core.py lines 1038 and 1402 — note that Agent.execute_task() uses self.executor_class (defaulting to CrewAgentExecutor) while Agent.kickoff() always uses AgentExecutor.
  5. Read line 515 of the same file — note that handle_reasoning(...) is gated on executor_class is not AgentExecutor, confirming behavior is not feature-equivalent across the two paths.

Expected behavior

There should be one agent executor implementation in crewai.agents that is the source of truth for the LLM loop, tool dispatch, hooks, memory, and Flow-based plan-and-execute orchestration. Whichever path the team chooses to keep, the other should become a thin re-export (during a deprecation window) and then be deleted, with a single test suite covering the unified executor.

Concretely, after resolution:

  • A single class implements the executor protocol (preferred candidate: the Flow-based AgentExecutor, since it is already on the kickoff path and adds plan/replan/observation features the legacy class lacks).
  • Agent.kickoff() and Agent.execute_task() flow through the same executor with no if executor_class is not AgentExecutor branches.
  • The crewai.experimental namespace either disappears or becomes genuinely experimental (i.e., not imported eagerly from crewai/__init__.py).
  • _EXECUTOR_TYPE_REGISTRY collapses from {"base", "crew", "experimental"} to {"base", "default"} (or similar).
  • Test files merge or split cleanly along sync/async, not along legacy/experimental.

Screenshots/Code snippets

Default-vs-actual mismatch in Agent:

# lib/crewai/src/crewai/agent/core.py:323-330
executor_class: Annotated[
    type[CrewAgentExecutor] | type[AgentExecutor],
    BeforeValidator(_validate_executor_class),
    PlainSerializer(_serialize_executor_class, return_type=str, when_used="json"),
] = Field(
    default=CrewAgentExecutor,        # ← default says legacy
    description="Class to use for the agent executor. Defaults to CrewAgentExecutor, can optionally use AgentExecutor.",
)

# lib/crewai/src/crewai/agent/core.py:1402  (inside Agent.kickoff)
executor = AgentExecutor(             # ← but kickoff hard-codes experimental
    llm=cast(BaseLLM, self.llm),
    agent=self,
    ...
)

# lib/crewai/src/crewai/agent/core.py:515
if self.executor_class is not AgentExecutor:
    handle_reasoning(self, task)      # ← behavior diverges between executors

Registry exposing both:

# lib/crewai/src/crewai/agents/agent_builder/base_agent.py:106-110
_EXECUTOR_TYPE_REGISTRY: dict[str, str] = {
    "base":         "crewai.agents.agent_builder.base_agent_executor.BaseAgentExecutor",
    "crew":         "crewai.agents.crew_agent_executor.CrewAgentExecutor",
    "experimental": "crewai.experimental.agent_executor.AgentExecutor",
}

Already-renamed-once smell in the experimental file:

# lib/crewai/src/crewai/experimental/agent_executor.py:2963
CrewAgentExecutorFlow = AgentExecutor  # Deprecated alias for AgentExecutor

Operating System

Ubuntu 20.04

Python Version

3.12

crewAI Version

1.14.5a3 (per lib/crewai/src/crewai/__init__.py and recent commit fa628732)

crewAI Tools Version

N/A — issue is internal to crewai, independent of crewai-tools version.

Virtual Environment

Venv

Evidence

$ wc -l lib/crewai/src/crewai/agents/crew_agent_executor.py \
        lib/crewai/src/crewai/experimental/agent_executor.py
  1615 lib/crewai/src/crewai/agents/crew_agent_executor.py
  2963 lib/crewai/src/crewai/experimental/agent_executor.py
  4578 total

$ rg -l "CrewAgentExecutor|AgentExecutor" lib/crewai | wc -l
27

Both classes inherit BaseAgentExecutor:

lib/crewai/src/crewai/agents/crew_agent_executor.py:94:
    class CrewAgentExecutor(BaseAgentExecutor):

lib/crewai/src/crewai/experimental/agent_executor.py:157:
    class AgentExecutor(Flow[AgentExecutorState], BaseAgentExecutor):

Both register their executor_type:

lib/crewai/src/crewai/agents/crew_agent_executor.py:101:
    executor_type: Literal["crew"] = "crew"

lib/crewai/src/crewai/experimental/agent_executor.py:174:
    executor_type: Literal["experimental"] = "experimental"

Production code imports the experimental executor (not lazy):

lib/crewai/src/crewai/__init__.py:75:
    from crewai.experimental.agent_executor import AgentExecutor as _AgentExecutor

lib/crewai/src/crewai/agent/core.py:77:
    from crewai.experimental.agent_executor import AgentExecutor

lib/crewai/src/crewai/utilities/agent_utils.py:47:
    from crewai.experimental.agent_executor import AgentExecutor

lib/crewai/src/crewai/hooks/llm_hooks.py:18:
    from crewai.experimental.agent_executor import AgentExecutor

Possible Solution

Recommended path — promote AgentExecutor, retire CrewAgentExecutor:

  1. Feature-parity audit: confirm AgentExecutor covers everything CrewAgentExecutor does. The known gap is handle_reasoning(...), currently skipped for AgentExecutor per the guard at agent/core.py:515 — port reasoning into AgentExecutor (or document why it's intentionally absent) before deletion.
  2. Move out of experimental/: relocate to crewai/agents/agent_executor.py, since the implementation is no longer experimental in any meaningful sense — Agent.kickoff() already depends on it unconditionally.
  3. Make it the default: change Agent.executor_class default from CrewAgentExecutor to AgentExecutor and remove the if self.executor_class is not AgentExecutor branches.
  4. Deprecation shim: keep crewai.agents.crew_agent_executor.CrewAgentExecutor as a thin re-export of AgentExecutor for one minor release with a DeprecationWarning. Also keep the CrewAgentExecutorFlow alias for the same window.
  5. Registry cleanup: collapse _EXECUTOR_TYPE_REGISTRY to {"base", "default"}; map both "crew" and "experimental" to "default" during the deprecation window.
  6. Test consolidation: merge tests/agents/test_agent_executor.py and tests/agents/test_async_agent_executor.py so coverage is sliced by sync/async, not by legacy-vs-experimental.
  7. Docs / changelog: announce the consolidation, the deprecation timeline, and any behavioral diffs (e.g., reasoning handling) in docs/en/changelog.mdx.

Alternative path — keep CrewAgentExecutor as the production default:

  • Move AgentExecutor back behind a real opt-in flag (don't import it from crewai/__init__.py, don't instantiate from Agent.kickoff()).
  • Then it actually earns the experimental/ directory name.

The first path matches the apparent direction of travel in the codebase (kickoff already uses it, alias was already renamed once) and is recommended.

Additional context

  • Repo / commit: crewAIInc/crewAI at main, head d165bcb6 ("fix(deps): move textual to crewai-cli and add certifi"), version 1.14.5a3.
  • Why "experimental" is a misnomer here: a class is experimental when users opt in. crewai/__init__.py imports it eagerly and Agent.kickoff() instantiates it unconditionally — there is no opt-in surface.
  • Why this is urgent vs. cosmetic: the divergence has already produced a behavioral split (reasoning runs for one executor and not the other). Every future feature added to one side increases the chance of silent drift between Agent.execute_task() and Agent.kickoff() paths. Bundling consolidation now is cheaper than maintaining two 1.5k–3k-line classes that share helpers and hooks.
  • Backwards compat: both class names are publicly exported and used in third-party code that subclasses or type-checks against them. A deprecation window (one minor release) with DeprecationWarning and a re-export shim should cover the realistic blast radius.
  • OS note: this is a code-organization issue, OS-independent. The "Other" answer above reflects that — confirmed by reading source on Windows 11.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

There should be one agent executor implementation in crewai.agents that is the source of truth for the LLM loop, tool dispatch, hooks, memory, and Flow-based plan-and-execute orchestration. Whichever path the team chooses to keep, the other should become a thin re-export (during a deprecation window) and then be deleted, with a single test suite covering the unified executor.

Concretely, after resolution:

  • A single class implements the executor protocol (preferred candidate: the Flow-based AgentExecutor, since it is already on the kickoff path and adds plan/replan/observation features the legacy class lacks).
  • Agent.kickoff() and Agent.execute_task() flow through the same executor with no if executor_class is not AgentExecutor branches.
  • The crewai.experimental namespace either disappears or becomes genuinely experimental (i.e., not imported eagerly from crewai/__init__.py).
  • _EXECUTOR_TYPE_REGISTRY collapses from {"base", "crew", "experimental"} to {"base", "default"} (or similar).
  • Test files merge or split cleanly along sync/async, not along legacy/experimental.

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING