{"record":{"id":"b8946a220404e625","repo":"JuliusBrussee/caveman","slug":"agno-scope-resolver-must-return-a-caveman-scope","errorCode":null,"errorMessage":"Agno scope resolver must return a Caveman Scope","messagePattern":"Agno scope resolver must return a Caveman Scope","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/middleware/python/caveman_middleware/agno.py","lineNumber":45,"sourceCode":"\nADAPTER = Adapter(\"agno\", \"0.1.0\", \"3.0.9\", \"agno-message-v1\")\n_SIGNATURES = {name: inspect.signature(getattr(Model, name)) for name in\n               (\"response\", \"aresponse\", \"response_stream\", \"aresponse_stream\")}\n\n\ndef scope_from_run(run, *, namespace: str) -> Scope:\n    \"\"\"Resolve a native Agent or Team run's session, with explicit branch metadata.\"\"\"\n    session = getattr(run, \"session_id\", None)\n    if not isinstance(session, str) or not session:\n        raise ValueError(\"Agno middleware requires a nonempty native session_id\")\n    metadata = getattr(run, \"metadata\", None) or {}\n    return Scope(namespace, session, metadata.get(\"caveman_branch_id\", \"main\"), metadata.get(\"caveman_cache_epoch\", \"0\"))\n\n\ndef _scope(source, run):\n    result = source if isinstance(source, Scope) else source(run)\n    if not isinstance(result, Scope):\n        raise TypeError(\"Agno scope resolver must return a Caveman Scope\")\n    return result\n\n\ndef _message_view(messages):\n    if type(messages) is not list or any(type(message) is not Message for message in messages):\n        return None\n    # Metrics/timers and checkpoint bookkeeping are not model input. All other\n    # native values participate in lineage; opaque values bypass the whole view.\n    excluded = {\"metrics\", \"created_at\", \"from_history\", \"checkpoint_status\", \"checkpoint_created_at\"}\n    try:\n        context = manifest([message.model_dump(mode=\"python\", exclude=excluded) for message in messages])\n    except (TypeError, ValueError):\n        return None\n    if context is None:\n        return None\n    names = {call[\"id\"]: call[\"function\"][\"name\"] for message in messages for call in (message.tool_calls or [])\n             if plain(call) and type(call.get(\"id\")) is str and plain(call.get(\"function\"))\n             and type(call[\"function\"].get(\"name\")) is str}","sourceCodeStart":27,"sourceCodeEnd":63,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/middleware/python/caveman_middleware/agno.py#L27-L63","documentation":"_scope normalizes the configured scope resolver: if it is not already a Scope it calls it with the run and requires the result to be a caveman Scope instance. Returning anything else (dict, tuple, None) is a programming error, so it raises TypeError.","triggerScenarios":"Passing a callable to the Agno middleware's scope option that returns a dict/tuple/string/None instead of a caveman_cloud Scope, or a resolver that returns None on some runs.","commonSituations":"Writing a custom session resolver after upgrading caveman-middleware and returning the old shape; confusing Scope with a plain (namespace, session) tuple; a resolver with early-return paths returning None.","solutions":["Return a Scope(namespace, session, branch_id, cache_epoch) from your resolver (import from caveman_cloud.middleware)","Or pass a Scope instance directly instead of a callable if the scope is static","Audit all return paths of the custom resolver so none return None/partial data","Add an isinstance(result, Scope) assertion in tests around your resolver"],"exampleFix":"# before\ndef resolver(run):\n    return run.session_id  # returns str -> TypeError\n# after\nfrom caveman_cloud.middleware import Scope\ndef resolver(run):\n    return Scope(\"my-ns\", run.session_id, \"main\", \"0\")","handlingStrategy":"validation","validationCode":"from caveman_cloud.middleware import Scope\ndef ensure_scope_result(resolver, run):\n    result = resolver if isinstance(resolver, Scope) else resolver(run)\n    if not isinstance(result, Scope):\n        raise TypeError(\"resolver must return a caveman_cloud Scope\")\n    return result","typeGuard":"def is_scope(obj) -> bool:\n    return isinstance(obj, Scope)","tryCatchPattern":"try:\n    scope = _scope(my_resolver, run)\nexcept TypeError as e:\n    if \"must return a Caveman Scope\" in str(e):\n        raise TypeError(f\"{my_resolver.__name__} must return Scope(namespace, session, branch, epoch)\") from e\n    raise","preventionTips":["Return Scope(...) from custom resolvers; never raw tuples/dicts/strings","Unit-test every return path of custom resolvers","Import Scope from caveman_cloud.middleware, not a hand-rolled class","Annotate resolvers with -> Scope so type checkers catch wrong returns"],"tags":["python","agno","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"}