JuliusBrussee/caveman · error · AttributeError
Caveman recovery tool is immutable
Error message
Caveman recovery tool is immutable
What it means
_FrozenFunctionTool is a FunctionTool subclass frozen after construction; assigning any attribute on it raises AttributeError with this message so the caveman recovery tool cannot be altered once registered.
Solutions
- Do not mutate the recovery tool; wrap or replace it with your own FunctionTool if customization is needed
- Create a new FunctionTool.from_defaults with the desired settings and use that instead
- Bypass with object.__setattr__ only as a deliberate last resort
Example fix
// before caveman_tool.fn = my_wrapper(caveman_tool.fn) // after wrapped_tool = FunctionTool.from_defaults(async_fn=my_wrapper(original_fn), name="caveman_retrieve")
Defensive patterns
Strategy: validation
Validate before calling
if getattr(tool, "_caveman_frozen", False):
raise RuntimeError("cannot mutate caveman recovery tool; build a new FunctionTool instead") Type guard
def is_frozen_tool(tool) -> bool:
return getattr(tool, "_caveman_frozen", False) Prevention
- Never mutate registered tools; construct replacements instead
- Keep the caveman_retrieve tool out of your own customization passes
- Snapshot metadata you need at setup time
When it happens
Trigger: Setting any attribute on the caveman_retrieve FunctionTool instance after creation, e.g. tool.metadata = ... or tool.custom_flag = True.
Common situations: Framework or user code that renames tools, decorates them, or mutates metadata of all tools in a registry before dispatch.
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
- Caveman recovery metadata is immutable
- Caveman delegates through public chat_with_tools methods
- Expected a native ToolSelection
- Expected an existing native LlamaIndex LLM
- Expected distinct native tools without caveman_retrieve
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/735954b790dae352.
Report an issue: GitHub.
Appendix: source
Thrown at packages/middleware/python/caveman_middleware/llama_index.py:106
class _FrozenRecoveryMetadata(_RecoveryMetadata):
def __init__(self, **kwargs):
super().__init__(**kwargs)
object.__setattr__(self, "_caveman_frozen", True)
def __setattr__(self, name, value):
if getattr(self, "_caveman_frozen", False):
raise AttributeError("Caveman recovery metadata is immutable")
super().__setattr__(name, value)
class _FrozenFunctionTool(FunctionTool):
def __init__(self, **kwargs):
super().__init__(**kwargs)
object.__setattr__(self, "_caveman_frozen", True)
def __setattr__(self, name, value):
if getattr(self, "_caveman_frozen", False):
raise AttributeError("Caveman recovery tool is immutable")
super().__setattr__(name, value)
class _Recovery:
def __init__(self, runtime, scope):
self.runtime, self.scope = runtime, scope
def recover(ctx: Context, handle: str, offset: int = 0, limit: int = 262144, query: str = ""):
page = runtime.retrieve(_scope(scope, ctx), handle=handle, offset=offset, limit=limit, query=query)
return json.dumps(page, ensure_ascii=False)
async def arecover(ctx: Context, handle: str, offset: int = 0, limit: int = 262144, query: str = ""):
page = await _async_runtime(runtime).retrieve(_scope(scope, ctx), handle=handle, offset=offset, limit=limit, query=query)
return json.dumps(page, ensure_ascii=False)
# FunctionTool inspects runtime annotations for its public context injection.
recover.__annotations__["ctx"] = Context
arecover.__annotations__["ctx"] = ContextView on GitHub (pinned to 3ee70a1026)