1. 从一次技能库失控说起:Hermes Curator 要解决的真实问题
如果你自建过 Agent,大概率遇到过这种局面:一开始只写了三五个技能文件,半年后目录里躺着三百多个 SKILL.md,其中一半是当时调试用的临时脚本,另一半功能高度重叠——fetch-url、get-webpage、read-link三个文件干的是同一件事。你不敢删,因为不知道哪个还在被引用;你也不想整理,因为逐个打开比对太费时间。
Hermes Curator 就是为这个场景设计的。它是 Hermes Agent 在 v0.12.0 引入的惰性后台任务,挂载在 Gateway 既有的 cron ticker 线程上,不单独起进程。核心目标只有一个:在你不用 Agent 的时候,自动把技能库从"三百个碎片"收敛成"一百个带小节的伞型技能",并且所有破坏性操作都可逆。
它适合谁?三类人值得细看:一是自建 Agent 框架、想借鉴技能治理思路的工程师;二是已经在用 Hermes、技能目录开始膨胀的重度用户;三是想理解"LLM 做语义判断 + 确定性代码守安全边界"这套混合架构怎么落地的人。本文会拆到状态机迁移条件、LLM fork 的凭据透传、tool-call 审计的校正逻辑,并给出可复制的配置片段和本地验证步骤。多模型接入部分,我会用 TaoToken 的统一 Key 通道来演示,因为 Curator 的 fork 机制对 base_url 和 api_key 的透传要求很严格,正好是个合适的验证场景。
先说结论性的设计主线,后面所有细节都围绕它展开:LLM 负责意图判断,确定性代码负责执行安全边界。规则状态机做时间维度的无争议清理,LLM 做内容相似性的语义判断,tool-call 审计校正 LLM 的幻觉,而删除这个动作被架构性禁止,一律降级为可逆归档。理解这条主线,再看代码就不会迷路。
2. TaoToken 统一 Key 通道:Curator fork 场景下的前置准备
Curator 的第二阶段会 fork 一个独立的子 AIAgent,通过load_config()+resolve_runtime_provider()读取你当前配置,然后显式传入 provider、model、api_key、base_url、api_mode 来实例化。这里有个历史坑:早期版本用空凭据触发,直接吃 HTTP 400。所以 fork 能不能跑通,取决于你的凭据配置是否完整、base_url 是否可达。
TaoToken 在这个环节的价值是:它提供统一的 Key 和 API 通道,一个 Key 可以路由到多个模型,base_url 固定,不需要为每个 provider 单独维护一套凭据。对 Curator 这种"fork 时原样透传父级运行时"的机制来说,配置面越小越不容易出错。你只需要在配置里写一次 base_url 和 api_key,fork 出来的子 Agent 继承同一套,OAuth-only 和 pool-backed 凭据都能正常工作。
前置准备分三步。第一步,拿到 Key。访问 https://taotoken.net/api-keys 创建,注意这个页面是控制台里的密钥管理入口,创建后立刻复制,页面刷新后不再完整显示。第二步,确认 base_url。API 通道地址是 https://taotoken.net/api,注意不要带任何查询参数,Curator 透传时会原样使用这个值。第三步,确认你要用的 Model ID。在 https://taotoken.net/models 可以查看当前可用的模型列表,把 Model ID 记下来,后面写进配置。
这里要强调一个容易踩的点:Curator 的 fork 会读取api_mode参数。如果你的配置里 api_mode 和实际通道不匹配(比如写成了某种需要额外鉴权头的模式),fork 出来的子 Agent 会在第一次 API 调用时报 401。稳妥做法是先用模型对话页面 https://taotoken.net/chat 手动发一条消息,确认 Key、base_url、Model ID 三者组合可用,再写进 Curator 配置。这一步花两分钟,能省掉后面半小时的排障。
另外提醒一句:Curator 的 LLM pass 一次完整 umbrella 合并可能需要 50 到 100 次 API 调用,作者实测 346 个技能跑了 86 次调用、约 6.5 分钟。所以你的 Key 需要有足够的额度余量,别在跑到一半时被限流中断,那样状态机会停在中间态,虽然可恢复,但排查起来麻烦。
3. 可复制配置:状态机参数与 Curator settings 片段
Curator 的配置读取集中在agent/curator.py,但用户侧可调的参数通过 settings 文件暴露。下面给出一份可直接复制的配置片段,路径按 Hermes 默认约定放在~/.hermes/settings.toml。如果你用的是 JSON 配置体系,等价结构在下一段给出。
# ~/.hermes/settings.toml [curator] enabled = true interval_hours = 168 # 默认 7 天,两次 LLM pass 的最小间隔 min_idle_hours = 2 # agent 空闲多久才允许启动,双重门控的第二层 stale_after_days = 30 # 上次使用超过 30 天且 active → 标记 stale archive_after_days = 90 # stale 且超过 90 天未用 → 归档到 .archive/ max_iterations = 9999 # fork 子 Agent 的迭代上限,别调低 pin_protected = true # 被 pin 的技能跳过所有自动迁移 [curator.llm] provider = "taotoken" model = "your-model-id" # 替换为 https://taotoken.net/models 里的 Model ID api_key = "sk-xxxxxxxx" # 替换为 https://taotoken.net/api-keys 创建的 Key base_url = "https://taotoken.net/api" api_mode = "chat_completions" # 与通道匹配,不匹配会 401如果你更习惯 JSON 体系(比如 Codex 风格的auth.json或 Cline MCP 的 settings),等价片段如下。注意三件套必须齐全:Base URL、Key、Model ID,缺一个 fork 就会失败。
{ "curator": { "enabled": true, "interval_hours": 168, "min_idle_hours": 2, "stale_after_days": 30, "archive_after_days": 90, "max_iterations": 9999, "pin_protected": true, "llm": { "provider": "taotoken", "model": "your-model-id", "api_key": "sk-xxxxxxxx", "base_url": "https://taotoken.net/api", "api_mode": "chat_completions" } } }几个参数的解释值得展开。interval_hours和min_idle_hours构成双重空闲门控:前者是时间条件,后者是活跃度条件,两者都满足才启动。为什么不用 cron 定时?PR 描述写得很清楚,用 inactivity-triggered 模式是为了避免在你活跃使用时消耗 token 配额、产生 API 调用噪音。max_iterations千万别按早期版本的 8 去设,一次完整 umbrella pass 需要 50 到 100 次 API 调用,设低了会在合并中途截断。
stale_after_days和archive_after_days对应第一阶段纯规则状态机,这一阶段完全不消耗 token。迁移逻辑是确定性的:上次使用超过 30 天且状态为 active 的标记为 stale;stale 且超过 90 天未用的归档到~/.hermes/skills/.archive/;曾被标记 stale 但近期又被使用的重新激活为 active。这三条规则没有任何 LLM 参与,是"显然废弃"技能的快速清理。
配置写完后,用 CLI 子命令确认状态。hermes curator status会输出当前是否启用、运行次数、上次运行时间、上次摘要、报告路径、间隔设置、stale 和 archive 阈值,以及使用次数最多和最少的各五个技能。如果 status 显示 ENABLED 但 runs 为 0,说明门控还没满足,可以手动触发一次验证:hermes curator run。想临时停掉用hermes curator pause,恢复用resume,把某个技能排除在自动迁移外用pin,取消用unpin,误归档了用restore <skill>拉回来。
4. 验证请求与成功结果:跑一次完整的 Curator pass
配置就绪后,验证分四步走,每步都有明确的成功标志,出问题能立刻定位到哪一层。
第一步,验证凭据通道。在终端直接发一条最小请求,确认 Key、base_url、Model ID 三件套可用:
curl -s https://taotoken.net/api/chat/completions \ -H "Authorization: Bearer sk-xxxxxxxx" \ -H "Content-Type: application/json" \ -d '{ "model": "your-model-id", "messages": [{"role": "user", "content": "reply with ok"}] }'成功标志是返回 JSON 里choices[0].message.content有内容。如果这里就报 401,别往下走,先回 https://taotoken.net/api-keys 确认 Key 没写错、没被删。如果报 model not found,去 https://taotoken.net/models 核对 Model ID 拼写。
第二步,手动触发 Curator 并观察日志。执行hermes curator run,然后盯住~/.hermes/logs/curator/下最新时间戳目录。每次运行会写两个文件:run.json是机器可读的,含完整 LLM 最终响应、所有 tool_call 记录、before/after 技能数量 diff;REPORT.md是人类可读的,含自动迁移摘要、LLM 合并结果、归档列表(前 50 条内联)、恢复命令提示。
第三步,检查状态机迁移是否符合预期。打开REPORT.md,看自动迁移摘要里 stale 和 archive 的数量。如果你有一个明确超过 90 天没用的测试技能,它应该出现在归档列表里。成功标志是归档条目存在,且原文件被移动到~/.hermes/skills/.archive/而不是被删除。
第四步,检查 LLM pass 的合并结果。run.json里的 tool_call 记录会显示skill_manage的 patch/create/write_file 操作。成功标志是:出现了新的伞型 SKILL.md,或者已有伞型技能被追加了小节,被合并的原始技能进入归档。作者实测的参考数据是 346 个技能经过 umbrella-first 策略后收敛到 118 个,减少 66%,所有内容保留在 references/ 里。
验证阶段有个细节要注意:Curator 的 fork 子 Agent 工具集被限制为skills_list、skill_view、skill_manage(patch/create/write_file)、terminal(仅用于归档),其他工具全部禁止。这是防止 Curator 扩权的设计。如果你在 run.json 里看到子 Agent 尝试调用被禁工具,说明配置或版本有问题,正常情况不应该出现。
跑通一次之后,hermes curator status的输出会更新。参考作者给出的示例格式:curator 显示 ENABLED,runs 计数加一,last run 显示时间,last summary 会写类似 "auto: no changes; llm: Consolidated nothing; tiny test run." 的摘要,last report 给出报告路径,后面跟着 interval、stale after、archive after 的当前值,以及 most used 和 least used 各五个技能。看到这个输出,说明整条链路通了。
5. 本篇常见报错排查:401、local proxy failed、reading choices、OAuth
排障部分按报错原文对照,每条给出触发原因和修复动作。这些是我在实际配置过程中遇到或见到的典型问题。
401 Unauthorized,fork 子 Agent 首次调用即失败。最常见的原因是 fork 时凭据没透传完整。Curator 通过load_config()+resolve_runtime_provider()读取配置后显式传入 provider、model、api_key、base_url、api_mode,五个参数缺一不可。检查你的 settings 里这五项是否都写了。另一个原因是 api_mode 与通道不匹配,比如通道期望 chat_completions 但你写了别的模式,鉴权头构造方式不同就会 401。修复:把 api_mode 改成chat_completions,并确认 base_url 是https://taotoken.net/api不带多余路径。
local proxy failed 或连接被拒。这个报错通常出现在 base_url 写错或网络不可达时。先确认 base_url 拼写,注意不要带尾部斜杠导致路径拼接成双斜杠。如果 base_url 正确但仍失败,用第 4 节的 curl 命令单独测通道,把 Curator 和网络问题隔离开。curl 通了说明是 Curator 配置问题,curl 不通说明是通道或 Key 问题。
reading 'choices' of undefined 或类似字段读取错误。这个报错说明请求发出去了、也返回了,但返回体结构不符合预期,代码在解析choices时拿到 undefined。常见原因是 Model ID 写错,通道返回了一个错误对象而不是正常的 completion 结构。修复:核对 Model ID,用 https://taotoken.net/models 的列表逐字比对。另一个可能是 api_mode 设错,导致解析路径不匹配。
OAuth 相关报错,提示凭据类型不支持。Curator 的 fork 设计上要继承父级完整运行时,OAuth-only 和 pool-backed 凭据都应该能正常工作。如果你用的是 OAuth 凭据却报错,检查 Hermes 版本是否包含 PR #17941 之后的修复。临时绕过方案是改用 API Key 方式配置,把 provider 指向 taotoken、填 api_key 和 base_url,这条路径最直接。
LLM pass 跑完但合并数为 0。这不是报错,但属于"没达到预期"。原因通常是提示策略问题。早期版本的被动审计提示会让模型倾向于在技能不完全相同时保持 keep,测试中 346 个技能只归档了 3 个。修复:确认你用的是 umbrella-first 提示策略(PR #17277 之后),它的核心要求是定性为"UMBRELLA-BUILDING 合并 pass"、判断标准是"维护者会写成 N 个独立文件还是一个带 N 小节的 SKILL.md"、并预设反驳"使用次数为 0 不是拒绝合并的理由"。
误归档后想恢复。用hermes curator restore <skill>。注意 PR #17941 解决的正是这个 UX 问题:用户看到技能被归档,分不清是真正废弃(pruning)还是内容已被吸收到新伞型技能(consolidation),误用 restore 会产生重复。判断方法看 REPORT.md 里的分类:如果归档项标注了 into 某个伞型技能,说明是 consolidation,restore 前先确认那个伞型技能里是否已有对应小节。
被 pin 的技能仍被迁移。不应该发生。安全不变量里明确写了被 pin 的技能跳过所有自动迁移,且模型永远不会自动 pin。如果你遇到这种情况,检查 pin 操作是否真的写入了状态文件,用hermes curator status确认 pin 列表。另外注意,内置技能和 Hub 技能有双重过滤(.bundled_manifest+.hub/lock.json),代码层面无法绕过,这类技能永远不会被 Curator 碰。
6. 把 Curator 的思路用起来:从统一 Key 到可观测的 Agent 调度
拆完 Curator 的实现,最值得带走的是它的架构取舍。它没有让 LLM 直接做删除决策,而是把 LLM 限制在一个有限工具集里做提案,所有破坏性操作降级为可逆归档,再用 tool-call 审计校正模型幻觉。这套"AI 提案、基础设施执行"的分工,比"AI 全权决策"稳得多,也更适合自建 Agent 的同学借鉴。
如果你想把这条链路跑起来,建议的顺序是:先在 https://taotoken.net/api-keys 建一个 Key,用 https://taotoken.net/chat 手动验证模型可用,再按第 3 节的配置片段写进 settings,最后用hermes curator run跑一次小规模测试。技能库不大时,可以把stale_after_days和archive_after_days调小,快速看到状态机迁移效果。
对于需要长期跑 Agent、频繁做技能治理的场景,Coding Plan 这类按周期计费的方式比按次调用更可控,尤其是 Curator 这种一次 pass 可能几十上百次调用的任务,额度规划清楚能避免跑到一半被限流。接入细节和参数说明在接入文档里有完整对照,遇到本文没覆盖的报错可以对着查。
最后留一个实用技巧:Curator 的run.json里保存了完整的 tool_call 记录,这是排查"为什么这个技能被合并/被归档"的最佳材料。每次觉得结果不符合预期时,先翻 run.json 看模型实际调用了什么、传了什么参数,比猜提示词有效得多。把可观测性做足,Agent 调度才不是黑盒。