CrewAI memory + checkpointing: fixing 'Unable to serialize unknown type: ModelMetaclass' (no checkpoint written)
Turn on Memory and checkpointing together and every task_completed checkpoint silently fails to write. Here's why, how to catch it, and a small patch until the fix ships.
Disclosure: I work on MemorySync (I'm Mohammed, the founder). I drafted this article with AI assistance and checked it against the GitHub issue, the open fix PR, the CrewAI docs, the CrewAI source on PyPI and the MemorySync docs listed under Sources on Oct 31, 2026. Code status: I ran the issue's own offline reproduction (scripted LLM and embedding responses, outbound connections blocked, no API keys) on crewai 1.15.28 and 1.15.33 with pydantic 2.12.5 on Python 3.12.15, then the same script with the workaround patch below, the CheckpointFailedEvent listener below, and a dump-and-restore check that mirrors the PR's test. I didn't run anything against a real model or embedder, the PR branch itself, a SQLite checkpoint provider, a hierarchical crew, or the MemorySync snippet.
You turned on CrewAI's built-in Memory and checkpointing on the same crew. The crew runs fine, but no checkpoint file shows up, and your logs have this after every finished task:
WARNING:crewai.state.checkpoint_listener:Auto-checkpoint failed for event task_completed
...
pydantic_core._pydantic_core.PydanticSerializationError: Unable to serialize unknown type: <class 'pydantic._internal._model_construction.ModelMetaclass'>
That's CrewAI issue #8318, "Memory-enabled checkpoints fail because RecallFlow redeclares initial_state without the base Flow serializer", reported on crewai 1.15.28.
Short version: there's no released fix yet. The fix is in an open PR, #8327. Until it ships, you can apply the same one-field change at startup (Step 3), or run checkpointed crews without Memory. Step 2 shows how to make the failure visible so it doesn't go unnoticed.
MemorySync doesn't fix this bug. The cause is in CrewAI's own memory flows, and plugging a different storage backend into Memory doesn't change that (more below). MemorySync comes up once, near the end.
Skip the memory plumbing. The MemorySync CrewAI guide shows a storage backend for CrewAI 1.x
Memory, plus agent tools and a context helper. It doesn't fix this checkpoint bug. When you're ready, see MemorySync. Founding offer: use code FOUNDING30 for 30% off Starter for your first 3 months (Settings → Billing).
Status (Nov 13, 2026)
- #8318 was opened Oct 20, 2026 (19:15 IST), is labeled
bug, and is still open with no comments as of Nov 13, 2026. - #8327, "fix(memory): keep initial_state json serializer on memory flows", was opened the same day and says "Fixes #8318". As of Nov 13, 2026 it's still open and not merged.
- The latest release on PyPI is crewai 1.15.41 (published Nov 13, 2026, 00:12 IST). I checked its source and
RecallFlow/EncodingFlowincrewai/memory/still redeclareinitial_statewithout the serializer (a source read, not a run). I ran the reproduction on 1.15.28 and 1.15.33 only.
Step 1: Confirm it's this bug
You're looking at this bug if all of these are true:
- The crew has CrewAI's built-in
Memory(for examplememory=Memory(...)on theCrew). - Checkpointing is on (
checkpoint=Trueor aCheckpointConfig), with the defaulttask_completedtrigger or another automatic event. - Your checkpoint folder stays empty (or has no new files), and the log shows
Auto-checkpoint failedwithUnable to serialize unknown typeandModelMetaclass.
Why it happens, per the issue and the PR: CrewAI memory runs two internal flows, RecallFlow (for recall) and EncodingFlow (for saving). Both redeclare the initial_state field as a plain class reference. That replaces the base Flow field, which carries a JSON serializer added in PR #5600 to turn that class into something JSON can hold. Without the serializer, the checkpoint's JSON dump reaches a raw Pydantic model class and stops.
From the installed 1.15.33 source, Memory.remember() always creates an EncodingFlow, and Memory.recall() creates a RecallFlow unless you pass depth="shallow". Agents call recall() with the default depth, so a crew with memory creates one of these flows almost immediately.
Here's what the issue's reproduction did in my runs. It completes the first task, deliberately fails the second, then tries to restore from the checkpoint with Crew.fork():
| crewai | Memory | Checkpoint files written | Restore with Crew.fork() |
|---|---|---|---|
| 1.15.28 | none (control) | 1 | restored; only the second task ran, with the first task's saved output in its prompt |
| 1.15.28 | Memory(...) |
0 | nothing to restore |
| 1.15.33 | none (control) | 1 | same as the 1.15.28 control |
| 1.15.33 | Memory(...) |
0 | nothing to restore |
The crew itself doesn't stop. CrewAI's checkpointing docs say automatic checkpoint writes behave this way on purpose: "a failed write is logged and the run continues." So the first time you notice may be when you try to resume and there's nothing to resume from.
Step 2: Make the failure visible
A failed automatic write emits a CheckpointFailedEvent that carries the error text. Register a listener once at startup:
from crewai.events.event_bus import crewai_event_bus
from crewai.events.types.checkpoint_events import CheckpointFailedEvent
@crewai_event_bus.on(CheckpointFailedEvent)
def on_checkpoint_failed(source, event):
print("checkpoint failed:", event.trigger, event.error)
On 1.15.33 without the patch, this printed:
checkpoint failed: task_completed Unable to serialize unknown type: <class 'pydantic._internal._model_construction.ModelMetaclass'>
Swap the print for your own alerting. With the patch from Step 3 loaded, it printed nothing in my run.
Step 3: Workaround until the fix ships
The PR changes one field on each of the two memory flows so it keeps the base serializer. You can make the same change at runtime. Save this as memory_checkpoint_patch.py and import it once at startup, before you build any crew, in every process that writes or restores checkpoints:
# Workaround for crewAI issue #8318 until PR #8327 ships.
from typing import Any
from pydantic import PlainSerializer
from crewai.flow.runtime import _serialize_initial_state # private helper
from crewai.memory.encoding_flow import EncodingFlow
from crewai.memory.recall_flow import RecallFlow
for flow_cls in (RecallFlow, EncodingFlow):
field = flow_cls.model_fields["initial_state"]
if not any(isinstance(m, PlainSerializer) for m in field.metadata):
field.metadata.append(
PlainSerializer(_serialize_initial_state, return_type=Any, when_used="json")
)
flow_cls.model_rebuild(force=True)
What it did in my runs, on both 1.15.28 and 1.15.33:
| Check | Without the patch | With the patch |
|---|---|---|
| Issue's reproduction, memory case: checkpoint files written | 0 | 1 |
Restore with Crew.fork() |
nothing to restore | restored; only the second task ran, with the first task's saved output in its prompt |
Auto-checkpoint failed lines in the log |
1 | 0 |
Dump a runtime state holding a RecallFlow, then an EncodingFlow, to JSON and restore it (the PR's test) |
both dumps fail with the error | both dump and restore |
Things to know before you use it:
- It relies on a private helper,
_serialize_initial_state, and on CrewAI's internal flow classes. A CrewAI update can rename or move them, so pin your crewai version while the patch is in place and re-run your checkpoint check after each upgrade. - It's the PR's change, applied from outside. I didn't install the PR branch; the patch adds the same serializer the PR adds.
- Remove it once a release contains the fix. The
if not any(...)guard skips fields that already have a serializer, but the cleaner path is to delete the import after upgrading. - My end-to-end run used read-only memory, as the issue's script does, so only
RecallFlowran during the crew.EncodingFlow(memory saves) was covered by the dump-and-restore check only.
Or run checkpointed crews without Memory. In the same reproduction, the crew without memory wrote its checkpoint and restored correctly on both versions. That's a real trade-off: you lose CrewAI's built-in recall for that crew.
Step 4: Check that checkpoints are written again
After adding the patch (or upgrading to a release that includes PR #8327), run one short crew with memory and checkpointing on, then:
- Confirm a new file appears in your checkpoint location (
./.checkpoints/by default). - Confirm the Step 2 listener printed nothing and there's no
Auto-checkpoint failedline in the log. - Restore from that file the way you do in production (
Crew.from_checkpoint(...),crew.kickoff(from_checkpoint=...)orCrew.fork(...)) and check that completed tasks are skipped. The checkpointing docs describe the expected behavior: "Completed tasks are skipped, memory and knowledge are rehydrated".
Skip the plumbing with MemorySync
Disclosure: I build MemorySync.
This doesn't fix the checkpoint bug. The MemorySync CrewAI guide documents MemorySyncStorage, a CrewAI StorageBackend you pass to Memory(storage=...) so "the crew's automatic save/recall loop runs against MemorySync instead of local LanceDB files." Because it plugs into CrewAI's own Memory, recall and saves still go through RecallFlow and EncodingFlow. From my reading of the CrewAI source (not a run), the same checkpoint failure applies, and you'd still need the Step 3 patch or a fixed CrewAI release. What it changes is where the memory records live: per the guide, they "survive redeploys" and are shared by every service using the same user_id.
From the guide:
Unverified (not run):
pip install crewai-memorysync
from crewai import Crew
from crewai.memory import Memory
from crewai_memorysync import MemorySyncStorage
crew = Crew(
agents=[researcher, writer],
tasks=[research_task, write_task],
memory=Memory(
storage=MemorySyncStorage(user_id="support-crew"),
),
)
crew.kickoff()
The guide says it needs crewai 1.0+ and Python 3.10+, and MEMORYSYNC_API_KEY in the environment. Snippet adapted from the MemorySync CrewAI guide; I haven't run it for this page. For a longer walkthrough, see Persistent memory for CrewAI 1.x.
Try it: MemorySync is optional here: the steps above work without it. If you want the memory layer from the snippet, see MemorySync, create an API key in the dashboard and drop the snippet into your agent.
Troubleshooting
- Still no checkpoint file with the patch loaded. Make sure the import runs before the crew is built, in the same process. Then read the
CheckpointFailedEventerror: if it no longer mentionsModelMetaclass, it's a different checkpoint problem. ImportErroron_serialize_initial_state. Your CrewAI version has moved or renamed the private helper. Check whether that release already includes the fix (Step 4) before adapting the patch.- You call
state.checkpoint()yourself. The checkpointing docs say manualstate.checkpoint()andstate.acheckpoint()calls "re-raise on failure", so with memory on you'd see this error raised instead of logged. I didn't run the manual path. Please provide an OpenAI API keywhen memory starts. That's a different error about the default embedder; see CrewAI memory=True: "Please provide an OpenAI API key".
Common questions
Did the crew's results or memory get lost? Not by this bug, in my runs: the tasks ran and the crew continued. What's missing is the checkpoint, so a crash or interruption can't resume from where it stopped.
Is it only the task_completed trigger?
The failure is in serializing the memory flows that are part of the run's state, so any automatic trigger that writes that state should hit it once a memory flow exists. I only tested the default task_completed trigger with the JSON provider.
Does depth="shallow" avoid it?
It avoids creating a RecallFlow for your own recall() calls, but agents use the default depth, and every save creates an EncodingFlow. I didn't test a shallow-only setup end to end.
Will the patch break restoring old checkpoints? In my runs, checkpoints from the crew without memory restored the same with and without the patch, and a state written with the patch also loaded in a process without it. I didn't test checkpoints written by other CrewAI versions.
Related pages
- CrewAI memory=True: "Please provide an OpenAI API key" when you're not using OpenAI (embedder fix)
- Persistent memory for CrewAI 1.x: keep crew memory across runs and redeploys
- LangGraph memory explained: checkpointer vs store, and per-user memory across threads
Sources
- github.com/crewAIInc/crewAI/issues/8318 (opened 2026-10-20, open, labeled bug, 0 comments; exact error, offline reproduction script, cause; read via the GitHub API 2026-10-31, status re-checked 2026-11-13)
- github.com/crewAIInc/crewAI/pull/8327 (opened 2026-10-20, open, not merged; diff for
recall_flow.pyandencoding_flow.pyand the added round-trip test; read 2026-10-31, still open and not merged 2026-11-13) - github.com/crewAIInc/crewAI/pull/5600 (base
Flowinitial_stateserializer, referenced by the issue and the PR) - PyPI: crewai 1.15.28 and 1.15.33 (release times; installed source of
crewai/memory/recall_flow.py,encoding_flow.py,unified_memory.py,flow/runtime/__init__.py,state/checkpoint_listener.pyandevents/types/checkpoint_events.py; 2026-10-31); crewai 1.15.41 (release time; source ofcrewai/memory/recall_flow.py,encoding_flow.pyandflow/runtime/__init__.py, read, not run; 2026-11-13) - CrewAI docs: Checkpointing (fetched 2026-10-31); Memory concepts, docs.crewai.com/en/concepts/memory (fetched 2026-10-31)
- Offline runs (2026-10-31): the issue's reproduction on crewai 1.15.28 and 1.15.33 with and without the patch; the
CheckpointFailedEventlistener on 1.15.33; a JSON dump-and-restore check ofRecallFlowandEncodingFlowmirroring the PR's test; all with scripted model and embedding responses and no network - MemorySync CrewAI guide: docs.memorysync.io/guides/crewai (fetched 2026-10-31)
Try it
For an overview of MemorySync, see the MemorySync homepage.
Next step: read the MemorySync CrewAI guide, then see MemorySync. Founding offer: use code FOUNDING30 for 30% off Starter for your first 3 months (Settings → Billing).
About the author
- Building MemorySync (memorysync.io) — persistent memory infrastructure for AI agents, Cursor, and Claude Code.
Comments
Join the discussionNo comments yet. Be the first to comment.
