JuliusBrussee/caveman · error · TypeError

CrewAI requires a MiddlewareRuntime and a stable Scope per…

Error message

CrewAI requires a MiddlewareRuntime and a stable Scope per agent context

What it means

CavemanLLM's __init__ validates that the keyword arguments runtime and scope are instances of MiddlewareRuntime and Scope respectively. The middleware requires a runtime for interception and a stable per-agent Scope so recovery and usage can be keyed consistently; passing anything else raises this TypeError.

Solutions

  1. Construct runtime with the library's MiddlewareRuntime factory and pass that instance.
  2. Build a proper Scope object (one per agent context) rather than a string or dict.
  3. Check isinstance(runtime, MiddlewareRuntime) and isinstance(scope, Scope) before constructing the wrapper.

Example fix

// before
wrapped = CavemanLLM(llm, runtime="on", scope="agent-1")
// after
from caveman_cloud.middleware import MiddlewareRuntime, Scope
wrapped = CavemanLLM(llm, runtime=MiddlewareRuntime(...), scope=Scope(...))
Defensive patterns

Strategy: type-guard

Validate before calling

from caveman_cloud.middleware import MiddlewareRuntime, Scope
if not isinstance(runtime, MiddlewareRuntime) or not isinstance(scope, Scope):
    raise TypeError("Pass MiddlewareRuntime and Scope instances")

Type guard

def valid_runtime_scope(runtime, scope) -> bool:
    from caveman_cloud.middleware import MiddlewareRuntime, Scope
    return isinstance(runtime, MiddlewareRuntime) and isinstance(scope, Scope)

Try / catch

try:
    wrapped = CavemanLLM(llm, runtime=runtime, scope=scope)
except TypeError as e:
    if "MiddlewareRuntime" in str(e):
        raise ConfigError("Build runtime/scope with the caveman middleware factories") from e
    raise

Prevention

When it happens

Trigger: Calling CavemanLLM(delegate, runtime=<not a MiddlewareRuntime>, scope=<not a Scope>) — e.g. runtime as a plain config dict or string mode, scope as a string identifier, or either argument omitted/None.

Common situations: Hand-rolling the runtime instead of using the library's factory; passing a scope name string where a Scope object is required; reusing a single global scope across agents instead of a stable per-agent context; upgrading caveman-middleware and old positional/keyword shapes no longer match.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/bbe2af3e63c144e0. Report an issue: GitHub.

Appendix: source

Thrown at packages/middleware/python/caveman_middleware/crewai.py:113

    Register before creating the Agent, whose executor snapshots native hooks.
    ``close`` removes only this delegate's registrations. The application keeps
    ownership of the native provider client and the Caveman runtime.
    """
    llm_type: str = "caveman"
    delegate: BaseLLM = Field(exclude=True, repr=False)
    runtime: Any = Field(exclude=True, repr=False)
    scope: Scope
    closed: bool = Field(default=False, exclude=True)
    _context: Any = PrivateAttr()
    _hook: Any = PrivateAttr(default=None)
    _completed: Any = PrivateAttr(default=None)
    _recovery: Any = PrivateAttr(default=None)

    def __init__(self, delegate, *, runtime, scope):
        if not isinstance(delegate, BaseLLM):
            raise TypeError("Expected an installed CrewAI BaseLLM")
        if not isinstance(runtime, MiddlewareRuntime) or not isinstance(scope, Scope):
            raise TypeError("CrewAI requires a MiddlewareRuntime and a stable Scope per agent context")
        super().__init__(delegate=delegate, runtime=runtime, scope=scope, model=delegate.model,
                         provider=delegate.provider, stream=delegate.stream, stop=list(delegate.stop),
                         is_litellm=delegate.is_litellm)
        self._context = contextvars.ContextVar(f"caveman_crewai_context_{id(self)}", default=None)
        supported = matches_framework(("crewai", "1.15", "2"))
        if not supported and runtime.mode != "off":
            runtime.decline("unsupported_version")
        if not supported or runtime.mode == "off":
            return
        reference = weakref.ref(self)

        @on(InterceptionPoint.PRE_MODEL_CALL)
        def before(context):
            model = reference()
            if model is not None and not model.closed and context.llm is model:
                # Store only a weak executor reference. Aborted hooks and stale
                # executor snapshots cannot retain complete message histories.
                model._context.set(weakref.ref(context.executor) if context.executor is not None else None)

View on GitHub (pinned to 3ee70a1026)