hermes-agent — 本课题摘录
读了哪几篇: 01-turn-lifecycle(一轮怎么跑完)、02-context-engineering(上下文工程)。
其余几篇本轮没读。
这一家和 cowagent 正好对立:cowagent 每轮从磁盘重建系统提示,hermes 拼一次就冻住整场会话。两边的理由都成立,值得放在一起看。
它对本课题回答了什么
决定一:系统提示分三层,而且拼一次就冻住
| 层 | 装什么 | 变化频率 |
|---|---|---|
| 稳定层 | 身份、行为守则、技能索引、环境探测、平台提 示 | 进程生命周期内固定 |
| 上下文层 | 调用方传入的说明 + 工作目录下的项目上下文文件 | 换目录才变 |
| 易变层 | 记忆快照、用户档案、日期/会话/模型行 | 每场会话变一次 |
一个反直觉的点:"易变层"这个名字描述的是内容的性质,不是重算的频率——它也只在会话开始算一次。
(依据:Agent 库 · Hermes Agent · 上下文工程 —— 系统提示怎么拼、上下文怎么省 —— 系统提示按 stable/context/volatile 三层拼一次就冻住整场会话、跨所有轮次复用,只有压缩事件才触发重建;volatile 层名字叫易变但也只在会话开始算一次)
为什么这么狠:上游的前缀缓存按逐字节相同的前缀匹配。系统提示是整个请求最靠前、最长的一块,改动一个字符,后面所有缓存全废。
四道防线保这个不变式:
| 防线 | 做法 |
|---|---|
| 只建一次 | 有缓存就不重建 |
| 跨进程复用 | 网关每轮新建一个 agent 对象,改从数据库读回上一轮的原文 |
| 时间戳只到天 | 只写"星期、月、日、年"加固定时区,不含分钟 |
| 记忆冻结快照 | 中途写记忆只落盘,不动系统提示;下次会话才刷新 |
时间戳那条特别值得记:分钟级精度会让每一条重建路径都拿到不同的字符串,前缀缓存必然失效。 模型真需要精确时间时可以调工具查。
这是"每轮都变的东西 不能进前缀"这条定则的第四个例子(kun 的实时用量、agentscope 的追加提示、nanobot 的时间放末尾)。 hermes 这个版本最狠:连日期都降精度。
记忆快照那条是"持久化"和"缓存"的一次正面冲突:记忆要立刻落盘,提示要一动不动。它的裁法是双轨——文件立刻写,快照下次会话再换。
跟 cowagent 的"每轮从磁盘重建"正好对立。 两边的判据其实是同一个:你更在乎"模型立刻看到最新状态",还是更在乎"账单"? cowagent 选前者(它的状态变化直接影响下一步对不对),hermes 选后者(它的记忆晚一场会话生效无所谓)。
对我们的最小原型:先按 hermes 这一侧走(冻住),因为便宜且简单;等出现"必须立刻看到"的状态时再单独处理那一项。
两个东西刻意不进缓存串:一段只在发请求那一刻追加的临时提示;以及工具定义(工具是通过接口参数走的,不混进提示文本)。
决定四最值得抄的一条:退出时必留一个可解释的原因
这个原因字符串初值是"未知",循环的每个出口都会覆盖它。
十三种出口,只有一种算正常:
| 类别 | 出口 |
|---|---|
| ✅ 正常(唯一) | 模型不要工具了,给了文字 |
| 用户主动 | 循环顶部发现中断标志 / 接口调用期间被打断 |
| 异常 | 预算耗尽 / 跑满上限 / 空转守卫刹车 / 空响应重试用尽 |
| 降级 | 流被打断,把已吐出的部分当答复 / 这轮空,复用上一轮文字 |
| 失败 | 重试用尽仍无响应 / 本地模型窗口撑不下工具 / 逼近上限时外层异常 |
| 说明有漏网路径 | 未知 |
(依据:Agent 库 · Hermes Agent · 一轮对话是怎么跑完的 —— 主循环与模型接入 —— _turn_exit_reason 初值 unknown、每个出口都覆盖它,共十三种出口中只有 text_response 是正常出口,其余分为用户主动/异常/降级/失败四类;unknown 留在结果里就说明有漏网路径)
本课题"什么时候停"这一问,到 hermes 这里得到了最完整的答案: 不是"什么时候停",而是"每一种停都要能说出名字"。
前面 fara 把超轮数混成完成、kun 专门给截断发警告、agentscope 把超轮数单列、cherry-studio 把让步停记成成功—— hermes 把这件事做成了制度:初值是"未知",谁没覆盖就暴露出来。
这条要抄,而且成本极低:一个字符串字段。
日志里有一条特别的判定:如果最后一条消息是工具结果、且不是用户打断,就把日志级别升为警告。
理由很实在:这正是用户报"agent 干到一半就不动了"的那个场景。 "历史以工具结果结尾"= 工具跑完了但没再问 模型 = 一定有问题。 这是个很好的不变量检查。
决定四(结构):两层账,外层预算 × 内层重试
一轮 = 外层若干次接口调用 × 每次内层若干次重试。
预算计数器管外层,重试状态管内层。
跟 db-gpt 的"网络重试 vs 循环重试"是同一个区分。 两层计数必须分开,否则"网络抖了三次"会被记成"跑了三轮"。
收尾阶段还有一手:预算耗尽时补一次"请总结"。
这比 fara 的"耗尽就当完成"体面得多:至少让模型把已经做到的事说出来。
决定一:超限时的六级处置,先便宜后昂贵
① 工具自己先截 字符数 / 行数 / 单行长度三个上限
② 单条结果落盘 超阈值写进临时目录,上下文里只留预览 + 路径
③ 整轮结果预算 按大小排序,最大的先外溢,直到总量回到预算内
④ 工具延迟披露 非核心工具收进目录,只留 3 个桥工具
⑤ 发请求前预检 粗估超阈值 → 先压再发(最多 3 轮)
⑥ 压缩 先免费手段,最后才调辅助模型做摘要
(依据:Agent 库 · Hermes Agent · 上下文工程 —— 系统提示怎么拼、上下文怎么省 —— 超限处置按成本递增六级——工具自截、单条结果落盘、整轮结果预算、工具延迟披露、发请求前预检、压缩;前三级每轮都跑,第四级是装配工具数组时的一次性决策)
这是"先便宜后贵的阶梯"的第四份(dexter 四道防线、browseros 四级降级、cherry-studio 折叠算划算)。 hermes 这份最全,而且它把"工具自己先截"排在了第一位——最便宜的那道防线在工具内部,不在循环里。
阈值算法有三个细节值得记:
- 先扣掉输出预留:厂商把输出上限从同一个窗口里切走,按整窗算门限会导致压缩还没触发就被拒。
- 取百分比再托底:免得大窗口模型在 50% 就早压。
- 处理退化情形:托底值一旦大于等于有效窗口,门限就永远够不到、自动压缩永不触发——这时改用 85%。
第三条是很好的防呆:一个"永远不会触发"的阈值比没有阈值更危险,因为它看起来是配了的。
压缩本身是可插拔的引擎接口,不是写死的。
它没回答什么
- 怎么认出模型要调工具——它用原生工具调用。
- 一批工具怎么并发——第 1 章提到并行派发,但这两篇没细讲判据。
坑与代价
- 冻住系统提示意味着会话中途学到的东西这场用不上。 它靠"记忆下次会话才刷新"接受了这个代价。
- 十三种出口意味着十三个地方要记得写那个字段。 它用"初值是未知"来兜——漏了会暴露,不会静默。
判断(无锚): 这个模式值得推广:任何"必须被覆盖的字段",初值都应该是一个显眼的、不合法的值。 如果错,会错在: 如果那个字段只进日志、没人看,那初值再显眼也没用,得配一条告警。
- 主循环函数约六千六百行。 拆解文档说骨架只有三段,但这个体量本身就是一个 警告——"一轮怎么跑完"这件事在真实产品里会长成什么样。
- 六级处置的阈值大多是经验值(五万字符、两千行、50%、85%、最多 3 轮)。