跳到主要内容

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 子命令

调度模型

支持四种调度格式:

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

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

任务存储

任务存储在 ~/.hermes/cron/jobs.json 中,具有原子写入语义(写入临时文件,然后重命名)。每条任务记录包含:

{
"id": "a1b2c3d4e5f6",
"name": "Daily briefing",
"prompt": "Summarize today's AI news and funding rounds",
"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. 更新 run_count,计算 next_run
g. 如果重复次数耗尽 → state = "completed"
h. 否则 → state = "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 脚本。该脚本在每个智能体轮次 之前 运行,其 stdout 被作为上下文注入到提示中。这实现了数据收集和变更检测模式:

# ~/.hermes/scripts/check_competitors.py
import requests, json
# 获取竞争对手的发布说明,与上次运行进行差异比较
# 将摘要打印到 stdout——智能体进行分析并报告

脚本超时默认为 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 对话
DingTalkdingtalk交付到钉钉
Feishufeishu交付到飞书
WeComwecom交付到企业微信
Weixinweixin交付到微信
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> # 删除任务

相关文档