{"record":{"id":"979e2f7b867b0457","repo":"JuliusBrussee/caveman","slug":"pydantic-ai-scope-resolver-must-return-a-caveman-scope","errorCode":null,"errorMessage":"Pydantic AI scope resolver must return a Caveman Scope","messagePattern":"Pydantic AI scope resolver must return a Caveman Scope","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/middleware/python/caveman_middleware/pydantic_ai.py","lineNumber":47,"sourceCode":"from ._native import Attempt, manifest, owner\nfrom ._versions import supports_framework\n\nADAPTER = Adapter(\"pydantic-ai\", \"0.1.0\", \"2.42.0\", \"pydantic-ai-message-v1\")\n\n\ndef scope_from_run(ctx: RunContext, *, namespace: str) -> Scope:\n    \"\"\"Use native conversation identity plus application-owned branch metadata.\"\"\"\n    if not isinstance(ctx.conversation_id, str) or not ctx.conversation_id:\n        raise ValueError(\"Pydantic AI middleware requires a native conversation_id\")\n    metadata = ctx.metadata or {}\n    return Scope(namespace, ctx.conversation_id, metadata.get(\"caveman_branch_id\", \"main\"),\n                 metadata.get(\"caveman_cache_epoch\", \"0\"))\n\n\ndef _scope(source, ctx=None):\n    scope = source if isinstance(source, Scope) else source(ctx)\n    if not isinstance(scope, Scope):\n        raise TypeError(\"Pydantic AI scope resolver must return a Caveman Scope\")\n    return scope\n\n\ndef _runtime(runtime):\n    return runtime.as_async() if isinstance(runtime, MiddlewareRuntime) else runtime\n\n\ndef _check_version(runtime):\n    return supports_framework(runtime, (\"pydantic-ai-slim\", \"2.42\", \"3\"))\n\n\ndef _protocol(model):\n    # Capability-only integration wraps the model selected for this request.\n    # Routing containers and other providers stay opaque until separately tested.\n    while isinstance(model, WrapperModel):\n        model = model.wrapped\n    if type(model).__name__ == \"OpenAIChatModel\" and type(model).__module__ == \"pydantic_ai.models.openai\":\n        return \"openai-chat\"","sourceCodeStart":29,"sourceCodeEnd":65,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/middleware/python/caveman_middleware/pydantic_ai.py#L29-L65","documentation":"The Pydantic AI adapter accepts either a Scope instance or a callable resolver that returns one. _scope validates the resolver's output; returning any other object (dict, tuple, None) means the middleware cannot address sessions, so a TypeError is raised.","triggerScenarios":"Passing a scope resolver callable to the Pydantic AI adapter that returns something other than caveman_cloud.middleware.Scope (e.g. a dict of namespace/conversation_id, or forgetting the return statement).","commonSituations":"Hand-rolled resolver functions written against an older adapter API that returned tuples; a resolver that conditionally returns None on cache miss; typos importing Scope so isinstance always fails.","solutions":["Make the resolver construct and return a Scope(namespace, conversation_id, branch_id, cache_epoch)","Ensure every code path in the resolver returns a Scope, including error paths","Verify Scope is imported from caveman_cloud.middleware, not a look-alike class"],"exampleFix":"// before\ndef my_scope(ctx):\n    return {\"namespace\": \"app\", \"id\": ctx.conversation_id}\n// after\ndef my_scope(ctx):\n    return Scope(\"app\", ctx.conversation_id, \"main\", \"0\")","handlingStrategy":"validation","validationCode":"from caveman_cloud.middleware import Scope\nresult = my_scope_resolver(ctx)\nassert isinstance(result, Scope), \"resolver must return a Caveman Scope\"","typeGuard":"def is_caveman_scope(x) -> bool:\n    return isinstance(x, Scope)","tryCatchPattern":"try:\n    model = CavemanModel(wrapped, runtime=rt, scope=my_scope_resolver)\nexcept TypeError as e:\n    if \"must return a Caveman Scope\" in str(e):\n        my_scope_resolver = make_default_scope_resolver(namespace=\"app\")\n        model = CavemanModel(wrapped, runtime=rt, scope=my_scope_resolver)\n    else:\n        raise","preventionTips":["Return Scope(...) explicitly from every resolver branch, never dicts or tuples","Never rely on implicit None returns from resolvers","Import Scope only from caveman_cloud.middleware","Unit-test resolvers by asserting isinstance(resolver(ctx), Scope)"],"tags":["python","pydantic-ai","scope","type-error"],"backgroundTag":"invalid-argument-value","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"}