用户拿到一份报告后说:“以后每天早上都做一份。”保存这段提示词还不够。补跑日期、模板更新、通知失败和历史追溯,都会让每天一次变成不同的执行。
我把模板、计划、运行和产出分开管理。模板定义怎么做,计划定义何时触发和使用哪个版本,运行固定实际输入,产出关联这一次执行。
版本决定一份报告的依据
模板版本保存指令、参数约束、输出格式与依赖。计划保存规则、时区、执行身份和模板版本。每个 Run 再保存计划时刻、业务区间、解析后的参数与尝试记录。
模板修改生成新版本,已有计划不会悄悄跟着升级;用户确认后影响未来运行。删除模板也不级联删除历史报告。查询旧结果不重新解释“昨天”,而是读取当次输入。
业务日期由计划时刻解析
每天当地时间 08:00 分析前一天,就按计划时区计算前一天零点至当天零点,再转成实际查询区间。采用左闭右开范围,避免 23:59:59 的精度遗漏。
from datetime import datetime, timedelta, time
from zoneinfo import ZoneInfo
zone = ZoneInfo("Asia/Shanghai")
scheduled = datetime(2026, 5, 8, 8, 0, tzinfo=zone)
day = scheduled.date() - timedelta(days=1)
start = datetime.combine(day, time.min, zone)
end = datetime.combine(day + timedelta(days=1), time.min, zone)
assert start.isoformat() == "2026-05-07T00:00:00+08:00"前一天不等于当前时间减 24 小时。有夏令时的地区,一个业务日可能不是 24 小时;下午补跑仍分析原计划的业务日。非零点业务边界、重复或不存在的本地触发时间,也要有明确规则,不能由底层库默认选择。
一次计划运行与一次尝试各有身份
计划标识与计划时刻仅对计划触发形成唯一约束。多个调度器扫描同一时点,只创建一份 Run。Run 与待派发事件在同一事务保存,避免创建成功却永远没有执行通知。
人工有意重算是新运行,保留 parentRun 与独立请求键;网络重试则是原 Run 的新 Attempt,沿用原参数与版本。两者不能都叫重新执行,然后在后台随机决定输入。
领取和完成使用代次条件,旧尝试不能覆盖新结果。上一轮没完成时,日报第一版采用串行并限制积压;暂停期间是否补跑由明确策略决定,恢复时列出待补日期,不自动放进半年任务。
生成与通知分别留下检查点
报告写入临时位置,完整后发布不可变版本并关联 Run。正式产出提交以后进程退出,恢复可跳过生成,继续通知,不再多生成一份。
通知使用 runId 与接收方组成稳定操作键,发出前保存操作记录。超时保留 sent_unknown,按下游能力核对是否已发送。对方不能可靠识别时,交给人工,不删记录后直接重发。
input_resolved、report_committed 与 notification_confirmed 对应不同证据。连接失败不抹去已发布报告,通知成功也不能补成报告内容已通过业务审核。
历史结果能重现到什么程度
严格重现需要输入快照或可重读的快照引用。数据源只能提供当前值,就记录查询时刻与限制,不能因提示词固定而承诺完全复现。模型配置和外部工具版本也进入运行记录。
验收覆盖重复扫描、跨日补跑、模板升级、通知超时与暂停恢复。08:00 的任务生成后通知失败,08:05 恢复仍引用同一报告和业务日,只核对通知。最终留下可追溯的报告和明确发送状态,这就是计划运行的完整结果。