1. 从 System Prompt 膨胀说起Hermes Agent 五层记忆架构到底解决什么问题如果你写过稍微复杂一点的 Agent大概率经历过这个循环第一版跑得挺好第二版加两条业务规则第三版加用户偏好第四版加历史决策记录……等到某天你打开prompt_builder.py发现 System Prompt 已经膨胀到八千多 token而模型在第五轮对话之后依然开始胡言乱语。这不是模型不行是架构选错了方向。Hermes Agent 的五层记忆架构本质上是在回答一个问题Agent 的记忆应该放在哪里才既便宜又准确又可追溯它的答案不是把上下文窗口撑到 200K而是把记忆拆成五个层次每一层用不同的存储介质和检索策略让 System Prompt 只承载真正需要每轮都出现的那部分内容。这套架构适合谁如果你正在做本地 Agent、CLI 编码助手、或者任何需要跨会话保持状态的 LLM 应用并且已经被 token 账单和失忆问题折磨过那这套分层思路值得完整复现一遍。它不依赖特定云服务核心存储就是本地 SQLite检索用 FTS5 全文索引压缩用辅助模型做摘要整套东西在单机上就能跑起来。我试过把原来一个 6000 token 的 System Prompt 拆成冻结偏好 技能索引 会话检索三部分之后单轮输入 token 直接降到 1800 左右而且模型对历史决策的引用准确率反而上升了。原因很简单信息密度降低了注意力不再被稀释。下面按三条主线展开System Prompt 怎么瘦身、SQLite FTS5 怎么建表和查询、上下文压缩前后 token 怎么对比验证。每一段都给出可复制的配置和命令你可以边看边在本地跑。2. TaoToken 前置准备给 Hermes Agent 配一个稳定的模型入口Hermes Agent 本身是运行时框架它需要调用外部 LLM 来完成推理和摘要。压缩中间轮次、生成技能描述、做用户建模这些都会产生额外的模型调用。如果模型入口不稳定压缩过程会频繁失败血缘链就断了。所以第一步是把模型接入层配好。TaoToken 在这里的角色是统一的模型接入网关。你不需要在 Hermes 的配置里分别填 Anthropic、OpenAI 的 key而是通过一个 Base URL 加一个 Key就能在多个模型之间切换。对于 Hermes 这种主模型 廉价摘要模型的组合场景特别合适——主推理用强模型压缩摘要用便宜模型两者走同一个入口。先拿到 API Key。访问 https://taotoken.net/api-keys 创建注意这个页面是控制台里的密钥管理入口创建后复制保存后面配置要用。然后确认你要用的模型 ID。打开 https://taotoken.net/models 可以看到当前可用的模型列表记下你打算用作主推理和摘要的两个 Model ID。Hermes 的压缩器默认会调用配置里指定的compression.model所以这里要提前想好。如果你更习惯在对话界面里先验证模型连通性可以打开 https://taotoken.net/chat 直接发一条测试消息确认返回正常再往下走。这一步能排除掉大部分Key 填错或模型名写错的低级问题。对于长期跑编码 Agent 的场景建议了解一下 Coding Plan它针对高频调用做了额度优化https://taotoken.net/coding-plan 。Hermes 的压缩和技能扫描会产生不少小请求用包月方案比按量计费更可控。接入文档在 https://taotoken.net/doc 里面有各语言 SDK 的调用示例和 Base URL 说明。Hermes 用的是 OpenAI 兼容协议所以配置里填https://taotoken.net/api作为 base_url 即可注意这个地址不带任何查询参数。配好之后你的 Hermes 配置里应该出现类似这样的结构一个base_url指向 TaoToken 的 API 地址一个api_key是你刚创建的密钥然后model字段填你在模型列表里选定的 ID。这三件套Base URL Key Model ID是后面所有配置的基础缺一个都会在启动时报认证错误。3. 可复制配置五层记忆的 settings 与 FTS5 建表语句这一节给出可以直接粘贴的配置片段。Hermes 的配置分两部分一部分是config.yaml里的运行时参数另一部分是 SQLite 数据库的 schema。两者要对应上否则 FTS5 索引不会自动同步。先看config.yaml的记忆相关段落。路径按你的实际安装位置调整这里用~/.hermes/profiles/default/config.yaml作为示例# ~/.hermes/profiles/default/config.yaml model: base_url: https://taotoken.net/api api_key: ${TAOTOKEN_API_KEY} model: claude-sonnet-4-6 compression: enabled: true threshold_tokens: 75000 protect_first_n: 3 protect_last_n: 6 max_passes: 3 model: gpt-4-turbo memory: provider: builtin memory_file: ~/.hermes/memories/MEMORY.md user_file: ~/.hermes/memories/USER.md snapshot_on_init: true database: path: ~/.hermes/hermes.db wal_mode: true fts5_tokenizer: unicode61 checkpoint_interval: 50 skills: dirs: - ~/.hermes/skills - ./skills - ./optional-skills cache_snapshot: ~/.hermes/.cache/skills_snapshot注意api_key用了环境变量引用不要把明文密钥写进配置文件。在 shell 里export TAOTOKEN_API_KEY你的密钥即可。接下来是数据库 schema。Hermes 的hermes_state.py在初始化时会建表但如果你想手动确认或重建下面是核心的 messages 表和 FTS5 虚拟表-- 主消息表 CREATE TABLE IF NOT EXISTS messages ( id INTEGER PRIMARY KEY AUTOINCREMENT, session_id TEXT NOT NULL, role TEXT NOT NULL, content TEXT NOT NULL, timestamp REAL NOT NULL, tool_calls TEXT, tool_results TEXT ); -- 会话表含血缘字段 CREATE TABLE IF NOT EXISTS sessions ( id TEXT PRIMARY KEY, parent_session_id TEXT, started_at REAL NOT NULL, ended_at REAL, message_count INTEGER DEFAULT 0, end_reason TEXT ); -- FTS5 全文索引虚拟表 CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5( content, tokenize unicode61 ); -- 触发器保持 FTS5 与主表同步 CREATE TRIGGER IF NOT EXISTS messages_ai AFTER INSERT ON messages BEGIN INSERT INTO messages_fts(rowid, content) VALUES (new.id, new.content); END; CREATE TRIGGER IF NOT EXISTS messages_ad AFTER DELETE ON messages BEGIN DELETE FROM messages_fts WHERE rowid old.id; END; CREATE TRIGGER IF NOT EXISTS messages_au AFTER UPDATE ON messages BEGIN UPDATE messages_fts SET content new.content WHERE rowid old.id; END;unicode61分词器对中文支持有限但能正确处理英文和数字。如果你的对话以中文为主可以在查询时用短语匹配来弥补分词粒度问题后面查询示例里会演示。建完表之后用PRAGMA journal_modeWAL;开启 WAL 模式这是并发写入稳定的关键。Hermes 的_execute_with_retry依赖BEGIN IMMEDIATE加随机退避来避免锁冲突WAL 模式让读操作不阻塞写操作。配置和 schema 都就位后启动 Hermes 应该能看到数据库初始化日志。如果报database is locked检查是不是有另一个进程占着同一个 db 文件。4. 验证请求FTS5 查询与上下文压缩前后的 token 对比配置写完不算完得亲眼看到记忆被写入、被检索、被压缩。这一节给三个验证动作每个都有预期输出。验证一FTS5 全文检索是否命中历史消息。先往数据库里塞几条测试消息或者直接用你之前跑过的会话数据。然后执行SELECT m.role, snippet(messages_fts, 0, [, ], ..., 30) AS snippet, m.timestamp FROM messages_fts JOIN messages m ON messages_fts.rowid m.id WHERE messages_fts MATCH docker deploy ORDER BY m.timestamp DESC LIMIT 5;预期输出里snippet列会把匹配到的关键词用方括号标出来比如如何部署 [docker] [deploy] 容器。如果返回空先确认messages_fts里有数据SELECT COUNT(*) FROM messages_fts;。计数为 0 说明触发器没生效检查建表语句是否完整执行。FTS5 的查询语法值得记几个docker AND deploy要求两个词都出现docker deploy精确匹配短语doc*前缀匹配docker NOT error排除包含 error 的记录。这些在排查历史决策时比向量检索精确得多因为它是字面匹配不会猜。验证二血缘链是否完整。压缩发生后旧会话的end_reason应该变成compressed新会话的parent_session_id指向旧会话。查询SELECT id, parent_session_id, message_count, end_reason FROM sessions ORDER BY started_at DESC LIMIT 5;预期看到类似这样的链最新会话的parent_session_id指向上一轮被压缩的会话而那个会话的end_reason是compressed。如果parent_session_id全是 NULL说明压缩器没有正确调用end_session()检查compression.enabled是否为 true。验证三压缩前后的 token 对比。这是最直观的一步。在压缩触发前后分别记录estimate_request_tokens_rough()的返回值。你可以在context_compressor.py的compress()入口和出口各加一行日志import logging logger logging.getLogger(hermes.compressor) def compress(self, messages): before self._estimate_tokens(messages) logger.info(fcompress start: {before} tokens, {len(messages)} messages) for _pass in range(self.max_passes): if self._estimate_tokens(messages) self.threshold_tokens: break messages self._compress_middle(messages) after self._estimate_tokens(messages) logger.info(fcompress done: {after} tokens, {len(messages)} messages) return messages跑一轮长对话触发压缩后看日志。典型结果是压缩前 78000 token、42 条消息压缩后 21000 token、11 条消息。中间那 31 条被摘要成了一条高密度文本而头 3 条和尾 6 条原样保留。如果你用 TaoToken 的模型对话页面做对照测试可以把压缩前后的消息分别贴进去观察模型对历史问题的回答质量。压缩后的版本如果依然能准确引用关键决策说明摘要质量合格。5. 常见报错排查401、local proxy failed、reading choices、OAuth配置和验证过程中最容易撞上四类报错逐个说清楚原因和解法。401 Unauthorized。这是认证失败九成是 Key 或 Base URL 的问题。先确认TAOTOKEN_API_KEY环境变量在当前 shell 里真的存在echo $TAOTOKEN_API_KEY。如果为空说明 export 没生效或者写在了别的 shell 配置里。然后确认base_url填的是https://taotoken.net/api不要多加斜杠或路径。最后确认model字段的 ID 在模型列表里真实存在拼错模型名有时也会返回 401 而非 404。local proxy failed。这个报错通常出现在 Hermes 尝试通过本地代理转发请求时。检查你的环境变量里有没有残留的HTTP_PROXY或HTTPS_PROXY设置如果有先unset掉再启动。Hermes 的 HTTP 客户端会读取系统代理配置一个失效的代理地址会导致连接直接失败。另外确认config.yaml里没有配置proxy字段除非你确实有本地转发服务在跑。Error reading choices。这个报错说明请求发出去了返回体也收到了但解析响应时找不到choices字段。常见原因是模型返回了错误结构比如被网关拦截返回了 HTML 错误页。先看完整响应体在run_agent.py的 API 调用处打印response.text。如果返回的是 HTML说明 Base URL 或路径不对请求没打到正确的 API 端点。如果返回的是 JSON 但结构不同检查你用的模型是否兼容 OpenAI 的 chat completions 格式。OAuth 相关报错。如果你在配置里启用了需要 OAuth 的模型提供商但没完成授权流程会看到 token 过期或 refresh 失败的提示。Hermes 的auth.json里存的是 OAuth token路径通常在~/.hermes/auth.json。检查这个文件是否存在且未过期。如果用的是 TaoToken 的 API Key 模式就不需要 OAuth把配置里的 OAuth 相关字段清空即可。排查顺序建议先看日志里完整的错误堆栈定位是网络层、认证层还是解析层再用 curl 手动打一次 API排除 Hermes 自身的问题最后对照配置逐项检查三件套Base URL Key Model ID。大部分报错在第二步就能定位。6. 把记忆分层用起来从配置到日常编码工作流配置跑通之后真正的价值在于日常使用。五层记忆不是摆设它改变的是你和 Agent 协作的方式。第一层短期记忆的头尾保护意味着你不需要手动清理对话。当 token 逼近阈值压缩器自动把中间的工具调用和报错修复过程摘要成一段高密度文本头部的任务定义和尾部的当前语境原样保留。你继续对话就行血缘链在后台维护。第二层长期偏好用冻结快照这个设计要理解清楚同一个会话内改MEMORY.md不会立即生效因为 System Prompt 用的是初始化时的快照。但 Gateway 模式每轮新建实例所以每轮都能读到最新文件。如果你在 CLI 里改完偏好发现没生效开个新会话即可。第三层 FTS5 检索是你主动用的。当 Agent 说我不记得之前怎么配的你可以直接查数据库SELECT content FROM messages_fts WHERE messages_fts MATCH nginx config ORDER BY rank LIMIT 3;ORDER BY rank会按 FTS5 的相关性排序比按时间排更准。找到原始消息后把内容贴回对话Agent 就能接着往下做。第四层技能库的用法是把重复操作固化成SKILL.md。比如你每次部署都要跑一套固定的构建命令就写一个技能文件Agent 扫描后会在需要时自动调用。技能目录的 mtime 变化会触发重新扫描改完文件不用重启。第五层用户建模目前通过插件实现适合需要跨会话积累用户画像的场景。如果你只是本地编码助手前四层已经够用。日常编码时我建议把主推理模型设强一点压缩摘要模型设便宜一点。压缩是高频小请求用便宜模型能显著降低成本而摘要质量对最终效果的影响远小于主推理。TaoToken 的模型列表里可以按价格和上下文长度筛选选一个性价比合适的做摘要。最后提醒一点FTS5 索引会随消息增长而变大10 万条消息大约 50MB对本地磁盘不是问题。但记得定期跑PRAGMA wal_checkpoint(PASSIVE);或者依赖 Hermes 每 50 次写入的自动 checkpoint防止 WAL 文件无限膨胀。数据库文件在~/.hermes/hermes.db备份直接复制这个文件即可血缘链和索引都在里面。