bytedance/deer-flow · error · NotImplementedError

update_fact not supported by

Error message

update_fact not supported by {type(self).__name__}

What it means

MemoryManager.update_fact raises NotImplementedError as the optional per-fact CRUD default for partial fact updates (content/category/confidence with omitted fields preserved). Like create_fact/delete_fact, it is a DeerMem capability: backends without locally-addressable fact records (noop, mem0, honcho, openviking) inherit the raising default. The error indicates the selected memory backend does not support editing individual facts.

Solutions

  1. Run the deermem backend (memory.manager_class: deermem) when per-fact editing is a requirement.
  2. Emulate the update on capable-remote backends as delete+recreate through the backend's supported paths, or let middleware-mode extraction correct facts conversationally.
  3. Custom backend authors: override update_fact on the MemoryManager subclass, preserving omitted-field semantics.
  4. Capability-check before exposing edit actions in tooling.

Example fix

# before
memory = manager.update_fact(fact_id, confidence=0.9, user_id="u1")

# after
if type(manager).update_fact is MemoryManager.update_fact:
    raise UnsupportedOperation("fact updates unsupported by this backend")
memory = manager.update_fact(fact_id, confidence=0.9, user_id="u1")
Defensive patterns

Strategy: type-guard

Validate before calling

from deerflow.agents.memory.manager import MemoryManager

def supports_fact_update(manager: MemoryManager) -> bool:
    return type(manager).update_fact is not MemoryManager.update_fact

Type guard

def supports_fact_update(manager: MemoryManager) -> bool:
    """True when the backend supports partial per-fact updates."""
    return type(manager).update_fact is not MemoryManager.update_fact

Try / catch

try:
    memory = manager.update_fact(fact_id, content=new_text, user_id=uid)
except NotImplementedError as e:
    raise UnsupportedMemoryOperation(str(e)) from e

Prevention

When it happens

Trigger: Calling MemoryManager.update_fact(fact_id, content=..., confidence=..., agent_name=..., user_id=...) — via the memory_update tool in memory.mode: tool, a fact-editing UI action, or a script — while memory.manager_class resolves to a backend that does not override update_fact.

Common situations: Editing a fact in a settings UI after the deployment switched to a remote memory backend; memory.mode: tool runs against mem0/honcho; scripted fact corrections (e.g. updating confidence thresholds) written for the default file backend.

Related errors


AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14). Data as JSON: /api/errors/a62c2b0f178470f3. Report an issue: GitHub.

Appendix: source

Thrown at backend/packages/harness/deerflow/agents/memory/manager.py:431

        *,
        agent_name: str | None = None,
        user_id: str | None = None,
    ) -> dict[str, Any]:
        """Delete one fact by id. Default: unsupported."""
        raise NotImplementedError(f"delete_fact not supported by {type(self).__name__}")

    def update_fact(
        self,
        fact_id: str,
        content: str | None = None,
        category: str | None = None,
        confidence: float | None = None,
        *,
        agent_name: str | None = None,
        user_id: str | None = None,
    ) -> dict[str, Any]:
        """Update one fact by id (preserving omitted fields). Default: unsupported."""
        raise NotImplementedError(f"update_fact not supported by {type(self).__name__}")

    # B-class: no agent-side caller yet -- signatures only, for future scenarios.
    # Default no-op so callers can invoke unconditionally without gating. (The
    # self-serving hooks on_delegation / on_session_end / on_memory_write are
    # deliberately NOT contracted: no caller, no event source, or subsumed by
    # the callbacks field.)
    def on_pre_compress(self, messages: list[Any]) -> str:
        """Memory -> compressor feedback (future memory-driven summary
        enrichment). Returns text to inject into the compression prompt
        (default: none)."""
        return ""

    def on_turn_start(self, turn_number: int, message: Any, **kwargs: Any) -> None:
        """Turn-start nudge (future background review). Default: no-op."""
        return None

    # ── Async (speculative) ──────────────────────────────────────────────
    # Interface placeholders so a future async LLM client can override without

View on GitHub (pinned to 1dd6ba1acb)