From 88e95befe740aeeafc13e28278aa81b66a4de3eb Mon Sep 17 00:00:00 2001 From: Greyson LaLonde Date: Fri, 22 May 2026 23:24:12 +0800 Subject: [PATCH] fix(experimental): allow AgentExecutor restore from checkpoint llm and prompt were declared required with exclude=True, making the model un-restorable from its own serialized output. Mirror the CrewAgentExecutor pattern: make them nullable with default None, keep exclude=True, and re-attach llm on the resume path alongside the other re-attached fields. Guard the two prompt-deref sites so the runtime invariant survives the looser type. --- lib/crewai/src/crewai/agent/core.py | 10 ++++++++++ .../src/crewai/experimental/agent_executor.py | 16 ++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/lib/crewai/src/crewai/agent/core.py b/lib/crewai/src/crewai/agent/core.py index 6ae09a581..7310c53be 100644 --- a/lib/crewai/src/crewai/agent/core.py +++ b/lib/crewai/src/crewai/agent/core.py @@ -1109,9 +1109,14 @@ class Agent(BaseAgent): """ if self.agent_executor is None: raise RuntimeError("Agent executor is not initialized.") + if not isinstance(self.llm, BaseLLM): + raise RuntimeError( + "LLM must be resolved before updating agent executor parameters." + ) if task is not None: self.agent_executor.task = task + self.agent_executor.llm = self.llm self.agent_executor.tools = tools self.agent_executor.original_tools = raw_tools self.agent_executor.prompt = prompt @@ -1411,6 +1416,11 @@ class Agent(BaseAgent): if _is_resuming_agent_executor(self.agent_executor): executor = self.agent_executor + if not isinstance(self.llm, BaseLLM): + raise RuntimeError( + "LLM must be resolved before resuming agent executor." + ) + executor.llm = self.llm executor.tools = parsed_tools executor.tools_names = get_tool_names(parsed_tools) executor.tools_description = render_text_description_and_args(parsed_tools) diff --git a/lib/crewai/src/crewai/experimental/agent_executor.py b/lib/crewai/src/crewai/experimental/agent_executor.py index 57e853666..0f31b8eb2 100644 --- a/lib/crewai/src/crewai/experimental/agent_executor.py +++ b/lib/crewai/src/crewai/experimental/agent_executor.py @@ -173,8 +173,10 @@ class AgentExecutor(Flow[AgentExecutorState], BaseAgentExecutor): executor_type: Literal["experimental"] = "experimental" suppress_flow_events: bool = True # always suppress for executor - llm: BaseLLM = Field(exclude=True) - prompt: SystemPromptResult | StandardPromptResult = Field(exclude=True) + llm: BaseLLM | None = Field(default=None, exclude=True) + prompt: SystemPromptResult | StandardPromptResult | None = Field( + default=None, exclude=True + ) max_iter: int = Field(default=25, exclude=True) tools: list[CrewStructuredTool] = Field(default_factory=list, exclude=True) tools_names: str = Field(default="", exclude=True) @@ -2585,6 +2587,11 @@ class AgentExecutor(Flow[AgentExecutorState], BaseAgentExecutor): self._kickoff_input = inputs.get("input", "") + if self.llm is None or self.prompt is None: + raise RuntimeError( + "AgentExecutor.llm or .prompt is unset; the executor was " + "not fully restored or initialized before execution." + ) if "system" in self.prompt: from crewai.llms.cache import mark_cache_breakpoint @@ -2686,6 +2693,11 @@ class AgentExecutor(Flow[AgentExecutorState], BaseAgentExecutor): self._kickoff_input = inputs.get("input", "") + if self.llm is None or self.prompt is None: + raise RuntimeError( + "AgentExecutor.llm or .prompt is unset; the executor was " + "not fully restored or initialized before execution." + ) if "system" in self.prompt: from crewai.llms.cache import mark_cache_breakpoint