跳到主要内容

定时任务内部机制

定时任务(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": "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. 通过 Agent 运行任务提示词
e. 将响应投递到配置的目标
f. 更新 run_count,计算下次运行时间
g. 如果重复次数已耗尽 → state = "completed"
h. 否则 → state = "scheduled"
5. 将更新后的任务写回 jobs.json
6. 释放调度器锁

网关集成

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

在 CLI 模式下,仅当运行 hermes cron 命令或在活动 CLI 会话期间才会触发定时任务。

全新会话隔离

每个定时任务在完全全新的 Agent 会话中运行:

  • 没有来自之前运行的对话历史
  • 没有之前定时任务执行的记忆(除非已持久化到记忆文件)
  • 提示词必须自包含——定时任务无法提出澄清问题
  • cronjob 工具集被禁用(递归保护)

基于技能的任务

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

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

这支持可复用的、经过测试的工作流,无需将完整指令粘贴到定时任务提示词中。例如:

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

基于脚本的任务

任务也可以通过 script 字段附加 Python 脚本。该脚本在每次 Agent 轮次之前运行,其 stdout 作为上下文注入到提示词中。这支持数据收集和变更检测模式:

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

脚本超时默认为 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/限流错误时启用同提供商密钥轮换。

这与网关的行为一致——没有它,定时任务 Agent 在速率限制时会失败而不尝试恢复。

投递模型

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

目标语法示例
原始聊天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),定时任务投递会附加:

  • 标识定时任务名称和任务的页眉
  • 说明 Agent 无法在对话中查看已投递消息的页脚

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

会话隔离

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

递归保护

定时任务运行的会话会禁用 cronjob 工具集。这防止:

  • 计划任务创建新的定时任务
  • 可能导致 token 用量爆炸的递归调度
  • 在任务内部意外修改任务调度

锁定机制

调度器使用跨进程的基于文件的锁(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> # 删除任务

相关文档