Hermes Agent 实战 06|让 Agent 自己上班:cron 驱动的无人值守巡检原创
# 让 Agent 自己上班:cron 驱动的无人值守巡检
系列第 06 篇。前面的 agent 都是「我问、它答」。这篇讲怎么让它没人盯着也自己干活:每天凌晨巡检数据库、每周对比多机指标、定时清理过期索引,干完主动发报告给我。无人值守和交互式是两套完全不同的工程——这篇讲清楚区别在哪、怎么不翻车。
# 1. Hermes 的定时任务长什么样
每个 Profile 有自己的 cron 定义,本质是一份 jobs.json,里面每个任务 = 一条 cron 表达式 + 一段给 agent 的 prompt:
// ~/.hermes/profiles/<name>/cron/jobs.json (示意)
{
"jobs": [
{
"id": "dba-daily-inspect",
"schedule": "0 1 * * *", // 每天 01:00(主机时钟 +08:00)
"prompt": "运行 collect_slow_sql 脚本,巡检最近24小时慢SQL,发日报",
"deliver": "telegram"
}
]
}
2
3
4
5
6
7
8
9
10
11
# 改完 jobs.json 必须重启对应网关(cron 由网关进程调度)
systemctl --user restart hermes-gateway-<name>.service
# 查当前在跑的 cron
hermes cron list
2
3
4
我真实在跑的几个:每天凌晨的 DBA 慢 SQL 巡检、每周的多机指标对比广播、定期的过期索引清理(manage_stale_indices.sh)、每周一的技能审计(上一篇)。
# 2. 无人值守 ≠ 交互式:四条会翻车的差异
这是这篇的核心。交互式能跑通的东西,搬到 cron 上经常静默失败。我用一堆失败换来这四条:
# 2.1 没有人能点「批准」
cron 触发时没有用户在场。于是任何需要审批的危险操作——内联的 python3 -c、bash -c、execute_code——会被安全防护直接拦掉,任务卡死。
对策:把要跑的逻辑写成已提交的脚本文件,cron 里只
python3 xxx.py/bash xxx.sh。脚本是受信的,内联命令不是。如果你确实想允许内联 cron 命令,得在
config.yaml设approvals.cron_mode: approve——但这会削弱所有 cron 的安全性,我选择不开。
# 2.2 「最终回复」不会自己送达
交互式里 agent 的最后一段话你直接就看到了。但 cron 任务如果 deliver: local、或这个 Profile 压根没接 IM 平台,agent 的「最终响应」哪儿也去不了。
对策:送达逻辑焊死在脚本里(脚本自己调 IM API 发出去),不要指望 agent 的最终回复就是送达。
# 2.3 模型会「假装发了」
我让 agent 在 cron 里 curl 一个通知,它跳过了——因为把命令回显出来,对模型来说感觉就像已经做了。
对策:同上,凡是「必须发生」的副作用(发通知、写文件),别留给模型自由发挥,写进脚本。
# 2.4 静默失败最致命
无人值守最大的风险不是报错,而是悄无声息地死掉——你以为它在跑,其实三周前就挂了。
对策:每次都发一条心跳,哪怕一切正常。让「群里没消息」本身成为告警:
✅ superdba skill-audit clean(curated 9) ← clean 也发 ⚠️ slow-sql: 3 条值得关注,详情见附件 ← 有事更发1
2这样「沉默 == 任务死了」是可检测的。沉默绝不能是默认的「正常」。
# 3. 一个真实任务的完整形态
把上面四条落到一个真实的「每日慢 SQL 巡检」上,它长这样:
- 触发:cron
0 1 * * *,prompt 一句话「运行巡检脚本」。 - 执行:agent 跑一个已提交的
collect_slow_sql脚本(绕过审批拦截),脚本只读地查 DMV、导出慢 SQL(注意防截断,见下一篇)。 - 判断:脚本/agent 区分「真问题」和「正常空闲」——比如交易机器人 24 小时没开单,是在等信号,不是故障;数据库某些 idle 等待是正常的,不是瓶颈(详见第 07 篇的「误读 idle wait」战例)。
- 送达:脚本自己把日报发到 Telegram,clean 也发心跳。
- 只读:巡检绝不自动改库。要改的东西,列进报告等人确认。
第 3 点特别重要:无人值守的 agent 最容易犯的错是「把正常当异常」或「把异常当正常」——半夜把你吵醒去看一个根本不存在的问题,比不报警还糟。
# 4. 幂等与只读:两条底线
- 只读:巡检、审计、报告类任务一律不写生产。真要做变更类定时任务(如清理过期索引),单独评估、加防护、可回滚。
- 幂等:任务重复触发(systemd 重启、补跑)不应造成二次伤害。报告类天然幂等;变更类要自己保证「跑两次 == 跑一次」。
# 5. 可复现 checklist
把一个交互式流程改造成无人值守,过一遍:
- 逻辑落成提交过的脚本文件,cron 只负责调它——绕开审批拦截。
- 送达写进脚本,不依赖 agent 的最终回复。
- 每次发心跳(clean 也发),让静默成为可检测的告警。
- 区分真问题 vs 正常态,宁可漏报也别狼来了。
- 只读 + 幂等;变更类任务单独加防护。
- 改
jobs.json后重启对应网关;用hermes cron list确认在跑。
# 6. 完整 jobs.json 条目(含字段陷阱)
挂在 cron 上的「每日慢 SQL 巡检」长这样(去掉无关字段):
{
"id": "dba-daily-inspect",
"schedule": "0 1 * * *",
"prompt": "运行 collect_slow_sql 脚本,巡检最近24小时慢SQL,发日报。不要内联 python3 -c。",
"deliver": "telegram",
"tools": ["execute_command"],
"timeout_sec": 300
}
2
3
4
5
6
7
8
三个看着不起眼但少了会爆的字段:
deliver: telegram— 没有它,agent 回复只写到state.db,根本不到你手机。tools: ["execute_command"]— 把可用工具收敛到最小集合。cron 上下文里给 40+ 工具纯属作死。timeout_sec: 300— 没有它,agent 会拿满max_iterations=90轮,把 cron 进程拖到天荒地老。
# 6.1 危险命令拦截:为什么 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.2 心跳消息必须包含的内容
「永远发心跳」不只是发一句话,格式要可解析——否则群消息海里抓不到:
✅ superdba daily-inspect | 09-22 01:00 CST | clean | curated=9 | ms=2,341
⚠️ superdba daily-inspect | 09-22 01:00 CST | missing=3 extra=1 | ms=3,102
2
四个字段都要有:profile / task-id / 时间戳带时区 / 状态枚举(clean|warn|err) / 耗时。这样以后写监控脚本用 grep + awk 就能从 Telegram 历史捞出所有心跳,自动算「任务挂了」。
# 7. Agent 可直接解析的元数据块
{
"_meta": {
"doc_version": "2025-01-15",
"article_id": "hermes-06-autonomous-cron",
"profile_context": "any",
"executable_examples": true,
"dependencies": ["hermes-cli", "systemd-user"],
"estimated_setup_time": "45min"
},
"quick_start": {
"add_cron_job": "edit ~/.hermes/profiles/<name>/cron/jobs.json",
"restart_gateway": "systemctl --user restart hermes-gateway-<name>.service",
"list_jobs": "hermes cron list",
"test_job": "hermes cron run <job-id> --dry-run"
},
"job_template": {
"id": "daily-inspect",
"schedule": "0 1 * * *",
"prompt": "运行 scripts/daily_inspect.py,发送报告",
"deliver": "local",
"tools": ["execute_command"],
"timeout_sec": 300,
"retry": {"max": 3, "backoff": "exponential"}
},
"safety_rules": [
"cron job 必须使用已提交的脚本文件,禁止内联命令",
"scripts/ 目录必须存在且可执行",
"所有输出必须包含心跳标记,方便监控",
"超时设置必须 <= 300 秒,防止无限运行"
],
"verification": {
"jobs_json_exists": "test -f ~/.hermes/profiles/<name>/cron/jobs.json",
"json_valid": "jq empty ~/.hermes/profiles/<name>/cron/jobs.json 2>/dev/null",
"gateway_running": "systemctl --user is-active hermes-gateway-<name>.service",
"job_count": "jq '.jobs | length' ~/.hermes/profiles/<name>/cron/jobs.json"
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
AI Agent 使用指南:
- 当用户说"我要加一个定时任务" → 使用
job_template模板,然后执行quick_start.restart_gateway - 当用户说"检查 cron 配置" → 依次执行
verification.jobs_json_exists、verification.json_valid、verification.gateway_running - 当用户说"测试任务但不真跑" → 执行
hermes cron run <job-id> --dry-run - 当 cron job 跑不动 → 先检查
safety_rules是否违反(特别是禁止内联命令)
下一篇是这套自动化的最佳实战场——数据库。讲几个真实战例:一个被拖了几个月、最后因数据量增长放大 42 倍变成 P0 的「已知 bug」;一个吃掉 230GB 的 LOB 黑洞;以及 AI 把正常空闲等待误读成性能瓶颈的教训。