hermes-agent/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/features/cron.md
Ben 751a300fca docs(cron): scope in_channel to channels; document DM continuation knob
Live DM testing showed a reply to a DM cron brief did NOT continue the job.
Root cause: for a 1:1 DM the governing knob is dm_top_level_threads_as_sessions
(default True), NOT reply_in_thread / cron_continuable_surface. Under the
default, each top-level DM keys to a per-message session (…:dm:<chat>:<ts>),
so a reply mints a new ts and can never converge with the flat …:dm:<chat>
session the cron seed creates.

A 1:1 DM has no thread-vs-timeline split, so "in_channel" has no coherent
meaning for a DM — cron_continuable_surface is a channel concept and is a
no-op for DMs. DM continuation is governed entirely by
dm_top_level_threads_as_sessions:
  - false → all top-level DMs share …:dm:<chat> → seed + reply converge → works
  - true (default) → per-message sessions → no continuation (cron or interactive)

Option A (chosen): document the requirement; no code change (the flat-DM seed
from the prior commit already lands correctly when the knob is false). Adds a
":::note 1:1 DMs" admonition to cron.md + the zh-Hans mirror.

Verification (real inbound handler, not a hard-coded assumption — the mistake
that made the earlier DM E2E falsely pass): tests/manual/cron_inchannel_dm_e2e.py
drives the REAL _handle_slack_message for a top-level DM under both knob values
and asserts false→converges (…:dm:D_TESTDM == seed), true→diverges
(…:dm:D_TESTDM:<ts>). See decisions.md D9.
2026-07-01 03:16:13 -07:00

30 KiB
Raw Blame History

sidebar_position title description
5 定时任务Cron 用自然语言调度自动化任务,通过单一 cron 工具管理,并附加一个或多个 skill

定时任务Cron

使用自然语言或 cron 表达式调度自动运行的任务。Hermes 通过单一 cronjob 工具暴露 cron 管理能力,采用动作式操作,而非分散的 schedule/list/remove 工具。

Cron 当前能做什么

Cron 任务可以:

  • 调度一次性或周期性任务
  • 暂停、恢复、编辑、触发和删除任务
  • 为任务附加零个、一个或多个 skill
  • 将结果回传到来源会话、本地文件或已配置的平台目标
  • 在全新的 agent 会话中运行,使用正常的静态工具列表
  • 无 agent 模式运行——按计划执行脚本,其 stdout 原样投递,零 LLM 参与(参见下方无 agent 模式章节)

所有这些功能均可通过 cronjob 工具由 Hermes 自身使用,因此你可以用自然语言创建、暂停、编辑和删除任务——无需 CLI。

:::warning Cron 运行的会话不能递归创建更多 cron 任务。Hermes 在 cron 执行内部禁用了 cron 管理工具,以防止失控的调度循环。 :::

创建定时任务

在聊天中使用 /cron

/cron add 30m "Remind me to check the build"
/cron add "every 2h" "Check server status"
/cron add "every 1h" "Summarize new feed items" --skill blogwatcher
/cron add "every 1h" "Use both skills and combine the result" --skill blogwatcher --skill maps

从独立 CLI

hermes cron create "every 2h" "Check server status"
hermes cron create "every 1h" "Summarize new feed items" --skill blogwatcher
hermes cron create "every 1h" "Use both skills and combine the result" \
  --skill blogwatcher \
  --skill maps \
  --name "Skill combo"

通过自然对话

直接向 Hermes 描述:

Every morning at 9am, check Hacker News for AI news and send me a summary on Telegram.

Hermes 会在内部使用统一的 cronjob 工具。

附带 skill 的 cron 任务

Cron 任务可以在运行 prompt提示词之前加载一个或多个 skill。

单个 skill

cronjob(
    action="create",
    skill="blogwatcher",
    prompt="Check the configured feeds and summarize anything new.",
    schedule="0 9 * * *",
    name="Morning feeds",
)

多个 skill

