05 - Cron 调度器实现
学习目标
本章聚焦 Hermes Agent 的内置 cron 调度器实现。你将了解:
- 四种作业时间格式解析(持续时间、every 短语、cron 表达式、ISO 时间戳)
- 作业字段的丰富配置(skills、model/provider 覆盖、script 预收集、context_from 链式)
- 三个硬化不变量:3 分钟硬中断、Catchup 窗口、文件锁防重复
no_agent模式的脚本直执行
前置知识
无特定前置知识。
项目实践
作业存储与格式解析
cron/jobs.py(1238 行)定义作业存储和格式解析:
存储位置:~/.hermes/cron/jobs.json
四种时间格式:
| 格式 | 示例 | 含义 |
|---|---|---|
| 持续时间(单次) | "30m"、"2h"、"1d" | 30 分钟 / 2 小时 / 1 天后执行 |
| ”every” 短语(周期) | "every 30m"、"every 2h" | 每 30 分钟 / 2 小时执行 |
| 5 字段 cron 表达式 | "0 9 * * 1-5" | 工作日每天 9:00 执行 |
| ISO 时间戳(单次) | "2026-05-27T09:00:00" | 指定时间执行一次 |
解析函数:
parse_duration():"30m"→ 30 分钟、"2h"→ 120 分钟、"1d"→ 1440 分钟parse_schedule():识别上述四种格式并返回对应的调度配置
作业字段配置
每个作业支持丰富的配置项:
| 字段 | 含义 |
|---|---|
skills | 加载特定技能执行作业 |
model / provider | 覆盖作业使用的模型和提供商 |
script | 预运行数据收集脚本(在 LLM 调用前执行) |
context_from | 链式作业:引用另一个作业的输出作为上下文 |
workdir | 作业工作目录 |
no_agent | 不启动 LLM,直接运行 script |
硬化不变量
1. Grace 窗口与 Catchup:
# grace 计算:调度周期的一半,限制 120s-7200sgrace = max(120, min(7200, period // 2))
# 特殊处理:每天作业 2h catchup,10 分钟作业 5min catchupcompute_next_run():对单次作业使用 grace 窗口(ONESHOT_GRACE_SECONDS=120)get_due_jobs():快速推进过期周期性作业跳过 grace 窗口,而非触发漏掉的运行(at-most-once 语义)
2. 3 分钟硬中断:
作业执行有 3 分钟硬中断限制,防止作业无限运行。
3. 文件锁防重复:
# fcntl LOCK_EX|LOCK_NB(非阻塞独占)on Unix# msvcrt on Windows# 锁被持有时立即返回 0,不阻塞Scheduler 每 60 秒 tick 一次,通过文件锁确保同一时间只有一个调度器实例运行。
Scheduler 执行流程
cron/scheduler.py(2040 行)实现调度执行:
执行细节:
- 每作业 AIAgent:为每个作业创建临时的
AIAgent实例 - ContextVar 隔离:清除
HERMES_SESSION_*变量,确保作业上下文不泄漏 - Inactivity 超时:默认 600 秒(10 分钟),可通过
HERMES_CRON_TIMEOUT环境变量调整;每 5 秒轮询agent.get_activity_summary() - Profile 上下文:临时设置
HERMES_HOME覆盖 + 环境变量快照/恢复 no_agent模式:不启动 LLM,直接运行 script,检查最后一行 stdout 的{"wakeAgent": false}JSON 门控
作业分区执行
- workdir+profile 作业:顺序执行(避免工作目录冲突)
- 其他作业:ThreadPoolExecutor 并行执行(
HERMES_CRON_MAX_PARALLEL可配置)
问题与规避
作业漏执行
问题:Agent 进程重启后,错过的作业如何处理?
对策:get_due_jobs() 快速推进过期作业跳过 grace 窗口,不触发漏掉的运行(at-most-once 语义)。如果需要 catchup,配置更大的 grace 窗口。
文件锁未释放
问题:调度器进程崩溃后文件锁未释放。
对策:使用 LOCK_NB(非阻塞锁),新 tick 立即返回而非等待。崩溃后操作系统自动释放文件锁。
设计取舍
内置轻量 cron vs 系统 cron
| 方案 | 优势 | 代价 |
|---|---|---|
| 内置(Hermes) | 自然语言格式、多平台投递、Agent 上下文管理 | 依赖 Agent 进程运行 |
| 系统 cron | 进程独立、系统级可靠性 | 不支持自然语言格式、多平台投递复杂 |
Hermes 选择内置 cron,因为其与 Agent 运行时深度集成(模型覆盖、技能加载、多平台投递),这是系统 cron 无法提供的。
参考来源
- 源码验证:
cron/jobs.py、cron/scheduler.py