Hermes 实战 :调度——cron 6:00 无人值守跑日报

早上 6:00,你还在睡觉,日报自己跑完了。collect.py 抓完四个源,curate.py 调 Hermes 把条目整理成正文,render.py 出网页------一条链在一个没人看着的时刻走完,执行记录落在磁盘上,等你起床再翻。

上篇回顾:推送有了,还差到点自动跑。那会儿跑一次日报要手动敲三下------collect、curate、render 各一条命令。这不叫自动化,叫「每天记得按一下」。这篇把「人肉定时」换成 Hermes 自己的调度器:用 cron 子命令建一个真实的 job,每天 6:00 把管线自动跑完。

这篇做两件事:用 cron 把 collect→curate→render 串成真实的定时任务;把「无人值守」的前提和兜底讲透。前提是 gateway 常驻,兜底是跨天去重。这两条,恰恰是大多数定时任务教程不讲的部分。

手动三步的尽头是无人值守

先把这轮要加的东西放进全景图,后面每一节都是给图里的一格装细节。

调度不是另起炉灶,是载体项目走到这的自然演化------手动三步跑顺了,下一步就是让它自己跑。

hermes cron 全家桶:十个子命令

先看命令面。hermes cron --help 把全部子命令列得很清楚,逐字:

text 复制代码
usage: hermes cron [-h] [--accept-hooks]
                   {list,create,add,edit,pause,resume,run,remove,rm,delete,status,runs,history,tick}
                   ...

Manage scheduled tasks

positional arguments:
  {list,create,add,edit,pause,resume,run,remove,rm,delete,status,runs,history,tick}
    list                List scheduled jobs
    create (add)        Create a scheduled job
    edit                Edit an existing scheduled job
    pause               Pause a scheduled job
    resume              Resume a paused job
    run                 Run a job on the next scheduler tick
    remove (rm, delete)
                        Remove a scheduled job
    status              Check if cron scheduler is running
    runs (history)      Show durable execution attempts
    tick                Run due jobs once and exit

options:
  -h, --help            show this help message and exit
  --accept-hooks        Auto-approve unseen shell hooks without a TTY prompt
                        (equivalent to HERMES_ACCEPT_HOOKS=1 /
                        hooks_auto_accept: true).

十个子命令(usage 里多出来的 add/rm/delete/history 是别名,不算单独功能),按用途分两组:管理 job 本身(list/create/edit/pause/resume/remove),触发与追溯(run/status/runs/tick)。顶层那个 --accept-hooks 是让无人值守时自动批准未见过的 shell 钩子,等同 HERMES_ACCEPT_HOOKS=1,后面手动触发时会用到。

create 的关键选项,hermes cron create --help 逐字:

text 复制代码
usage: hermes cron create [-h] [--name NAME] [--deliver DELIVER]
                          [--repeat REPEAT] [--skill SKILLS] [--script SCRIPT]
                          [--no-agent] [--workdir WORKDIR]
                          schedule [prompt]

positional arguments:
  schedule           Schedule like '30m', 'every 2h', or '0 9 * * *'
  prompt             Optional self-contained prompt or task instruction

options:
  -h, --help         show this help message and exit
  --name NAME        Optional human-friendly job name
  --deliver DELIVER  Delivery target: origin, local, telegram, discord,
                     signal, or platform:chat_id
  --repeat REPEAT    Optional repeat count
  --skill SKILLS     Attach a skill. Repeat to add multiple skills.
  --script SCRIPT    Path to a script under ~/.hermes/scripts/. Default mode:
                     script stdout is injected into the agent's prompt each
                     run. With --no-agent: the script IS the job and its
                     stdout is delivered verbatim. .sh/.bash files run via
                     bash, everything else via Python.
  --no-agent         Skip the LLM entirely --- run --script on schedule and
                     deliver its stdout directly. Empty stdout = silent.
                     Classic watchdog pattern (memory alerts, disk alerts, CI
                     pings).
  --workdir WORKDIR  Absolute path for the job to run from. Injects AGENTS.md
                     / CLAUDE.md / .cursorrules from that directory and uses
                     it as the cwd for terminal/file/code_exec tools. Omit to
                     preserve old behaviour (no project context files).

