bytedance/deer-flow · error · ValueError

retrieval scope agentName must be a string or null

Error message

retrieval scope agentName must be a string or null

What it means

ValueError from _scope_key in deermem's FTS5 retrieval layer: scope dict's 'agentName' is present but not a str and not None. Same rationale as the userId check — scope values are JSON-encoded into the composite document key and must be strings or absent/null.

Source

Thrown at backend/packages/harness/deerflow/agents/memory/backends/deermem/deermem/core/retrieval.py:549

        }

    def close(self) -> None:
        with self._lock:
            self._conn.close()


def _scope_value(value: str | None) -> str:
    """Encode a typed scope value so ``None`` cannot collide with a user id."""
    return json.dumps(value, ensure_ascii=False, separators=(",", ":"))


def _scope_key(scope: dict[str, str | None]) -> tuple[str, str]:
    user_id = scope.get("userId")
    agent_name = scope.get("agentName")
    if user_id is not None and not isinstance(user_id, str):
        raise ValueError("retrieval scope userId must be a string or null")
    if agent_name is not None and not isinstance(agent_name, str):
        raise ValueError("retrieval scope agentName must be a string or null")
    return _scope_value(user_id), _scope_value(agent_name)


class FTS5RetrievalAdapter:
    """Scope-aware ``RetrievalPort`` adapter backed by one SQLite FTS5 DB.

    The index is derived data. Canonical facts remain in Markdown and storage
    notifications update only the addressed row. A deterministic composite
    document id prevents equal fact ids in two user/agent scopes from
    overwriting each other.
    """

    def __init__(self, db_path: str | Path = ":memory:") -> None:
        self._engine = FTS5Retrieval(db_path)

    @staticmethod
    def _document_id(fact_id: str, scope: dict[str, str | None]) -> str:
        scope_user, scope_agent = _scope_key(scope)

View on GitHub (pinned to 1dd6ba1acb)

Solutions

  1. Stringify agentName when building scopes: {"agentName": str(name)} or omit the key when not applicable.
  2. Centralize scope construction in one typed helper so userId/agentName are always str|None.
  3. Add a unit test asserting all scope producers emit str|None values.

Example fix

# before
scope = {"userId": "u1", "agentName": agent.id}  # int

# after
scope = {"userId": "u1", "agentName": str(agent.id)}
Defensive patterns

Strategy: type-guard

Validate before calling

scope = {'userId': str(user_id) if user_id is not None else None,
        'agentName': str(agent_name) if agent_name is not None else None}

Type guard

def is_valid_scope(scope) -> bool:
    return (
        isinstance(scope, dict)
        and (scope.get('userId') is None or isinstance(scope.get('userId'), str))
        and (scope.get('agentName') is None or isinstance(scope.get('agentName'), str))
    )

Prevention

When it happens

Trigger: Passing a scope like {"userId": "u1", "agentName": 3} or agentName as some object into FTS5RetrievalAdapter operations.

Common situations: Agent name taken from an enum/int field and not stringified; programmatic scope construction from untyped dict data (JSON with numeric value); inconsistent agentName typing between writer and reader paths causing index misses and, when malformed, this error.

Related errors


AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14). Data as JSON: /api/errors/6a71f9979e6027a9. Report an issue: GitHub.