Cron 内部机制

Cron 子系统提供计划任务执行功能——从简单的一次性延迟任务到重复执行的 cron 表达式任务,支持技能注入和跨平台投递。

关键文件

文件用途
cron/jobs.py任务模型、存储、对 jobs.json 的原子读写
cron/scheduler.py调度器循环——到期任务检测、执行、重复跟踪
tools/cronjob_tools.py面向模型的 cronjob 工具注册和处理器
gateway/run.py网关集成——在长运行循环中执行 cron 滴答
hermes_cli/cron.pyCLI hermes cron 子命令

调度模型

支持四种调度格式:

格式示例行为
相对延迟30m2h1d一次性,在指定时长后触发
间隔every 2hevery 30m重复执行,按固定间隔触发
Cron 表达式0 9 * * *标准 5 字段 cron 语法(分钟、小时、日、月、星期)
ISO 时间戳2025-01-15T09:00:00一次性,在指定时间精确触发

面向模型的接口是一个单一的 cronjob 工具,支持动作式操作:createlistupdatepauseresumerunremove

任务存储

任务存储在 ~/.hermes/cron/jobs.json 中,采用原子写入语义(先写入临时文件,再重命名)。每个任务记录包含:

{
  "id": "a1b2c3d4e5f6",
  "name": "每日简报",
  "prompt": "总结今天的人工智能新闻和融资情况",
  "schedule": {
    "kind": "cron",
    "expr": "0 9 * * *",
    "display": "0 9 * * *"
  },
  "skills": ["ai-funding-daily-report"],
  "deliver": "telegram:-1001234567890",
  "repeat": {
    "times": null,
    "completed": 42
  },
  "state": "scheduled",
  "enabled": true,
  "next_run_at": "2025-01-16T09:00:00Z",
  "last_run_at": "2025-01-15T09:00:00Z",
  "last_status": "ok",
  "created_at": "2025-01-01T00:00:00Z",
  "model": null,
  "provider": null,
  "script": null
}

任务生命周期状态

状态含义
scheduled(已调度)活跃,将在下一个计划时间触发
paused(已暂停)已挂起——恢复前不会触发
completed(已完成)重复次数已用尽,或一次性任务已触发
running(运行中)当前正在执行(瞬态状态)

向后兼容

较旧的任务可能包含单个 skill 字段而非 skills 数组。调度器在加载时会自动归一化——单个 skill 被提升为 skills: [skill]

调度器运行机制

滴答周期

调度器按周期性的滴答运行(默认:每 60 秒):

tick()
  1. 获取调度器锁(防止滴答重叠)
  2. 从 jobs.json 加载所有任务
  3. 筛选到期任务(next_run <= now 且 state == "scheduled")
  4. 对每个到期任务:
     a. 将状态设置为 "running"
     b. 创建全新的 AIAgent 会话(无对话历史)
     c. 按顺序加载附加的技能(作为用户消息注入)
     d. 通过代理运行任务提示
     e. 将响应投递到配置的目标
     f. 更新运行次数,计算下一次运行时间
     g. 如果重复次数已用尽 → 状态设为 "completed"
     h. 否则 → 状态设为 "scheduled"
  5. 将更新后的任务写回 jobs.json
  6. 释放调度器锁

网关集成

在网关模式下,调度器在专用的后台线程中运行(gateway/run.py 中的 _start_cron_ticker),该线程在处理消息的同时每 60 秒调用一次 scheduler.tick()

在 CLI 模式下,cron 任务仅在运行 hermes cron 命令或在活跃的 CLI 会话期间触发。

全新会话隔离

每个 cron 任务在全新的代理会话中运行:

  • 不保留先前运行的对话历史
  • 不记忆之前的 cron 执行记录(除非持久化到记忆/文件中)
  • 提示必须自包含——cron 任务无法提出澄清性问题
  • cronjob 工具集被禁用(递归防护)

技能支持的任务

cron 任务可以通过 skills 字段附加一个或多个技能。执行时:

  1. 按指定顺序加载技能
  2. 每个技能的 SKILL.md 内容作为上下文注入
  3. 任务的提示作为任务指令追加
  4. 代理处理组合后的技能上下文和提示

这使得可重用、经过测试的工作流成为可能,而无需将完整指令粘贴到 cron 提示中。例如:

创建每日融资报告 → 附加 "ai-funding-daily-report" 技能

