mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-06 18:12:47 +08:00
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).
176 lines
6.8 KiB
Python
176 lines
6.8 KiB
Python
"""Abstract base class for pluggable memory providers.
|
|
|
|
Memory providers give the agent persistent recall across sessions. Multiple
|
|
providers can be active simultaneously — the MemoryManager orchestrates them.
|
|
|
|
Built-in memory (MEMORY.md / USER.md) is always active as the first provider.
|
|
External providers (Honcho, Hindsight, Mem0, etc.) are additive — they never
|
|
disable the built-in store.
|
|
|
|
Three registration paths:
|
|
1. Built-in: BuiltinMemoryProvider — always present, not removable.
|
|
2. First-party: Ship with the repo, activated by config (e.g. Honcho).
|
|
3. Plugin: External packages register via ctx.register_memory_provider().
|
|
|
|
Lifecycle (called by MemoryManager, wired in run_agent.py):
|
|
initialize() — connect, create resources, warm up
|
|
system_prompt_block() — static text for the system prompt
|
|
prefetch(query) — background recall before each turn
|
|
sync_turn(user, asst) — async write after each turn
|
|
get_tool_schemas() — tool schemas to expose to the model
|
|
handle_tool_call() — dispatch a tool call
|
|
shutdown() — clean exit
|
|
|
|
Optional hooks (override to opt in):
|
|
on_turn_start(turn, message) — per-turn tick (scope cooling, etc.)
|
|
on_session_end(messages) — end-of-session extraction
|
|
on_pre_compress(messages) — extract before context compression
|
|
on_memory_write(action, target, content) — mirror built-in memory writes
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from abc import ABC, abstractmethod
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class MemoryProvider(ABC):
|
|
"""Abstract base class for memory providers."""
|
|
|
|
@property
|
|
@abstractmethod
|
|
def name(self) -> str:
|
|
"""Short identifier for this provider (e.g. 'builtin', 'honcho', 'hindsight')."""
|
|
|
|
# -- Core lifecycle (implement these) ------------------------------------
|
|
|
|
@abstractmethod
|
|
def is_available(self) -> bool:
|
|
"""Return True if this provider is configured, has credentials, and is ready.
|
|
|
|
Called during agent init to decide whether to activate the provider.
|
|
Should not make network calls — just check config and installed deps.
|
|
"""
|
|
|
|
@abstractmethod
|
|
def initialize(self, session_id: str, **kwargs) -> None:
|
|
"""Initialize for a session.
|
|
|
|
Called once at agent startup. May create resources (banks, tables),
|
|
establish connections, start background threads, etc.
|
|
|
|
kwargs always include:
|
|
- hermes_home (str): The active HERMES_HOME directory path. Use this
|
|
for profile-scoped storage instead of hardcoding ``~/.hermes``.
|
|
|
|
kwargs may also include: platform, model, user_id, and other session context.
|
|
"""
|
|
|
|
def system_prompt_block(self) -> str:
|
|
"""Return text to include in the system prompt.
|
|
|
|
Called during system prompt assembly. Return empty string to skip.
|
|
This is for STATIC provider info (instructions, status). Prefetched
|
|
recall context is injected separately via prefetch().
|
|
"""
|
|
return ""
|
|
|
|
def prefetch(self, query: str) -> str:
|
|
"""Recall relevant context for the upcoming turn.
|
|
|
|
Called before each API call. Return formatted text to inject as
|
|
context, or empty string if nothing relevant. Implementations
|
|
should be fast — use background threads for the actual recall
|
|
and return cached results here.
|
|
"""
|
|
return ""
|
|
|
|
def queue_prefetch(self, query: str) -> None:
|
|
"""Queue a background recall for the NEXT turn.
|
|
|
|
Called after each turn completes. The result will be consumed
|
|
by prefetch() on the next turn. Default is no-op — providers
|
|
that do background prefetching should override this.
|
|
"""
|
|
|
|
def sync_turn(self, user_content: str, assistant_content: str) -> None:
|
|
"""Persist a completed turn to the backend.
|
|
|
|
Called after each turn. Should be non-blocking — queue for
|
|
background processing if the backend has latency.
|
|
"""
|
|
|
|
@abstractmethod
|
|
def get_tool_schemas(self) -> List[Dict[str, Any]]:
|
|
"""Return tool schemas this provider exposes.
|
|
|
|
Each schema follows the OpenAI function calling format:
|
|
{"name": "...", "description": "...", "parameters": {...}}
|
|
|
|
Return empty list if this provider has no tools (context-only).
|
|
"""
|
|
|
|
def handle_tool_call(self, tool_name: str, args: Dict[str, Any], **kwargs) -> str:
|
|
"""Handle a tool call for one of this provider's tools.
|
|
|
|
Must return a JSON string (the tool result).
|
|
Only called for tool names returned by get_tool_schemas().
|
|
"""
|
|
raise NotImplementedError(f"Provider {self.name} does not handle tool {tool_name}")
|
|
|
|
def shutdown(self) -> None:
|
|
"""Clean shutdown — flush queues, close connections."""
|
|
|
|
# -- Optional hooks (override to opt in) ---------------------------------
|
|
|
|
def on_turn_start(self, turn_number: int, message: str) -> None:
|
|
"""Called at the start of each turn with the user message.
|
|
|
|
Use for turn-counting, scope management, periodic maintenance.
|
|
"""
|
|
|
|
def on_session_end(self, messages: List[Dict[str, Any]]) -> None:
|
|
"""Called when a session ends (explicit exit or timeout).
|
|
|
|
Use for end-of-session fact extraction, summarization, etc.
|
|
messages is the full conversation history.
|
|
"""
|
|
|
|
def on_pre_compress(self, messages: List[Dict[str, Any]]) -> None:
|
|
"""Called before context compression discards old messages.
|
|
|
|
Use to extract insights from messages about to be compressed.
|
|
messages is the list that will be summarized/discarded.
|
|
"""
|
|
|
|
def get_config_schema(self) -> List[Dict[str, Any]]:
|
|
"""Return config fields this provider needs for setup.
|
|
|
|
Used by 'hermes memory setup' to walk the user through configuration.
|
|
Each field is a dict with:
|
|
key: config key name (e.g. 'api_key', 'mode')
|
|
description: human-readable description
|
|
secret: True if this should go to .env (default: False)
|
|
required: True if required (default: False)
|
|
default: default value (optional)
|
|
choices: list of valid values (optional)
|
|
url: URL where user can get this credential (optional)
|
|
env_var: explicit env var name for secrets (default: auto-generated)
|
|
|
|
Return empty list if no config needed (e.g. local-only providers).
|
|
"""
|
|
return []
|
|
|
|
def on_memory_write(self, action: str, target: str, content: str) -> None:
|
|
"""Called when the built-in memory tool writes an entry.
|
|
|
|
action: 'add', 'replace', or 'remove'
|
|
target: 'memory' or 'user'
|
|
content: the entry content
|
|
|
|
Use to mirror built-in memory writes to your backend.
|
|
"""
|