ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

【hermes-agent】Hermes Agent 自我进化系统深度分析(Part 2-2):Curator 技能库的配置与验证

【hermes-agent】Hermes Agent 自我进化系统深度分析(Part 2-2):Curator 技能库的配置与验证

1. 从一次“技能越用越乱”的现场说起

Hermes Agent 的自我进化系统里,Curator 是那个负责“技能库全局维护”的后台编排器。它要解决的问题很具体:Agent 在长期运行中会不断创建技能,技能一多,命名开始发散、职责开始重叠、有些技能几个月没人调用却还挂在库里占着 system prompt 的触发窗口。Curator 就是那个在 Agent 空闲时自动跑一遍、把技能从 active 推到 stale、再推到 archived 的维护者。

我把它理解成一个“图书馆管理员”:它不写书(不创建业务技能),只负责把长期没人借的书从书架挪到密集书库,把内容重叠的几本小册子合并成一本文集,而且只归档不删除,随时可以restore回来。这个“只归档不删除”的不变量,是整套机制敢在后台自动跑的前提。

这篇 Part 2-2 聚焦落地:Curator 技能库的配置与验证。面向本地 Agent 开发场景,我会给出可复制的~/.hermes/config.yaml配置片段、技能注册与触发条件的检查方式、以及自我进化链路的验证动作。适合已经跑通 Hermes Agent 基础对话、想让技能库自动收敛的开发者。核心检索词就三个:Hermes Agent、Curator、技能库自我进化系统。

需要先明确 Curator 的五大不变量,后面所有配置和验证都围绕它们展开:

只触碰 curator-managed 技能,不碰 bundled、hub-installed、external-dir 技能;只归档不删除,归档可恢复;pinned 技能豁免所有自动转换;LLM fork 使用 auxiliary client,不触碰主 session 的 prompt cache;空闲触发,不需要 cron 守护进程。

这五条决定了你配置时哪些开关是安全的、哪些操作永远不会发生。下面从配置读取链路开始拆。

2. TaoToken 前置:给 Curator 的 auxiliary 模型接上稳定通道

Curator 的 LLM 合并 Pass 是可选的(consolidate默认 OFF),但一旦你打开它,它就会 fork 一个AIAgent,用enabled_toolsets=["skills"]去读技能、合并技能。这个 fork 走的是 auxiliary 模型槽位,也就是auxiliary.curator.*。所以第一步是把 auxiliary 的 provider 配好。

我实测下来,把 auxiliary 指向 TaoToken 的兼容接口是最省事的做法,因为它的 Base URL 和 Key 管理方式和主模型一致,resolve_runtime_provider能直接解析。先拿 Key:打开 https://taotoken.net/api-keys ,创建一个 API Key,复制出来。注意这个 Key 只用于本地配置,不要提交到仓库。

拿到 Key 之后,你需要确认两件事:Base URL 用https://taotoken.net/api(注意 API 地址不带任何查询参数),Model ID 用你在控制台里能看到的模型名。控制台入口在 https://taotoken.net/console ,模型列表和可用模型都在里面。如果你不确定该选哪个模型做 Curator 的合并 Pass,可以先在 https://taotoken.net/models 里用对话页面试一下,确认模型能正常返回再写进配置。

这里有个容易踩的坑:Curator 的 auxiliary 槽位和主聊天模型是分开解析的。_resolve_review_runtime的优先级是三级——先看auxiliary.curator.{provider,model},两者都设为非 auto 才生效;再看已废弃的curator.auxiliary.{provider,model};最后 fallback 到主model.{provider,default}。所以如果你只配了主模型,Curator 的 fork 会直接复用主模型,这本身没问题,但如果你想让 Curator 用更便宜的模型跑合并,就必须显式配auxiliary.curator。

另外,_resolve_review_provider会把 binding 交给resolve_runtime_provider,和 CLI 走同一条解析路径。这意味着你在 CLI 里能正常对话的 provider 配置,Curator 也能用。如果你在 CLI 里已经能跑通,那 Curator 的 provider 解析基本不会出问题。