脚本支持的任务

任务还可以通过 script 字段附加一个 Python 脚本。该脚本在每次代理轮次之前运行,其标准输出作为上下文注入到提示中。这支持数据收集和变化检测模式:

# ~/.hermes/scripts/check_competitors.py
import requests, json
# 获取竞争对手发布说明,与上次运行进行差异比较
# 将摘要输出到标准输出——代理进行分析和报告

脚本超时默认为 120 秒。_get_script_timeout() 通过三层链解析超时限制:

  1. 模块级覆盖——_SCRIPT_TIMEOUT(用于测试/猴子补丁)。仅在与默认值不同时使用。
  2. 环境变量——HERMES_CRON_SCRIPT_TIMEOUT
  3. 配置——config.yaml 中的 cron.script_timeout_seconds(通过 load_config() 读取)
  4. 默认值——120 秒

提供者恢复

run_job() 将用户配置的回退提供者和凭据池传递给 AIAgent 实例:

  • 回退提供者——从 config.yaml 读取 fallback_providers(列表)或 fallback_model(旧版字典),匹配网关的 _load_fallback_model() 模式。作为 fallback_model= 传递给 AIAgent.__init__,该方法将两种格式归一化为回退链。
  • 凭据池——通过 agent.credential_pool 中的 load_pool(provider) 加载,使用解析后的运行时提供者名称。仅在池中有凭据时传递(pool.has_credentials())。在 429/速率限制错误时启用同提供者密钥轮换。

这镜像了网关的行为——没有它,cron 代理会在遇到速率限制时直接失败而不尝试恢复。

投递模型

Cron 任务结果可以投递到任何支持的平台:

目标语法示例
源聊天origin投递到创建任务的聊天
本地文件local保存到 ~/.hermes/cron/output/
Telegramtelegramtelegram:<chat_id>telegram:-1001234567890
Discorddiscorddiscord:#channeldiscord:#engineering
Slackslack投递到 Slack 首页频道
WhatsAppwhatsapp投递到 WhatsApp 首页
Signalsignal投递到 Signal
Matrixmatrix投递到 Matrix 首页房间
Mattermostmattermost投递到 Mattermost 首页
Emailemail通过电子邮件投递
SMSsms通过短信投递
Home Assistanthomeassistant投递到 HA 对话
DingTalk(钉钉)dingtalk投递到钉钉
Feishu(飞书)feishu投递到飞书
WeCom(企业微信)wecom投递到企业微信
Weixin(微信)weixin投递到微信
BlueBubblesbluebubbles通过 BlueBubbles 投递到 iMessage
QQ Botqqbot通过官方 API v2 投递到 QQ

对于 Telegram 话题,使用格式 telegram:<chat_id>:<thread_id>(例如 telegram:-1001234567890:17585)。

响应包装

默认情况下(cron.wrap_response: true),cron 投递会包装以下内容:

  • 标识 cron 任务名称和任务的头部
  • 说明代理无法在对话中看到已投递消息的尾部

cron 响应中的 [SILENT] 前缀会完全抑制投递——适用于仅需写入文件或执行副作用的任务。

会话隔离

Cron 投递不会镜像到网关会话的对话历史中。它们仅存在于 cron 任务自己的会话中。这防止了目标聊天对话中出现消息交替违规。

递归防护

Cron 运行的会话中禁用了 cronjob 工具集。这防止了:

  • 调度任务创建新的 cron 任务
  • 递归调度导致令牌消耗暴涨
  • 在任务内部意外修改任务调度

锁定

调度器使用跨进程的基于文件的锁定(Unix 上使用 fcntl.flock,Windows 上使用 msvcrt.locking)来防止重叠滴答执行同一到期任务批次两次——即使在网关的进程内滴答器和独立的 hermes cron / 手动 tick() 调用之间也是如此。如果无法获取锁,tick() 立即返回 0。

CLI 接口

hermes cron CLI 提供直接的任务管理:

hermes cron list                    # 显示所有任务
hermes cron create                  # 交互式任务创建(别名:add)
hermes cron edit <job_id>           # 编辑任务配置
hermes cron pause <job_id>          # 暂停运行中的任务
hermes cron resume <job_id>         # 恢复已暂停的任务
hermes cron run <job_id>            # 立即触发执行
hermes cron remove <job_id>         # 删除任务

相关文档