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.
This commit is contained in:
HexLab98
2026-08-20 08:19:32 +07:00
committed by Teknium
parent 37fa4a7c63
commit d5cddae187
3 changed files with 45 additions and 3 deletions

View File

@@ -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)

View File

@@ -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]:

View File

@@ -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