两个位置参数:schedule 和 prompt。schedule 支持两种写法------cron 表达式(0 9 * * *)和自然语言(30mevery 2h)。后面我用 0 6 * * *,五段依次是「分 时 日 月 周」,代表每天 6:00。

几个选项先划重点:

  • --script:指定 scripts/ 目录下的脚本。默认模式下脚本 stdout 注入 agent 的 prompt;--no-agent 下脚本本身就是 job。
  • --no-agent:完全跳过 LLM,按计划跑脚本、把 stdout 直接投递。空输出 = 静默,这是 watchdog 模式。
  • --workdir:job 的运行目录,会把该目录的 AGENTS.md / CLAUDE.md / .cursorrules 注入上下文。
  • --deliver:输出投递目标,默认 local。

改 job 用 hermes cron edit <id>,选项和 create 基本对齐(--schedule / --script / --no-agent / --workdir 都有),还多了 --agent 能把 no-agent 的 job 改回 agent 模式。这串帮助里有几个设计判断,后面展开。先落地。

把管线接进 cron:建一个真实的 job

先写调度入口脚本。cron --script 要求脚本路径在 scripts/ 下------Windows 的 Hermes home 是 %LOCALAPPDATA%\hermes,所以放在 %LOCALAPPDATA%\hermes\scripts\run_daily_report.py。逻辑很直接:os.chdir 到日报目录,按序用 subprocess 跑 collect→curate→render,stdout 输出每步的最后一行作简报。

python 复制代码
#!/usr/bin/env python3
"""H7 调度入口:6:00 无人值守跑「各家 AI 动态日报」管线。

串起 collect → curate → render 三步,stdout 输出简报,供 cron 投递。
注:curate.py 内部已调 hermes chat 汇总,此脚本本身不需要 LLM;
    用 hermes cron --no-agent 挂载,脚本即 job、stdout 即投递内容。
"""
import os
import subprocess
import sys

DAILY = r"D:\CF-AICoding\hermes-lab\daily-report"
STEPS = ["collect.py", "curate.py", "render.py"]  # 顺序即依赖


def main() -> int:
    os.chdir(DAILY)
    lines = []
    for step in STEPS:
        p = subprocess.run(
            [sys.executable, step],
            capture_output=True,
            text=True,
            encoding="utf-8",
            errors="replace",
        )
        out = (p.stdout or "").strip()
        err = (p.stderr or "").strip()
        if p.returncode != 0:
            print(f"[fail] {step} rc={p.returncode}")
            if err:
                print(err)
            return p.returncode
        # 取该步最后一行输出作简报;无输出则如实标注
        tail = out.splitlines()[-1] if out else "(no output)"
        lines.append(f"[ok] {step}: {tail}")
    print("\n".join(lines))
    return 0


if __name__ == "__main__":
    sys.exit(main())

一个细节:curate.py 内部已经调 hermes chat 汇总,所以这个调度脚本本身不需要 LLM。用 --no-agent 挂载,脚本即 job、stdout 即投递内容,外层不用再起一个 agent 循环。

建 job 的命令和输出,逐字取自本机:

text 复制代码
$ hermes cron create "0 6 * * *" --name "daily-report-0600" --script "run_daily_report.py" --no-agent --workdir "D:\CF-AICoding\hermes-lab\daily-report"
Created job: 21967688cba0
  Name: daily-report-0600
  Schedule: 0 6 * * *
  Script: run_daily_report.py
  Mode: no-agent (script stdout delivered directly)
  Workdir: D:\CF-AICoding\hermes-lab\daily-report
  Next run: 2026-08-29T06:00:00+08:00
  ⚠  Gateway is not running --- jobs won't fire automatically.
     Start it with: hermes gateway install
                    sudo hermes gateway install --system  # Linux servers
     Check status:  hermes cron status

job 建好了,id 是 21967688cba0,Next run 已经算到次日 6:00。看到这一步,你八成以为建好 cron job 就完事------不是。最后三行那个 ⚠ 警告,才是这篇最重要的工程事实。

无人值守的前提:gateway 常驻

hermes cron status 把话说得很直白,逐字:

解法两条路:hermes gateway install 把 gateway 装成服务(Windows 装成 Scheduled Task,登录自启;Linux 装成 system 服务),或者 hermes gateway run 前台一直跑着。装成服务才是真正无人值守的前提------前台跑意味着你得留着那个终端。

