当你在多台服务器上运行 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 步流程:
- 源站 Hermes 接收消息,检测到
edge:前缀 - 剥离前缀,SSH 到边缘服务器,运行
hermes-guarded - 容器内边缘 Hermes 加载
edge-twinprofile - 系统提示词告诉它:"你运行在边缘服务器上"
- 记忆网关注入两个节点的过往记忆上下文
- 边缘 Hermes 执行
df -h,格式化响应 - 响应经 SSH → 网关 → 你的聊天返回
- 此次交互本身被采集为新记忆(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 反向代理
- 节点标签(
origin、edge)是元数据而非安全边界——它们用于上下文提示,不强制执行访问控制
记忆分层参考
| 层级 | 内容 | 存储 | 标记方式 |
|---|---|---|---|
| L0 | 原始对话记录 | FTS5 / SQLite | session_key 前缀 |
| L1 | 结构化记忆 | FTS5 + 向量 | node 字段 |
| L2 | 画像 + 场景块 | Markdown 文件 | HTML 注释 |
| L3 | 衍生元洞察 | 网关合成 | 继承自 L1/L2 |
进阶方向
- 三节点或更多:同样的模式可以直接扩展。添加第三个 profile 并设置
node: staging,三者共享同一网关。每个节点独立标记和识别。 - 记忆访问策略:扩展网关,增加基于节点的过滤器——"边缘分身可读全部,但 staging 分身只读自身记忆"
- 定期同步检查:使用 cron 任务定期验证所有节点都能连接到网关且拥有近期记忆条目
- 多渠道感知:标记记忆的来源渠道(微信、Telegram、CLI),实现更丰富的上下文注入
核心思路很简单:一个记忆池,按来源标记。每个 Hermes 实例知道自己在哪里运行,知道每条记忆来自哪里,并据此做出推理。这让两个孤立的 Agent 变成一支协调的团队——而不需要替换任何一个。
标签:Hermes Agent, AI 记忆, 多节点架构, Podman, 边缘计算, Agent 架构