JuliusBrussee/caveman · error · TypeError
Synchronous calls require MiddlewareRuntime
Error message
Synchronous calls require MiddlewareRuntime
What it means
Raised inside wrap_model_call in the LangChain middleware: a synchronous model call path was entered that requires MiddlewareRuntime, which is unavailable in this configuration. The middleware refuses to fake a synchronous compress/recover cycle without the engine-backed runtime.
Solutions
- Build the adapter/connection with a MiddlewareRuntime so sync is set.
- Run the agent asynchronously (ainvoke) if only an async runtime exists.
- Create separate adapter instances for sync and async runtimes rather than sharing one.
Example fix
// before middleware = CavemanLangChainMiddleware(_Connection(async_runtime, scope)) agent.invoke(input) # wrap_model_call -> TypeError // after middleware = CavemanLangChainMiddleware(_Connection(MiddlewareRuntime(...), scope)) agent.invoke(input)
Defensive patterns
Strategy: type-guard
Validate before calling
if connection.sync is None:
raise RuntimeError('wrap_model_call (sync) requires MiddlewareRuntime; register only the async middleware') Type guard
def has_sync_middleware(conn): return getattr(conn, 'sync', None) is not None
Try / catch
try:
out = agent.invoke(input)
except TypeError as e:
if 'Synchronous calls require MiddlewareRuntime' in str(e):
raise RuntimeError('Use ainvoke with your async runtime, or build the adapter with MiddlewareRuntime') from e
raise Prevention
- Register sync middleware on sync agents and async middleware on async agents; never share one connection.
- Validate at startup that connection.sync exists for every sync agent in the app.
- Document which adapter constructors install sync vs async hooks.
When it happens
Trigger: Registering the middleware as a synchronous LangChain agent middleware while the connection was created with only an AsyncMiddlewareRuntime (sync=None).
Common situations: Using LangGraph's sync AgentExecutor/stream with an adapter built for async; the same middleware object attached to both sync and async agents but configured with one runtime.
Understand the failure class
Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.
Related errors
- Synchronous document compression requires MiddlewareRuntime
- Synchronous LangChain calls require MiddlewareRuntime
- Synchronous recovery requires MiddlewareRuntime
- ASGI context must come from authenticated server state
- Expected a native LangChain BaseChatModel
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/88fdaa9c83c573bf.
Report an issue: GitHub.
Appendix: source
Thrown at packages/middleware/python/caveman_middleware/langchain.py:215
except (TypeError, ValueError, AttributeError, RecursionError):
return False
def binding(self, request, runtime):
if self.recovery_tool is None or runtime.mode != "compress" or owner.get() is not None:
return None
actual = [tool for tool in request.tools if getattr(tool, "name", None) == "caveman_retrieve" or (plain(tool) and tool.get("name") == "caveman_retrieve")]
if len(actual) != 1 or actual[0] is not self.recovery_tool or not self._recovery_intact() or request.response_format is not None or request.tool_choice not in (None, "auto"):
return None
return runtime.recovery(_scope(self.connection.scope))
@property
def overhead(self):
return json.dumps({"name": "caveman_retrieve", "description": RECOVERY_DESCRIPTION, "input_schema": RECOVERY_SCHEMA}, ensure_ascii=False, separators=(",", ":"))
def wrap_model_call(self, request, handler):
runtime = self.connection.sync
if runtime is None:
raise TypeError("Synchronous calls require MiddlewareRuntime")
binding = self.binding(request, runtime)
messages, attempt = self.connection.prepare(request.messages, binding=binding, overhead=self.overhead if binding else None, prefix=[request.system_message] if request.system_message else [])
if attempt is None:
return handler(request)
attempt.observe("dispatch_intent")
token = owner.set(attempt)
try:
response = handler(request.override(messages=messages))
_observe_messages(attempt, response)
return response
except BaseException:
attempt.observe("failed")
raise
finally:
owner.reset(token)
async def awrap_model_call(self, request, handler):
runtime = self.connection.async_runtimeView on GitHub (pinned to 3ee70a1026)