诚实边界:本机我没把 gateway 装成服务,cron 到 6:00 自动 fire 的那一下我也没真等过。这篇讲的「无人值守自动触发」机制是实打实的------create 的 ⚠ 警告、cron status 的检查,都是实跑取证的输出。但自动触发那一刻,标「待核实」。我验证的是手动触发路径,下一节讲。

手动触发与追溯:cron run + runs + 落盘

gateway 没跑,不等于 job 不能跑。cron 有个专门绕开 gateway 的调试通道:cron run <id> 直接触发。

text 复制代码
$ hermes cron run 21967688cba0 --accept-hooks
Triggered job: daily-report-0600 (21967688cba0)
  Next run: 2026-08-29T06:00:00+08:00
  Ran now: succeeded.

cron runs 记录这次执行------durable execution,执行记录持久化,不是进程内存里过一下就没了:

text 复制代码
0cb54fe76a424dcdb73bacb7bf70f67f  completed  job=21967688cba0  source=direct  2026-08-28T21:25:27.719765+08:00

source=direct,记录的是我这次手动触发;自动触发时这个字段会不一样,但本机没真等过 6:00,具体值我没取到,不瞎写。每一行的执行 ID,都能对着 executions.db 追。

执行输出落在 %LOCALAPPDATA%\hermes\cron\output\<job_id>\<时间戳>.md。我这次触发的输出,逐字:

text 复制代码
# Cron Job: daily-report-0600

**Job ID:** 21967688cba0
**Run Time:** 2026-08-28 21:25:45
**Mode:** no_agent (script)

---

[ok] collect.py:   - 跨天去重:过滤掉 24 条昨天已报道的标题
[ok] curate.py: 今日无新动态:24 条与昨日已报道重复,已剔除。日报沿用昨日内容。
[ok] render.py: 已报道档案更新:新增 0 条标题 → history.json

注意 Mode 是 no_agent (script)------没有 agent 循环,纯脚本,这是 --no-agent 的直接体现。文件路径带着 job id 和时间戳,每次执行一落盘,天然可追溯。

顺带说明:这个 job 的 deliver 是 local,输出只落本地文件。系列第 7 篇《多渠道接入》的 push.py 还没接进调度------推送要凭据,本机没配,标「待核实」。推送跑通的无人值守,等凭据配好才算数。

重复触发也不会重复:跨天去重兜底

上面那份输出,内行人一眼能看出它有多「安全」。当时 24 条新闻全都报道过了,于是 collect 全部滤掉、curate 走了「今日无新动态」守卫、render 归档 0 条。

这意味着:即使 cron 在无人值守下重复触发、触发时机错了、一天跑了两遍,日报也不会产生两份重复内容。跨天去重在这儿成了调度的安全网------系列第 4 篇《记忆系统拆解》做的去重,反过来给调度兜底。

curate.py 里那个守卫是关键(逐字):items 为空时不白调 Hermes,直接写「今日无新动态」。空输出时 --no-agent 的 stdout 就一句话,投递也不会变成垃圾。

自动化最怕的不是「跑挂了」,是「跑挂了还产出一堆重复的东西」。跨天去重把后一种风险焊死了。

job 生命周期:pause / resume / tick

job 不是建好就一劳永逸。生命周期命令,逐字:

text 复制代码
$ hermes cron pause 21967688cba0
Paused job: daily-report-0600 (21967688cba0)

$ hermes cron resume 21967688cba0
Resumed job: daily-report-0600 (21967688cba0)
  Next run: 2026-08-29T06:00:00+08:00

resume 之后 Next run 还是 2026-08-29T06:00------暂停不改变下一次运行的时间点。cron tick 是「把到期 job 跑一遍就退出」,我触发它时没有到期 job,它静默返回,一个字都不输出。这个行为后面在工程判断里会用到。

job 的完整状态,画成生命周期图:

持久化落在 %LOCALAPPDATA%\hermes\cron\ 下:jobs.json 存 job 定义,executions.db 存执行记录,output/ 存每次 stdout。jobs.json 里这个 job 的完整字段,逐字(节选):

