多 Hermes 记忆共享实战:跨节点 Agent 记忆架构全指南

当你在多台服务器上运行 Hermes Agent 时——比如一台在家里的开发机上,另一台在远程 VPS 上——每个实例维护着自己独立的记忆。它们互不知道对方学到了什么。你刚跟本地 Hermes 聊过某个服务器配置,切换到远程 Hermes 后它却一无所知。本文将介绍一套完整的多节点记忆共享架构,从根本上解决这个问题。

核心问题

每个 Hermes 实例默认写入本地的 SQLite 记忆存储。如果你在不同服务器上运行着两个 Hermes,你就有了两个记忆孤岛:

服务器 A (源站)              服务器 B (边缘)
┌──────────────┐           ┌──────────────┐
│ Hermes A     │           │ Hermes B     │
│ memory.db  ✓ │           │ memory.db  ✗ │
│ "A 知道的"    │           │ "毫无上下文"   │
│              │           │              │
└──────────────┘           └──────────────┘

解决方案:部署一个中心化的记忆网关,两个实例都向它写入和读取,每条记忆记录都标注来源节点。

架构总览

我们的设计采用三层模型:

  • 消息前端(源站)—— 处理用户对话,转发消息
  • 记忆后端(边缘)—— 中心化记忆网关,存储所有记忆
  • 数字分身(边缘,容器化)—— 运行在边缘节点的第二个 Hermes,共享同一记忆池但拥有位置感知上下文
你的消息
       │
       ▼
┌──────────────────┐
│  源站 Hermes       │  ← 消息前端
│  (你的聊天机器人)   │
└────────┬─────────┘
         │ memory provider → 中心网关 (8420 端口)
         ▼
┌──────────────────┐
│  记忆网关          │  ← 中心化存储
│  L0 → L1 → L2     │     (容器化部署)
└────────┬─────────┘
         │ 共享读写
         ▼
┌──────────────────┐
│  边缘 Hermes       │  ← 数字分身
│  ("edge-twin")    │     共享同一记忆池
└──────────────────┘

第一步:部署记忆网关

记忆网关运行在边缘服务器的 Podman 容器中。它提供 REST API 用于记忆的采集(capture)和召回(recall),底层由向量存储支持语义搜索、FTS5 支持关键词匹配。

容器配置

容器的关键环境变量:

MODEL_NAME=你的模型名
MODEL_PROVIDER=你的提供商     # 如 deepseek, openai, anthropic
MODEL_BASE_URL=https://api.example.com/v1
TDAI_GATEWAY_HOST=0.0.0.0
TDAI_GATEWAY_PORT=8420
TDAI_DATA_DIR=/opt/data/tdai-memory
HERMES_HOME=/opt/data

8420 端口暴露到宿主机,使两个 Hermes 实例都能访问。使用 systemd 服务保持容器运行:

[Unit]
Description=Hermes 记忆网关
After=network-online.target

[Service]
ExecStart=/usr/bin/podman run --rm --replace \
  --name hermes-memory \
  -p 8420:8420 \
  --env-file /root/.hermes/.env \
  -v hermes_data:/opt/data \
  你的镜像名:latest
Restart=always

[Install]
WantedBy=multi-user.target

验证:curl http://你的边缘服务器:8420/health

第二步:接入源站 Hermes

在源站启用记忆提供者,添加到当前 Profile 的 config.yaml

# config.yaml —— 添加到已有 profile
memory_providers:
  tencentdb:
    gateway_host: "你的边缘服务器"   # 或私有网络 IP
    gateway_port: 8420
    enabled: true
    node: "origin"                     # 标识此节点的记忆来源

重启网关后,所有后续对话都会流经中心记忆管道:每轮对话自动采集,每次上下文注入自动召回。

第三步:创建边缘数字分身

为边缘 Hermes 创建专用 Profile——我们称之为 edge-twin。这个 profile:

  • 使用相同的记忆网关(共享记忆池)
  • 获得位置感知的系统提示词(告诉它自己在哪里运行)
  • 拥有独立的 user_id 前缀用于会话标识
# 创建 profile
hermes profile create edge-twin

# 设置系统提示词(注入位置感知)
hermes config set --profile edge-twin \
  system_prompt_addition "你运行在边缘服务器上(容器化部署)。
你的身份是 edge-twin。通过中心记忆网关与源站 Hermes 共享记忆。"

边缘 Profile 的关键配置:

user_id: "edge-twin@你的域名.com"      # 与源站区分
model: "你的模型"
provider: "你的提供商"
model_base_url: "https://api.example.com/v1"

memory_providers:
  tencentdb:
    gateway_host: "127.0.0.1"          # 边缘容器本地
    gateway_port: 8420
    enabled: true
    node: "edge"                         # 标识边缘来源的记忆

第四步:三层节点标记

这是整个架构最重要的设计决策。简单地共享记忆是不够的——你需要知道每条记忆来自哪个节点。否则,边缘 Hermes 可能会"误以为"它自己经历过源站的对话。我们在三个层面实现节点标记:

第零层:会话键前缀

每条对话都有一个 session_key。采集时,节点前缀被预先插入:

源站 → session_key: "origin:20260523_082446"
边缘 → session_key: "edge:20260524_091532"

