bytedance/deer-flow · error · KeyError

{fact_id}

Error message

{fact_id}

What it means

On storage backends that implement apply_changes and get_fact, delete_memory_fact first fetches the fact by id; if get_fact returns None the id is unknown and KeyError(fact_id) is raised. This checks existence before issuing the delete changeset so a bogus id cannot silently succeed.

Source

Thrown at backend/packages/harness/deerflow/agents/memory/backends/deermem/deermem/core/updater.py:1001

            updated_memory = dict(memory_data)
            updated_memory["facts"] = _trim_facts_to_max([*memory_data.get("facts", []), copy.deepcopy(candidate)], self._config.max_facts)
            if self._save_memory_to_file(updated_memory, agent_name, user_id=user_id, expected_revision=int(memory_data.get("revision") or 0)):
                # If the cap evicted the just-added (lower-confidence) fact,
                # signal via None so callers don't report a dangling id as
                # "added".
                stored = any(f.get("id") == fact_id for f in updated_memory["facts"])
                return updated_memory, (fact_id if stored else None)
            logger.info("Retrying capped fact creation from a fresh snapshot after a revision conflict")
        raise OSError("Failed to save memory data after creating fact")

    def delete_memory_fact(self, fact_id: str, agent_name: str | None = None, *, user_id: str | None = None) -> dict[str, Any]:
        """Delete a fact by its id and persist the updated memory data."""
        if agent_name is None:
            raise ValueError("agent_name")
        if getattr(type(self._storage), "apply_changes", None) is not MemoryStorage.apply_changes and hasattr(self._storage, "get_fact"):
            deleted = self._storage.get_fact(fact_id, agent_name=agent_name, user_id=user_id)
            if deleted is None:
                raise KeyError(fact_id)
            global_memory = self.get_memory_data(user_id=user_id)
            self._storage.apply_changes(
                {"deletes": [fact_id], "deleteRevisions": {fact_id: int(deleted.get("revision") or 1)}},
                agent_name=agent_name,
                user_id=user_id,
                expected_manifest_revision=int(global_memory.get("revision") or 0),
                allow_manifest_rebase=True,
            )
            return self.get_memory_data(agent_name, user_id=user_id)
        memory_data = self.get_memory_data(agent_name, user_id=user_id)
        facts = memory_data.get("facts", [])
        updated_facts = [fact for fact in facts if fact.get("id") != fact_id]
        if len(updated_facts) == len(facts):
            raise KeyError(fact_id)
        deleted = next(fact for fact in facts if fact.get("id") == fact_id)
        if getattr(type(self._storage), "apply_changes", None) is not MemoryStorage.apply_changes:
            self._storage.apply_changes(
                {"deletes": [fact_id], "deleteRevisions": {fact_id: int(deleted.get("revision") or 1)}},

View on GitHub (pinned to 1dd6ba1acb)

Solutions

  1. Handle KeyError as a 404/idempotent 'already gone' case instead of crashing
  2. Re-fetch the fact list before showing delete actions so stale ids are not offered
  3. Guard deletes with an existence check against get_memory_data()

Example fix

// before
memory.delete_memory_fact(fid, agent_name=agent)
// after
try:
    memory.delete_memory_fact(fid, agent_name=agent)
except KeyError:
    pass  # already deleted; treat as success for idempotent UI
Defensive patterns

Strategy: try-catch

Validate before calling

facts = memory.get_memory_data(agent_name=agent).get("facts", [])
if not any(f.get("id") == fact_id for f in facts):
    raise HTTPException(404, "fact not found")

Try / catch

try:
    memory.delete_memory_fact(fact_id, agent_name=agent)
except KeyError:
    pass  # idempotent: already deleted

Prevention

When it happens

Trigger: Deleting a fact id that was already deleted, an id from a different user or agent bucket, or a truncated/typo'd id string (ids look like 'fact_ab12cd34').

Common situations: Stale UI list after another session deleted the fact, double-submit of a delete button, or ids carried across user contexts.

Related errors


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