json 复制代码
{
  "id": "21967688cba0",
  "name": "daily-report-0600",
  "script": "run_daily_report.py",
  "no_agent": true,
  "schedule": { "kind": "cron", "expr": "0 6 * * *", "display": "0 6 * * *" },
  "repeat": { "times": null, "completed": 1 },
  "enabled": true,
  "state": "scheduled",
  "next_run_at": "2026-08-29T06:00:00+08:00",
  "last_run_at": "2026-08-28T21:25:45.675342+08:00",
  "last_status": "ok",
  "deliver": "local",
  "workdir": "D:\\CF-AICoding\\hermes-lab\\daily-report"
}

enabled / state 是 pause / resume 改的;last_run_at / last_status 是我手动触发后更新的。整个 job 就是一份可读的 JSON------调度器重启、机器重启,都靠这份文件恢复现场。

什么任务值得上调度

cron 用起来了,该回答一个更本质的问题:什么任务值得上调度?我的判断三条,缺一不可。

第一,固定时间。日报在每天 6:00,时间点是定的。一个「等我有空再跑」的任务,不需要 cron。

第二,确定性流程。collect→curate→render 每一步都是脚本,输入输出确定,没有需要 agent 临时决策、来回确认的环节。凡是跑一半会卡住问你「要不要继续」的任务,都不适合无人值守------没人回答它。

第三,可离线查结果。执行输出落盘,你起床再翻,不耽误事。反过来,一个「跑完必须立刻有人处理」的任务,调度只负责跑,不负责唤醒人。

对照这三条,日报是教科书式的调度对象。顺带说省 token:--no-agent 模式下,cron 只跑脚本,不起 agent 循环。curate.py 内部调 Hermes 汇总那一下才消耗 token,外层没有额外 LLM 开销。这是最省的定时跑法------脚本即 job,stdout 即投递内容。

还有 watchdog 模式:--no-agent 帮助里那句「Empty stdout = silent」。意思是脚本输出为空时,这次执行就是安静的,不产生任何投递。适合「有告警才说话」的任务------磁盘超了才输出一行。

结论:无人值守上线,新问题也跟着来

回到开头那个画面。日报现在能自己跑了------job 建好、手动触发验证跑通、执行输出可追溯,跨天去重兜底。README 那句「H7 用 Hermes 的 cron 在 6:00 自动跑」的承诺,兑现了一半:job 在,手动触发在;6:00 自动那一下,等 gateway 装成服务才算数。

但无人值守不是把问题消掉了,是把问题换了一批。以前你盯着跑,现在没人盯------跑挂了谁发现?以前 key 在环境变量里,现在多了一个常驻的 gateway 进程------凭据面变大了;以前手动跑一次花一次钱,现在每天自动跑------成本不声不响地累积。这三件,就是我下一篇要处理的:安全、本地模型、成本运维。

跑起来的调度,让日报第一次像一个「产品」而不是一个「脚本」。但产品是有运维责任的。下一篇见。

最后留个问题:如果让你给自己的 agent 配一个定时任务,你想让它每天替你做什么?查天气、盯服务器告警、把关注的博客打包成早报、还是让它定时清理你的下载目录?

想看我怎么处理无人值守的安全与运维,关注别丢。

相关推荐
3A Cloud1 小时前
Huashu Design:把 AI Agent 变成一间以 HTML 为画布的设计工作室
前端·人工智能·html
xwz小王子1 小时前
Science Robotics | 从模仿到创造:BeyondMimic如何让机器人“学到”人类的敏捷与多才多艺
人工智能·机器人
克里斯蒂亚诺更新1 小时前
LoRA和QLoRA(量化版)
人工智能
Ai思想家1 小时前
一次模型调用会留下什么:聚合平台的日志留存与数据边界
大数据·服务器·网络·人工智能
Fairy要carry1 小时前
初创实习-IMU模型
人工智能·算法·机器学习
枫叶林FYL1 小时前
【群体智能集群控制工程实践】第1章 群体智能核心原理
人工智能·深度学习
是Yu欸1 小时前
实测openPangu-2.0-Pro:昇腾原生, 505B大模型的开源答卷
人工智能·开源·大模型·昇腾·pangu
FPGA开源工坊1 小时前
ISP图像处理--Bayer Raw格式
图像处理·人工智能·verilog·fpga·isp
2601_967659891 小时前
2026停车场巡检机器人排行:地下停车场夜间巡逻怎么选
人工智能·机器人