本文首发地址 https://h89.cn/archives/584.html

我一开始以为 Hermes Memory 接 Hindsight 很简单:跑个 setup,填个配置,重启,结束。

结果最坑的地方不是 Docker,也不是 PostgreSQL,而是模式名太容易误导:local 到底是本地 Docker,还是进程内 embedded?配置看起来正常,memory status 也显示 available,但 session 结束后什么都没记下来。

这篇文章不讲泛泛的“长期记忆有什么用”,只记录我怎么把 Hermes 接到本地 Hindsight Docker,也就是 local_external 路线,以及为什么我最后放弃了 local_embedded

cover

背景

Hermes Agent 自带内置记忆系统(MEMORY.md + USER.md),每次对话启动时把这两个文件注入上下文。对话少的时候够用,跨几十个 session 之后,文件越来越长,上下文塞不进去就开始漏记。

Hindsight 解决的是这个问题:把长期事实写进后端记忆库,按语义检索,只召回相关内容,而不是把整份记忆文件塞进 context。


架构:两种完全不同的部署模式

Hindsight 有两条完全不同的技术路径,文档经常混在一起说,容易迷糊:

路径 本质 跑在哪 模型
Dockerghcr.io/vectorize-io/hindsight:latest HTTP API server + PostgreSQL,数据走外部 LLM API Docker 容器 你配置的 OpenRouter / OpenAI key
CLI 工具pip install hindsight-all local_embedded 模式,daemon 内嵌在进程里 直接跑在主机上 HuggingFace 本地模型(embedding + extraction)

本文讲的是 Docker 路线 + Hermes 连接到本地容器(即 local_external),不是 local_embedded


hermes memory setup 向导干了什么

在 Hermes 里执行 hermes memory setup,会启动一个交互式向导,让你选 memory provider,然后根据你选的方案引导配置。

选 Hindsight 之后,它会问你:

  1. 模式:cloud(连接 api.hindsight.vectorize.io)还是 local(连接本地 Docker)
  2. API Key:如果选 cloud,需要从官网申请 key;如果选 local,Docker 镜像自带的不需要 key
  3. profile 名字:默认 hermes

向导最后会:

  • ~/.hermes/.env 里写入对应的环境变量
  • 如果选 local_embedded,会帮你 pip install hindsight-all(关键!后面会说到这个)

实际配置步骤(local_external 模式)

第一步:起 Hindsight Docker

实际 docker-compose.yml 配置(/vol1/1000/Tools/hindsight-docker/docker-compose.yml):

version: "3.8"
services:
  hindsight:
    image: ghcr.io/vectorize-io/hindsight:latest
    container_name: hindsight
    restart: unless-stopped
    ports:
      - "127.0.0.1:8888:8888"   # API
      - "127.0.0.1:9999:9999"   # Web UI
    environment:
      - HINDSIGHT_API_LLM_API_KEY=<你的 OpenRouter key>
      - HINDSIGHT_API_LLM_BASE_URL=https://openrouter.ai/api/v1
      - HINDSIGHT_API_LLM_MODEL=xiaomi/mimo-v2-flash
      - HINDSIGHT_LOG_LEVEL=info
      - HINDSIGHT_CONTROL_PLANE_ENABLED=true
    volumes:
      # PostgreSQL 数据目录,正确 mount
      - ./data:/home/hindsight/.pg0
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8888/health"]
      interval: 30s
      timeout: 10s
      retries: 3

PostgreSQL 数据路径:Hindsight 的 PostgreSQL 数据在容器内的 /home/hindsight/.pg0(不是 .hindsight),bind mount 到 ./data 是正确的。如果这个目录是空的(第一次启动),Hindsight 会初始化一个空数据库,后续 container rmup 会重新初始化。

第二步:让 Hermes 知道连哪个

Hermes 默认连云端 API(cloud 模式)。要改成连本地 Docker,在 ~/.hermes/.env 里加两行:

HINDSIGHT_MODE=local
HINDSIGHT_API_URL=http://localhost:8888

