内存提供者插件
✅ 1. Understand the Role of a Memory Provider
A memory provider in Hermes Agent manages persistent or temporary data (e.g., conversation history, user context, task state) across interactions. It allows agents to "remember" past conversations or actions.
Hermes supports multiple memory backends via plugins, such as:
- In-memory (default)
- Redis
- PostgreSQL
- SQLite
- Custom databases
You can extend this by building your own.
✅ 2. Plugin Structure Requirements
Your memory provider plugin must follow the Hermes Plugin Specification:
Directory Layout Example:
hermes-memory-plugin/
├── index.js # Entry point
├── package.json # Required metadata
├── README.md # Documentation
└── config.schema.json # Optional: schema for configuration validation
✅ 3. Implement index.js (Main Plugin File)
This file exports a class that implements the required interface.
// index.js
class MemoryProviderPlugin {
constructor(config = {}) {
this.config = config;
this.name = 'custom-memory-provider';
this.type = 'memory';
// Initialize connection (e.g., DB, cache, etc.)
this.init();
}
async init() {
// Connect to your storage backend (e.g., MongoDB, Redis, etc.)
console.log(`Initializing ${this.name} with config:`, this.config);
// Example: connect to Redis, SQLite, or any other store
// throw new Error('Not implemented yet');
}
async save(key, value, ttl = null) {
// Save data under a key with optional TTL (time-to-live)
// e.g., await db.set(key, value, { ttl })
console.log(`Saving key: ${key}, value:`, value);
return true;
}
async get(key) {
// Retrieve data by key
const result = await this.fetchFromStorage(key);
return result || null;
}
async delete(key) {
// Delete data by key
await this.removeFromStorage(key);
return true;
}
async clear() {
// Clear all stored data
await this.clearAllStorage();
return true;
}
async listKeys(pattern = '*') {
// Return list of keys matching pattern
const keys = await this.getAllKeys();
return keys.filter(k => k.includes(pattern));
}
// Helper methods (implementation depends on backend)
async fetchFromStorage(key) {
// Replace with actual logic
return null;
}
async removeFromStorage(key) {
// Replace with actual logic
}
async clearAllStorage() {
// Replace with actual logic
}
async getAllKeys() {
// Replace with actual logic
return [];
}
}
// Export the plugin
module.exports = MemoryProviderPlugin;
🔧 Note: You can use libraries like
ioredis,sqlite3,mongodb, etc., depending on your backend.
✅ 4. Define package.json
Ensure it includes the correct fields for Hermes compatibility.
{
"name": "hermes-memory-custom",
"version": "1.0.0",
"description": "Custom memory provider for Hermes Agent",
"main": "index.js",
"type": "module",
"keywords": ["hermes", "agent", "memory", "plugin"],
"author": "Your Name",
"license": "MIT",
"dependencies": {
"ioredis": "^5.0.0"
},
"engines": {
"node": ">=18.0.0"
},
"hermes": {
"type": "memory",
"name": "custom-memory-provider",
"description": "A custom memory provider using Redis",
"configSchema": "./config.schema.json"
}
}
📌 The
"hermes"field is crucial — it tells Hermes how to load and validate the plugin.
✅ 5. Optional: Add Configuration Schema (config.schema.json)
Define expected config options for your plugin.
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Custom Memory Provider Config",
"type": "object",
"properties": {
"host": {
"type": "string",
"default": "localhost",
"description": "Redis host"
},
"port": {
"type": "number",
"default": 6379,
"description": "Redis port"
},
"password": {
"type": "string",
"nullable": true,
"description": "Redis password (if required)"
},
"db": {
"type": "number",
"default": 0,
"description": "Redis database index"
},
"ttl": {
"type": "number",
"default": 3600,
"description": "Default time-to-live in seconds"
}
},
"required": []
}
✅ 6. Register & Use the Plugin
Once built, place the plugin in your Hermes project’s plugins/ directory or publish it to npm.
Then, configure Hermes to use it in hermes.config.js:
// hermes.config.js
module.exports = {
plugins: [
{
name: 'hermes-memory-custom',
enabled: true,
config: {
host: 'localhost',
port: 6379,
db: 0,
ttl: 7200
}
}
],
// ... other config
};
✅ 7. Test Your Plugin
Start Hermes and verify:
- The plugin loads without errors.
- Memory operations (
save,get,delete) work. - Data persists across sessions (if applicable).
Use logs or debug tools to trace calls.
✅ 8. Best Practices
- Use async/await consistently.
- Handle errors gracefully (e.g., connection failures).
- Support TTL (time-to-live) for automatic cleanup.
- Provide meaningful logging.
- Document usage in
README.md. - Consider security: avoid exposing sensitive config values.
🚀 Bonus: Publish to NPM
If you want others to use your plugin:
npm login
npm publish --access public
Then users can install it via:
npm install hermes-memory-custom
📚 Resources
- Hermes Agent GitHub – Check official docs
- Hermes Plugin Guide
- OpenRouter / OpenAI Integration Examples
Let me know if you'd like a working example using Redis, PostgreSQL, or SQLite!"
构建内存提供者插件
内存提供者插件为 Hermes Agent 提供持久化、跨会话的知识,超越内置的 MEMORY.md 和 USER.md。本指南将介绍如何构建一个。
内存提供者是两种 提供者插件 类型之一。另一种是 上下文引擎插件,它可替换内置的上下文压缩器。两者均遵循相同的模式:单选、配置驱动,并通过 hermes plugins 进行管理。
目录结构
每个内存提供者都位于 plugins/memory/<name>/:
plugins/memory/my-provider/
├── __init__.py # MemoryProvider implementation + register() entry point
├── plugin.yaml # Metadata (name, description, hooks)
└── README.md # Setup instructions, config reference, tools
MemoryProvider 抽象基类
你的插件需实现来自 agent/memory_provider.py 的 MemoryProvider 抽象基类:
from agent.memory_provider import MemoryProvider
class MyMemoryProvider(MemoryProvider):
@property
def name(self) -> str:
return "my-provider"
def is_available(self) -> bool:
"""Check if this provider can activate. NO network calls."""
return bool(os.environ.get("MY_API_KEY"))
def initialize(self, session_id: str, **kwargs) -> None:
"""Called once at agent startup.
kwargs always includes:
hermes_home (str): Active HERMES_HOME path. Use for storage.
"""
self._api_key = os.environ.get("MY_API_KEY", "")
self._session_id = session_id
# ... implement remaining methods
必需方法
核心生命周期
| 方法 | 调用时机 | 是否必须实现? |
|---|---|---|
name (属性) | 始终调用 | 是 |
is_available() | 代理初始化,激活前 | 是 —— 不得进行网络调用 |
initialize(session_id, **kwargs) | 代理启动时 | 是 |
get_tool_schemas() | 初始化后,用于工具注入 | 是 |
handle_tool_call(name, args) | 当代理使用你的工具时 | 是(如果你有工具) |
配置
| 方法 | 用途 | 是否必须实现? |
|---|---|---|
get_config_schema() | 声明 hermes memory setup 所需的配置字段 | 是 |
save_config(values, hermes_home) | 将非敏感配置写入本地存储位置 | 是(除非仅支持环境变量) |
可选钩子
| 方法 | 调用时机 | 使用场景 |
|---|---|---|
system_prompt_block() | 系统提示词组装时 | 提供静态的提供者信息 |
prefetch(query) | 每次 API 调用前 | 返回回忆中的上下文 |
queue_prefetch(query) | 每轮对话结束后 | 为下一轮预热 |
sync_turn(user, assistant) | 每轮完整对话结束后 | 持久化对话内容 |
on_session_end(messages) | 对话结束时 | 最终提取/刷新数据 |
on_pre_compress(messages) | 上下文压缩前 | 在丢弃前保存洞察 |
on_memory_write(action, target, content) | 内置内存写入时 | 同步到你的后端 |
shutdown() | 程序退出时 | 清理连接 |
配置 Schema
get_config_schema() 返回由 hermes memory setup 使用的字段描述列表:
def get_config_schema(self):
return [
{
"key": "api_key",
"description": "My Provider API key",
"secret": True, # → written to .env
"required": True,
"env_var": "MY_API_KEY", # explicit env var name
"url": "https://my-provider.com/keys", # where to get it
},
{
"key": "region",
"description": "Server region",
"default": "us-east",
"choices": ["us-east", "eu-west", "ap-south"],
},
{
"key": "project",
"description": "Project identifier",
"default": "hermes",
},
]
带有 secret: True 和 env_var 的字段将被传入 .env。非敏感字段将传递给 save_config()。
get_config_schema() 中的每个字段都会在 hermes memory setup 期间被提示。具有大量选项的提供者应保持 schema 尽可能简洁——只包含用户必须配置的字段(如 API 密钥、必需凭证)。可选设置应在配置文件参考文档中说明(例如 $HERMES_HOME/myprovider.json),而不是在设置过程中全部提示。这能保持设置向导快速高效,同时仍支持高级配置。参见 Supermemory 提供者示例——它仅提示 API 密钥;其余所有选项均存在于 supermemory.json 中。
保存配置
def save_config(self, values: dict, hermes_home: str) -> None:
"""Write non-secret config to your native location."""
import json
from pathlib import Path
config_path = Path(hermes_home) / "my-provider.json"
config_path.write_text(json.dumps(values, indent=2))
对于仅支持环境变量的提供者,可保留默认的空操作。
插件入口点
def register(ctx) -> None:
"""Called by the memory plugin discovery system."""
ctx.register_memory_provider(MyMemoryProvider())
plugin.yaml
name: my-provider
version: 1.0.0
description: "Short description of what this provider does."
hooks:
- on_session_end # list hooks you implement
线程约定
sync_turn() 必须是非阻塞的。 如果你的后端存在延迟(如 API 调用、LLM 处理),请在守护线程中运行相关工作:
def sync_turn(self, user_content, assistant_content):
def _sync():
try:
self._api.ingest(user_content, assistant_content)
except Exception as e:
logger.warning("Sync failed: %s", e)
if self._sync_thread and self._sync_thread.is_alive():
self._sync_thread.join(timeout=5.0)
self._sync_thread = threading.Thread(target=_sync, daemon=True)
self._sync_thread.start()
配置文件隔离
所有存储路径 必须 使用来自 initialize() 的 hermes_home 关键字参数,而非硬编码的 ~/.hermes:
# CORRECT — profile-scoped
from hermes_constants import get_hermes_home
data_dir = get_hermes_home() / "my-provider"
# WRONG — shared across all profiles
data_dir = Path("~/.hermes/my-provider").expanduser()
测试
参见 tests/agent/test_memory_plugin_e2e.py,了解使用真实 SQLite 提供者的完整端到端测试流程。
from agent.memory_manager import MemoryManager
mgr = MemoryManager()
mgr.add_provider(my_provider)
mgr.initialize_all(session_id="test-1", platform="cli")
# Test tool routing
result = mgr.handle_tool_call("my_tool", {"action": "add", "content": "test"})
# Test lifecycle
mgr.sync_all("user msg", "assistant msg")
mgr.on_session_end([])
mgr.shutdown_all()
添加 CLI 命令
内存提供者插件可以注册自己的 CLI 子命令树(例如 hermes my-provider status、hermes my-provider config)。该功能基于约定发现机制,无需修改核心文件。
工作原理
- 在插件目录中添加一个
cli.py文件 - 定义一个
register_cli(subparser)函数来构建 argparse 命令树 - 内存插件系统通过
discover_plugin_cli_commands()在启动时发现它 - 你的命令将出现在
hermes <provider-name> <subcommand>下
活跃提供者控制: 只有当你的提供者是配置中当前激活的 memory.provider 时,你的 CLI 命令才会显示。如果用户未配置你的提供者,则你的命令不会出现在 hermes --help 中。
示例
# plugins/memory/my-provider/cli.py
def my_command(args):
"""Handler dispatched by argparse."""
sub = getattr(args, "my_command", None)
if sub == "status":
print("Provider is active and connected.")
elif sub == "config":
print("Showing config...")
else:
print("Usage: hermes my-provider <status|config>")
def register_cli(subparser) -> None:
"""Build the hermes my-provider argparse tree.
Called by discover_plugin_cli_commands() at argparse setup time.
"""
subs = subparser.add_subparsers(dest="my_command")
subs.add_parser("status", help="Show provider status")
subs.add_parser("config", help="Show provider config")
subparser.set_defaults(func=my_command)
参考实现
参见 plugins/memory/honcho/cli.py,获取包含 13 个子命令、跨配置文件管理(--target-profile)以及配置读写的完整示例。
带 CLI 的目录结构
plugins/memory/my-provider/
├── __init__.py # MemoryProvider implementation + register()
├── plugin.yaml # Metadata
├── cli.py # register_cli(subparser) — CLI commands
└── README.md # Setup instructions
单提供者规则
同一时间只能激活一个外部内存提供者。如果用户尝试注册第二个,MemoryManager 会以警告拒绝。这可防止工具模式膨胀和后端冲突。