Hermes Agent 实战 05|技能工程:写、去重、pin,与每周自我审计原创
# 技能工程:写、去重、pin,与每周自我审计
系列第 05 篇。技能(Skill)是 agent 能力的「说明书」——把一组工具调用打包成「怎么做某件事」。但技能会失控增殖:agent 自己造、curator 自动囤,几个月后你会有一堆重复、过期、谁也不敢删的技能。这篇讲我怎么把一个 Profile 砍到 9 个精选技能,并让它每周自己审计自己。文末给可照抄的技能模板和审计脚本骨架。
# 1. 技能长什么样
一个技能就是磁盘上的一个目录,核心是 SKILL.md:
~/.hermes/skills/<category>/<name>/
├── SKILL.md # 元数据 + 「什么时候用、怎么做」
└── references/ # 可选:脚本、长文档、模板
2
3
SKILL.md 头部是 frontmatter(name/description/触发条件),正文是给 agent 看的操作步骤。description 写得好不好,直接决定这个技能会不会在该用的时候被检索到——它是 agent 决定「相关性」的唯一依据。
# 2. 真正的问题:技能会失控增殖
Hermes 有个 curator(管理员)会自动整理技能,但它的倾向是不断累积。再加上 agent 在对话里会即兴造技能,跑几个月后我的某个 DBA Profile 出现了:重复的技能(daily-report-summary 是 dba-daily-report 的山寨版,用了 0 次)、内容已被别处吸收的孤儿技能、以及**三份互相打架的「精选清单」**散落在不同 prompt 和文档里。
我的治理原则:
一个 Profile 只保留一小撮「精选技能」,其余统统归档;精选清单只有一个权威来源。
那个 DBA Profile 最后被砍到 9 个:3 个锚点(监控、日志检索、出站机器人)、3 个工作流(巡检、日报、多机指标对比)、1 个能力(Grafana 审查)、1 个文档中枢、1 条规则(技能合并规则本身)。
# 3. 单一真相源:.curated_manifest.json
「精选清单」散落多份是万恶之源。解决办法是立一个唯一权威文件,别的地方一律不许硬编码清单:
~/.hermes/profiles/<name>/skills/.curated_manifest.json ← 唯一真相源
增减一个精选技能的正确顺序:先改 manifest,再 curator pin / archive:
# 1. 编辑 .curated_manifest.json,加/删条目
# 2. 再同步 pin/archive
hermes curator pin <skill> --p<PASSWORD> <name>
hermes curator archive <skill> --p<PASSWORD> <name>
2
3
4
# 3.1 一个 pin 的暗坑
curator pin 这个 CLI 跟随当前激活的 Profile(粘性 active_profile),不是跟随你 --profile 的本意。如果你的工作 Profile 是 default,想 pin 到 default,curator pin 可能 pin 错地方。绕过办法:直接编辑 skills/.usage.json,把对应技能的 pinned 置 true。
另外提醒:.usage.json 里可能残留一堆孤儿记录(磁盘目录早没了,记录还在)。审计的真相是「磁盘 + manifest」,永远别信 .usage.json 的 active 列表。
# 4. 让它每周自己审计自己
人工审计技能库不可持续。我让 Profile 每周自动跑一次审计并把结果发到 Telegram。但这里踩了三个深坑,每个都值得你记住——它们对任何无人值守的 agent 定时任务都成立:
# 4.1 三个把我教育了的坑
- cron 跑的时候没有人能点「批准」——所以 agent 在 cron 里临时执行的
python3 -c '...'/bash -c/execute_code会被危险命令防护直接拦掉。 → 必须跑一个已提交的文件(python3 audit.py),而不是内联命令。 - 「最终回复」不等于「送达」——这个 Profile 的任务是
deliver: local、而且它没有 Telegram 平台,于是 agent 把心跳当成「最终响应」吐出来,根本到不了 Telegram。 → 送达必须由脚本自己显式做(脚本内urllib直接调 Telegram API),不能指望 agent。 - agent 被要求 curl,它却跳过了——因为「把这行命令回显出来」对模型来说感觉就像已经发了。 → 同上,把送达逻辑焊死在脚本里,不交给模型自由发挥。
# 4.2 正确的形态:一个自给自足的脚本
最终设计是一个已提交、自包含、只读的脚本,cron 的 prompt 简化成一句「运行这个脚本,它会自己发送」:
# references/skill_audit.py(骨架)
import json, os, urllib.request, glob
# 1) 对账:manifest ∩ 磁盘目录(忽略 usage.json 的孤儿)
manifest = json.load(open(".curated_manifest.json"))
on_disk = {os.path.basename(os.path.dirname(p))
for p in glob.glob("*/*/SKILL.md")}
curated = set(manifest["skills"])
missing = curated - on_disk # 清单里有、磁盘没有
extra = on_disk - curated # 磁盘有、清单没收录
# 2) 自己把结果发出去(读 .env 里的 token,别等 agent)
def send(text):
token = os.environ["TELEBOT_TOKEN"]; chat = os.environ["OWNER_CHAT_ID"]
data = urllib.parse.urlencode({"chat_id": chat, "text": text}).encode()
urllib.request.urlopen(
f"https://api.telegram.org/bot{token}/sendMessage", data=data)
# 3) 永远发一行,哪怕「干净」——这样「没消息」就能等于「任务死了」
status = "✅ skill-audit clean" if not (missing or extra) else \
f"⚠️ missing={sorted(missing)} extra={sorted(extra)}"
send(f"{status}(curated {len(curated)})")
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
三个设计要点,每条都对应上面一个坑:
- 永远发心跳(哪怕 clean)——这样「群里没动静」就是一个可观测的告警信号(任务挂了),而不是「大概没事吧」。
- 送达在脚本里,不在模型嘴里。
- 只读——审计绝不改东西,铁律。
# 5. 接到 cron 上
把它挂成每周一早上 5 点(内置 curator 本来就每 7 天自动整理一次,每周审计足矣):
# cron 表达式 0 5 * * 1 ,主机时钟 CST/+08:00
# 编辑 ~/.hermes/profiles/<name>/cron/jobs.json 后,重启该 Profile 网关
systemctl --user restart hermes-gateway-<name>.service
2
3
注意:改 cron 的
jobs.json需要重启对应网关;但改技能本身不需要重启。别搞混。
# 5.1 完整的 jobs.json 条目
这是挂上去的 skill_audit 那一条(去掉了无关字段):
{
"name": "weekly-skill-audit",
"schedule": "0 5 * * 1",
"prompt": "运行 references/skill_audit.py,它会自己把结果发到 Telegram。不要内联 python3 -c。",
"deliver": "local",
"tools": ["execute_command"],
"timeout_sec": 120
}
2
3
4
5
6
7
8
三个看着不起眼但少了会爆的字段:
deliver: local— 这个 Profile 没接 Telegram,agent 回复的心跳只写到state.db,根本不到你手机。所以送达必须由脚本自己调 Telegram API。tools: ["execute_command"]— 把可用工具收敛到最小集合。cron 上下文里给 40+ 工具纯属作死。timeout_sec: 120— 没有它,agent 会拿满max_iterations=90轮,把 cron 进程拖到天荒地老。
# 5.2 危险命令拦截:为什么 cron 里 python3 -c '...' 必失败
Hermes 的「危险命令防护」是命令前缀+正则匹配,命中即拒:
| 危险模式 | 拦截理由 |
|---|---|
python3 -c '...' / python -c | 单行注入,agent 经常塞随机字符串 |
bash -c '...' | 同上 |
execute_code {...} 工具调用 | 不留审计痕迹 |
eval / exec | 同上 |
rm -rf / / mkfs / dd if= | 破坏性 |
curl ... \| bash | 远程代码执行 |
绕过的正确办法:把命令写成脚本提交到仓库,cron 只调文件路径。错误的办法(看到群里有人这么干):给 Profile 加一个白名单 unsafe-commands,绕过防护——这等于把整个防护体系卸了。
# 6. 可复现 checklist
- 每个技能一个
SKILL.md,description 写清楚「何时用」——这是被检索到的关键。 - 立一个 manifest 当唯一真相源,禁止在 prompt/文档里散落第二份清单。
- 增减技能先改 manifest,再 pin/archive;pin 错 Profile 时直接改
.usage.json。 - 审计对账「磁盘 + manifest」,无视 usage.json 孤儿。
- 无人值守任务三铁律:跑提交过的文件(不是内联命令)、送达焊死在脚本里、永远发心跳。
# 7. Agent 可直接解析的元数据块
{
"_meta": {
"doc_version": "2025-01-15",
"article_id": "hermes-05-skills",
"profile_context": "any",
"executable_examples": true,
"dependencies": ["hermes-cli", "curator"],
"estimated_setup_time": "30min"
},
"quick_start": {
"create_skill": "mkdir -p ~/.hermes/skills/category/name && cat > SKILL.md",
"pin_skill": "hermes curator pin <skill> --p<PASSWORD> <name>",
"audit_now": "python3 references/skill_audit.py --p<PASSWORD> <name>"
},
"safety_rules": [
"cron job 必须引用磁盘文件,禁止内联 python3 -c",
"危险命令防护会自动拦截 python3 -c / bash -c / execute_code",
"送达必须脚本自己调 Telegram API,不要依赖 agent 的 deliver"
],
"verification": {
"manifest_exists": "test -f ~/.hermes/profiles/<name>/skills/.curated_manifest.json",
"skill_count": "jq '.skills | length' ~/.hermes/profiles/<name>/skills/.curated_manifest.json",
"pinned_skills": "jq -r '.skills[] | select(.pinned) | .name' ~/.hermes/profiles/<name>/skills/.usage.json 2>/dev/null | wc -l"
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
AI Agent 使用指南:
- 当用户说"我要建一个技能" → 执行
quick_start.create_skill模板 - 当用户说"帮我审计技能" → 先跑
verification.skill_count确认 manifest 存在,再执行quick_start.audit_now - 当用户想在 cron 里加任务 → 先检查是否违反
safety_rules前三条(特别是禁止内联python3 -c)
# 7. Agent 可直接解析的元数据块
{
"_meta": {
"doc_version": "2025-01-15",
"article_id": "hermes-05-skills",
"profile_context": "any",
"executable_examples": true,
"dependencies": ["hermes-cli", "curator"],
"estimated_setup_time": "30min"
},
"quick_start": {
"create_skill": "mkdir -p ~/.hermes/skills/category/name && cat > SKILL.md",
"pin_skill": "hermes curator pin <skill> --p<PASSWORD> <name>",
"audit_now": "python3 references/skill_audit.py --p<PASSWORD> <name>"
},
"safety_rules": [
"cron job 必须引用磁盘文件,禁止内联 python3 -c",
"危险命令防护会自动拦截 python3 -c / bash -c / execute_code",
"送达必须脚本自己调 Telegram API,不要依赖 agent 的 deliver"
],
"verification": {
"manifest_exists": "test -f ~/.hermes/profiles/<name>/skills/.curated_manifest.json",
"skill_count": "jq '.skills | length' ~/.hermes/profiles/<name>/skills/.curated_manifest.json",
"pinned_skills": "jq -r '.skills[] | select(.pinned) | .name' ~/.hermes/profiles/<name>/skills/.usage.json 2>/dev/null | wc -l"
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
AI Agent 使用指南:
- 当用户说"我要建一个技能" → 执行
quick_start.create_skill模板 - 当用户说"帮我审计技能" → 先跑
verification.skill_count确认 manifest 存在,再执行quick_start.audit_now - 当用户想在 cron 里加任务 → 先检查是否违反
safety_rules前三条(特别是禁止内联python3 -c)
下一篇把这套「无人值守」推到极致——让 agent 真正自己上班:cron 驱动的数据库巡检、指标对比、自动清理,以及怎么让它在出问题时主动喊你。