JuliusBrussee/caveman · error · ValueError
Rebind CavemanLLM from the original provider configuration…
Error message
Rebind CavemanLLM from the original provider configuration and trusted runtime; active middleware is not serializable
What it means
to_config_dict serializes the LLM configuration (e.g. for CrewAI's config export/caching). An active CavemanLLM wraps a live runtime, bindings, and contextvars that cannot be serialized safely, so the method unconditionally raises ValueError directing you to re-create the wrapper from the original provider configuration instead.
Solutions
- Serialize the ORIGINAL delegate LLM's config (llm.delegate.to_config_dict()) instead of the wrapper.
- Keep the original provider configuration (model, api key env, params) and reconstruct CavemanLLM(delegate, runtime, scope) after deserialization.
- Avoid exporting config from wrapped LLMs; wrap at load time, not at save time.
Example fix
// before cfg = caveman_llm.to_config_dict() // after cfg = caveman_llm.delegate.to_config_dict() # later: caveman_llm = CavemanLLM(LLM(**cfg), runtime=runtime, scope=scope)
Defensive patterns
Strategy: try-catch
Validate before calling
if hasattr(llm, "delegate"):
cfg = llm.delegate.to_config_dict()
else:
cfg = llm.to_config_dict() Type guard
def serializable_llm(llm) -> bool:
return not hasattr(llm, "delegate") Try / catch
try:
cfg = llm.to_config_dict()
except ValueError:
cfg = llm.delegate.to_config_dict() # serialize the native LLM instead Prevention
- Serialize the original provider LLM, then re-wrap after loading.
- Never pickle/export crews containing CavemanLLM instances.
- Keep the original provider config (model, params) alongside saved crews for re-wrapping.
- Guard any config-export code path with a hasattr(llm, 'delegate') check.
When it happens
Trigger: Calling to_config_dict() on a CavemanLLM instance — e.g. CrewAI (or user code) exporting agent/LLM config to a dict, saving crews, or round-tripping configuration through a cache.
Common situations: Persisting a configured crew to disk/JSON; CrewAI's internal config export paths encountering the wrapped LLM; attempting to pickle/serialize a crew containing the middleware-wrapped LLM.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- MCP changes require the ownership transaction
- assembly slot content is not JSON-serializable
- assembly slot content is not JSON-serializable
- cannot safely launch non-Node Windows command shim
- cave_claude_subagent_bridge_unavailable
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/9ad0597bd5189f99.
Report an issue: GitHub.
Appendix: source
Thrown at packages/middleware/python/caveman_middleware/crewai.py:185
return self.delegate.supports_stop_words()
def get_context_window_size(self):
return self.delegate.get_context_window_size()
def supports_multimodal(self):
return self.delegate.supports_multimodal()
def format_text_content(self, text):
return self.delegate.format_text_content(text)
def get_file_uploader(self):
return self.delegate.get_file_uploader()
def get_token_usage_summary(self):
return self.delegate.get_token_usage_summary()
def to_config_dict(self):
raise ValueError("Rebind CavemanLLM from the original provider configuration and trusted runtime; active middleware is not serializable")
def _options(self, messages, tools, available_functions, from_task, from_agent, response_model):
reference = self._context.get()
self._context.set(None)
executor = reference() if reference is not None else None
if owner.get() is not None:
return None
reason = ("closed" if self.closed else "off" if self.runtime.mode == "off" else
"unsupported_version" if not matches_framework(("crewai", "1.15", "2")) else
"unsupported_shape" if type(messages) is not list else None)
if reason:
return Attempt(self.runtime, self.scope, str(uuid.uuid4()), str(uuid.uuid4()),
passive=True, reason=reason, adapter="crewai"), {}, None
context = manifest(messages)
if context is None:
return Attempt(self.runtime, self.scope, str(uuid.uuid4()), str(uuid.uuid4()),
passive=True, reason="unsupported_shape", adapter="crewai"), {}, None
bound = (executor is not None and executor.llm is self and executor.messages is messagesView on GitHub (pinned to 3ee70a1026)