Hermes Curator 实现原理深度解析
Hermes Agent 在v0.12.0 版本中新增了Curator功能,我们一起来拆解一下。自建Agent的同学可以考虑借鉴Hermes的思想。
1. 架构总览
Curator 不是一个独立进程,而是钩挂在 Gateway 既有 cron ticker 线程上的惰性后台任务。核心代码分布在以下新增文件:
| 文件 | 职责 |
|---|---|
agent/curator.py | 核心编排:配置读取、空闲门控、状态机迁移、LLM fork 调用 |
tools/skill_usage.py | 技能使用数据的 sidecar .usage.json I/O,原子写入 |
hermes_cli/curator.py | CLI 子命令:status/run/pause/resume/pin/unpin/restore |
gateway/run.py | 在 cron ticker 中注入 maybe_run_curator() |
2. 触发条件:双重空闲门控
Curator 的核心设计目标是零打扰,因此采用了两层门控,而非时钟调度:
CLI 启动 / Gateway 启动
│
▼
检查 .curator_state
├── last_run_at 距今 < interval_hours(默认 7 天)? → 跳过
└── 满足时间条件
│
▼
agent 空闲时间 ≥ min_idle_hours(默认 2 小时)?
├── 否 → 跳过(用户正在使用中)
└── 是 → 启动后台线程执行 Curator
为什么不用 cron 调度? PR 描述明确说明:使用 inactivity-triggered 模式,避免在用户活跃使用时消耗 token 配额和产生 API 调用噪音。
3. 第一阶段:纯规则状态机(无 LLM)
在调用任何 LLM 之前,Curator 先做一次完全确定性的自动迁移:
对每个 agent 创建的技能:
├── 上次使用 > 30 天 且状态=active → 标记为 stale
├── 上次使用 > 90 天 且状态=stale → 归档到 ~/.hermes/skills/.archive/
└── 曾被标记 stale 但近期被使用 → 重新激活为 active
这一阶段完全不消耗 token,是对"显然废弃"技能的快速清理。
4. 第二阶段:LLM Umbrella-Building Pass
这是 Curator 的核心,也是技术含量最高的部分。
4.1 Fork 一个独立 AIAgent
_run_llm_review() 通过 load_config() + resolve_runtime_provider() 读取用户当前配置,然后显式传入 provider、model、api_key、base_url、api_mode 来实例化一个子 AIAgent——这解决了早期版本用空凭据触发 HTTP 400 的问题。
关键参数:
max_iterations=9999(早期版本 8 次太低,一次完整 umbrella pass 需要 50-100 次 API 调用)- 工具集限制为
skills_list、skill_view、skill_manage(patch/create/write_file)、terminal(用于归档),禁止其他工具,防止 Curator 扩权
4.2 Umbrella-First Prompt 策略
PR #17277 的核心贡献是重写了 Curator 的系统提示,从"被动审计"改为"主动伞型合并":
原始提示(#16049)让模型倾向于在技能不完全相同时保持 keep,导致 LLM pass 基本无效(测试中 346 个技能只归档了 3 个)。
新提示的核心要求:
- 定性框架:"这是一次 UMBRELLA-BUILDING 合并 pass,不是被动审计"
- 判断标准:"维护者会将这 N 个技能写成 N 个独立文件,还是一个带 N 个小节的 SKILL.md?"
- 三种合并手段:合并入已有伞型技能 / 创建新伞型 SKILL.md / 降级为
references/、templates/、scripts/支持文件 - 预设反驳:明确告知模型"使用次数为 0"不是拒绝合并的理由(按内容判断,不按 use_count 判断);"各有独立触发词"也不是理由(两两独立性是错误标准)
实际效果(作者本人测试,346 个技能,opus-4.7,86 次 API 调用,约 6.5 分钟,缓存命中率 99%,费用约 $4-7):
| 轮次 | 版本 | 合并数 | 归档数 | 结果 |
|---|---|---|---|---|
| 1 | 原版 #16049 | 0 | 0 | 仅扫描,无动作 |
| 2 | 合并版 | 44 | 3 | 发现重复但未伞型化 |
| 3 | umbrella-first | 249 | 18 | 346 → 118 个技能(-66%),所有内容保留在 references/ |
5. 第三阶段:结果分类(混合模型声明 + 启发式)
PR #17941 解决了一个 UX 问题:用户看到技能"被归档",不知道是因为真正废弃(pruning)还是内容已被吸收到了新的伞型技能(consolidation),导致误用 hermes curator restore 产生重复。
解决方案是双信号分类:
信号 1:模型结构化 YAML 声明
Curator Prompt 要求模型在最终响应末尾输出一个结构化 YAML 块:
consolidations:
- from: anthropic-api
into: llm-providers
reason: duplicate content, now a subsection
prunings:
- name: random-old-notes
reason: pre-curator junk, no overlap with live skills
信号 2:Tool-call 启发式审计
扫描本次运行中所有 skill_manage 调用,检查是否有对幸存技能的 write/patch/create 操作引用了被删除技能的名字。捕捉两种偏差:
- 模型漏报(真实合并了但 YAML 里没写)
- 模型幻觉(YAML 里写了一个不存在的伞型目标)
_reconcile_classification() 合并逻辑:
模型声明 umbrella 存在 → 以模型 rationale 为准
模型声明 umbrella 不存在(幻觉)→ fallback 到启发式,或标记为 prune
仅启发式发现 → 标注 "(detected via tool-call audit)"
模型声明 prune + rationale → 原文呈现 reason
6. 使用计数基础设施(skill_usage.py)
Curator 的判断依赖可靠的使用数据。PR #17932(先于 #17277)在三个真实激活路径上打点:
- 斜线命令调用(slash invocation)
--skill预加载skill_view工具调用
每个技能目录下维护一个 .usage.json sidecar 文件,记录 use_count、view_count、last_used_at、patch_count(被修改次数),所有写入原子化以防并发损坏。
7. 安全不变量(load-bearing invariants)
PR 明确标注了四条"承重不变量"——任何修改这些逻辑的 PR 都会导致测试失败:
- 永不碰内置/Hub 技能:双重过滤
.bundled_manifest+.hub/lock.json,代码层面无法绕过 - 永不删除,只归档:所有操作最终调用
terminal移动到.archive/,可通过hermes curator restore <skill>恢复 - 被 pin 的技能跳过所有自动迁移:用户主动 pin,模型永远不会自动 pin
- Fork 继承父级完整运行时:凭据、Provider、Model 原样透传,OAuth-only 和 pool-backed 凭据都能正常工作
8. 可观测性:run.json + REPORT.md
每次运行后在 ~/.hermes/logs/curator/{YYYYMMDD-HHMMSS}/ 写入两个文件(PR #17307):
run.json:机器可读,含完整 LLM 最终响应、所有 tool_call 记录、before/after 技能数量 diffREPORT.md:人类可读,含自动迁移摘要、LLM 合并结果、归档列表(前 50 条内联)、恢复命令提示
hermes curator status 输出示例:
curator: ENABLED
runs: 3
last run: 2d ago
last summary: auto: no changes; llm: Consolidated nothing; tiny test run.
last report: /home/me/.hermes/logs/curator/20260429-062119
interval: every 7d
stale after: 30d unused
archive after: 90d unused
most used (top 5):
my-dev-workflow use=42 view=0 last_used=1d ago
...
least used (top 5):
old-experiment use=0 view=0 last_used=never
...
总结:设计哲学
Curator 的整体设计遵循一条主线:LLM 负责意图判断,确定性代码负责执行安全边界。
- 规则状态机做"无争议"的时间维度清理
- LLM 做"内容相似性"的语义判断(这是纯规则做不到的)
- Tool-call 审计做"LLM 幻觉"的校正
- 所有破坏性操作(删除)被架构性禁止,降级为可逆归档
这套架构与其说是"AI 做决策",不如说是"AI 在有限工具集和严格安全边界内做提案,基础设施负责最终执行"。