{"record":{"id":"f2118a9f5a288c25","repo":"JuliusBrussee/caveman","slug":"synchronous-agno-recovery-requires-middlewareruntime","errorCode":null,"errorMessage":"Synchronous Agno recovery requires MiddlewareRuntime","messagePattern":"Synchronous Agno recovery requires MiddlewareRuntime","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/middleware/python/caveman_middleware/agno.py","lineNumber":147,"sourceCode":"    def __init__(self, runtime, scope):\n        self.sync = runtime if isinstance(runtime, MiddlewareRuntime) else None\n        self.async_runtime = runtime.as_async() if self.sync else runtime\n        self.scope, self.recovery_tool = scope, None\n        self.active = contextvars.ContextVar(\"caveman_agno_run\", default=None)\n        self.version_supported = matches_framework((\"agno\", \"3.0\", \"4\"))\n        if not self.version_supported and runtime.mode != \"off\":\n            runtime.decline(\"unsupported_version\")\n\n    def passive(self, runtime, reason):\n        return Attempt(runtime, None, str(uuid.uuid4()), str(uuid.uuid4()), passive=True, reason=reason, adapter=ADAPTER.id), {}, None, None\n\n    def register(self):\n        def recover(handle: str, run_context: RunContext, offset: int = 0, limit: int = 262144, query: str = \"\"):\n            frame = self.active.get()\n            if frame is None:\n                raise MiddlewareError(\"recovery_unavailable\")\n            if self.sync is None:\n                raise TypeError(\"Synchronous Agno recovery requires MiddlewareRuntime\")\n            if run_context is not None:\n                raise_if_cancelled(run_context.run_id)\n            return json.dumps(self.sync.retrieve(frame.scope, handle=handle, offset=offset, limit=limit, query=query),\n                              ensure_ascii=False, separators=(\",\", \":\"))\n\n        async def arecover(handle: str, run_context: RunContext, offset: int = 0, limit: int = 262144, query: str = \"\"):\n            frame = self.active.get()\n            if frame is None:\n                raise MiddlewareError(\"recovery_unavailable\")\n            if run_context is not None:\n                await araise_if_cancelled(run_context.run_id)\n            return json.dumps(await self.async_runtime.retrieve(frame.scope, handle=handle, offset=offset, limit=limit, query=query),\n                              ensure_ascii=False, separators=(\",\", \":\"))\n\n        # Agno preserves a Function's entrypoint identity in its per-run copy.\n        # Explicit processing avoids schema rewriting and keeps RunContext hidden.\n        self.recovery_tool = Function(name=\"caveman_retrieve\", description=RECOVERY_DESCRIPTION,\n            parameters=copy.deepcopy(RECOVERY_SCHEMA), entrypoint=recover, skip_entrypoint_processing=True)","sourceCodeStart":129,"sourceCodeEnd":165,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/middleware/python/caveman_middleware/agno.py#L129-L165","documentation":"The synchronous Agno recover callback delegates retrieval to a synchronous MiddlewareRuntime. If the adapter was constructed only with an async runtime, `self.sync` is None and synchronous recovery cannot proceed, so a TypeError is raised.","triggerScenarios":"Registering the adapter with only `async_runtime` (or a MiddlewareRuntime lacking a sync counterpart) and then calling the sync `recover(handle, run_context, ...)` function.","commonSituations":"An async-only application (e.g. FastAPI/Agno async agents) that later invokes the sync recover path from a tool or CLI script.","solutions":["Pass a synchronous MiddlewareRuntime when constructing the adapter so self.sync is set","Call the async `arecover` variant instead of sync `recover`","Wrap the sync call path to construct/reuse a MiddlewareRuntime"],"exampleFix":"# before\nadapter = AgnoMiddleware(async_runtime=AsyncMiddlewareRuntime(...))\nrecover(handle, run_context)  # TypeError\n# after\nfrom caveman_cloud.middleware import MiddlewareRuntime\nadapter = AgnoMiddleware(async_runtime=AsyncMiddlewareRuntime(...), sync=MiddlewareRuntime(...))\nrecover(handle, run_context)","handlingStrategy":"type-guard","validationCode":"if adapter_sync_runtime is None:\n    raise RuntimeError('sync recovery needs a MiddlewareRuntime')","typeGuard":"from caveman_cloud.middleware import MiddlewareRuntime\ndef supports_sync_recover(adapter) -> bool:\n    return adapter.sync is not None and isinstance(adapter.sync, MiddlewareRuntime)","tryCatchPattern":"try:\n    payload = recover(handle, run_context)\nexcept TypeError:\n    payload = await arecover(handle, run_context)  # async fallback","preventionTips":["Configure both sync and async runtimes when using mixed call paths","Prefer arecover in async codebases","Add a startup assertion that sync runtime is present if sync paths are used"],"tags":["python","middleware","sync-async","typeerror"],"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"}