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