Skill 按顺序加载。Prompt 作为任务指令叠加在这些 skill 之上。

cronjob(
    action="create",
    skills=["blogwatcher", "maps"],
    prompt="Look for new local events and interesting nearby places, then combine them into one short brief.",
    schedule="every 6h",
    name="Local brief",
)

当你希望定时 agent 继承可复用的工作流,而不必将完整的 skill 文本塞入 cron prompt 本身时,这非常有用。

在指定项目目录中运行任务

Cron 任务默认与任何代码仓库脱离运行——不加载 AGENTS.mdCLAUDE.md.cursorrules,终端/文件/代码执行工具从 gateway 启动时的工作目录运行。传入 --workdirCLIworkdir=(工具调用)可更改此行为:

# 独立 CLIschedule 和 prompt 为位置参数)
hermes cron create "every 1d at 09:00" \
  "Audit open PRs, summarize CI health, and post to #eng" \
  --workdir /home/me/projects/acme
# 在聊天中,通过 cronjob 工具
cronjob(
    action="create",
    schedule="every 1d at 09:00",
    workdir="/home/me/projects/acme",
    prompt="Audit open PRs, summarize CI health, and post to #eng",
)

设置 workdir 后:

  • 该目录中的 AGENTS.mdCLAUDE.md.cursorrules 会被注入系统 prompt发现顺序与交互式 CLI 相同)
  • terminalread_filewrite_filepatchsearch_filesexecute_code 均以该目录为工作目录(通过 TERMINAL_CWD
  • 路径必须是已存在的绝对目录——相对路径和不存在的目录在创建/更新时会被拒绝
  • 编辑时传入 --workdir ""(或工具中的 workdir="")可清除该设置并恢复原有行为

:::note 串行化 设置了 workdir 的任务在调度器 tick 时串行运行,而非在并行池中运行。这是有意为之——TERMINAL_CWD 是进程全局变量,两个 workdir 任务同时运行会互相破坏各自的 cwd。无 workdir 的任务仍像以前一样并行运行。 :::

在指定 profile 中运行 cron 任务

默认情况下cron 任务继承创建它的 gateway/CLI 所属的 Hermes profile。传入 --profile <name>CLIprofile=cronjob 工具)可将任务重定向到不同的 profile——调度器会解析该 profile 的 HERMES_HOME,在运行期间临时切换到该 profile加载其 .envconfig.yaml,并在其中执行任务:

# 将任务固定到 `night-ops` profile无论在哪里调度
hermes cron create "every 1d at 03:00" \
  "Tail the security log and flag anomalies" \
  --profile night-ops
# 在聊天中,通过 cronjob 工具
cronjob(
    action="create",
    schedule="every 1d at 03:00",
    prompt="Tail the security log and flag anomalies",
    profile="night-ops",
)

使用 --profile default 可显式固定到根 Hermes profile。指定的 profile 必须已存在;调度器不会动态创建 profile。在 cron edit 时清除 profile 固定,传入空字符串(--profile ""profile="")——任务将恢复在调度器当前所在的 profile 中运行。

如果固定的 profile 后来被删除,调度器会记录警告并回退到在当前 profile 中运行该任务,而不是崩溃——因此过期的 profile 引用不会卡住任务。

:::note 串行化 设置了 profile 的任务也串行运行,原因与 workdir 固定任务相同:切换 HERMES_HOME 是进程全局变更,两个 profile 固定任务并行运行会产生竞争。未固定的任务仍在正常并行池中运行。 :::

编辑任务

无需删除并重建任务来修改它们。

:::tip 任务引用 下方(以及生命周期操作中)的 <job_id> 占位符也接受任务名称(不区分大小写)——当你记得 morning-digest 但不记得十六进制 ID 时很方便。精确的任务 ID 优先于名称匹配;如果引用不是 ID 且名称匹配到多个任务,命令会拒绝执行并打印候选 ID 供你消歧义。 :::

聊天

