Appearance
3.8 定时任务 Schedule
本章概览:Schedule 拥有"会话本地"提醒:提醒的持久状态在原始 Session 日志里,进程本地 owner 只在会话有活根代理时等待。本章覆盖
schedule/change事件、after/at/every 三种选择器、确定性日历规范化、persistence_uncertain与交付生命周期。
概述 / 定位
Schedule 族只有一个包(dsh-schedule),刻意不暴露公开 Schedule 服务或可变数据库:工具与运行时向 Session 事件流追加,到期工作经 Agent 的普通 follow-up 队列回到同一对话。v1 支持正安全整数 after_seconds 延迟、显式绝对 at 目标、以及至少五分钟的固定 every_seconds 间隔。进程本地 owner 只在会话有活根代理时等待;冷会话恢复时补办过期工作,从不暗示存在外部通知通道。
出处:
packages/schedule/README.md(deepseek-harness-src/packages/schedule/README.md)
日志即状态
版本化 schedule/change 事件是唯一持久 Schedule 权威,含三种操作:create(完整记录)、delete(终态、仅 id)、dispatch(one-shot 仅 id;Every 携带决策时间 acceptedAt 并直接前进到下一个锚点对齐目标)。定时器、工具值、模型 follow-up 都是日志的可弃投影——重建只需从日志折叠;测试用显式样本或假定时器,不为此加生产时钟服务。严格解码拒绝未知版本、多余字段、复用 id、形状不匹配的 dispatch,以及针对非活跃记录的 delete/dispatch 转换。普通会话折叠完整日志;fork 只折叠 seedLength 之后的事件,不继承父会话的活跃提醒。
每条 create 记录含稳定的会话本地 ScheduleId、修剪后的 prompt 与四位数年份的 RFC 3339 UTC scheduledAt:after 记录还存 afterSeconds;at 记录不存提交的偏移、本地日历字段或解释时区;every 记录存 everySeconds,scheduledAt 是最早的、尚未派发的创建锚点对齐出现。
出处:
packages/schedule/schedule/README.md(deepseek-harness-src/packages/schedule/schedule/README.md)、docs/subsystems/schedule.md(deepseek-harness-src/docs/subsystems/schedule.md)
绝对时间输入的严格校验
at 选择器是严格的 YYYY-MM-DDTHH:mm:ss[.S|.SS|.SSS](Z|±HH:MM) 字符串,或 { date, time, time_zone } 本地形式——本地形式必须显式 UTC 或合法 IANA Area/Location 时区。缺失 time_zone、无偏移字符串、多余键、非法偏移、非未来目标都被拒绝。Schedule 拥有确定性日历规范化:夏令时间隙(daylight-saving gap)内的本地时间被拒绝;重叠取第一个更早时刻。成功创建只保留规范 UTC scheduledAt——没有任何 Schedule 路径读浏览器、Session 头部、模型 time-context、连接或进程时区,重放因此不依赖环境时区状态。
persistence_uncertain 与单队列
每个从折叠读取或决策的管理操作先 await ctx.sessions.flush(session);缺失、被拒或脱离的持久化路径返回稳定错误 persistence_uncertain——绝不把未确认的活日志后缀当作 list 或 not-found 答案。成功 create 与实际 delete 在追加后再 await 一次屏障才确认变更。一个 Agent 作用域队列把所有被接受的管理事务与活 owner 的到期事务从预检到追加后屏障串行化。每次成功的管理预检还会请活 owner 重算——这是对上次屏障返回 persistence_uncertain 的保留 create/delete 批次的恢复路径,无需专门的持久化重试定时器。
管理工具与交付生命周期
三个工具 schedule_create / schedule_list / schedule_delete:create 要求恰好一个选择器并先做纯形状校验;list 返回创建顺序的活跃记录,带 state: "scheduled" | "overdue" 与 deliveryMode: "session-local";delete 拒绝空/空白 id,未知或终态 id 返回 { deleted: false, code: "schedule_not_found" }。
到期处理:活 owner 从持久折叠派生最早目标,超过 Node 定时器范围的等待分片进行,每次唤醒重读壁钟——回拨不会提前触发,前跳使记录 overdue。到期 one-shot 有优先级、一次一轮;无 one-shot 到期时,全部 overdue Every 记录按目标与创建顺序组成一批。到期先 checkpoint;若轮次或其他维护任务占用 Agent,空闲阶段认领(idle-phase claim)被拒、记录保持活跃并等待 whenIdle() 重试。成功的维护任务重折叠、取样一次决策时间、构造固定 framing(提醒内容经 JSON 转义,标注为不可信提醒内容而非新指令)、同步 followup()、追加 dispatch 后释放阶段。Every 记录用整数运算选定各自最新的到期锚点对齐出现并直接前进到第一个未来目标——错过区间不枚举、不重放;不同 overdue 记录各贡献一次出现,无共享复发门。dispatch 意味着 follow-up 已排队并记录,不意味着模型成功或用户已读;framing 或同步入队失败则不写 dispatch。
与 time-context 的关系
time-context 不是 Schedule 依赖:组合可挂 dsh-time-context 让模型在浏览器 request-local 时区解释自然语言日期(官方 Schedule Web overlay 如此),但模型仍必须向 schedule_create 传显式偏移或 time_zone——Schedule 从不导入或推断模型上下文。混合或缺失来源的措辞由 time-context 指导模型反问,而不是由 Schedule 承担。
关键设计决策
- 无服务、无库,日志即状态:全部可重建;管理操作先冲日志再决策。
- 严格绝对时间:确定性日历规范化、DST 间隙拒绝、重叠取早、只存规范 UTC。
- 持久化不确定性显式:
persistence_uncertain让调用方知道写入未确认,而不是猜测。 - 会话本地交付:无外部通知通道;冷会话零工作,重开时补办过期。
- Every 只补最新:不枚举错过区间;批量把多个 due 记录合成一个 follow-up,限制模型轮次;五分钟下限限制定时器频率。
- 单队列串行化:管理事务与到期事务共享一个 Agent 作用域队列,无并发交错。
扩展点
- 新规则/选择器:扩展版本化记录联合与严格折叠(未知版本被拒绝,需版本演进)。
- 交付策略:在"到期 → follow-up 队列"边界后接自己的消费方(如 UI 渲染或通知)。
- 时区解释:在工具边界之前(模型侧 time-context 或浏览器 overlay)做自然语言 → 显式偏移/时区转换。
- 新管理入口:三个工具注册在活根代理的
agent.ctx;组合其他工具集时按同样作用域规则挂载。
参考资料
- packages/schedule/README.md — 能力族总览(
deepseek-harness-src/packages/schedule/README.md) - packages/schedule/schedule/README.md — 记录、工具与交付(
deepseek-harness-src/packages/schedule/schedule/README.md) - docs/subsystems/schedule.md — 会话本地 Schedule 参考(
deepseek-harness-src/docs/subsystems/schedule.md) - 设计笔记:durable web schedule、conversational schedule delivery、explicit schedule time zone、bounded fixed-rate schedule