然后重启 Hermes(CLI 模式退出会话重进,Gateway 模式发 /restart)。

第三步:验证写入

在 Hermes 里说几句话触发记忆写入,验证时分两层看:先查 documents,确认 retain 同步写入成功;再等异步任务跑完,查 memory_units,确认 fact extraction 成功。hindsight_recall / hindsight_reflect 也能验证召回效果,但刚写完不一定马上能搜到。

docker exec -u hindsight hindsight sh -c \
  "PGPASSWORD=hindsight /home/hindsight/.pg0/installation/18.1.0/bin/psql \
   -h 127.0.0.1 -p 5432 -d hindsight -U hindsight -c '\dt'"

能看到这些表:

表名 用途
documents 原始对话片段(retain 写入)
chunks 切分后的块(做 embedding 用的)
memory_units 提取出的结构化事实
async_operations 异步任务队列状态

关键点:

  • documents 是同步写入的,所以查表能立刻看到最新对话
  • memory_units 是异步提取,LLM 处理有延迟,刚说完的话不会立刻出现 fact,需要等几秒
  • async_operations 表记录异步任务状态,可以查进度:
docker exec hindsight sh -c \
  'PGPASSWORD=hindsight /home/hindsight/.pg0/installation/18.1.0/bin/psql \
   -d hindsight -c "SELECT operation_type, status, created_at FROM async_operations ORDER BY created_at DESC LIMIT 5;"'
  • 如果 API key 是假的占位符,documents 能写进去(同步路径),但 memory_units 可能长期没有产出,需要查 async_operations.status 和容器日志确认失败原因
  • 触发方式:Hermes 的 on_session_end hook 在每次会话结束时自动调用,不需要手动 hindsight_retain

查询示例(连接容器内 PostgreSQL):

# 查看最新一条 document
docker exec hindsight sh -c \
  'PGPASSWORD=hindsight /home/hindsight/.pg0/installation/18.1.0/bin/psql \
   -d hindsight -c "SELECT LEFT(original_text, 100), created_at FROM documents ORDER BY created_at DESC LIMIT 1;"'

# 查看 memory_units 总数
docker exec hindsight sh -c \
  'PGPASSWORD=hindsight /home/hindsight/.pg0/installation/18.1.0/bin/psql \
   -d hindsight -c "SELECT COUNT(*) FROM memory_units;"'

# 查看所有表
docker exec hindsight sh -c \
  'PGPASSWORD=hindsight /home/hindsight/.pg0/installation/18.1.0/bin/psql \
   -d hindsight -c "\dt"'

Memory 使用流程:retain / recall / reflect 是怎么配合的

这篇文章主要写怎么跑起来,但跑起来之后怎么用,才是真正有意思的部分。

三个核心工具

Hindsight 插件暴露三个工具,可以在对话中直接调用:

工具 触发方式 作用
hindsight_retain 模型主动调用,或 auto_retain 自动触发 把当前对话内容写进记忆库
hindsight_recall 模型主动调用,或 auto_recall 自动触发 从记忆库里搜索相关内容
hindsight_reflect 模型主动调用 基于记忆库里的多条记忆,让模型综合推理后回答

日常对话中你不需要手动调用这些——插件通过 hook 自动化了这个流程。

记忆是怎么流转的

你说了一句话
    ↓
对话结束(on_session_end hook)
    ↓
Hindsight 插件收到完整对话内容
    ↓ retain() 同步调用
原始文本写入 documents 表
    ↓ 异步 background task
切 chunk → embedding → 写入 chunks 表
    ↓ 异步 LLM fact extraction
结构化事实写入 memory_units 表
你问了一个问题
    ↓
pre_llm_call hook 触发
    ↓ recall() 搜索
从 memory_units 语义匹配最相关的记忆
    ↓
召回的记忆作为上下文注入 prompt
    ↓
模型基于记忆 + 当前对话生成回答

关键点:

  • documents 是同步写入的,说完就能查到
  • memory_units 是异步提取,LLM 处理有延迟,刚说完的话要等几秒才会出现结构化事实
  • auto_retainauto_recall 默认开启,不需要手动操作

