# 三行速读
- 这章让 “时间” 成为工作触发入口。
- 调度器只负责触发,执行仍回到统一主循环。
- durable + lock + 过期策略是调度稳定性的三件套。
# 先修知识
- 已理解 s13 的后台执行与通知回流。
- 了解 cron 表达式基础语法。
# 读完后你应该能做到(可检验清单)
# 本篇要解决什么
s13 让任务能后台运行,但触发入口仍偏 “被动”—— 通常要等用户当前轮输入。s14 增加时间触发,让系统可以在约定时刻自动发起工作。
核心目标:让 “时间” 成为统一 loop 的一个输入源。
# 用一个类比先理解
像闹钟提醒:
- 你提前设好时间和内容;
- 到点自动响铃;
- 你按同一套流程处理提醒。
cron 就是给 Agent 加了 “可编排闹钟”。
# 为什么这一步必须现在做
没有时间触发,很多周期性工作(巡检、摘要、重试)都要人工盯着。s14 把这类重复动作自动化,但仍然保持在同一条执行主线里。
# 关键代码怎么读
# 1) 调度持久化参数
1 | SCHEDULED_TASKS_FILE = WORKDIR / ".claude" / "scheduled_tasks.json" |
scheduled_tasks.json 让调度跨会话保留, AUTO_EXPIRY_DAYS 防止规则长期遗留。
# 2) CronLock 防止重复触发
1 | class CronLock: |
这一步避免多个会话同时触发同一计划任务。
# 3) cron 表达式匹配
1 | def cron_matches(expr: str, dt: datetime) -> bool: |
教学版手写匹配器,便于理解 cron 语义,不依赖外部黑盒库。
# 4) 通知回灌 loop
1 | # check thread -> queue |
调度不是旁路执行,而是重新进入同一 loop,权限和上下文策略可以复用。
# 你可以怎么复现
- 创建一个每 5 分钟触发的规则。
- 开启会话并观察通知注入。
- 删除规则,确认停止触发。
# 常见误区
- 误区 1:把 cron 当执行器,而不是触发器。
- 误区 2:忘记去重锁,出现重复触发。
- 误区 3:缺少过期策略,规则堆积。
# 一句话总结
s14 的本质是把 “未来工作” 接回当前主循环,形成时间驱动的自动推进能力。
# 补充解读:调度器为什么要做持久化 + 锁
# A. create() 的四个核心字段
cron (何时触发)、 prompt (触发后做什么)、 recurring (是否重复)、 durable (是否跨会话保存)。
这四项是调度记录最小完备集。
# B. jitter 的工程意义
1 | JITTER_MINUTES = [0, 30] |
如果大量任务都卡在整点触发,会形成尖峰。抖动机制能把负载摊平。
# C. drain_notifications() 与主 loop 的衔接
调度线程只负责 “发现触发时机并入队”,真正执行还是交给主 loop。这避免了线程里直接执行业务造成一致性问题。
# D. durable 与 session-only 的差异
- durable: 重启后仍在。
- session-only: 会话结束即失效。
学习时建议先用 session-only,验证逻辑后再开 durable。
# 进阶练习
- 实现
cron_pause/cron_resume。 - 给调度任务加
max_runs。 - 做一次 “跨重启” 验证 durable 行为。
# 再补一层:如何把调度做得 “稳” 而不是 “多”
# A. 先从 one-shot 任务练起
新手建议先用一次性调度验证链路:创建 -> 触发 -> 注入 -> 执行 -> 自动清理。等这条链稳定后再开启 recurring。
# B. recurring 任务必须带治理策略
至少补两件事:
- 自动过期或人工停用。
- 触发失败后的重试上限。
否则规则会长期积累,系统会越来越重。
# C. 锁冲突要有可观测输出
当 CronLock 拿不到锁时,建议打印或记录 “已有会话持锁” 的事件,不然你会以为规则没生效。
# D. 触发动作尽量幂等
同一任务偶发重复触发时,幂等动作能显著降低事故概率。比如 “写入前先检查是否已存在同一执行记录”。
# 统一术语口径(本章)
Cron Expression:定义任务触发时间规则的五段式表达。Recurring:按规则重复触发。Durable Schedule:跨会话保留的调度记录。
# 章节衔接(从易到难)
- 本章解决 “时间驱动触发”。
- 下一章
s15进入 “从单 Agent 到持久化团队协作”。