JuliusBrussee/caveman · error · ValueError

Agno middleware requires a nonempty native session_id

Error message

Agno middleware requires a nonempty native session_id

What it means

scope_from_run derives a Caveman Scope from a native Agno Agent/Team run, and the session_id is the mandatory scope key for branching and recovery. If the run object has no session_id, or it is empty/non-string, scoping is impossible so it raises ValueError.

Solutions

  1. Ensure the Agent/Team run has a nonempty session_id: pass session_id="<id>" when creating/running it
  2. Generate a session id if your workflow has none (e.g. uuid4().hex) before invoking the adapter
  3. Check you are passing the run object that actually carries session_id
  4. Loosen the check only if you can supply an alternative scope key via a custom scope resolver returning a Scope

Example fix

# before
agent.run(input)  # run.session_id is None -> ValueError
# after
agent.run(input, session_id=str(uuid4()))
Defensive patterns

Strategy: validation

Validate before calling

def ensure_run_scopable(run):
    sid = getattr(run, "session_id", None)
    if not isinstance(sid, str) or not sid:
        raise ValueError("run must carry a nonempty session_id before adapter use")
    return sid

Type guard

def has_session(run) -> bool:
    sid = getattr(run, "session_id", None)
    return isinstance(sid, str) and bool(sid)

Try / catch

try:
    scope = scope_from_run(run, namespace="my-ns")
except ValueError as e:
    if "session_id" in str(e):
        run.session_id = str(uuid4())  # or recreate the run with one
        scope = scope_from_run(run, namespace="my-ns")
    else:
        raise

Prevention

When it happens

Trigger: Calling scope_from_run (or the adapter's state/_frame path that uses it) with a run object created without session_id, with session_id="", or with a mock/stub lacking the attribute.

Common situations: Running an Agno agent in ephemeral/no-session mode; constructing runs manually in tests without session_id; upgrading agno where session assignment became lazy; passing the wrong object (e.g. a Run result instead of the run config).

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


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

Appendix: source

Thrown at packages/middleware/python/caveman_middleware/agno.py:37

    from agno.tools.function import Function
except ModuleNotFoundError as error:
    raise ImportError("Install caveman-middleware[agno] to use the Agno adapter") from error

from caveman_cloud.middleware import Adapter, Candidate, MiddlewareError, MiddlewareRuntime, Scope
from caveman_cloud.middleware.runtime import RECOVERY_DESCRIPTION, RECOVERY_SCHEMA
from ._native import Attempt, manifest, owner, plain
from ._versions import matches_framework

ADAPTER = Adapter("agno", "0.1.0", "3.0.9", "agno-message-v1")
_SIGNATURES = {name: inspect.signature(getattr(Model, name)) for name in
               ("response", "aresponse", "response_stream", "aresponse_stream")}


def scope_from_run(run, *, namespace: str) -> Scope:
    """Resolve a native Agent or Team run's session, with explicit branch metadata."""
    session = getattr(run, "session_id", None)
    if not isinstance(session, str) or not session:
        raise ValueError("Agno middleware requires a nonempty native session_id")
    metadata = getattr(run, "metadata", None) or {}
    return Scope(namespace, session, metadata.get("caveman_branch_id", "main"), metadata.get("caveman_cache_epoch", "0"))


def _scope(source, run):
    result = source if isinstance(source, Scope) else source(run)
    if not isinstance(result, Scope):
        raise TypeError("Agno scope resolver must return a Caveman Scope")
    return result


def _message_view(messages):
    if type(messages) is not list or any(type(message) is not Message for message in messages):
        return None
    # Metrics/timers and checkpoint bookkeeping are not model input. All other
    # native values participate in lineage; opaque values bypass the whole view.
    excluded = {"metrics", "created_at", "from_history", "checkpoint_status", "checkpoint_created_at"}
    try:

View on GitHub (pinned to 3ee70a1026)