“30 分钟后检查部署”“每两小时看一次磁盘”“工作日九点总结日志”,都不是当前轮次要立刻完成的事。没有调度器,Agent 只能把提醒责任重新交还给用户。
Hermes 最有价值的取舍是:到期任务不会走一条新执行链,而是重新变成一条消息。
这篇只回答一个问题:怎样让 Agent 在未来主动做事,又不重新实现一套执行、重试和回复系统?
Hermes 给出的答案是:JobScheduler 在独立线程中检查延时、间隔和 cron 任务,到期后构造 MessageEvent 或直接触发同一 run_conversation,让计划任务复用现有执行链。
调度器必须独立于用户输入
把时间检查塞进主循环,CLI 的 input 会阻塞,Gateway 又根本没有同样的输入方式。Hermes 把 JobScheduler 放进独立线程,让入口是否活跃不影响到期判断。
主线程通过 cron_tool 写任务,后台线程读取并推进任务,因此 JobStore 需要锁。调度不是单线程小功能,而是一份被多个执行者共享的状态。
延时、间隔和 cron 服务不同表达
“30 分钟后”是一次性延时,“每两小时”是固定间隔,“工作日九点”适合 cron。强迫所有自然语言意图转换成 cron,会把简单需求变成日期计算。
Hermes 保留三种格式,让简单场景简单表达。解析时仍要处理 cron 与 Python 对星期编号定义不同等细节。
任务到期后,重新成为一条消息
Gateway 模式中,调度器不会直接执行 shell 命令,而是构造 MessageEvent 送进 Gateway。于是工具调用、权限审批、错误恢复、子 Agent 和会话持久化全部复用。
任务结果也能沿原平台回到用户。调度器只决定何时触发,Agent 决定如何完成,渠道适配器决定如何送达。
持久化与清理决定任务会不会失控
一次性任务触发后必须删除,否则下一轮扫描还会重复执行;循环任务则更新 next_fire。jobs.json 写到一半进程崩溃,会让下次启动无法解析。
Hermes 建议先写临时文件再 rename,并在读取损坏文件时降级处理。调度能力的可信度,往往取决于这些不显眼的状态细节。
“30 分钟后检查部署”如何落地
用户提出需求后,Agent 调用 cron tool。调度表达式解析器把 30m 转成具体触发时间,并标记为一次性任务;JobStore 将任务内容、session key、回复平台和 next fire 等信息写入 jobs.json。
后台 JobScheduler 持续检查到期时间。任务到点后,它不会直接执行部署检查命令,而是构造一条带原会话信息的消息。
Gateway 模式下,这条消息进入 _handle_message;CLI 模式下,则由另一种 fire callback 调用同一个 run_conversation()。Agent 获得完整工具、权限和错误恢复能力,执行结果再沿原渠道返回用户。
回复完成后,一次性任务从 JobStore 删除;循环任务只推进下一次触发时间。整条链路把 调度、执行、会话和回复 分开,避免 cron 模块逐渐复制出一套平行 Agent。
三个容易走偏的地方
- 在主线程轮询任务,到期检查被输入或其他工作阻塞。
- JobStore 多线程读写不加锁,遍历与修改发生冲突。
- 一次性任务触发后不删除,导致重复通知或重复执行。
产品经理可以怎样评审
评审这项能力,可以先检查三条:
- 调度器只负责何时触发,不应复制 Agent 已有的执行能力。
- 任务状态至少要区分一次性、循环、下次触发时间和原始回复渠道。
- 当需求出现依赖链和复杂补偿时,应评估工作流引擎,而不是继续膨胀 cron。
原材料教学版使用本地时间;生产时区与分布式去重没有展开,本文保留为边界。
