Skip to content

Cron 定时

源码版本v2026.7.20

职责

让 agent 能「到点自动跑」:定时报告、备份、简报等无需人在场。cron/ 子系统提供调度器、作业存储、执行记录、生命周期守卫、蓝图/建议目录,以及独立的 toolsets 隔离(定时任务可禁用部分工具,防止无人值守时干危险事)。网关用 60s 心跳驱动调度器。

设计动机

无人值守是这套子系统的核心问题。agent 在凌晨三点自己跑起来,谁盯着?Hermes 的回答是「调度链路上层层降权」:toolsets 默认裁掉交互类和能再开 cron 的 cronjob;lifecycle_guard 把脚本里 systemctl restart hermes-gateway 这类命令拦在调度前;executions 表给每次执行留可查的痕迹,崩溃后标 unknown 而不是冒充成功。让「agent 自己跑」能审计、能回滚。

关键文件

数据流

  1. 网关启动 _start_cron_ticker:22335,每 60s 触发调度器(实际已迁到 cron/scheduler_provider.py::InProcessCronScheduler,旧符号作 shim)。
  2. 调度器读作业存储(cron/jobs.py:106),按 cron 表达式选到期作业。
  3. 每个到期作业:
  4. 崩溃 → 下次 recover_interrupted_executions(cron/executions.py:156)恢复标记。
  5. 结果经网关投递。

调度器对 toolset 的裁剪硬编码三项,叠加用户 config.yamlagent.disabled_toolsets:

python
def _resolve_cron_disabled_toolsets(cfg: dict) -> list[str]:
    """Toolsets a cron-spawned agent must never receive."""
    disabled = ["cronjob", "messaging", "clarify"]
    agent_cfg = (cfg or {}).get("agent") or {}
    user_disabled = agent_cfg.get("disabled_toolsets") or []
    for name in user_disabled:
        name = str(name).strip()
        if name and name not in disabled:
            disabled.append(name)
    return disabled

cronjob 被禁是不让 cron 自己再造 cron(误触能级联开一堆作业),messaging/clarify 是交互类,无人值守只会挂着等不到人。脚本型作业走 _run_job_script,路径被关在 HERMES_HOME/scripts/ 里防 traversal。

执行状态机在 executions.py,状态只有 claimed → running → completed/failed/unknown,终态只能写一次(finish_execution 的 UPDATE 加 WHERE status IN ('claimed','running') 保护,重复写直接 rowcount=0 返回 None)。崩溃恢复只把「进程已不存在」的执行标成 unknown,不假装成功也不自动重跑——子进程可能已经写了副作用,也可能没,留给操作者去查,而不是猜 failed 让人以为重跑就完事。lifecycle_guardsystemctl restart hermes-gatewaypkill hermes gateway 编成正则,创建时和执行前各扫一次,把 prompt 和 script 拼起来扫,堵住「prompt 里说一句、脚本里干一刀」的拆分绕过。

边界与失败

  • 秒级漂移与误伤:60s 心跳意味着触发点最多差一分钟。mark_running_jobs_interrupted 不区分哪个 PID 被杀,凡在杀的时刻在跑的作业统统打断,可能误伤快收尾的作业。
  • unknown 状态的副作用:崩溃恢复标 unknown 后不重跑,但脚本可能已发过消息/写过文件。下游不能假设「未完成」。
  • 租约心跳卡死:_run_job_script_with_claim_heartbeat 用独立线程续租,心跳线程本身卡住(如 GIL 长持有)时,另一个调度进程可能把同一个 one-shot 作业再派一次,产生重复副作用。
  • prompt 注入盲区:运行时才扫 assembled prompt(含 skill 内容),恶意 skill 要等调度那一刻才被拦下,创建作业时看不到。

小结

Cron 子系统的重点是「无人值守的安全性」:toolsets 隔离、lifecycle_guard 防误操作、execution 状态机做崩溃恢复。调度本身由网关心跳驱动,不需要外部 cron 守护进程。

非官方社区学习站,内容以 MIT 许可的 NousResearch/hermes-agent 源码为依据。