{"record":{"id":"6a71f9979e6027a9","repo":"bytedance/deer-flow","slug":"retrieval-scope-agentname-must-be-a-string-or-null","errorCode":null,"errorMessage":"retrieval scope agentName must be a string or null","messagePattern":"retrieval scope agentName must be a string or null","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"backend/packages/harness/deerflow/agents/memory/backends/deermem/deermem/core/retrieval.py","lineNumber":549,"sourceCode":"        }\n\n    def close(self) -> None:\n        with self._lock:\n            self._conn.close()\n\n\ndef _scope_value(value: str | None) -> str:\n    \"\"\"Encode a typed scope value so ``None`` cannot collide with a user id.\"\"\"\n    return json.dumps(value, ensure_ascii=False, separators=(\",\", \":\"))\n\n\ndef _scope_key(scope: dict[str, str | None]) -> tuple[str, str]:\n    user_id = scope.get(\"userId\")\n    agent_name = scope.get(\"agentName\")\n    if user_id is not None and not isinstance(user_id, str):\n        raise ValueError(\"retrieval scope userId must be a string or null\")\n    if agent_name is not None and not isinstance(agent_name, str):\n        raise ValueError(\"retrieval scope agentName must be a string or null\")\n    return _scope_value(user_id), _scope_value(agent_name)\n\n\nclass FTS5RetrievalAdapter:\n    \"\"\"Scope-aware ``RetrievalPort`` adapter backed by one SQLite FTS5 DB.\n\n    The index is derived data. Canonical facts remain in Markdown and storage\n    notifications update only the addressed row. A deterministic composite\n    document id prevents equal fact ids in two user/agent scopes from\n    overwriting each other.\n    \"\"\"\n\n    def __init__(self, db_path: str | Path = \":memory:\") -> None:\n        self._engine = FTS5Retrieval(db_path)\n\n    @staticmethod\n    def _document_id(fact_id: str, scope: dict[str, str | None]) -> str:\n        scope_user, scope_agent = _scope_key(scope)","sourceCodeStart":531,"sourceCodeEnd":567,"githubUrl":"https://github.com/bytedance/deer-flow/blob/1dd6ba1acb03700589994b0366c5d1c7d05e2eff/backend/packages/harness/deerflow/agents/memory/backends/deermem/deermem/core/retrieval.py#L531-L567","documentation":"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.","triggerScenarios":"Passing a scope like {\"userId\": \"u1\", \"agentName\": 3} or agentName as some object into FTS5RetrievalAdapter operations.","commonSituations":"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.","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."],"exampleFix":"# before\nscope = {\"userId\": \"u1\", \"agentName\": agent.id}  # int\n\n# after\nscope = {\"userId\": \"u1\", \"agentName\": str(agent.id)}","handlingStrategy":"type-guard","validationCode":"scope = {'userId': str(user_id) if user_id is not None else None,\n        'agentName': str(agent_name) if agent_name is not None else None}","typeGuard":"def is_valid_scope(scope) -> bool:\n    return (\n        isinstance(scope, dict)\n        and (scope.get('userId') is None or isinstance(scope.get('userId'), str))\n        and (scope.get('agentName') is None or isinstance(scope.get('agentName'), str))\n    )","tryCatchPattern":null,"preventionTips":["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."],"tags":["typing","retrieval","deermem","memory","scope"],"backgroundTag":null,"analyzedSha":"1dd6ba1acb03700589994b0366c5d1c7d05e2eff","analyzedAt":"2026-08-14T21:20:34.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}