JuliusBrussee/caveman · error · TypeError
Pydantic AI scope resolver must return a Caveman Scope
Error message
Pydantic AI scope resolver must return a Caveman Scope
What it means
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.
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
Example fix
// before
def my_scope(ctx):
return {"namespace": "app", "id": ctx.conversation_id}
// after
def my_scope(ctx):
return Scope("app", ctx.conversation_id, "main", "0") Defensive patterns
Strategy: validation
Validate before calling
from caveman_cloud.middleware import Scope result = my_scope_resolver(ctx) assert isinstance(result, Scope), "resolver must return a Caveman Scope"
Type guard
def is_caveman_scope(x) -> bool:
return isinstance(x, Scope) Try / catch
try:
model = CavemanModel(wrapped, runtime=rt, scope=my_scope_resolver)
except TypeError as e:
if "must return a Caveman Scope" in str(e):
my_scope_resolver = make_default_scope_resolver(namespace="app")
model = CavemanModel(wrapped, runtime=rt, scope=my_scope_resolver)
else:
raise Prevention
- 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)
When it happens
Trigger: 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).
Common situations: 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.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- Agno scope resolver must return a Caveman Scope
- AutoGen requires a stable Caveman Scope for each agent or…
- Expected an existing native Pydantic AI Model
- LiteLLM scope must be a trusted Caveman Scope
- Pydantic AI middleware requires a native conversation_id
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/979e2f7b867b0457.
Report an issue: GitHub.
Appendix: source
Thrown at packages/middleware/python/caveman_middleware/pydantic_ai.py:47
from ._native import Attempt, manifest, owner
from ._versions import supports_framework
ADAPTER = Adapter("pydantic-ai", "0.1.0", "2.42.0", "pydantic-ai-message-v1")
def scope_from_run(ctx: RunContext, *, namespace: str) -> Scope:
"""Use native conversation identity plus application-owned branch metadata."""
if not isinstance(ctx.conversation_id, str) or not ctx.conversation_id:
raise ValueError("Pydantic AI middleware requires a native conversation_id")
metadata = ctx.metadata or {}
return Scope(namespace, ctx.conversation_id, metadata.get("caveman_branch_id", "main"),
metadata.get("caveman_cache_epoch", "0"))
def _scope(source, ctx=None):
scope = source if isinstance(source, Scope) else source(ctx)
if not isinstance(scope, Scope):
raise TypeError("Pydantic AI scope resolver must return a Caveman Scope")
return scope
def _runtime(runtime):
return runtime.as_async() if isinstance(runtime, MiddlewareRuntime) else runtime
def _check_version(runtime):
return supports_framework(runtime, ("pydantic-ai-slim", "2.42", "3"))
def _protocol(model):
# Capability-only integration wraps the model selected for this request.
# Routing containers and other providers stay opaque until separately tested.
while isinstance(model, WrapperModel):
model = model.wrapped
if type(model).__name__ == "OpenAIChatModel" and type(model).__module__ == "pydantic_ai.models.openai":
return "openai-chat"View on GitHub (pinned to 3ee70a1026)