From d5cddae187ba75b8431cd792fa953a8455baa6f8 Mon Sep 17 00:00:00 2001 From: HexLab98 Date: Thu, 20 Aug 2026 08:19:32 +0700 Subject: [PATCH] fix(memory): drop dead memory tool and guidance when built-in stores are off With memory.memory_enabled and memory.user_profile_enabled both false, agent_init never builds a MemoryStore -- but check_memory_requirements() returned True unconditionally and MEMORY_GUIDANCE was gated only on the tool being present in valid_tool_names. So the tool shipped in every request's schema while answering "Memory is not available" on every call, and the system prompt still told the model to save durable facts there. Gate both on the config flags, using the store predicate for the tool and the already-resolved agent state for the guidance (config is not re-read mid-conversation, so the prompt stays byte-stable). Either flag alone still backs the tool, so only turning both off removes it. This lets a user running a third-party provider (Hindsight, Mem0, ...) turn the built-in files off without paying for the dead surface on every API call. The provider's own tools are unaffected: hiding the built-in tool moves the decision onto the toolset gate, and listing memory under agent.disabled_toolsets remains the only switch that takes those down. --- agent/system_prompt.py | 10 +++++++- tools/memory_tool.py | 29 ++++++++++++++++++++-- website/docs/user-guide/features/memory.md | 9 +++++++ 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/agent/system_prompt.py b/agent/system_prompt.py index 7d5bba76b3..64399226d5 100644 --- a/agent/system_prompt.py +++ b/agent/system_prompt.py @@ -415,7 +415,15 @@ def build_system_prompt_parts(agent: Any, system_message: Optional[str] = None) # Tool-aware behavioral guidance: only inject when the tools are loaded tool_guidance = [] - if "memory" in agent.valid_tool_names: + # MEMORY_GUIDANCE instructs the model to save facts to the built-in + # MEMORY.md/USER.md stores. With both disabled in config no store is built, + # so the guidance would steer the model at a tool whose every call returns + # "Memory is not available". Defaults to True for the rare code paths that + # build an agent view without going through agent_init. + builtin_memory_active = getattr(agent, "_memory_enabled", True) or getattr( + agent, "_user_profile_enabled", True + ) + if "memory" in agent.valid_tool_names and builtin_memory_active: tool_guidance.append(MEMORY_GUIDANCE) if "session_search" in agent.valid_tool_names: tool_guidance.append(SESSION_SEARCH_GUIDANCE) diff --git a/tools/memory_tool.py b/tools/memory_tool.py index 44effd02c2..c2ba72dc0e 100644 --- a/tools/memory_tool.py +++ b/tools/memory_tool.py @@ -1143,9 +1143,34 @@ def memory_tool( return json.dumps(result, ensure_ascii=False) +def builtin_memory_stores_enabled() -> bool: + """Return whether either built-in store (MEMORY.md / USER.md) is enabled. + + ``agent_init`` only builds a ``MemoryStore`` when at least one of + ``memory.memory_enabled`` / ``memory.user_profile_enabled`` is true, so with + both off the tool dispatches against ``store=None`` and every call fails + with "Memory is not available". + + Fails open when config can't be read: an unreadable config must not strip a + tool that would otherwise work. + """ + try: + from hermes_cli.config import load_config_readonly + + section = (load_config_readonly() or {}).get("memory") + if not isinstance(section, dict): + return True + return bool(section.get("memory_enabled", True)) or bool( + section.get("user_profile_enabled", True) + ) + except Exception: + logger.debug("Could not read memory config for availability", exc_info=True) + return True + + def check_memory_requirements() -> bool: - """Memory tool has no external requirements -- always available.""" - return True + """Available unless both built-in memory stores are disabled in config.""" + return builtin_memory_stores_enabled() def apply_memory_pending(payload: Dict[str, Any], store: "MemoryStore") -> Dict[str, Any]: diff --git a/website/docs/user-guide/features/memory.md b/website/docs/user-guide/features/memory.md index 624465821e..f76863da65 100644 --- a/website/docs/user-guide/features/memory.md +++ b/website/docs/user-guide/features/memory.md @@ -240,6 +240,15 @@ memory: write_approval: false # false = write freely (default) | true = require approval ``` +Setting **both** `memory_enabled` and `user_profile_enabled` to `false` turns the +built-in stores off completely: the `memory` tool is dropped from the schema and +its guidance block is dropped from the system prompt, so the model is never told +about a tool it cannot use. An external provider set via `memory.provider` +(Hindsight, Mem0, Honcho, …) is unaffected and keeps its own tools — use this +when you want a third-party memory backend *instead of* the built-in files. +Listing `memory` under `agent.disabled_toolsets` is the heavier switch: it hides +external provider tools too. + ## Controlling memory writes (`write_approval`) By default the agent saves memory freely — including from the background