自动化利器:Cron 定时任务
本教程共 25 篇 · 第 16 篇 · 更新于 2026-07-26 · 约 20 分钟阅读
16. 自动化利器:Cron 定时任务
本节目标:搞懂 Hermes Agent 的定时任务系统怎么用—从创建任务、调度格式、结果投递,到无 Agent 脚本模式和任务链式编排,学完你能让智能体在未来某个时间主动做事。
到上一章为止,Agent 的工作方式都是「用户说一句,它做一句」。用户不说话,它就闲着。但真实场景里你会说:
- 「30 分钟后帮我看看部署有没有成功。」
- 「每 2 小时查一次磁盘,超过 80% 就告警。」
- 「每个工作日早上 9 点总结昨天的 git log。」
这些需求的共同点:你希望 Agent 在未来某个时间主动做事。这就是定时任务(Cron)要解决的问题。
核心理念:任务即消息
Hermes 的定时任务有个很妙的设计—到期后不是直接执行命令,而是构造一条 MessageEvent,伪装成用户消息送进 Gateway。从 Gateway 往下,所有逻辑完全复用:工具调用、错误恢复、权限检查、上下文压缩,一行都不用重写。
打个比方:定时任务就像一个闹钟,到点了自己替你发一条消息给 Agent。Agent 不知道这是闹钟发的还是你发的,它照常处理、照常回复,结果自动路由回你原来的聊天窗口。
统一的 cronjob 工具
当前最新稳定版 v2026.7.20 把所有定时任务操作收拢进一个 cronjob 工具,用 action 参数区分操作类型,而不是给你一堆 schedule/list/remove 分散工具:
cronjob(action="create", ...)
cronjob(action="list")
cronjob(action="update", job_id="...")
cronjob(action="pause", job_id="...")
cronjob(action="resume", job_id="...")
cronjob(action="run", job_id="...")
cronjob(action="remove", job_id="...")
这意味着你可以直接用自然语言让 Agent 管理任务,不需要碰命令行:
每天早上 9 点查一下 Hacker News 的 AI 新闻,发摘要到我的 Telegram。
Agent 会自动调用 cronjob(action="create", ...) 把任务建好。
三种创建方式
方式一:聊天里用 /cron 命令
/cron add 30m "提醒我检查构建结果"
/cron add "every 2h" "检查服务器状态"
/cron add "every 1h" "总结新的订阅源条目" --skill blogwatcher
/cron add "every 1h" "同时用两个技能并合并结果" --skill blogwatcher --skill maps
方式二:命令行 CLI
hermes cron create "every 2h" "检查服务器状态"
hermes cron create "every 1h" "总结订阅源" --skill blogwatcher
hermes cron create "every 1h" "合并两个技能的结果" \
--skill blogwatcher \
--skill maps \
--name "Skill combo"
方式三:自然语言
直接跟 Agent 说人话,它会自己调 cronjob 工具:
Every morning at 9am, check Hacker News for AI news and send me a summary on Telegram.
调度格式
Hermes 支持四种调度格式,覆盖从「30 分钟后」到「每月第一天」的各种场景:
相对延时(一次性)
30m -> 30 分钟后执行一次
2h -> 2 小时后执行一次
1d -> 1 天后执行一次
这种格式最常用—用户说「30 分钟后提醒我」,直接写 30m 就行,不用算 cron 表达式。
间隔(循环)
every 30m -> 每 30 分钟
every 2h -> 每 2 小时
every 1d -> 每天
Cron 表达式(五字段)
0 9 * * * -> 每天 9:00
0 9 * * 1-5 -> 工作日 9:00
0 */6 * * * -> 每 6 小时
30 8 1 * * -> 每月 1 号 8:30
0 0 * * 0 -> 每周日 0:00
Note五字段顺序是:分 时 日 月 星期。星期 0 和 7 都是周日。
ISO 时间戳(一次性)
2026-03-15T09:00:00 -> 2026 年 3 月 15 日 9:00 执行一次
重复次数控制
默认情况下,延时和 ISO 时间戳跑一次,间隔和 cron 表达式永久循环。可以用 repeat 覆盖:
cronjob(
action="create",
prompt="...",
schedule="every 2h",
repeat=5, # 只跑 5 次就停
)
结果投递
定时任务跑完后,结果往哪发?deliver 参数控制这件事。
投递目标速查
| 目标 | 示例 | 说明 |
|---|---|---|
origin | --deliver origin | 发回创建任务时的聊天(消息平台默认) |
local | --deliver local | 只存本地文件(CLI 默认) |
telegram | --deliver telegram | Telegram home 频道 |
telegram:123456 | --deliver telegram:123456 | 指定 Telegram 聊天 |
telegram:-100123:17585 | --deliver telegram:-100123:17585 | 指定 Telegram 话题(chat_id:thread_id) |
discord | --deliver discord | Discord home 频道 |
discord:#engineering | --deliver discord:#engineering | 按频道名指定 |
feishu | --deliver feishu | 飞书 home 频道 |
all | --deliver all | 扇出到所有已连接的 home 频道 |
telegram,discord | --deliver telegram,discord | 指定多个平台 |
origin,all | --deliver origin,all | origin 加上所有其他频道 |
all 是在触发时动态解析的—你创建任务时还没接 Telegram,后来接上了,下次触发时就会自动发到 Telegram。
[SILENT] 静默标记
监控类任务最怕刷屏。在 prompt 里告诉 Agent:没事就只回 [SILENT]:
检查 nginx 是否在运行。如果一切正常,只回复 [SILENT]。否则报告问题。
Hermes 看到 [SILENT] 就不投递消息,但输出仍然存到本地 ~/.hermes/cron/output/ 留档。只有成功运行才能被静默,失败的任务一定会通知你。
可续接任务
默认情况下,定时任务发完消息就结束了—你回复它,它不知道自己之前说过什么。开启 mirror_delivery 后,投递的消息会写入会话历史,你直接回复就能接着聊:
# ~/.hermes/config.yaml
cron:
mirror_delivery: false # 设 true 让 cron 投递可续接
行为是「线程优先」的:支持线程的平台(Telegram 话题、Discord/Slack 线程)每次投递开一个独立线程,回复在这个线程里带完整上下文;只支持私信的平台(WhatsApp、Signal)则把摘要写入 origin 私信会话。
调度器怎么工作
后台调度循环
Cron 的执行由 Gateway 守护进程负责。Gateway 每 60 秒 tick 一次,检查有没有到期的任务:
- 从
~/.hermes/cron/jobs.json加载任务 - 检查
next_run_at是否到了当前时间 - 为每个到期任务启动一个全新的 Agent 会话
- 可选地注入附加技能
- 运行 prompt 到完成
- 投递最终响应
- 更新运行元数据和下次触发时间
文件锁 ~/.hermes/cron/.tick.lock 防止多个 tick 重叠导致同一个任务被重复执行。
执行历史
每次 cron 尝试都记录在 ~/.hermes/cron/executions.db 里,状态流转是 claimed -> running -> 终态(completed / failed / unknown)。重启后,Hermes 会检查被遗弃的尝试—只有确认原进程确实没了,才标记为 unknown。unknown 只是审计记录,不会自动重跑。
查看历史:
hermes cron runs --limit 20 # 最近 20 次执行
hermes cron runs <job-id> --limit 20 # 某个任务的执行历史
安装为系统服务
让 Gateway 长期在后台跑,cron 才能按时触发:
hermes gateway install # 安装为用户级服务
sudo hermes gateway install --system # Linux: 开机自启的系统级服务
hermes gateway # 或者前台运行
生命周期管理
任务不是建完就只能删了重来,Hermes 提供完整的生命周期操作:
/cron list # 列出所有任务
/cron pause <job_id> # 暂停(保留任务但不调度)
/cron resume <job_id> # 恢复(重新计算下次触发时间)
/cron run <job_id> # 立即触发一次(用于测试)
/cron remove <job_id> # 永久删除
/cron edit <job_id> --schedule "every 4h" # 改调度
/cron edit <job_id> --prompt "新任务描述" # 改 prompt
/cron edit <job_id> --skill blogwatcher --skill maps # 换技能
/cron edit <job_id> --remove-skill blogwatcher # 移除某个技能
/cron edit <job_id> --clear-skills # 清空所有技能
Tip
<job_id>也可以填任务名称(不区分大小写)。如果你记不住十六进制 ID,用名字更方便。但名字不唯一—如果多个任务同名,命令会拒绝执行并列出候选 ID 让你选。
CLI 也有对应的命令:
hermes cron list
hermes cron pause <job_id_or_name>
hermes cron resume <job_id_or_name>
hermes cron run <job_id_or_name>
hermes cron status # 查看调度器状态
hermes cron tick # 手动触发一次 tick
生命周期防护机制
Provider 快照与失败关闭
这是个很重要的安全设计。创建任务时,如果你没显式指定 provider 和 model,Hermes 会快照当前全局默认的 provider 和 model 到任务上。
为什么这么做?防止你后来把全局默认切到一个付费模型,然后无人值守的 cron 任务默默花你的钱。
如果全局默认后来变了,任务会失败关闭(fail closed):跳过这次运行,不调用推理 API,发一条告警让你手动指定 provider/model:
cronjob action=update job_id=... provider=... model=...
Note如果你想让任务跟随全局默认,改完全局默认后手动更新任务即可。用
hermes setup --portal配置的 OAuth 认证对无人值守任务最友好,因为 token 会自动刷新。
禁止递归创建
WarningCron 运行的会话里不能创建新的 cron 任务。Hermes 在 cron 执行时禁用 cron 管理工具,防止失控的调度循环。
技能绑定
一个 cron 任务可以绑定零个、一个或多个技能。技能在 prompt 执行前按顺序加载:
单技能
cronjob(
action="create",
skill="blogwatcher",
prompt="检查配置的订阅源,总结新内容。",
schedule="0 9 * * *",
name="Morning feeds",
)
多技能
cronjob(
action="create",
skills=["blogwatcher", "maps"],
prompt="找新的本地活动和附近有意思的地方,合并成一份简报。",
schedule="every 6h",
name="Local brief",
)
技能按列表顺序加载,prompt 是叠加在技能之上的任务指令。好处是你不用把完整的技能文本塞进 cron prompt 里。
无 Agent 模式:纯脚本任务
不是所有定时任务都需要 LLM 推理。内存告警、磁盘检查、心跳上报—这些脚本本身就能产出最终消息,让 Agent 参与纯属浪费 token。
用 no_agent=True 创建纯脚本任务:
hermes cron create "every 5m" \
--no-agent \
--script memory-watchdog.sh \
--deliver telegram \
--name "memory-watchdog"
语义很清晰:
- 脚本 stdout(去掉首尾空白)直接作为消息投递,一字不改。
- stdout 为空 -> 静默 tick,不投递。这就是看门狗模式:「没事别说话」。
- 非零退出或超时 -> 投递错误告警,防止坏掉的看门狗沉默失败。
- 不消耗任何 token,不碰推理层。
Tip你甚至可以直接跟 Agent 说「每 5 分钟检查一次内存,超过 85% 就在 Telegram 通知我」,Agent 会自动判断这是看门狗场景,写脚本到
~/.hermes/scripts/,然后用no_agent=True创建任务。
脚本必须放在 ~/.hermes/scripts/ 目录下。.sh / .bash 文件用 /bin/bash 执行,其他文件用当前 Python 解释器执行。
wakeAgent 门控:省 token 的利器
如果你的 cron 任务绑定了预检脚本(script= 参数),脚本可以在运行时决定要不要叫醒 Agent。在 stdout 最后一行输出:
{"wakeAgent": false}
Hermes 就跳过这次 Agent 调用。这对高频轮询(每 1-5 分钟)特别有用—状态没变就不花 token:
#!/usr/bin/env python
# ~/.hermes/scripts/new-rows.py
import json, sqlite3
conn = sqlite3.connect("/home/me/data/app.db")
n = conn.execute(
"SELECT COUNT(*) FROM messages WHERE ts > strftime('%s','now','-2 hours')"
).fetchone()[0]
if n < 1:
print(json.dumps({"wakeAgent": False})) # 没新数据,跳过
else:
print(json.dumps({"wakeAgent": True, "context": {"new_rows": n}}))
省钱的逻辑是:脚本做机械检查(查数据库、比文件 mtime、看外部标志),只在状态真的变了时才让 Agent 推理。
任务链式编排:context_from
Cron 任务默认在隔离会话里跑,互相不知道对方的输出。但有时候任务 B 恰好需要任务 A 的产出。context_from 参数把这个连接自动接上:
# 任务 1:收集原始数据
cronjob(
action="create",
prompt="抓取 Hacker News 上最新的 10 条 AI/ML 新闻,保存到 ~/.hermes/data/briefs/raw.md。",
schedule="0 7 * * *",
name="AI News Collector",
)
# 任务 2:筛选排名 -- 自动接收任务 1 的输出作为上下文
cronjob(
action="create",
prompt="读取 raw.md,给每条新闻打分,输出前 5 名到 ranked.md。",
schedule="30 7 * * *",
context_from="<任务1的ID>",
name="AI News Triage",
)
# 任务 3:生成推文 -- 自动接收任务 2 的输出作为上下文
cronjob(
action="create",
prompt="读取 ranked.md,为每条新闻写 3 条推文草稿,发到 Telegram。",
schedule="0 8 * * *",
context_from="<任务2的ID>",
name="AI News Brief",
)
任务 2 触发时,Hermes 从 ~/.hermes/cron/output/{job1_id}/ 读取任务 1 最近一次成功输出,自动拼在任务 2 的 prompt 前面。链可以任意长,也支持多个上游:
cronjob(
action="create",
name="daily-digest",
schedule="every day 7am",
context_from=["ai-news-fetch", "github-prs-fetch"], # 两个上游的输出拼接
prompt="用上面的输出写每日简报。",
)
Note链式读取的是最近一次完成的输出,不会等待同一个 tick 里正在运行的上游任务。所以调度时间要错开—任务 1 在 7:00,任务 2 在 7:30,确保任务 1 跑完了任务 2 才触发。
在项目目录里运行
默认情况下,cron 任务不加载任何项目上下文(没有 AGENTS.md、CLAUDE.md),终端和文件工具在 Gateway 启动目录下工作。用 --workdir 指定项目目录:
hermes cron create "every 1d at 09:00" \
"审查未合并的 PR,总结 CI 状态,发到 #eng" \
--workdir /home/me/projects/acme
设置 workdir 后:
- 该目录的
AGENTS.md、CLAUDE.md、.cursorrules会注入系统 prompt。 terminal、read_file、write_file等工具以该目录为工作目录。- 路径必须是已存在的绝对路径,相对路径和不存在的目录会被拒绝。
Warning带
workdir的任务在调度 tick 里串行执行,不走并行池。因为 cron worker 通过进程全局的终端状态应用工作目录,两个 workdir 任务同时跑会互相污染 cwd。不带 workdir 的任务仍然并行。
工具集控制
Cron 任务在全新会话里跑,默认获得你在 hermes tools 里为 cron 平台配置的工具集。可以用 enabled_toolsets 做更细的按任务控制:
cronjob(
action="create",
name="weekly-news-summary",
schedule="every sunday 9am",
enabled_toolsets=["web", "file"], # 只要 web 和 file,不要 terminal/browser
prompt="总结本周 AI 新闻:..."
)
这关乎成本控制—给每个「抓新闻」的小任务都带上 browser、delegation 工具集,会让工具 schema 在每次 LLM 调用时膨胀 prompt。
Prompt 必须自包含
这是 cron 任务最重要的规则,我单独拎出来强调:
WarningCron 任务在全新的 Agent 会话里运行,没有你当前聊天的任何记忆。Prompt 必须包含 Agent 需要的所有信息。
反面教材:「检查一下那个服务器的问题」—Agent 完全不知道「那个服务器」是哪个。
正确写法:「SSH 到 192.168.1.100,用户名 deploy,运行 systemctl status nginx 检查 nginx 是否在运行,并验证 https://example.com 返回 HTTP 200。」
把 URL、仓库名、格式偏好、投递指令都写进 prompt 里,别指望 Agent 能猜。
配置项汇总
在 ~/.hermes/config.yaml 里可以调整这些 cron 全局行为:
cron:
wrap_response: false # false = 投递原始输出,不包头尾
script_timeout_seconds: 1800 # 预检脚本超时(默认 3600 秒)
mirror_delivery: false # true = 投递可续接
wrap_response 默认是 true,投递的消息会带一个头尾:
Cronjob Response: Morning feeds
-------------
<Agent 输出>
Note: The agent cannot see this message, and therefore cannot respond to it.
如果你觉得这个包装多余,设成 false 就只投递原始输出。
脚本超时也可以用环境变量覆盖,优先级是:环境变量 > config.yaml > 3600 秒默认值:
HERMES_CRON_SCRIPT_TIMEOUT=1800
安全
定时任务的 prompt 在创建和更新时会扫描注入和凭据泄露模式。包含隐形 Unicode 技巧、SSH 后门尝试、明显的密钥外泄 payload 的 prompt 会被拦截。
任务存储在 ~/.hermes/cron/jobs.json,输出在 ~/.hermes/cron/output/{job_id}/{timestamp}.md。
Tip管理任务请用
cronjob工具、hermes cron命令或/cron聊天命令,别直接改jobs.json。直接编辑可能因为文件写入安全策略被静默拦截,而且没有文件变更验证器的校验。