{"record":{"id":"01b2bec7a907282d","repo":"bytedance/deer-flow","slug":"failed-to-save-memory-data-after-creating-fact","errorCode":null,"errorMessage":"Failed to save memory data after creating fact","messagePattern":"Failed to save memory data after creating fact","errorType":"exception","errorClass":"OSError","httpStatus":null,"severity":"error","filePath":"backend/packages/harness/deerflow/agents/memory/backends/deermem/deermem/core/updater.py","lineNumber":992,"sourceCode":"            raise AssertionError(\"bounded create retry did not return or raise\")\n        # Legacy single-file path: same duplicate-rejection contract as the\n        # apply_changes path above. A revision-conflicted save (False) reloads\n        # the fresh snapshot and re-runs the duplicate check, so a concurrent\n        # creator's commit is rejected with ValueError(\"Duplicate fact\")\n        # instead of surfacing as a generic save failure.\n        for attempt in range(3):\n            memory_data = self.get_memory_data(agent_name, user_id=user_id) if attempt == 0 else self.reload_memory_data(agent_name, user_id=user_id)\n            _raise_if_duplicate_fact_content(memory_data, candidate_key)\n            updated_memory = dict(memory_data)\n            updated_memory[\"facts\"] = _trim_facts_to_max([*memory_data.get(\"facts\", []), copy.deepcopy(candidate)], self._config.max_facts)\n            if self._save_memory_to_file(updated_memory, agent_name, user_id=user_id, expected_revision=int(memory_data.get(\"revision\") or 0)):\n                # If the cap evicted the just-added (lower-confidence) fact,\n                # signal via None so callers don't report a dangling id as\n                # \"added\".\n                stored = any(f.get(\"id\") == fact_id for f in updated_memory[\"facts\"])\n                return updated_memory, (fact_id if stored else None)\n            logger.info(\"Retrying capped fact creation from a fresh snapshot after a revision conflict\")\n        raise OSError(\"Failed to save memory data after creating fact\")\n\n    def delete_memory_fact(self, fact_id: str, agent_name: str | None = None, *, user_id: str | None = None) -> dict[str, Any]:\n        \"\"\"Delete a fact by its id and persist the updated memory data.\"\"\"\n        if agent_name is None:\n            raise ValueError(\"agent_name\")\n        if getattr(type(self._storage), \"apply_changes\", None) is not MemoryStorage.apply_changes and hasattr(self._storage, \"get_fact\"):\n            deleted = self._storage.get_fact(fact_id, agent_name=agent_name, user_id=user_id)\n            if deleted is None:\n                raise KeyError(fact_id)\n            global_memory = self.get_memory_data(user_id=user_id)\n            self._storage.apply_changes(\n                {\"deletes\": [fact_id], \"deleteRevisions\": {fact_id: int(deleted.get(\"revision\") or 1)}},\n                agent_name=agent_name,\n                user_id=user_id,\n                expected_manifest_revision=int(global_memory.get(\"revision\") or 0),\n                allow_manifest_rebase=True,\n            )\n            return self.get_memory_data(agent_name, user_id=user_id)","sourceCodeStart":974,"sourceCodeEnd":1010,"githubUrl":"https://github.com/bytedance/deer-flow/blob/1dd6ba1acb03700589994b0366c5d1c7d05e2eff/backend/packages/harness/deerflow/agents/memory/backends/deermem/deermem/core/updater.py#L974-L1010","documentation":"create_memory_fact retries the whole read-check-write cycle 3 times when _save_memory_to_file loses a revision race. If every attempt fails (or the storage write fails for non-race reasons), it gives up with this OSError. The apply_changes backends handle conflicts internally, so this is the legacy/conditional-save path's exhaustion signal.","triggerScenarios":"Sustained concurrent writes to the same agent's memory file (e.g. two threads creating facts in a tight loop) that win the revision race 3 times in a row, or a persistent I/O failure (read-only file, full disk) making every save attempt fail.","commonSituations":"Parallel test suites hammering the same memory file, a background memory-update loop plus user-issued memory_add calls, or a broken storage mount that fails every write.","solutions":["Reduce concurrency: serialize fact creation per agent (lock or queue) so revision races cannot repeat","Retry the create call after a short backoff — a fresh snapshot usually succeeds once the concurrent writer finishes","Verify the memory directory is writable and has disk space (persistent failure also exhausts retries)","Use a storage backend implementing apply_changes, which resolves conflicts server-side"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"for attempt in range(3):\n    try:\n        _, fact_id = memory.create_memory_fact(content, agent_name=agent)\n        break\n    except OSError:\n        if attempt == 2:\n            raise\n        time.sleep(0.5 * (attempt + 1))","preventionTips":["Serialize fact creation per agent (lock/queue) so revision races cannot repeat","Use an apply_changes-capable storage backend when concurrent writers exist"],"tags":["memory","deermem","concurrency","persistence","retry-exhausted"],"backgroundTag":null,"analyzedSha":"1dd6ba1acb03700589994b0366c5d1c7d05e2eff","analyzedAt":"2026-08-14T21:20:34.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}