bytedance/deer-flow · error · ValueError

agent_name is required to get a fact

Error message

agent_name is required to get a fact

What it means

MemoryStorage.get_fact() requires agent_name because facts live in per-agent memory files (path is derived from agent_name and user_id); there is no global fact lookup. The ValueError fires immediately, before path resolution, locking, or legacy migration, so a failed call has no side effects.

Source

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

    @staticmethod
    def _scope_kwargs(scope: dict[str, str | None]) -> dict[str, str]:
        kwargs: dict[str, str] = {}
        if scope.get("userId") is not None:
            kwargs["user_id"] = str(scope["userId"])
        if scope.get("agentName") is not None:
            kwargs["agent_name"] = str(scope["agentName"])
        return kwargs

    def get_fact(
        self,
        fact_id: str,
        *,
        user_id: str | None = None,
        agent_name: str | None = None,
    ) -> dict[str, Any] | None:
        if agent_name is None:
            raise ValueError("agent_name is required to get a fact")
        path = self._get_memory_file_path(agent_name, user_id=user_id)
        key = self._cache_key(agent_name, user_id=user_id)
        legacy_path = self._legacy_agent_memory_path(path, agent_name)
        notifications: list[RetrievalNotification] = []
        with self._scope_lock(key), _process_file_lock(path.parent / ".memory.lock", float(getattr(self._config, "file_lock_timeout_seconds", 10))):
            self._recover_if_needed(path)
            if legacy_path.exists():
                _, _, notifications = self._migrate_locked(path, agent_name, user_id=user_id, include_global=False)
            fact, _ = self._read_fact(path, fact_id, user_id=user_id, agent_name=agent_name)
        self._dispatch_retrieval_notifications(notifications, user_id=user_id, agent_name=agent_name)
        return copy.deepcopy(fact)

    def list_facts(
        self,
        *,
        user_id: str | None = None,
        agent_name: str | None = None,
        filters: dict[str, Any] | None = None,

View on GitHub (pinned to 1dd6ba1acb)

Solutions

  1. Supply the agent scope: storage.get_fact(fact_id, agent_name=agent_name, user_id=user_id).
  2. If you only know the fact id, first locate the owning agent with list_facts(agent_name=..., filters={'id': fact_id}) per known agent, or maintain an id->agent index.
  3. Fix API contracts so fact ids are always handled together with their agent scope.

Example fix

# before
fact = storage.get_fact(fact_id, user_id=user_id)

# after
fact = storage.get_fact(fact_id, agent_name=agent_name, user_id=user_id)
Defensive patterns

Strategy: validation

Validate before calling

if agent_name is None:
    raise HTTPException(400, "fact lookup requires agent_name")
fact = storage.get_fact(fact_id, agent_name=agent_name, user_id=user_id)

Prevention

When it happens

Trigger: storage.get_fact('fact_123') or storage.get_fact('fact_123', user_id=u) with agent_name omitted while routing a generic 'read fact by id' API that assumed global ids.

Common situations: Building a generic fact-inspection endpoint that only receives fact_id; refactoring a caller that previously loaded facts via list_facts(user-only) and now tries get_fact without the agent scope; passing agent_name=None as a sentinel for 'any agent'.

Related errors


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