这使得原始对话历史(L0)按来源节点分离,防止时间线混淆。

第一层:结构化记忆的节点字段

当网关从对话中提取结构化记忆(L1)时,会添加 node 字段:

{
  "type": "episodic",
  "node": "origin",
  "content": "用户询问了 Nginx 配置问题",
  "timestamp": "2026-05-23T08:24:00Z"
}

召回时,两个节点的记忆都会被搜索,但 node 字段提供了来源上下文。

第二层:画像与场景块

对于用户画像数据和长篇场景块(L2),使用 HTML 注释标记:

<!-- node: origin -->
# 用户:偏好简洁回复,居住在城市 A

<!-- node: edge -->
# 系统:边缘服务器运行 Podman 5.x,2 核 CPU

这让记忆注入器能够按来源筛选或语境化画像数据。

第五步:跨节点消息转发

一个杀手级特性:从源站 Hermes,你可以用简单的前缀将消息转发给边缘分身。在网关平台适配器中,任何以 edge: 开头的消息被拦截并路由到远程 Hermes:

# 在 adapter_base.py 的 handle_message() 中:
if msg_text.startswith("edge:"):
    prompt = msg_text[5:].strip()
    response = _call_edge_hermes(prompt)
    return response

_call_edge_hermes 函数通过 SSH 进入边缘服务器,在容器内执行 Hermes CLI:

def _call_edge_hermes(prompt: str) -> str:
    cmd = f'ssh 边缘服务器 "podman exec hermes-memory \
           hermes --profile edge-twin chat -q {shlex.quote(prompt)}"'
    result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
    return result.stdout

由于边缘 Hermes 共享同一记忆网关,它能获取完整的对话上下文,带着共享的知识返回响应。

第六步:并发保护

对容器化 Hermes 的多个并发调用可能导致竞态条件。一个简单的 flock 保护脚本可以防范:

#!/bin/bash
# /usr/local/bin/hermes-guarded —— 并发安全调用
LOCKFILE="/tmp/hermes-guard.lock"

exec 200>"$LOCKFILE"
flock -w 30 200 || { echo "ERROR: 获取锁超时"; exit 1; }

podman exec hermes-memory \
  hermes --profile edge-twin chat -q "$*" 2>/dev/null

完整流程

当你在源站聊天中发送 edge: 检查磁盘空间 时,以下是端到端发生的 8 步流程:

  1. 源站 Hermes 接收消息,检测到 edge: 前缀
  2. 剥离前缀,SSH 到边缘服务器,运行 hermes-guarded
  3. 容器内边缘 Hermes 加载 edge-twin profile
  4. 系统提示词告诉它:"你运行在边缘服务器上"
  5. 记忆网关注入两个节点的过往记忆上下文
  6. 边缘 Hermes 执行 df -h,格式化响应
  7. 响应经 SSH → 网关 → 你的聊天返回
  8. 此次交互本身被采集为新记忆(node: edge)

验证清单

  • [ ] 记忆网关健康检查端点返回 200
  • [ ] 源站对话出现在网关中,标注 node: origin
  • [ ] 边缘对话出现在网关中,标注 node: edge
  • [ ] edge: 我是谁,我在哪里? 返回位置感知响应
  • [ ] edge: 用户今天早些时候问过什么? 能召回源站记忆
  • [ ] 并发 edge: 调用不会崩溃(flock 保护生效)
  • [ ] 容器重启干净(systemd Restart=always)

隐私与安全说明

  • 所有节点间通信应走私有网络(Tailscale / WireGuard),不经过公网
  • 记忆网关绝不暴露到公网——仅绑定 127.0.0.1 或私有网络接口
  • API 密钥存放在各节点的 .env 文件中,绝不出现在 config.yaml 或 systemd unit 文件里
  • 节点间 SSH 应使用密钥认证,禁止密码登录
  • 记忆网关不加密静态数据——生产环境建议前置一层 TLS 反向代理
  • 节点标签(originedge)是元数据而非安全边界——它们用于上下文提示,不强制执行访问控制

记忆分层参考

层级内容存储标记方式
L0原始对话记录FTS5 / SQLitesession_key 前缀
L1结构化记忆FTS5 + 向量node 字段
L2画像 + 场景块Markdown 文件HTML 注释
L3衍生元洞察网关合成继承自 L1/L2

进阶方向

  • 三节点或更多:同样的模式可以直接扩展。添加第三个 profile 并设置 node: staging,三者共享同一网关。每个节点独立标记和识别。
  • 记忆访问策略:扩展网关,增加基于节点的过滤器——"边缘分身可读全部,但 staging 分身只读自身记忆"
  • 定期同步检查:使用 cron 任务定期验证所有节点都能连接到网关且拥有近期记忆条目
  • 多渠道感知:标记记忆的来源渠道(微信、Telegram、CLI),实现更丰富的上下文注入

核心思路很简单:一个记忆池,按来源标记。每个 Hermes 实例知道自己在哪里运行,知道每条记忆来自哪里,并据此做出推理。这让两个孤立的 Agent 变成一支协调的团队——而不需要替换任何一个。


标签:Hermes Agent, AI 记忆, 多节点架构, Podman, 边缘计算, Agent 架构