关于 Coding Plan:如果你打算让 Curator 长期在后台跑合并 Pass,用按量计费的 Key 可能成本不好控。TaoToken 的 Coding Plan( https://taotoken.net/coding-plan )更适合这种“后台周期性调用”的场景,因为它的额度模型对周期性任务更友好。我建议先用按量 Key 把链路验证通,确认 Curator 的行为符合预期后,再切到 Coding Plan 做长期运行。

最后提醒一句:Curator 的 fork 是skip_memory=True、skip_context_files=True的,它不会读你的 memory,也不会加载 context 文件。所以 auxiliary 模型只需要能正常响应 skill 工具调用即可,不需要它理解你的项目上下文。

3. 可复制配置:curator section 与 auxiliary 槽位

Curator 的配置全部读自~/.hermes/config.yaml的curatorsection。_load_config()调用_read_config_section("curator", ...),配置文件缺失时返回{},所有配置项都有默认值。下面是一份可以直接复制的配置片段,我按“保守起步”和“开启合并”两种场景分别给。

先看保守起步版,只开确定性状态转换,不 fork LLM:

# ~/.hermes/config.yaml curator: enabled: true # 默认 true,显式写出来更清楚 interval_hours: 168 # 7 天跑一次,默认值 min_idle_hours: 2 # 至少空闲 2 小时才触发 stale_after_days: 30 # 30 天未活动标记 stale archive_after_days: 90 # 90 天未活动归档 prune_builtins: true # 允许归档 bundled 技能 consolidate: false # 关闭 LLM 合并 Pass

这份配置下,Curator 只做纯时间驱动的状态转换,零 LLM 成本。apply_automatic_transitions会遍历curated_report(),对每个 curator-managed 技能计算 anchor 时间,然后按stale_cutoff和archive_cutoff做三态转换。整个过程不调用任何模型。

再看开启合并的版本,需要把 auxiliary 槽位配上:

# ~/.hermes/config.yaml curator: enabled: true interval_hours: 168 min_idle_hours: 2 stale_after_days: 30 archive_after_days: 90 prune_builtins: true consolidate: true # 开启 LLM 合并 Pass auxiliary: curator: provider: taotoken # 非 auto 才生效 model: your-model-id # 换成控制台里确认可用的 Model ID api_key: sk-xxxxxxxx # 你的 TaoToken API Key base_url: https://taotoken.net/api extra_body: {} # 可选,透传给请求体

这里的三件套必须写全:Base URL 是https://taotoken.net/api,Key 是你在 API Keys 页面创建的,Model ID 是控制台里确认可用的模型名。三者缺一,resolve_runtime_provider就可能 fallback 到主模型,或者直接解析失败。

配置项的含义和默认值对照如下:

配置项默认值类型作用
enabledtrueboolCurator 是否启用
interval_hours168int运行间隔(小时)
min_idle_hours2float最小空闲小时数
stale_after_days30int多少天未使用标记 stale
archive_after_days90int多少天未使用归档
prune_builtinstruebool是否允许归档 bundled 技能
consolidatefalsebool是否启用 LLM 合并 Pass

有几个细节值得单独说。enabled默认是 true,也就是说你什么都不配,Curator 也是开着的,只是consolidate默认 false,所以它只会做确定性转换。prune_builtins默认 true,意味着 bundled 技能也会被纳入归档候选,但注意_cron_referenced_skills()会把 cron 引用的技能视为 pinned,保护它们不被自动转换。

interval_hours默认 168 小时(7 天),配合should_run_now()的首次延迟播种逻辑:第一次观察时last_run_at为 None,Curator 会把它设为当前时间并返回 False,推迟一个 interval。这个设计意图很明确——fresh install 或 update 不应该在第一次 tick 就修改技能库。所以如果你刚配好,别急着等它跑,它要等一个完整 interval 才会真正执行。

min_idle_hours默认 2 小时,这个门控只在 caller 提供idle_for_seconds测量值时才 enforce。maybe_run_curator的签名是idle_for_seconds: Optional[float] = None,如果 caller 不传,这个检查就跳过。所以它不是一个强制的空闲检测,而是给上层调用者一个可选的节流手段。

配置写完后,状态文件在~/.hermes/skills/.curator_state,是 JSON 格式,由atomic_json_write原子写入。你可以直接cat它看当前状态:

cat ~/.hermes/skills/.curator_state

正常应该看到last_run_at、run_count、paused等字段。如果文件不存在,说明 Curator 还没跑过第一次,load_state()会返回基础 dict。

4. 验证请求:技能注册、触发条件与自我进化链路检查

配置写完,接下来是验证。Curator 的验证分三层:技能注册是否正确、触发条件是否满足、自我进化链路是否闭合。我按顺序给可复制的命令和预期结果。

第一层,验证技能注册。Curator 只触碰 curator-managed 技能,判断依据是curated_report()返回的行。你可以用 CLI 看当前有哪些技能被纳入管理:

hermes curator status

这个命令会显示 Curator 的启用状态、暂停状态、上次运行摘要,以及技能统计。如果consolidate开着,还会显示 auxiliary 模型的解析结果。预期输出里应该能看到enabled: true、paused: false,以及技能总数。

再看使用遥测,确认技能的use_count、view_count、last_activity_at是否正常记录:

hermes curator usage --sort last_activity

这个命令列出所有技能的使用统计。重点看last_activity_at字段,它是apply_automatic_transitions计算 anchor 时间的首选来源。如果某个技能的last_activity_at是never,Curator 会 fallback 到created_at,再 fallback 到now。这意味着从未活跃的技能不会自我归档,因为它的 anchor 是创建时间,而创建时间通常很新。

第二层,验证触发条件。should_run_now()的门控有三个:is_enabled()、is_paused()、以及last_run_at是否超过interval_hours。你可以手动触发一次 dry-run 来预览会发生什么,而不实际修改技能库:

hermes curator run --dry-run

dry-run 模式下,run_curator_review会跳过apply_automatic_transitions,只计数候选,不执行任何转换。同时它会构建CURATOR_DRY_RUN_BANNER前置到 prompt,指示 LLM fork 只报告不修改。预期输出会告诉你“如果真跑,会归档哪些技能、会合并哪些技能”。

如果你想看完整的 dry-run 报告,去~/.hermes/logs/curator/下找最新的时间戳目录:

ls -lt ~/.hermes/logs/curator/ | head cat ~/.hermes/logs/curator/<最新时间戳>/REPORT.md

REPORT.md 里会分章节列出 consolidated、pruned、added、state_transitions、cron_rewrites。dry-run 的报告同样会写入,并记录在state.last_report_path,所以你可以反复预览。

第三层,验证自我进化链路。这一步要确认 Curator 的 fork 能正常调用 skill 工具。先确认 auxiliary 模型解析正确:

hermes curator status | grep -i model

预期能看到解析出的 model 和 provider。如果显示(not resolved),说明 auxiliary 配置没生效,检查auxiliary.curator.provider是否设成了非 auto 的值。

然后手动跑一次带 consolidate 的同步运行,观察工具调用:

hermes curator run --sync --consolidate

--sync让 LLM Pass 在调用线程执行,而不是 daemon 线程,这样你能直接看到输出。--consolidate强制开启合并 Pass,覆盖配置里的consolidate: false。预期输出会包含auto: N marked stale, M archived这样的摘要,以及llm: ...的合并结果。

如果合并 Pass 真的产生了 consolidation,摘要里会追加 rename 行,格式是• old-name → umbrella-name。同时_build_rename_summary会在有 umbrella 产生时提示你 pin 它:

hermes curator pin <umbrella-name>

pin 之后,这个技能就豁免所有自动转换,apply_automatic_transitions会直接跳过它。这是保护重要 umbrella 不被后续 stale 转换误伤的标准做法。

最后验证归档可恢复。找一个被归档的技能,确认它在.archive/下:

ls ~/.hermes/skills/.archive/ hermes curator restore <skill-name>

restore会把技能从归档目录移回活跃目录。这个动作验证了“只归档不删除”的不变量确实成立。

5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth

Curator 的报错大多集中在 auxiliary 模型解析和 fork 执行两个环节。我按真实遇到的频率排一下。

401 Unauthorized。最常见的原因是auxiliary.curator.api_key没配,或者配了但resolve_runtime_provider没拿到。Curator 的_resolve_review_runtime会把非空的api_key作为 explicit override 传给resolve_runtime_provider,这样它就不会复用主聊天的 credential chain。如果你只配了主模型的 Key,没配 auxiliary 的 Key,Curator 的 fork 可能拿不到凭证。检查方式:

grep -A6 "auxiliary:" ~/.hermes/config.yaml

确认curator子节点下有api_key和base_url。如果 Key 是对的还报 401,检查 Base URL 是不是写成了带路径的形式,正确值是https://taotoken.net/api。

local proxy failed。这个报错通常出现在 provider 解析阶段,resolve_runtime_provider尝试连接时失败。Curator 的_resolve_review_provider把异常吞掉并返回空 rp,然后_run_llm_review里AIAgent构造时可能因为base_url为空而走默认路径。排查顺序:先确认base_url配置正确,再确认网络能到达该地址。注意 Curator 的 fork 是quiet_mode=True且 stdout/stderr 重定向到 devnull 的,所以错误信息不会直接打印,你需要看run.json里的llm_error字段:

cat ~/.hermes/logs/curator/<时间戳>/run.json | grep -i error

reading choices 报错。这个通常意味着模型返回的响应格式不符合预期,run_conversation在解析choices字段时失败。Curator 的 fork 用的是api_mode从resolve_runtime_provider解析出来的模式,如果 provider 返回的是非标准格式,就会在这里炸。检查run.json里的model和provider字段,确认解析出的模型确实支持 OpenAI 兼容格式。如果模型 ID 写错了,provider 可能返回一个错误页面而不是 JSON,也会触发这个报错。

OAuth 相关报错。如果你用的是需要 OAuth 的 provider,Curator 的 fork 可能拿不到有效的 token。Curator 的_resolve_review_runtime支持credential_pool参数,但配置里需要显式提供。对于 TaoToken 这种 API Key 模式,不涉及 OAuth,所以如果你看到 OAuth 报错,说明 provider 解析 fallback 到了主模型,而主模型用的是 OAuth。解决办法是显式配auxiliary.curator.provider为非 auto 的值。

还有一个隐蔽的坑:_run_llm_review里review_agent._memory_nudge_interval = 0和_skill_nudge_interval = 0是为了防止 Curator 递归 spawn 自己的 review。如果你在日志里看到 Curator 反复触发,检查这两个值是否被覆盖。正常情况下它们应该是 0。

排查时统一看run.json,它是机器可读的完整记录,包含tool_call_counts、archived、consolidated、pruned、state_transitions、cron_rewrites、llm_error等字段。REPORT.md 是它的可读渲染版,但排查细节时 run.json 更全。

6. 把 Curator 接进你的日常开发流

配置和验证都跑通之后,Curator 的日常使用其实很轻。它默认在 Agent 空闲时自动触发,你不需要手动干预。但有几个操作值得养成习惯。

第一,重要 umbrella 创建后立刻 pin。_build_rename_summary会在 consolidation 产生 umbrella 时提示你 pin,但如果你自己手动创建了 umbrella,也应该主动 pin。pin 之后它豁免所有自动转换,不会被 stale 或 archive 误伤。

第二,定期看hermes curator status和hermes curator usage。status 给你整体健康度,usage 给你每个技能的活跃度。如果发现某个技能use_count长期为 0 但last_activity_at很新,说明它可能只是被 view 过没被真正调用,Curator 的use_count == 0保护逻辑会让它暂时不被归档。

第三,dry-run 是你的朋友。在调整stale_after_days或archive_after_days之前,先跑一次hermes curator run --dry-run,看新阈值下会归档哪些技能。确认无误再跑真实运行。

第四,归档不是终点。hermes curator restore <name>随时能把技能捞回来,hermes curator list-archived能看归档列表。所以不用怕 Curator 误判,它的最大破坏性操作就是归档,而归档可恢复。

如果你想让 Curator 的合并 Pass 长期稳定运行,建议把 auxiliary 切到 Coding Plan( https://taotoken.net/coding-plan ),它的额度模型对周期性后台任务更友好。接入文档在 https://taotoken.net/doc ,里面有 provider 配置的完整说明。模型对话页面在 https://taotoken.net/models ,可以用来快速验证某个模型是否适合做 Curator 的合并 Pass。

最后一步,确认你的 Curator 状态文件里run_count在增长,last_run_summary有内容,last_report_path指向最新的报告目录。这三个字段正常,说明自我进化链路已经闭合,Curator 在按预期工作。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进