crewai - ✅(Solved) Fix [BUG] Checkpoint serialization fails with <class 'function'> after #5544 fix [1 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#5620Fetched 2026-04-26 05:13:39
View on GitHub
Comments
0
Participants
1
Timeline
2
Reactions
0
Participants
Timeline (top)
cross-referenced ×1labeled ×1

After the recent fix for ModelMetaclass serialization in checkpoints (#5544), I’m still encountering a serialization issue when checkpointing a Flow.

This time, the error is related to function objects, not Pydantic model classes.

Checkpointing fails when using on_events=["method_execution_finished"], even though the user-defined state is fully JSON-serializable.

Error Message

Auto-checkpoint failed for event method_execution_finished Traceback (most recent call last): File ".../site-packages/crewai/state/checkpoint_listener.py", line 242, in _on_any_event _do_checkpoint(state, cfg, event) File ".../site-packages/crewai/state/checkpoint_listener.py", line 144, in _do_checkpoint payload = state.model_dump(mode="json") File ".../site-packages/pydantic/main.py", line 463, in model_dump return self.pydantic_serializer.to_python( pydantic_core._pydantic_core.PydanticSerializationError: Unable to serialize unknown type: <class 'function'>

Root Cause

After the recent fix for ModelMetaclass serialization in checkpoints (#5544), I’m still encountering a serialization issue when checkpointing a Flow.

This time, the error is related to function objects, not Pydantic model classes.

Checkpointing fails when using on_events=["method_execution_finished"], even though the user-defined state is fully JSON-serializable.

Fix Action

Fixed

PR fix notes

PR #5621: Fix #5620: Serialize guardrail callable fields for checkpointing

Description (problem / solution / changelog)

Summary

Fixes #5620 — Checkpoint serialization fails with PydanticSerializationError: Unable to serialize unknown type: <class 'function'> when a Task carries callable guardrails.

Root Cause

The guardrail and guardrails fields on Task accept callable (function) values, but had no custom JSON serializer. When RuntimeState.model_dump(mode="json") serialized Crew entities during checkpointing, these callable fields caused Pydantic to raise a serialization error.

This is the same class of issue fixed in commit d6d04717c for output_pydantic/output_json/response_model/converter_cls (class references), but for function references instead.

Fix

Added PlainSerializer annotations to both guardrail and guardrails fields that convert callables to their dotted-path strings via callable_to_string (the same utility already used by SerializableCallable for callback, step_callback, etc.). String guardrails (LLM guardrail descriptions) pass through unchanged.

Changes

  • lib/crewai/src/crewai/task.py: Added _serialize_guardrail_item, _serialize_guardrail, and _serialize_guardrails helper functions. Annotated guardrail and guardrails fields with PlainSerializer.
  • lib/crewai/tests/test_checkpoint.py: Added TestGuardrailCheckpointSerialization test class with 8 tests covering callable guardrails, list of guardrails, string guardrails, mixed guardrails, None guardrails, and end-to-end checkpoint serialization through RuntimeState.

Review & Testing Checklist for Human

  • Verify the fix resolves the original issue: create a Flow with CheckpointConfig(on_events=["method_execution_finished"]) and tasks that use callable guardrails — checkpointing should succeed without PydanticSerializationError
  • Verify that existing guardrail behavior (both callable and string-based) is not affected at runtime (guardrails still execute correctly)
  • Verify checkpoint restoration still works for Crews with guardrail tasks

Notes

The serialized dotted-path for callable guardrails (e.g. "mymodule.my_guardrail") is stored in the checkpoint JSON. On restoration, this string will be treated as a string guardrail (LLM guardrail description) rather than resolved back to the original callable — this matches the existing behavior pattern for SerializableCallable fields which require CREWAI_DESERIALIZE_CALLBACKS=1 for round-trip resolution.

Link to Devin session: https://app.devin.ai/sessions/3e1188e133494e19bd9fee5bf350cdea

Changed files

  • lib/crewai/src/crewai/task.py (modified, +41/-3)
  • lib/crewai/tests/test_checkpoint.py (modified, +147/-0)

Code Example

CheckpointConfig(
    location="./.checkpoints",
    on_events=["method_execution_finished"],
    provider=JsonProvider(),
)

---

generator:
  role: "Family generator"
  goal: "Generate a list of families with structured data"
  backstory: "Expert in generating synthetic family datasets"

---

generation_task:
  description: >
    Generate EXACTLY ONE FAMILY with {sizes} members.
    Each family must include: id, name, size, members
    Each member must include: id, name, role, age, sports
    Output must be valid JSON matching the expected structure.
  expected_output: >
    A FamilyList containing exactly one Family with {sizes} members.
  agent: generator

---

from crewai import Agent, Crew, Process, Task
from crewai.agents.agent_builder.base_agent import BaseAgent
from crewai.project import CrewBase, agent, crew, task

from sporty_family_flow.models.family import FamilyList
from sporty_family_flow.guardrails.family_guardrails import is_family_guardrail


@CrewBase
class FamilyCrew:
    """Family Crew"""

    agents: list[BaseAgent]
    tasks: list[Task]

    agents_config = "config/agents.yaml"
    tasks_config = "config/tasks.yaml"

    @agent
    def generator(self) -> Agent:
        return Agent(
            config=self.agents_config["generator"]
        )

    @task
    def generation_task(self) -> Task:
        return Task(
            config=self.tasks_config["generation_task"],
            output_pydantic=FamilyList,
            guardrails=[is_family_guardrail],
        )

    @crew
    def crew(self) -> Crew:
        return Crew(
            agents=self.agents,  # Automatically created by the @agent decorator
            tasks=self.tasks,  # Automatically created by the @task decorator
            process=Process.sequential,
        )

---

from sporty_family_flow.models.family import FamilyList

def is_family_guardrail(output):
    families = output.pydantic.families
    for family in families:
        if family.size <= 1:
            return (False, f"Family {family.name} has an invalid size: {family.size}. Size must be greater than 1. Add members to this family.")
    return (True, output)

---

from pydantic import BaseModel
from typing import List


class Member(BaseModel):
    member_id: int
    name: str
    role: str
    age: int
    sports: List[str]


class Family(BaseModel):
    family_id: int
    name: str
    size: int
    members: List[Member]


class FamilyList(BaseModel):
    families: List[Family]

---

from pydantic import BaseModel
from crewai.flow import Flow, listen, start
from crewai import CheckpointConfig
from crewai.state import JsonProvider

import sporty_family_flow.listeners
from sporty_family_flow.crews.family_crew.family_crew import FamilyCrew
from sporty_family_flow.models.family import Family, FamilyList


class FamilyState(BaseModel):
    families: list[Family] = []


class FamilyFlow(Flow[FamilyState]):

    @start()
    def get_input(self):
        return(input("Enter the sizes of your families: "))

    @listen(get_input)
    def create_families(self, sizes: str):
        result = (FamilyCrew().crew().kickoff(inputs={"sizes": sizes}))
        families = result.pydantic.families
        self.state.families = families

    @listen(create_families)
    def print_families(self):
        for family in self.state.families:
            log = f"""
            ----- Family {family.family_id} ----- \n\n
            Family Name: {family.name}\n
            Size: {family.size}\n
            MEMBERS: \n
            """
            for member in family.members:
                log += f"""
                \t Member: {member.name} \n
                \t Age: {member.age} \n
                \t Role: {member.role} \n
                \t Sports: {', '.join(member.sports)}
                """
            print(log)


def kickoff():
    cp_config = CheckpointConfig(
        location="./.checkpoints",
        on_events=["method_execution_finished"],
        provider=JsonProvider(),
        max_checkpoints=5
    )
    family_flow = FamilyFlow(checkpoint=cp_config)
    family_flow.kickoff()


def plot():
    FamilyFlow().plot()

if __name__ == "__main__":
    kickoff()

---

Auto-checkpoint failed for event method_execution_finished
Traceback (most recent call last):
  File ".../site-packages/crewai/state/checkpoint_listener.py", line 242, in _on_any_event
    _do_checkpoint(state, cfg, event)
  File ".../site-packages/crewai/state/checkpoint_listener.py", line 144, in _do_checkpoint
    payload = state.model_dump(mode="json")
  File ".../site-packages/pydantic/main.py", line 463, in model_dump
    return self.__pydantic_serializer__.to_python(
pydantic_core._pydantic_core.PydanticSerializationError: Unable to serialize unknown type: <class 'function'>
RAW_BUFFERClick to expand / collapse

Description

After the recent fix for ModelMetaclass serialization in checkpoints (#5544), I’m still encountering a serialization issue when checkpointing a Flow.

This time, the error is related to function objects, not Pydantic model classes.

Checkpointing fails when using on_events=["method_execution_finished"], even though the user-defined state is fully JSON-serializable.

Steps to Reproduce

  1. Create a Flow with a simple Pydantic state (only primitives and nested models)
  2. Enable checkpointing:
CheckpointConfig(
    location="./.checkpoints",
    on_events=["method_execution_finished"],
    provider=JsonProvider(),
)
  1. Run the flow
  2. Observe checkpointing logs

Expected behavior

Checkpointing should:

  • Successfully serialize the state to JSON
  • Or safely ignore non-serializable internal fields (e.g. functions, callbacks)

Screenshots/Code snippets

Same as in (#5544)

Agents (agents.yaml)

generator:
  role: "Family generator"
  goal: "Generate a list of families with structured data"
  backstory: "Expert in generating synthetic family datasets"

Tasks (tasks.yaml)

generation_task:
  description: >
    Generate EXACTLY ONE FAMILY with {sizes} members.
    Each family must include: id, name, size, members
    Each member must include: id, name, role, age, sports
    Output must be valid JSON matching the expected structure.
  expected_output: >
    A FamilyList containing exactly one Family with {sizes} members.
  agent: generator

Crew (family_crew.py)

from crewai import Agent, Crew, Process, Task
from crewai.agents.agent_builder.base_agent import BaseAgent
from crewai.project import CrewBase, agent, crew, task

from sporty_family_flow.models.family import FamilyList
from sporty_family_flow.guardrails.family_guardrails import is_family_guardrail


@CrewBase
class FamilyCrew:
    """Family Crew"""

    agents: list[BaseAgent]
    tasks: list[Task]

    agents_config = "config/agents.yaml"
    tasks_config = "config/tasks.yaml"

    @agent
    def generator(self) -> Agent:
        return Agent(
            config=self.agents_config["generator"]
        )

    @task
    def generation_task(self) -> Task:
        return Task(
            config=self.tasks_config["generation_task"],
            output_pydantic=FamilyList,
            guardrails=[is_family_guardrail],
        )

    @crew
    def crew(self) -> Crew:
        return Crew(
            agents=self.agents,  # Automatically created by the @agent decorator
            tasks=self.tasks,  # Automatically created by the @task decorator
            process=Process.sequential,
        )

Guardrail (family_guardrails.py)

from sporty_family_flow.models.family import FamilyList

def is_family_guardrail(output):
    families = output.pydantic.families
    for family in families:
        if family.size <= 1:
            return (False, f"Family {family.name} has an invalid size: {family.size}. Size must be greater than 1. Add members to this family.")
    return (True, output)

Pydantic Model (family.py)

from pydantic import BaseModel
from typing import List


class Member(BaseModel):
    member_id: int
    name: str
    role: str
    age: int
    sports: List[str]


class Family(BaseModel):
    family_id: int
    name: str
    size: int
    members: List[Member]


class FamilyList(BaseModel):
    families: List[Family]

Flow (main.py)

from pydantic import BaseModel
from crewai.flow import Flow, listen, start
from crewai import CheckpointConfig
from crewai.state import JsonProvider

import sporty_family_flow.listeners
from sporty_family_flow.crews.family_crew.family_crew import FamilyCrew
from sporty_family_flow.models.family import Family, FamilyList


class FamilyState(BaseModel):
    families: list[Family] = []


class FamilyFlow(Flow[FamilyState]):

    @start()
    def get_input(self):
        return(input("Enter the sizes of your families: "))

    @listen(get_input)
    def create_families(self, sizes: str):
        result = (FamilyCrew().crew().kickoff(inputs={"sizes": sizes}))
        families = result.pydantic.families
        self.state.families = families

    @listen(create_families)
    def print_families(self):
        for family in self.state.families:
            log = f"""
            ----- Family {family.family_id} ----- \n\n
            Family Name: {family.name}\n
            Size: {family.size}\n
            MEMBERS: \n
            """
            for member in family.members:
                log += f"""
                \t Member: {member.name} \n
                \t Age: {member.age} \n
                \t Role: {member.role} \n
                \t Sports: {', '.join(member.sports)}
                """
            print(log)


def kickoff():
    cp_config = CheckpointConfig(
        location="./.checkpoints",
        on_events=["method_execution_finished"],
        provider=JsonProvider(),
        max_checkpoints=5
    )
    family_flow = FamilyFlow(checkpoint=cp_config)
    family_flow.kickoff()


def plot():
    FamilyFlow().plot()

if __name__ == "__main__":
    kickoff()

Operating System

Ubuntu 24.04

Python Version

3.12

crewAI Version

1.14.3

crewAI Tools Version


Virtual Environment

Venv

Evidence

Checkpointing fails with the following error:

Auto-checkpoint failed for event method_execution_finished
Traceback (most recent call last):
  File ".../site-packages/crewai/state/checkpoint_listener.py", line 242, in _on_any_event
    _do_checkpoint(state, cfg, event)
  File ".../site-packages/crewai/state/checkpoint_listener.py", line 144, in _do_checkpoint
    payload = state.model_dump(mode="json")
  File ".../site-packages/pydantic/main.py", line 463, in model_dump
    return self.__pydantic_serializer__.to_python(
pydantic_core._pydantic_core.PydanticSerializationError: Unable to serialize unknown type: <class 'function'>

Possible Solution

  • Exclude callable objects from checkpoint serialization
  • Ensure that internal Flow metadata (listeners, callbacks, methods) is not included in the serialized state
  • Alternatively, serialize only the user-defined state instead of the full internal state

Additional context

The previous fix (#5544) correctly addressed serialization of Pydantic model classes (ModelMetaclass) using schema-based serialization.

However, a similar issue appears to persist for function objects, likely introduced by Flow internals (listeners, decorators, or execution context) during intermediate events.

This suggests that checkpointing currently attempts to serialize more than just the user-defined state.

extent analysis

TL;DR

Exclude callable objects from checkpoint serialization to resolve the PydanticSerializationError.

Guidance

  1. Identify and exclude callable objects: Review the Flow's internal state and exclude any callable objects (functions, methods, or callbacks) from the serialization process.
  2. Serialize only user-defined state: Modify the checkpointing configuration to serialize only the user-defined state, rather than the full internal state, to prevent serialization of Flow metadata.
  3. Implement custom serialization: Consider implementing custom serialization logic to handle specific types, such as functions, that are not serializable by default.
  4. Verify serialization: Test the modified checkpointing configuration to ensure that it correctly serializes the user-defined state and excludes callable objects.

Example

class FamilyFlow(Flow[FamilyState]):
    # ...

    def model_dump(self, mode="json"):
        # Custom serialization logic to exclude callable objects
        state_dict = self.state.dict()
        state_dict = {k: v for k, v in state_dict.items() if not callable(v)}
        return json.dumps(state_dict)

Notes

The provided code snippet is a simplified example and may require modifications to fit the specific use case. Additionally, the model_dump method may need to be overridden in the FamilyState class or a custom serialization logic implemented to handle specific types.

Recommendation

Apply a workaround by excluding callable objects from checkpoint serialization, as the root cause is related to the serialization of function objects. This approach will allow the checkpointing process to continue while a more permanent solution is developed.

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

Checkpointing should:

  • Successfully serialize the state to JSON
  • Or safely ignore non-serializable internal fields (e.g. functions, callbacks)

Still need to ship something?

×6

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

Back to top recommendations

TRENDING

crewai - ✅(Solved) Fix [BUG] Checkpoint serialization fails with <class 'function'> after #5544 fix [1 pull requests, 1 participants]