bytedance/deer-flow · error · ValueError

agent_name is required to persist facts

Error message

agent_name is required to persist facts

What it means

Raised by FileMemoryStorage.save() when the payload contains a non-empty facts list but agent_name is None. Facts in this backend are always scoped to an agent (per-agent memory files keyed by agent_name and user_id); there is no global fact repository. The guard runs before any locking or I/O, so nothing is written when it fires.

Source

Thrown at backend/packages/harness/deerflow/agents/memory/backends/deermem/deermem/core/storage.py:1087

        Repository callers should prefer ``apply_changes`` to avoid even that
        full comparison scan.
        """
        path = self._get_memory_file_path(agent_name, user_id=user_id)
        key = self._cache_key(agent_name, user_id=user_id)
        lock_path = path.parent / ".memory.lock"
        notifications: list[RetrievalNotification] = []
        try:
            if not isinstance(memory_data, dict):
                raise ValueError("memory_data must be an object")
            if agent_name is not None and "facts" not in memory_data:
                raise ValueError("memory_data.facts is required for an agent full save")
            facts_raw = memory_data.get("facts", [])
            if not isinstance(facts_raw, list):
                raise ValueError("memory_data.facts must be a list")
            if any(not isinstance(fact, dict) for fact in facts_raw):
                raise ValueError("memory_data.facts must contain only fact objects")
            if agent_name is None and facts_raw:
                raise ValueError("agent_name is required to persist facts")
            with self._scope_lock(key), _process_file_lock(lock_path, float(getattr(self._config, "file_lock_timeout_seconds", 10))):
                self._recover_if_needed(path)
                ids = [str(fact.get("id") or "") for fact in facts_raw]
                if len(ids) != len(set(ids)):
                    raise ValueError("Duplicate fact ids are not allowed")
                old_ids = set(self._agent_entries(path, agent_name, user_id=user_id)) if agent_name is not None else set()
                summaries = None
                if agent_name is None:
                    summaries = {"user": memory_data.get("user", {}), "history": memory_data.get("history", {})}
                _, notifications = self._commit_changes_locked(
                    path,
                    user_id=user_id,
                    agent_name=agent_name,
                    upserts=copy.deepcopy(facts_raw),
                    deletes=sorted(old_ids - set(ids)),
                    summaries=summaries,
                    expected_revision=expected_revision,
                )

View on GitHub (pinned to 1dd6ba1acb)

Solutions

  1. Pass the agent_name you originally loaded the facts with: storage.save(memory_data, agent_name='my-agent', user_id=user_id).
  2. If the save is intentionally user-global (summaries only), strip facts: save({'user': ..., 'history': ...}, user_id=...) with no 'facts' key.
  3. Add a call-site assertion that agent_name is not None whenever facts are present.

Example fix

# before
storage.save(memory_data, user_id=user_id)  # memory_data contains facts

# after
storage.save(memory_data, agent_name=agent_name, user_id=user_id)
Defensive patterns

Strategy: validation

Validate before calling

facts = memory_data.get("facts") or []
if facts and agent_name is None:
    raise HTTPException(400, "agent_name required when facts are present")
storage.save(memory_data, agent_name=agent_name, user_id=user_id)

Prevention

When it happens

Trigger: storage.save({'facts': [...fact dicts...]}) with agent_name omitted/None (a user-global summary-only save), while still including facts in the payload.

Common situations: Copy-pasting an agent-scoped save call into a user-summary sync path; a caller that loads a full document (which includes facts) and re-saves it without the agent_name it loaded it with; refactoring that drops the agent_name parameter.

Related errors


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