{"record":{"id":"5cd484e370b6646d","repo":"bytedance/deer-flow","slug":"memory-mode-tool-requires-a-backend-that-impleme","errorCode":null,"errorMessage":"memory mode='tool' requires a backend that implements search(), but {type(self).__name__} does not override search(). Use mode='middleware' or a backend that overrides search() (and sets supports_search=True).","messagePattern":"memory mode='tool' requires a backend that implements search\\(\\), but (.+?) does not override search\\(\\)\\. Use mode='middleware' or a backend that overrides search\\(\\) \\(and sets supports_search=True\\)\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"backend/packages/harness/deerflow/agents/memory/manager.py","lineNumber":193,"sourceCode":"        invariants (e.g. storage_path is a directory) stay on ``DeerMemConfig``.\n\n        ``supports_search`` (ClassVar flag) must match whether ``search()`` is\n        actually overridden, so the declarative flag can't drift from the\n        implementation -- a backend that overrides ``search()`` but forgets\n        ``supports_search = True`` (or sets the flag without overriding) is a bug\n        caught at instantiation, not a misleading tool-mode rejection or a runtime\n        ``NotImplementedError`` on the first ``memory_search`` call.\n        \"\"\"\n        search_overridden = type(self).search is not MemoryManager.search\n        if type(self).supports_search != search_overridden:\n            raise ValueError(\n                f\"{type(self).__name__}.supports_search={type(self).supports_search} \"\n                f\"is inconsistent with search(): search() is \"\n                f\"{'overridden' if search_overridden else 'inherited (not implemented)'}. \"\n                f\"Set supports_search={search_overridden} on the backend to match.\"\n            )\n        if self.mode == \"tool\" and not search_overridden:\n            raise ValueError(\n                f\"memory mode='tool' requires a backend that implements search(), but {type(self).__name__} does not override search(). Use mode='middleware' or a backend that overrides search() (and sets supports_search=True).\"\n            )\n        return self\n\n    # ── Tier 1: @abstractmethod ─────────────────────────────────────────\n    # Every backend MUST implement these (write + read-inject are the backend's\n    # fundamental duties). Missing one is a severe bug (memory is persistent\n    # state) -- @abstractmethod catches it at instantiation. noop implements\n    # them as no-op / \"\".\n    @abstractmethod\n    def add(\n        self,\n        thread_id: str,\n        messages: list[Any],\n        *,\n        agent_name: str | None = None,\n        user_id: str | None = None,\n        trace_id: str | None = None,","sourceCodeStart":175,"sourceCodeEnd":211,"githubUrl":"https://github.com/bytedance/deer-flow/blob/1dd6ba1acb03700589994b0366c5d1c7d05e2eff/backend/packages/harness/deerflow/agents/memory/manager.py#L175-L211","documentation":"MemoryManager's model_post_init validation rejects mode='tool' when the concrete backend does not override the base search() method. Tool mode registers memory_search as a model tool, which requires a real retrieval implementation; a backend inheriting the base NotImplementedError search() cannot serve it. The same validator also enforces that supports_search matches whether search() is overridden.","triggerScenarios":"Constructing any MemoryManager subclass with mode='tool' while the subclass leaves search() unimplemented and/or supports_search inconsistent — e.g. enabling memory.mode: tool with a backend that only does passive capture. Caught at instantiation, deliberately before the first memory_search tool call could hit NotImplementedError.","commonSituations":"Flipping memory.mode to tool in config.yaml without checking backend capabilities; writing a custom MemoryManager backend and forgetting to implement search() while setting supports_search=True (that direction instead gives the supports_search-inconsistency ValueError from the same validator).","solutions":["If you need tool mode, switch to a backend that implements search() (e.g. DeerMem or honcho set supports_search=True), or implement search() on your custom backend and set supports_search=True","Otherwise keep memory.mode: middleware (passive capture/injection)","For custom backends, verify supports_search == (type(self).search is not MemoryManager.search) so the consistency check passes"],"exampleFix":"# before: custom backend claims tool support without search()\nclass MyBackend(MemoryManager):\n    supports_search = True   # but search() not overridden -> validator error\n\n# after\nclass MyBackend(MemoryManager):\n    supports_search = True\n    def search(self, query, top_k=5, *, user_id=None, agent_name=None, category=None):\n        ...  # real retrieval implementation","handlingStrategy":"validation","validationCode":"from deerflow.agents.memory.manager import MemoryManager\nbackend_type = resolve_manager_class(memory_cfg)\nsearch_overridden = backend_type.search is not MemoryManager.search\nif memory_cfg.get(\"mode\", \"middleware\") == \"tool\":\n    assert search_overridden, f\"{backend_type.__name__} cannot serve mode='tool': no search() override\"\nassert backend_type.supports_search == search_overridden","typeGuard":"def backend_supports_tool_mode(backend_type: type) -> bool:\n    return backend_type.search is not MemoryManager.search and backend_type.supports_search is True","tryCatchPattern":null,"preventionTips":["Before enabling memory.mode: tool, confirm the backend sets supports_search=True and overrides search()","When writing custom backends, keep supports_search in lockstep with the search() override","Construct managers through the validating factory so this fails at startup, not mid-run"],"tags":["memory","tool-mode","validation","backend"],"backgroundTag":null,"analyzedSha":"1dd6ba1acb03700589994b0366c5d1c7d05e2eff","analyzedAt":"2026-08-14T21:20:34.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}