Hermes Curator 实现原理深度解析

0 阅读6分钟

Hermes Curator 实现原理深度解析

Hermes Agent 在v0.12.0 版本中新增了Curator功能,我们一起来拆解一下。自建Agent的同学可以考虑借鉴Hermes的思想。

数据来源:PR #17277#17307#17941#18033


Hermes Curator 实现原理

1. 架构总览

Curator 不是一个独立进程,而是钩挂在 Gateway 既有 cron ticker 线程上的惰性后台任务。核心代码分布在以下新增文件:

文件职责
agent/curator.py核心编排:配置读取、空闲门控、状态机迁移、LLM fork 调用
tools/skill_usage.py技能使用数据的 sidecar .usage.json I/O,原子写入
hermes_cli/curator.pyCLI 子命令: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 调用噪音。

Curator 双重空闲门控决策树


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() 读取用户当前配置,然后显式传入 providermodelapi_keybase_urlapi_mode 来实例化一个子 AIAgent——这解决了早期版本用空凭据触发 HTTP 400 的问题。

关键参数:

  • max_iterations=9999(早期版本 8 次太低,一次完整 umbrella pass 需要 50-100 次 API 调用)
  • 工具集限制为 skills_listskill_viewskill_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原版 #1604900仅扫描,无动作
2合并版443发现重复但未伞型化
3umbrella-first24918346 → 118 个技能(-66%),所有内容保留在 references/

Curator 两阶段执行对比


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_countview_countlast_used_atpatch_count(被修改次数),所有写入原子化以防并发损坏。


7. 安全不变量(load-bearing invariants)

PR 明确标注了四条"承重不变量"——任何修改这些逻辑的 PR 都会导致测试失败:

  1. 永不碰内置/Hub 技能:双重过滤 .bundled_manifest + .hub/lock.json,代码层面无法绕过
  2. 永不删除,只归档:所有操作最终调用 terminal 移动到 .archive/,可通过 hermes curator restore <skill> 恢复
  3. 被 pin 的技能跳过所有自动迁移:用户主动 pin,模型永远不会自动 pin
  4. Fork 继承父级完整运行时:凭据、Provider、Model 原样透传,OAuth-only 和 pool-backed 凭据都能正常工作

4 条承重不变量


8. 可观测性:run.json + REPORT.md

每次运行后在 ~/.hermes/logs/curator/{YYYYMMDD-HHMMSS}/ 写入两个文件(PR #17307):

  • run.json:机器可读,含完整 LLM 最终响应、所有 tool_call 记录、before/after 技能数量 diff
  • REPORT.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 在有限工具集和严格安全边界内做提案,基础设施负责最终执行"。