跳转到内容

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-7200s
grace = max(120, min(7200, period // 2))
# 特殊处理:每天作业 2h catchup,10 分钟作业 5min catchup
  • compute_next_run():对单次作业使用 grace 窗口(ONESHOT_GRACE_SECONDS=120
  • get_due_jobs():快速推进过期周期性作业跳过 grace 窗口,而非触发漏掉的运行(at-most-once 语义)

2. 3 分钟硬中断

作业执行有 3 分钟硬中断限制,防止作业无限运行。

3. 文件锁防重复

~/.hermes/cron/.tick.lock
# 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.pycron/scheduler.py