{"record":{"id":"30a1e6e3909fa887","repo":"bytedance/deer-flow","slug":"memory-data-facts","errorCode":null,"errorMessage":"memory_data.facts","messagePattern":"memory_data\\.facts","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"backend/packages/harness/deerflow/agents/memory/backends/deermem/deermem/core/updater.py","lineNumber":833,"sourceCode":"            raise ValueError(\"memory_data\")\n        memory_data = copy.deepcopy(memory_data)\n        empty = create_empty_memory()\n        for section in (\"user\", \"history\"):\n            incoming_section = memory_data.get(section, {})\n            if not isinstance(incoming_section, dict):\n                raise ValueError(f\"memory_data.{section}\")\n            complete_section = copy.deepcopy(empty[section])\n            for key, value in incoming_section.items():\n                if key in complete_section and isinstance(complete_section[key], dict) and isinstance(value, dict):\n                    complete_section[key].update(copy.deepcopy(value))\n                else:\n                    complete_section[key] = copy.deepcopy(value)\n            memory_data[section] = complete_section\n        if agent_name is not None and getattr(type(self._storage), \"apply_changes\", None) is not MemoryStorage.apply_changes:\n            current = self.get_memory_data(agent_name, user_id=user_id)\n            incoming_facts = copy.deepcopy(memory_data.get(\"facts\", []))\n            if not isinstance(incoming_facts, list) or any(not isinstance(fact, dict) for fact in incoming_facts):\n                raise ValueError(\"memory_data.facts\")\n            for fact in incoming_facts:\n                fact[\"id\"] = str(fact.get(\"id\") or f\"fact_{uuid.uuid4().hex}\")\n                fact[\"confidence\"] = _coerce_source_confidence(fact)\n            current_by_id = {str(fact.get(\"id\")): fact for fact in current.get(\"facts\", []) if isinstance(fact, dict)}\n            incoming_ids = {str(fact.get(\"id\")) for fact in incoming_facts}\n            self._storage.apply_changes(\n                {\n                    \"upserts\": incoming_facts,\n                    \"upsertRevisions\": {str(fact.get(\"id\")): (int(current_by_id[str(fact.get(\"id\"))].get(\"revision\") or 1) if str(fact.get(\"id\")) in current_by_id else None) for fact in incoming_facts},\n                    \"deletes\": [fact_id for fact_id in current_by_id if fact_id not in incoming_ids],\n                    \"deleteRevisions\": {fact_id: int(fact.get(\"revision\") or 1) for fact_id, fact in current_by_id.items() if fact_id not in incoming_ids},\n                    \"summaries\": {\"user\": copy.deepcopy(memory_data.get(\"user\", {})), \"history\": copy.deepcopy(memory_data.get(\"history\", {}))},\n                },\n                agent_name=agent_name,\n                user_id=user_id,\n                expected_manifest_revision=int(current.get(\"revision\") or 0),\n            )\n            return self._storage.load(agent_name, user_id=user_id)","sourceCodeStart":815,"sourceCodeEnd":851,"githubUrl":"https://github.com/bytedance/deer-flow/blob/1dd6ba1acb03700589994b0366c5d1c7d05e2eff/backend/packages/harness/deerflow/agents/memory/backends/deermem/deermem/core/updater.py#L815-L851","documentation":"Thrown by DeerMem's memory-import path when the incoming 'facts' section of memory_data is not a JSON list of objects. Before diffing against current facts, the updater validates that memory_data['facts'] is a list whose entries are all dicts, and refuses anything else. This is a caller-input contract: imported memory must round-trip the shape that get_memory_data() produces.","triggerScenarios":"Calling the memory import API (import_memory_data / the tool that feeds it) with memory_data={'facts': {'id': 'x'}} (dict instead of list), facts being a list of strings/numbers, facts being null, or facts entries like \"some fact\" instead of {'content': ...} dicts.","commonSituations":"Hand-written import payloads, exporting from another memory system and mapping 'facts' to a single object, JSON schema drift after upgrading the backend, or a frontend form posting facts as a plain string array.","solutions":["Pass facts as a list of dicts: memory_data['facts'] = [{'id': ..., 'content': ..., 'category': ..., 'confidence': ...}, ...]","If importing from an export, verify the export was produced by get_memory_data() for the same backend version","Validate the payload shape before calling import (see validationCode)"],"exampleFix":"// before\nawait memory.import_memory_data({\n  agent_name: \"researcher\",\n  memory_data: { facts: { content: \"likes tea\" } },\n});\n// after\nawait memory.import_memory_data({\n  agent_name: \"researcher\",\n  memory_data: { facts: [{ content: \"likes tea\" }] },\n});","handlingStrategy":"validation","validationCode":"def is_valid_import_payload(memory_data: dict) -> bool:\n    facts = memory_data.get(\"facts\", [])\n    return isinstance(facts, list) and all(isinstance(f, dict) for f in facts)","typeGuard":null,"tryCatchPattern":"try:\n    memory.import_memory_data(memory_data, agent_name=agent)\nexcept ValueError as e:\n    if str(e) == \"memory_data.facts\":\n        raise HTTPException(400, \"facts must be a list of objects\")\n    raise","preventionTips":["Only import payloads exported by get_memory_data() from the same backend version","Validate the facts shape at your API boundary before forwarding to the backend"],"tags":["memory","deermem","validation","import"],"backgroundTag":null,"analyzedSha":"1dd6ba1acb03700589994b0366c5d1c7d05e2eff","analyzedAt":"2026-08-14T21:20:34.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}