# 三行速读

  1. 这章让 “时间” 成为工作触发入口。
  2. 调度器只负责触发,执行仍回到统一主循环。
  3. durable + lock + 过期策略是调度稳定性的三件套。

# 先修知识

  • 已理解 s13 的后台执行与通知回流。
  • 了解 cron 表达式基础语法。

# 读完后你应该能做到(可检验清单)

# 本篇要解决什么

s13 让任务能后台运行,但触发入口仍偏 “被动”—— 通常要等用户当前轮输入。s14 增加时间触发,让系统可以在约定时刻自动发起工作。

核心目标:让 “时间” 成为统一 loop 的一个输入源。

# 用一个类比先理解

像闹钟提醒:

  • 你提前设好时间和内容;
  • 到点自动响铃;
  • 你按同一套流程处理提醒。

cron 就是给 Agent 加了 “可编排闹钟”。

# 为什么这一步必须现在做

没有时间触发,很多周期性工作(巡检、摘要、重试)都要人工盯着。s14 把这类重复动作自动化,但仍然保持在同一条执行主线里。

# 关键代码怎么读

# 1) 调度持久化参数

1
2
3
SCHEDULED_TASKS_FILE = WORKDIR / ".claude" / "scheduled_tasks.json"
CRON_LOCK_FILE = WORKDIR / ".claude" / "cron.lock"
AUTO_EXPIRY_DAYS = 7

scheduled_tasks.json 让调度跨会话保留, AUTO_EXPIRY_DAYS 防止规则长期遗留。

# 2) CronLock 防止重复触发

1
2
3
4
5
class CronLock:
def acquire(self) -> bool:
...
os.kill(stored_pid, 0)
...

这一步避免多个会话同时触发同一计划任务。

# 3) cron 表达式匹配

1
2
3
4
def cron_matches(expr: str, dt: datetime) -> bool:
fields = expr.strip().split()
...
return True

教学版手写匹配器,便于理解 cron 语义,不依赖外部黑盒库。

# 4) 通知回灌 loop

1
2
3
# check thread -> queue
# queue drained at top of agent_loop
# injected as user messages

调度不是旁路执行,而是重新进入同一 loop,权限和上下文策略可以复用。

# 你可以怎么复现

  1. 创建一个每 5 分钟触发的规则。
  2. 开启会话并观察通知注入。
  3. 删除规则,确认停止触发。

# 常见误区

  • 误区 1:把 cron 当执行器,而不是触发器。
  • 误区 2:忘记去重锁,出现重复触发。
  • 误区 3:缺少过期策略,规则堆积。

# 一句话总结

s14 的本质是把 “未来工作” 接回当前主循环,形成时间驱动的自动推进能力。

# 补充解读:调度器为什么要做持久化 + 锁

# A. create() 的四个核心字段

cron (何时触发)、 prompt (触发后做什么)、 recurring (是否重复)、 durable (是否跨会话保存)。

这四项是调度记录最小完备集。

# B. jitter 的工程意义

1
2
JITTER_MINUTES = [0, 30]
JITTER_OFFSET_MAX = 4

如果大量任务都卡在整点触发,会形成尖峰。抖动机制能把负载摊平。

# C. drain_notifications() 与主 loop 的衔接

调度线程只负责 “发现触发时机并入队”,真正执行还是交给主 loop。这避免了线程里直接执行业务造成一致性问题。

# D. durable 与 session-only 的差异

  • durable: 重启后仍在。
  • session-only: 会话结束即失效。

学习时建议先用 session-only,验证逻辑后再开 durable。

# 进阶练习

  1. 实现 cron_pause / cron_resume
  2. 给调度任务加 max_runs
  3. 做一次 “跨重启” 验证 durable 行为。

# 再补一层:如何把调度做得 “稳” 而不是 “多”

# A. 先从 one-shot 任务练起

新手建议先用一次性调度验证链路:创建 -> 触发 -> 注入 -> 执行 -> 自动清理。等这条链稳定后再开启 recurring。

# B. recurring 任务必须带治理策略

至少补两件事:

  1. 自动过期或人工停用。
  2. 触发失败后的重试上限。

否则规则会长期积累,系统会越来越重。

# C. 锁冲突要有可观测输出

CronLock 拿不到锁时,建议打印或记录 “已有会话持锁” 的事件,不然你会以为规则没生效。

# D. 触发动作尽量幂等

同一任务偶发重复触发时,幂等动作能显著降低事故概率。比如 “写入前先检查是否已存在同一执行记录”。

# 统一术语口径(本章)

  • Cron Expression :定义任务触发时间规则的五段式表达。
  • Recurring :按规则重复触发。
  • Durable Schedule :跨会话保留的调度记录。

# 章节衔接(从易到难)

  • 本章解决 “时间驱动触发”。
  • 下一章 s15 进入 “从单 Agent 到持久化团队协作”。