f503eb6647
Introduces a pluggable MemoryProvider ABC so external memory backends can integrate with Hermes without modifying core files. Each backend becomes a plugin implementing a standard interface, orchestrated by MemoryManager. Key architecture: - agent/memory_provider.py — ABC with core + optional lifecycle hooks - agent/memory_manager.py — single integration point in the agent loop - agent/builtin_memory_provider.py — wraps existing MEMORY.md/USER.md Profile isolation fixes applied to all 6 shipped plugins: - Cognitive Memory: use get_hermes_home() instead of raw env var - Hindsight Memory: check $HERMES_HOME/hindsight/config.json first, fall back to legacy ~/.hindsight/ for backward compat - Hermes Memory Store: replace hardcoded ~/.hermes paths with get_hermes_home() for config loading and DB path defaults - Mem0 Memory: use get_hermes_home() instead of raw env var - RetainDB Memory: auto-derive profile-scoped project name from hermes_home path (hermes-<profile>), explicit env var overrides - OpenViking Memory: read-only, no local state, isolation via .env MemoryManager.initialize_all() now injects hermes_home into kwargs so every provider can resolve profile-scoped storage without importing get_hermes_home() themselves. Plugin system: adds register_memory_provider() to PluginContext and get_plugin_memory_providers() accessor. Based on PR #3825. 46 tests (37 unit + 5 E2E + 4 plugin registration).
114 lines
3.8 KiB
Python
114 lines
3.8 KiB
Python
"""BuiltinMemoryProvider — wraps MEMORY.md / USER.md as a MemoryProvider.
|
|
|
|
Always registered as the first provider. Cannot be disabled or removed.
|
|
This is the existing Hermes memory system exposed through the provider
|
|
interface for compatibility with the MemoryManager.
|
|
|
|
The actual storage logic lives in tools/memory_tool.py (MemoryStore).
|
|
This provider is a thin adapter that delegates to MemoryStore and
|
|
exposes the memory tool schema.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import logging
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
from agent.memory_provider import MemoryProvider
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class BuiltinMemoryProvider(MemoryProvider):
|
|
"""Built-in file-backed memory (MEMORY.md + USER.md).
|
|
|
|
Always active, never disabled by other providers. The `memory` tool
|
|
is handled by run_agent.py's agent-level tool interception (not through
|
|
the normal registry), so get_tool_schemas() returns an empty list —
|
|
the memory tool is already wired separately.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
memory_store=None,
|
|
memory_enabled: bool = False,
|
|
user_profile_enabled: bool = False,
|
|
):
|
|
self._store = memory_store
|
|
self._memory_enabled = memory_enabled
|
|
self._user_profile_enabled = user_profile_enabled
|
|
|
|
@property
|
|
def name(self) -> str:
|
|
return "builtin"
|
|
|
|
def is_available(self) -> bool:
|
|
"""Built-in memory is always available."""
|
|
return True
|
|
|
|
def initialize(self, session_id: str, **kwargs) -> None:
|
|
"""Load memory from disk if not already loaded."""
|
|
if self._store is not None:
|
|
self._store.load_from_disk()
|
|
|
|
def system_prompt_block(self) -> str:
|
|
"""Return MEMORY.md and USER.md content for the system prompt.
|
|
|
|
Uses the frozen snapshot captured at load time. This ensures the
|
|
system prompt stays stable throughout a session (preserving the
|
|
prompt cache), even though the live entries may change via tool calls.
|
|
"""
|
|
if not self._store:
|
|
return ""
|
|
|
|
parts = []
|
|
if self._memory_enabled:
|
|
mem_block = self._store.format_for_system_prompt("memory")
|
|
if mem_block:
|
|
parts.append(mem_block)
|
|
if self._user_profile_enabled:
|
|
user_block = self._store.format_for_system_prompt("user")
|
|
if user_block:
|
|
parts.append(user_block)
|
|
|
|
return "\n\n".join(parts)
|
|
|
|
def prefetch(self, query: str) -> str:
|
|
"""Built-in memory doesn't do query-based recall — it's injected via system_prompt_block."""
|
|
return ""
|
|
|
|
def sync_turn(self, user_content: str, assistant_content: str) -> None:
|
|
"""Built-in memory doesn't auto-sync turns — writes happen via the memory tool."""
|
|
|
|
def get_tool_schemas(self) -> List[Dict[str, Any]]:
|
|
"""Return empty list.
|
|
|
|
The `memory` tool is an agent-level intercepted tool, handled
|
|
specially in run_agent.py before normal tool dispatch. It's not
|
|
part of the standard tool registry. We don't duplicate it here.
|
|
"""
|
|
return []
|
|
|
|
def handle_tool_call(self, tool_name: str, args: Dict[str, Any], **kwargs) -> str:
|
|
"""Not used — the memory tool is intercepted in run_agent.py."""
|
|
return json.dumps({"error": "Built-in memory tool is handled by the agent loop"})
|
|
|
|
def shutdown(self) -> None:
|
|
"""No cleanup needed — files are saved on every write."""
|
|
|
|
# -- Property access for backward compatibility --------------------------
|
|
|
|
@property
|
|
def store(self):
|
|
"""Access the underlying MemoryStore for legacy code paths."""
|
|
return self._store
|
|
|
|
@property
|
|
def memory_enabled(self) -> bool:
|
|
return self._memory_enabled
|
|
|
|
@property
|
|
def user_profile_enabled(self) -> bool:
|
|
return self._user_profile_enabled
|