/cron edit <job_id> --schedule "every 4h"
/cron edit <job_id> --prompt "Use the revised task"
/cron edit <job_id> --skill blogwatcher --skill maps
/cron edit <job_id> --remove-skill blogwatcher
/cron edit <job_id> --clear-skills

独立 CLI

hermes cron edit <job_id> --schedule "every 4h"
hermes cron edit <job_id> --prompt "Use the revised task"
hermes cron edit <job_id> --skill blogwatcher --skill maps
hermes cron edit <job_id> --add-skill maps
hermes cron edit <job_id> --remove-skill blogwatcher
hermes cron edit <job_id> --clear-skills

注意:

  • 重复使用 --skill 会替换任务已附加的 skill 列表
  • --add-skill 追加到现有列表,不替换
  • --remove-skill 删除指定的已附加 skill
  • --clear-skills 删除所有已附加的 skill

生命周期操作

Cron 任务现在拥有比创建/删除更完整的生命周期。

聊天

/cron list
/cron pause <job_id>
/cron resume <job_id>
/cron run <job_id>
/cron remove <job_id>

独立 CLI

hermes cron list
hermes cron pause <job_id>
hermes cron resume <job_id>
hermes cron run <job_id>
hermes cron remove <job_id>
hermes cron status
hermes cron tick

各操作说明:

  • pause — 保留任务但停止调度
  • resume — 重新启用任务并计算下次运行时间
  • run — 在下次调度器 tick 时触发任务
  • remove — 彻底删除任务

工作原理

Cron 执行由 gateway 守护进程处理。 Gateway 每 60 秒 tick 一次调度器,在隔离的 agent 会话中运行到期的任务。

hermes gateway install     # 安装为用户服务
sudo hermes gateway install --system   # Linux服务器开机启动的系统服务
hermes gateway             # 或在前台运行

hermes cron list
hermes cron status

Gateway 调度器行为

每次 tick 时Hermes

  1. ~/.hermes/cron/jobs.json 加载任务
  2. 对照当前时间检查 next_run_at
  3. 为每个到期任务启动全新的 AIAgent 会话
  4. 可选地将一个或多个已附加的 skill 注入该新会话
  5. 将 prompt 运行至完成
  6. 投递最终响应
  7. 更新运行元数据和下次调度时间

~/.hermes/cron/.tick.lock 处的文件锁防止重叠的调度器 tick 重复运行同一批任务。

投递选项

调度任务时,你可以指定输出的去向:

选项 说明 示例
"origin" 回传到任务创建的来源 消息平台上的默认值
"local" 仅保存到本地文件(~/.hermes/cron/output/ CLI 上的默认值
"telegram" Telegram 主频道 使用 TELEGRAM_HOME_CHANNEL
"telegram:123456" 按 ID 指定的 Telegram 会话 直接投递
"telegram:-100123:17585" 指定 Telegram 话题 chat_id:thread_id 格式
"discord" Discord 主频道 使用 DISCORD_HOME_CHANNEL
"discord:#engineering" 按频道名指定的 Discord 频道 按频道名
"slack" Slack 主频道
"whatsapp" WhatsApp 主账号
"signal" Signal
"matrix" Matrix 主房间
"mattermost" Mattermost 主频道
"email" 邮件
"sms" 通过 Twilio 发送 SMS
"homeassistant" Home Assistant
"dingtalk" 钉钉
"feishu" 飞书/Lark
"wecom" 企业微信
"weixin" 微信WeChat
"bluebubbles" BlueBubblesiMessage
"qqbot" QQ Bot腾讯 QQ
"all" 扇出到所有已连接的主频道 触发时解析
"telegram,discord" 扇出到指定的一组频道 逗号分隔列表
"origin,all" 投递到来源加上所有其他已连接频道 可组合任意 token

Agent 的最终响应会自动投递,无需在 cron prompt 中调用 send_message

路由意图(all

all 让你将一个 cron 任务发送到所有已配置的消息频道,无需逐一列举名称。它在触发时解析,因此在你配置 TELEGRAM_HOME_CHANNEL 之前创建的任务,会在下次 tick 时自动纳入 Telegram。

语义:all 展开为所有已配置主频道的平台。零个也没问题;任务只是没有投递目标,并在上游记录为投递失败。

all 可与显式目标组合。origin,all 投递到来源会话加上所有其他已连接的主频道,按 (platform, chat_id, thread_id) 去重。

Telegram cron 话题(TELEGRAM_CRON_THREAD_ID

启用 Telegram 话题模式后,根 DM 被保留为系统大厅——发送到那里的回复会被拒绝并附带大厅提示,reply_to_message_id 会被丢弃,因此你无法回复落在主聊天中的 cron 消息。

将 cron 指向专用的论坛话题:

  1. 在 Telegram 中打开机器人 DM创建一个名为 Cron 的话题。长按话题标题 → 复制链接;末尾的整数即为该话题的 message_thread_id
  2. .env 中设置 TELEGRAM_CRON_THREAD_ID=<该 id>

这仅适用于 cron 投递。TELEGRAM_HOME_CHANNEL_THREAD_ID(用于其他地方,如重启通知)不受影响。显式的 deliver="telegram:chat_id:thread_id" 目标仍优先于环境变量。对 cron 消息的回复现在会进入已有的话题会话,你可以直接在其中操作。

响应包装

默认情况下,投递的 cron 输出会带有页眉和页脚,以便接收方知道这来自定时任务:

Cronjob Response: Morning feeds
-------------

<agent output here>

Note: The agent cannot see this message, and therefore cannot respond to it.

若要投递不带包装的原始 agent 输出,将 cron.wrap_response 设为 false

# ~/.hermes/config.yaml
cron:
  wrap_response: false

可继续任务(回复 cron 投递)

默认情况下cron 投递是「发完即忘」的:消息发送出去,但不会进入聊天的对话历史, 因此如果你回复它agent 并不记得自己说过什么。将任务设为可继续后,投递的简报 就变成一段你可以回复进去的对话——agent 会把简报保留在上下文中,而不会反问 「Task #2 是什么?」。

选择性启用,默认关闭。可在配置中全局启用,或通过 cronjob 工具的 attach_to_session 按任务启用(会覆盖该任务的全局设置):

# ~/.hermes/config.yaml
cron:
  mirror_delivery: false   # 设为 true 使 cron 投递可继续

行为为优先使用话题,范围限定在任务的来源聊天:

  • 支持话题的平台Telegram 话题、Discord/Slack 话题):每次投递都会新建 专用话题,并将简报植入该话题的会话中,因此在话题内回复即可带完整上下文继续。
  • 仅 DM 的平台WhatsApp、Signal、SMS不存在话题因此简报会被镜像进 来源 DM 会话——DM 本身就是继续的载体。

只有来源聊天会被触及:扇出/广播目标(all、显式的其他聊天投递)永远不会被设为可继续。

平铺频道内继续Slack

上面的优先话题行为每次投递都会新建专用话题。如果你希望可继续任务平铺落在频道 时间线中——不新建话题——将 Slack 的继续投递方式设为 in_channel

# ~/.hermes/config.yaml
slack:
  cron_continuable_surface: in_channel   # 默认:"thread"
  reply_in_thread: false                 # 必需搭配(见下)
  require_mention: false                 # 纯文本回复即可继续任务

in_channel 模式下,简报作为普通的顶层频道消息投递(不新建话题),你的回复通过 频道的共享会话继续任务。三项设置协同工作:

  • cron_continuable_surface: in_channel——投递时跳过新建话题。
  • reply_in_thread: false(必需)——让机器人在频道中平铺回复你,并将其 归入简报所植入的同一个整频道会话。缺少它时继续功能仍可用,但回复会出现在话题里 (安全回退为话题式继续,绝不会丢失回复——网关会在启动时记录一条警告便于发现不匹配)。
  • require_mention: false(或将该频道加入 free_response_channels)——这样你 可以用纯文本消息回复;否则机器人只在你每次 @ 提及它时才被唤醒。

由于继续载体是整频道会话,它是共享的:频道里的其他闲聊——以及第二个可继续的 in_channel 任务——都会加入同一段滚动对话。这是「平铺在频道中」的固有取舍,与 reply_in_thread: false 用户已经接受的取舍相同;若希望每次投递的后续讨论相互隔离, 请使用默认的 thread 方式。

这目前是 Slack 的能力。其他平台接受该键,但会回退到 thread 方式(它们的继续原语 不同);该选择按平台设置,位于各平台的配置下。这是网关侧的配置项——/restart 即可 生效;无需重新安装 Slack 应用。

:::note 1:1 私信DM cron_continuable_surface频道设置——1:1 私信没有「话题 vs 时间线」的区分 (私信本身就是平铺的),因此该键在私信中无效。决定私信 cron 投递是否可继续的是另一个 已有的独立开关 slack.dm_top_level_threads_as_sessions

  • false——所有顶层私信共享同一个滚动私信会话,因此可继续的 cron 简报与你的回复 落在同一个会话里,任务得以带上下文继续。这正是私信中可继续 cron 所需要的。
  • true(默认)——每条顶层私信消息各自成为独立会话,因此对已投递简报的回复会开启 一个全新、不含该简报记录的会话。此模式下继续功能不可用(对 cron 或任何平铺投递皆然)。

所以,若要让 cron 任务在 1:1 私信中可继续,请设置 slack.dm_top_level_threads_as_sessions: false。私信不需要(也会忽略) cron_continuable_surface。 :::

静默抑制

如果 agent 的最终响应以 [SILENT] 开头,投递将被完全抑制。输出仍会保存到本地以供审计(位于 ~/.hermes/cron/output/),但不会向投递目标发送任何消息。

这对于只在出现问题时才需要上报的监控任务很有用:

Check if nginx is running. If everything is healthy, respond with only [SILENT].
Otherwise, report the issue.

失败的任务无论 [SILENT] 标记如何都会投递——只有成功的运行才能被静默。

脚本超时

预运行脚本(通过 script 参数附加)的默认超时为 120 秒。如果你的脚本需要更长时间——例如,包含随机延迟以避免类机器人的时序模式——可以增加此值:

# ~/.hermes/config.yaml
cron:
  script_timeout_seconds: 300   # 5 分钟

或设置 HERMES_CRON_SCRIPT_TIMEOUT 环境变量。解析顺序为:环境变量 → config.yaml → 默认 120 秒。

无 agent 模式(纯脚本任务)

对于不需要 LLM 推理的周期性任务——经典的看门狗、磁盘/内存告警、心跳、CI ping——在创建时传入 no_agent=True。调度器按计划运行你的脚本,并直接投递其 stdout完全跳过 agent

hermes cron create "every 5m" \
  --no-agent \
  --script memory-watchdog.sh \
  --deliver telegram \
  --name "memory-watchdog"

语义:

  • 脚本 stdout去除首尾空白→ 原样作为消息投递。
  • stdout 为空 → 静默 tick,不投递。这是看门狗模式:"只在出现问题时才说话"。
  • 非零退出或超时 → 投递错误告警,确保损坏的看门狗不会静默失败。
  • 最后一行输出 {"wakeAgent": false} → 静默 tick与 LLM 任务使用相同的门控)。
  • 无 token、无模型、无 provider 回退——任务永远不会触及推理层。

.sh/.bash 文件在 /bin/bash 下运行;其他文件在当前 Python 解释器(sys.executable)下运行。脚本必须位于 ~/.hermes/scripts/(与预运行脚本门控相同的沙箱规则)。

Agent 为你设置这些

cronjob 工具的 schema 直接向 Hermes 暴露了 no_agent,因此你可以在聊天中描述一个看门狗,让 agent 来配置它:

Ping me on Telegram if RAM is over 85%, every 5 minutes.

Hermes 会通过 write_file 将检查脚本写入 ~/.hermes/scripts/,然后调用:

cronjob(action="create", schedule="every 5m",
        script="memory-watchdog.sh", no_agent=True,
        deliver="telegram", name="memory-watchdog")

当消息内容完全由脚本决定时(看门狗、阈值告警、心跳),它会自动选择 no_agent=True。同一工具也让 agent 可以暂停、恢复、编辑和删除任务——整个生命周期都通过聊天驱动,无需任何人接触 CLI。

参见纯脚本 Cron 任务指南获取实际示例。

通过 context_from 串联任务

Cron 任务在隔离的会话中运行,不保留之前运行的记忆。但有时一个任务的输出恰好是下一个任务所需的输入。context_from 参数自动建立这种连接——任务 B 的 prompt 在运行时会将任务 A 的最新输出作为上下文前置。

# 任务 1收集原始数据
cronjob(
    action="create",
    prompt="Fetch the top 10 AI/ML stories from Hacker News. Save them to ~/.hermes/data/briefs/raw.md in markdown format with title, URL, and score.",
    schedule="0 7 * * *",
    name="AI News Collector",
)

# 任务 2分类——接收任务 1 的输出作为上下文
# 从 cronjob(action="list") 获取任务 1 的 ID
cronjob(
    action="create",
    prompt="Read ~/.hermes/data/briefs/raw.md. Score each story 110 for engagement potential and novelty. Output the top 5 to ~/.hermes/data/briefs/ranked.md.",
    schedule="30 7 * * *",
    context_from="<job1_id>",
    name="AI News Triage",
)

# 任务 3发布——接收任务 2 的输出作为上下文
cronjob(
    action="create",
    prompt="Read ~/.hermes/data/briefs/ranked.md. Write 3 tweet drafts (hook + body + hashtags). Deliver to telegram:7976161601.",
    schedule="0 8 * * *",
    context_from="<job2_id>",
    name="AI News Brief",
)

工作原理:

  • 任务 2 触发时Hermes 从 ~/.hermes/cron/output/{job1_id}/*.md 读取任务 1 的最新输出
  • 该输出自动前置到任务 2 的 prompt
  • 任务 2 无需硬编码"读取此文件"——它以上下文形式接收内容
  • 链可以是任意长度:任务 1 → 任务 2 → 任务 3 → …

context_from 接受的格式:

格式 示例
单个任务 ID字符串 context_from="a1b2c3d4"
多个任务 ID列表 context_from=["job_a", "job_b"]

输出按列表顺序拼接。

适用场景:

  • 多阶段流水线(收集 → 过滤 → 格式化 → 投递)
  • 步骤 N 依赖步骤 N1 输出的依赖任务
  • 一个任务聚合多个其他任务结果的扇入模式

Provider 恢复

Cron 任务继承你配置的回退 provider 和凭证池轮换。如果主 API key 被限速或 provider 返回错误cron agent 可以:

  • 回退到备用 provider,前提是你在 config.yaml 中配置了 fallback_providers(或旧版 fallback_model
  • 轮换到下一个凭证,即同一 provider 的凭证池中的下一个

这意味着高频运行或在高峰时段运行的 cron 任务更具弹性——单个被限速的 key 不会导致整次运行失败。

调度格式

Agent 的最终响应会自动投递——你无需在 cron prompt 中为同一目标包含 send_message。如果 cron 运行调用了 send_message 且目标与调度器已投递的目标完全相同Hermes 会跳过该重复发送,并告知模型将面向用户的内容放在最终响应中。仅对额外或不同的目标使用 send_message

相对延迟(一次性)

30m     → 30 分钟后运行一次
2h      → 2 小时后运行一次
1d      → 1 天后运行一次

间隔(周期性)

every 30m    → 每 30 分钟
every 2h     → 每 2 小时
every 1d     → 每天

Cron 表达式

0 9 * * *       → 每天上午 9:00
0 9 * * 1-5     → 工作日上午 9:00
0 */6 * * *     → 每 6 小时
30 8 1 * *      → 每月 1 日上午 8:30
0 0 * * 0       → 每周日午夜

ISO 时间戳

2026-03-15T09:00:00    → 2026 年 3 月 15 日上午 9:00 一次性运行

重复行为

调度类型 默认重复次数 行为
一次性(30m、时间戳) 1 运行一次
间隔(every 2h 永久 运行直到删除
Cron 表达式 永久 运行直到删除

可以覆盖:

cronjob(
    action="create",
    prompt="...",
    schedule="every 2h",
    repeat=5,
)

以编程方式管理任务

面向 agent 的 API 是单一工具:

cronjob(action="create", ...)
cronjob(action="list")
cronjob(action="update", job_id="...")
cronjob(action="pause", job_id="...")
cronjob(action="resume", job_id="...")
cronjob(action="run", job_id="...")
cronjob(action="remove", job_id="...")

对于 update,传入 skills=[] 可删除所有已附加的 skill。

Cron 任务可用的工具集

Cron 在全新的 agent 会话中运行每个任务不附加任何聊天平台。默认情况下cron agent 获得你在 hermes tools 中为 cron 平台配置的工具集——不是 CLI 默认值,也不是所有工具。

hermes tools
# → 在 curses UI 中选择 "cron" 平台
# → 像 Telegram/Discord 等平台一样切换工具集开关

通过 cronjob.create(或通过 cronjob.update 对现有任务)上的 enabled_toolsets 字段可进行更精细的单任务控制:

cronjob(action="create", name="weekly-news-summary",
        schedule="every sunday 9am",
        enabled_toolsets=["web", "file"],      # 仅 web + file无 terminal/browser 等
        prompt="Summarize this week's AI news: ...")

当任务上设置了 enabled_toolsets 时,它优先生效;否则 hermes tools 的 cron 平台配置生效;否则 Hermes 回退到内置默认值。这对成本控制很重要:在每个小型"获取新闻"任务中携带 moabrowserdelegation 会在每次 LLM 调用时膨胀工具 schema prompt。

完全跳过 agentwakeAgent

如果你的 cron 任务附加了预检脚本(通过 script=),脚本可以在运行时决定 Hermes 是否应该调用 agent。在 stdout 最后一行输出如下格式:

{"wakeAgent": false}

……cron 将完全跳过本次 tick 的 agent 运行。适用于高频轮询(每 15 分钟),只在状态实际发生变化时才需要唤醒 LLM——否则你会为一遍遍的零内容 agent 轮次付费。

# 预检脚本
import json, sys
latest = fetch_latest_issue_count()
prev = read_state("issue_count")
if latest == prev:
    print(json.dumps({"wakeAgent": False}))   # 跳过本次 tick
    sys.exit(0)
write_state("issue_count", latest)
print(json.dumps({"wakeAgent": True, "context": {"new_issues": latest - prev}}))

省略 wakeAgent 时,默认为 true(照常唤醒 agent

实用方案:低成本预运行门控

wakeAgent 门控提供了一种零成本的方式,用于决定定时任务是否应该消耗任何 LLM token。三种模式覆盖了大多数使用场景。

文件变更门控——仅在被监视文件自上次成功 tick 以来有新内容时运行。调度器记录每个任务的 last_run_at;将其与文件的 mtime 比较。

#!/bin/bash
# ~/.hermes/scripts/feed-changed.sh
FEED="$HOME/data/feed.json"
STATE="$HOME/.hermes/scripts/.feed-changed.last"
test -f "$FEED" || { echo '{"wakeAgent": false}'; exit 0; }
mtime=$(stat -c %Y "$FEED")
last=$(cat "$STATE" 2>/dev/null || echo 0)
if [ "$mtime" -le "$last" ]; then
  echo '{"wakeAgent": false}'
else
  echo "$mtime" > "$STATE"
  echo '{"wakeAgent": true}'
fi
cronjob(action="create", name="process-feed",
        schedule="every 30m",
        script="feed-changed.sh",
        prompt="A new ~/data/feed.json has landed. Summarize what changed.")

外部标志门控——仅在其他进程发出就绪信号时运行(例如,部署 hook 落下一个文件CI 任务在状态存储中设置一个值)。

#!/bin/bash
# ~/.hermes/scripts/flag-ready.sh
if test -f /tmp/new-data-ready; then
  rm -f /tmp/new-data-ready
  echo '{"wakeAgent": true}'
else
  echo '{"wakeAgent": false}'
fi
cronjob(action="create", name="nightly-analysis",
        schedule="0 9 * * *",
        script="flag-ready.sh",
        prompt="Run the nightly analysis over today's batch.")

SQL 计数门控——仅在你自己的数据库中有新行需要处理时运行。脚本还可以通过 context 将计数传递给 agent让 agent 无需重新查询就知道数据量。

#!/usr/bin/env python
# ~/.hermes/scripts/new-rows.py
import json, sqlite3
conn = sqlite3.connect("/home/me/data/app.db")
n = conn.execute(
    "SELECT COUNT(*) FROM messages WHERE ts > strftime('%s','now','-2 hours')"
).fetchone()[0]
if n < 1:
    print(json.dumps({"wakeAgent": False}))
else:
    print(json.dumps({"wakeAgent": True, "context": {"new_rows": n}}))
cronjob(action="create", name="summarize-new-msgs",
        schedule="every 2h",
        script="new-rows.py",
        prompt="Summarize the new messages from the last 2 hours.")

同样的模式适用于任何可以从脚本查询的数据源——Postgres、HTTP API、你自己的状态存储——无需将 SQL 求值器内置到 cron 子系统中。

:::tip Hermes 自身的 ~/.hermes/state.db 是内部 schema会在版本间变更。不要从预运行门控中查询它——指向你自己的数据库或 feed。 :::

致谢:此方案集由 @iankar8 在 #2654 中的探索所启发,该 PR 提议将 sql/file/command 触发器作为并行机制添加。script + wakeAgent 门控已以零成本覆盖了所有三种情况,因此该工作以文档形式落地。

串联任务:context_from

Cron 任务可以通过在 context_from 中列出其他任务的名称(或 ID来消费这些任务最近一次成功运行的输出

cronjob(action="create", name="daily-digest",
        schedule="every day 7am",
        context_from=["ai-news-fetch", "github-prs-fetch"],
        prompt="Write the daily digest using the outputs above.")

被引用任务最近一次完成的输出会作为上下文注入到本次运行的 prompt 之上。每个上游条目必须是有效的任务 ID 或名称(参见 cronjob action="list")。注意:串联读取的是最近一次完成的输出——它不会等待同一 tick 中正在运行的上游任务。

任务存储

任务存储在 ~/.hermes/cron/jobs.json。任务运行的输出保存到 ~/.hermes/cron/output/{job_id}/{timestamp}.md

任务可能将 modelprovider 存储为 null。省略这些字段时Hermes 在执行时从全局配置中解析它们。只有设置了单任务覆盖时,这些字段才会出现在任务记录中。

存储使用原子文件写入,因此中断的写入不会留下部分写入的任务文件。

自包含的 prompt 仍然重要

:::warning 重要 Cron 任务在完全全新的 agent 会话中运行。Prompt 必须包含 agent 所需的一切,除非已由附加的 skill 提供。 :::

错误: "Check on that server issue"

正确: "SSH into server 192.168.1.100 as user 'deploy', check if nginx is running with 'systemctl status nginx', and verify https://example.com returns HTTP 200."

安全性

定时任务的 prompt 在创建和更新时会扫描 prompt 注入和凭证外泄模式。包含不可见 Unicode 技巧、SSH 后门尝试或明显的密钥外泄载荷的 prompt 会被拦截。