{"record":{"id":"065bab14e89d5495","repo":"JuliusBrussee/caveman","slug":"llamaindex-scope-resolver-must-return-a-caveman-scope","errorCode":null,"errorMessage":"LlamaIndex scope resolver must return a Caveman Scope","messagePattern":"LlamaIndex scope resolver must return a Caveman Scope","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/middleware/python/caveman_middleware/llama_index.py","lineNumber":46,"sourceCode":"\nfrom caveman_cloud.middleware import Adapter, Candidate, MiddlewareError, MiddlewareRuntime, RecoveryBinding, Scope\nfrom caveman_cloud.middleware.runtime import RECOVERY_DESCRIPTION, RECOVERY_SCHEMA\nfrom ._native import Attempt, manifest, owner\nfrom ._usage import usage\nfrom ._versions import matches_framework, supports_framework\n\nADAPTER = Adapter(\"llama-index\", \"0.1.0\", \"0.14.24\", \"llama-index-message-v1\")\nRAG_ADAPTER = Adapter(\"llama-index-rag\", \"0.1.0\", \"0.14.24\", \"llama-index-node-v1\")\n\n\ndef _check_version(runtime):\n    return supports_framework(runtime, (\"llama-index-core\", \"0.14\", \"0.15\"))\n\n\ndef _scope(source, context=None):\n    result = source if isinstance(source, Scope) else source(context)\n    if not isinstance(result, Scope):\n        raise TypeError(\"LlamaIndex scope resolver must return a Caveman Scope\")\n    return result\n\n\ndef _async_runtime(runtime):\n    return runtime.as_async() if isinstance(runtime, MiddlewareRuntime) else runtime\n\n\ndef _protocol(model, runtime):\n    provider = (type(model).__module__, type(model).__name__)\n    supported = {\n        (\"llama_index.llms.openai.base\", \"OpenAI\"): (\"llama-index-llms-openai\", \"0.8\", \"1\", \"openai-chat\"),\n        (\"llama_index.llms.anthropic.base\", \"Anthropic\"): (\"llama-index-llms-anthropic\", \"0.12\", \"1\", \"anthropic-messages\"),\n    }\n    match = supported.get(provider)\n    if match and not supports_framework(runtime, match[:3]):\n        return None\n    if match:\n        sdk = (\"openai\", \"2.54\", \"4\") if match[3] == \"openai-chat\" else (\"anthropic\", \"0.125\", \"2\")","sourceCodeStart":28,"sourceCodeEnd":64,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/middleware/python/caveman_middleware/llama_index.py#L28-L64","documentation":"_scope accepts either a Scope instance or a resolver callable invoked with the workflow Context; it then validates the result. If the callable returns anything that is not a caveman_cloud.middleware.Scope (None, dict, str, framework object), it raises TypeError — recovery and tool gating need a real Scope to attribute calls.","triggerScenarios":"Passing a scope resolver function whose return value is not a Scope — e.g. returning None on a cache miss, returning a dict of user claims, or returning a string session id — to recover/arecover/_options/take_step or the reader path.","commonSituations":"Resolver written before the Scope class existed and returning legacy dict context; early-return None when workflow context is empty; returning result.value or context metadata instead of a constructed Scope; duplicate caveman-cloud installs causing a different Scope class.","solutions":["Make the resolver return caveman_cloud.middleware.Scope(...) in every branch, including error/empty paths","If a miss is possible, raise or substitute a default Scope instead of returning None","Convert framework context data into a Scope explicitly before returning it","Check for duplicate caveman-cloud versions (pip show caveman-cloud) so isinstance matches one class"],"exampleFix":"// before\ndef resolve(ctx):\n    return ctx.store.get(\"session\")  # may be a dict or None\n\n// after\nfrom caveman_cloud.middleware import Scope\ndef resolve(ctx):\n    session = ctx.store.get(\"session\")\n    return Scope(subject=session[\"user\"], call_type=\"agent_step\") if session else Scope.anonymous()","handlingStrategy":"type-guard","validationCode":"result = resolver(context) if not isinstance(resolver, Scope) else resolver\nif not isinstance(result, Scope):\n    raise TypeError(\"scope resolver must return caveman_cloud.middleware.Scope\")","typeGuard":"def resolves_to_scope(resolver, context=None) -> bool:\n    from caveman_cloud.middleware import Scope\n    candidate = resolver if isinstance(resolver, Scope) else resolver(context)\n    return isinstance(candidate, Scope)","tryCatchPattern":"try:\n    scope = adapter_step(scope_resolver, context)\nexcept TypeError as e:\n    if \"must return a Caveman Scope\" in str(e):\n        scope = default_scope()  # construct a valid Scope and continue/degrade","preventionTips":["Have resolvers return a Scope in every branch; never return None on misses","Convert dicts/user claims into Scope explicitly at the resolver boundary","Unit-test resolvers asserting isinstance(result, Scope)","Keep one caveman-cloud version installed so the Scope class identity matches"],"tags":["python","type-error","scope-resolver","llama-index"],"backgroundTag":"type-mismatch","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}