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
- Stringify agentName when building scopes: {"agentName": str(name)} or omit the key when not applicable.
- Centralize scope construction in one typed helper so userId/agentName are always str|None.
- 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
- Canonicalize agentName to str|None in one place.
- Use TypedDict/Pydantic models for scope dicts so mypy catches int leakage.
- Never mix enum instances into scope dicts without str() conversion.
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
- retrieval scope userId must be a string or null
- retrieval fact.id must be a non-empty string
- retrieval fact.content must be a non-empty string
- guaranteed_categories must be an iterable of strings, not a
- unsupported FTS5 retrieval mode: {mode}
AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14).
Data as JSON: /api/errors/6a71f9979e6027a9.
Report an issue: GitHub.