实际对话示例

第一轮:建立偏好记忆

你:以后这种事直接做,不用问我确认。
Agent:好的,以后这类操作我会直接执行。
    → retain: "用户偏好:日常任务直接执行,不需要事先确认"

第二轮:基于记忆自动执行

你:帮我重启一下服务器。
Agent:(recall 到上一条记忆)好,直接执行。
    → 直接执行,没有弹"确认要重启吗"

第三轮:跨session记忆

你(第二天):帮我部署新版本
Agent:(recall 历史记忆)记得你上次说直接执行,这次也直接跑了。

怎么查记忆(手动验证)

不需要进数据库,Hermes 里直接问:

问 Hermes:回忆一下我之前说过哪些偏好?
→ hindsight_recall 触发 → 返回之前 retain 的记忆片段

或者直接查 Docker 里的 PostgreSQL:

# 查看最近5条记忆事实
docker exec hindsight sh -c \
  'PGPASSWORD=hindsight /home/hindsight/.pg0/installation/18.1.0/bin/psql \
   -d hindsight -c "SELECT content, created_at FROM memory_units ORDER BY created_at DESC LIMIT 5;"'

# 搜索包含特定关键词的记忆
docker exec hindsight sh -c \
  'PGPASSWORD=hindsight /home/hindsight/.pg0/installation/18.1.0/bin/psql \
   -d hindsight -c "SELECT content FROM memory_units WHERE content LIKE '\''%偏好%'\'';"'

怎么删/修正错误记忆

修正(通常不是原地覆盖,而是追加修正事实):

在 Hermes 里直接说:

你:之前我说错了,我其实希望重要操作还是确认一下。
Agent:好的,已更新。以后重要操作会先确认。
→ 新增一条修正记忆,后续召回时模型需要处理新旧记忆的冲突

这类修正更像“追加一条更晚、更明确的事实”,不应该假设旧记录一定会被物理覆盖。如果要彻底移除错误记忆,还是要进数据库删除对应的 memory_units

彻底删除(进数据库):

# 删特定 memory_unit
docker exec hindsight sh -c \
  'PGPASSWORD=hindsight /home/hindsight/.pg0/installation/18.1.0/bin/psql \
   -d hindsight -c "DELETE FROM memory_units WHERE id = '\''<id>'\'';"'

# 清空所有记忆(测试用)
docker exec hindsight sh -c \
  'PGPASSWORD=hindsight /home/hindsight/.pg0/installation/18.1.0/bin/psql \
   -d hindsight -c "TRUNCATE documents, chunks, memory_units;"'

bank_id 是干什么的

bank_id 是记忆库的逻辑分区。默认是 hermes

如果你想让不同项目/不同用途的记忆完全隔离:

# 设为 project-alpha
HINDSIGHT_BANK_ID=project-alpha

不同 bank_id 的记忆互不可见,适合多项目共用同一个 Hindsight 实例的场景。

什么情况下记忆会失效

容易丢记忆的三个场景:

  1. API key 无效documents 能写(同步),但 memory_units 可能长期没有产出;具体是失败还是 pending,要查 async_operations.status 和容器日志
  2. 删掉宿主机数据目录或挂错 volume:如果 ./data:/home/hindsight/.pg0 还在,docker compose rm 只删容器,不会删除记忆;真正危险的是删除 ./data、改挂载路径,或者用匿名 volume 后连 volume 一起删
  3. 切换 bank_id:之前的记忆全部不可见,像是换了一个新账号

资源消耗:每次对话都占很多资源正常吗

是的,这是正常现象。每次对话结束触发 retain 时,Hindsight Docker 要走完一整条流水线:

1. 接收完整对话文本(同步,轻)
2. 切 chunk + embedding(本地 CPU 密集)
3. 调用 LLM 做 fact extraction(网络 I/O + 本地处理)
4. 写入 PostgreSQL(磁盘 I/O)

资源消耗拆解:

组件 是否本地 资源类型
PostgreSQL 数据库 本地 内存 + 磁盘 I/O
Embedding 模型 本地(镜像自带) CPU 峰值
Reranker 模型 本地(镜像自带) CPU 峰值
LLM fact extraction 网络调用 网络 I/O + 本地解析

实测观察:

  • idle 状态:CPU ≈ 0,内存约 200-500MB
  • retain 触发时:CPU 短时峰值,持续几秒到十几秒
  • 长对话 + fact extraction:内存可能跳到 1-2GB

为什么调用 API 还占本地资源:

Hindsight Docker 跑在你自己的机器上,即使 LLM 调用走网络,响应的解析、chunk 的 embedding 计算、结果写入 PostgreSQL 这些操作全在本地 CPU 和内存里完成。embedding 和 reranking 那两步是完全本地的 CPU 密集型任务,和网络无关。

怎么降低资源占用:

  • slim 镜像:把 embedding/reranking 模型剥离,依赖外部 API,本地占用降到几百 MB,但需额外配置 embedding provider
    image: ghcr.io/vectorize-io/hindsight:latest-slim
  • 关掉 auto_retain:改成按需手动 retain,完全可控
    HINDSIGHT_AUTO_RETAIN=false
  • 减少单次对话长度:短对话的 chunk 数少,embedding 开销有限

延迟 retain 和批量写入:当前不支持

想要"结束对话后等 2 分钟无活动再 retain",或者"积累几条对话后一起批量写入"——Hindsight 插件目前不支持。这是 on_session_end hook 的设计边界,不是可配置的参数。

如果你频繁开短 session,每次都会触发一次 retain + embedding + fact extraction 调用。建议把 HINDSIGHT_AUTO_RETAIN=false,重要的事手动说"记住"。


Bug #7718:local_embedded 的静默失败

这是我踩的最深的一个坑。

症状:在 ~/.hermes/hindsight/config.json 里写了 "mode": "local",然后 Hermes 启动正常、插件加载正常、memory status 显示 available,但所有记忆写入全部静默失败,整个 session 结束后什么都没记下来。

这个 WARNING 不在 TUI 里显示,只在 gateway.log 里——所以你对着 TUI 怎么看都是正常的,数据就是写不进去。

根因:插件的 plugin.yaml 只声明了 hindsight-client>=0.4.22,但 local_embedded 代码路径实际上需要 hindsight-all(提供顶层 hindsight 模块)。两者的包名完全不同:

包名 提供 用途
hindsight-client hindsight_client / hindsight_client_api 连接远程 API
hindsight-all hindsight(顶层模块) embedded 本地模式

hermes memory setup 向导在交互式选择 local_embedded 时会正确安装 hindsight-all,但:

  • 用户手动写 config.json
  • 从旧版升级(旧的 "mode": "local" 自动映射为 "local_embedded",没有任何迁移提示)
  • 从备份恢复配置

这些情况下 hindsight-all 永远不会装,报错长这样:

WARNING plugins.memory.hindsight: Hindsight sync failed: No module named 'hindsight'

而且这个 WARNING 不在 TUI 里显示,只在 gateway.log 里。

为什么不容易踩这个坑:如果你用的是 Docker 路线(HINDSIGHT_MODE=local + 外部 Docker),Hermes 只是 HTTP 客户端,不需要 hindsight-all,只要 hindsight-client 就够了。但如果你选了 local_embedded 并且手动配了 config.json,就会触发这个 bug。

为什么我最终选 local_external:两个原因。一是 Bug #7718 的静默失败太难排查,TUI 看起来完全正常但数据写不进去;二是 local_external 天然隔离——Hindsight 跑在独立容器里,Hermes 重启、升级、甚至崩溃都不影响记忆库,进程边界清晰。


参考


本文链接:Hermes Memory 本地踩坑复盘:Hindsight local_external 才是稳妥路线 - https://h89.cn/archives/584.html

版权声明:原创文章 遵循 CC 4.0 BY-SA 版权协议,转载请附上原文链接和本声明。

标签: Hermes, Hindsight, local, external, memory, embedded

添加新评论