工程化 / 大模型应用工程化
15. 长任务与断点续做:换一个会话,为什么还能接着干
原文备份 · 第 17 / 21 篇 · 查看极客时间原知识库 ↗
本篇目录 · 10 节
来源:大模型应用工程化
设想上一轮的循环执行到一半,终端关闭了。第二天,你重新打开项目,看见运行状态写着 interrupted,输出目录却已经有一份复习资料。
这时直接说“继续刚才的任务”,可能产生两种错误:助手以为文件尚未生成,又导出一次;或者看到文件存在,就把整个任务标为完成,跳过了验证。
长任务能接着做,靠的是把已发生的事实、尚未确认的部分和下一步动作保存下来,并在接手时重新核对。恢复会话可以找回讨论,恢复工作则必须面对当前文件与真实执行结果。
本篇会构造一次明确的教学中断:导出已经写出文件,但成功回执尚未保存。然后用一个新的执行过程检查现场,只补做产物核验,另存交接记录。原有文件和中断状态都保留,不重跑导出。
配套:工作区说明 · 完整 ZIP。Python 3.11 或更高版本、标准库即可;不需要 API Key、SDK 或模型账户。候选与中断情景由作者准备,不是 Kimi 实际会话,不代表真实服务事故。
一、三个“接着干”,其实不是一件事
| 你想恢复什么 | 需要找回什么 | 不能仅靠它保证什么 |
|---|---|---|
| 聊天会话 | 过去的讨论、工具记录、任务意图 | 当前工作目录没有变化 |
| 开发工作 | 代码版本、未提交修改、测试和未完成事项 | 上次启动的动作没有产生副作用 |
| 程序执行 | 已持久化状态、实际产物、外部回执 | 可以从任意一行原封不动继续 |
例如,Kimi 的旧会话记得“下一步运行导出”,但那次导出可能已经完成,只是会话未能拿到最终结果。另一方面,即使保存着完整聊天,代码也可能被你手动修改过,过去通过的测试不能自动覆盖今天的版本。
因此,接手时先问“现在有什么证据”,再问“聊天里上次说要做什么”。二者有冲突时,要把差异写出来,不要默默选一个听起来顺畅的故事。
Anthropic 关于长任务的工程实践也把进度文件、Git 历史、逐步工作和接手时的验证放在一起。其核心用途是让新的上下文尽快了解项目现状,而不只是延长聊天记录。参考:Effective harnesses for long-running agents。
二、明确本篇的起点和接手范围
解压后进入 lesson-15 根目录。原笔记助手、导出器、Harness 和 Loop 都与第14篇保持一致。应用仍以 DeepSeek 为模型服务,Kimi 是辅助开发工具。
新增的主要文件是:
prepare_handoff.py # 在新目录构造教学中断
recover_export.py # 检查现场,必要时另存接手记录
verify_recovery.py # 独立核对接手结果
docs/recovery/TASK.md # 当前任务范围
docs/recovery/HANDOFF_TEMPLATE.md
先读当前任务。它只允许处理本课的固定 valid 导出样例,以及特定的中断状态。它不是通用恢复工具,也不会恢复上一篇的整个 Loop。
还有一个容易漏掉的前提:先确认旧执行过程已经结束,当前只有一个人在操作这些目录。 如果旧进程还在写文件,你的新会话同时开始“恢复”,两个执行者会对同一份现场作出不同判断。
本课的准备命令是同步执行的,返回以后再运行下一条就可以。真实任务则要确认原终端任务或服务的状态,不能只凭聊天页面没动静判断进程已停止。这里没有提供分布式锁或自动进程探测。
三、构造“文件已经写好,回执还没保存”的现场
macOS / Linux:
python3 prepare_handoff.py --run resume-01 --case exported
Windows PowerShell:
py -3 prepare_handoff.py --run resume-01 --case exported
预期输出:
{"status": "interrupted", "case": "exported", "simulated": true, "actual_api_calls": 0}
命令退出0,表示教学场景准备成功;不代表导出流程正常结束。
这个脚本先调用原来的本地导出器,真正写出资料和清单,再注入一个 KeyboardInterrupt。Harness 捕获中断、保存状态,但没有机会写出导出成功回执。随后准备脚本添加 SIMULATION.json,明确标记这是作者构造的情景。
你不需要自己抢时间按 Ctrl+C,更不要为了这个练习强制关闭应用或拔电。作者也没有把这次注入测试宣称为真实断电、进程强杀或模型故障测试。
打开 runs/resume-01:
runs/resume-01/
├── context.json
├── state.json
├── SIMULATION.json
└── artifact/
├── review.md
└── manifest.json
这里应该没有 01-export.json 和 02-verify.json。state.json 中整个运行是 interrupted,而导出步骤停留在 running。
这个现场说明:状态没有更新为成功,不能反推出动作没有发生。 程序在写文件与保存回执之间,总可能有一个时间间隔。接手者需要同时看意图、状态和产物。
四、第一步只检查,不急着重新执行
macOS / Linux:
python3 recover_export.py --run resume-01
Windows PowerShell:
py -3 recover_export.py --run resume-01
预期结果:
{"decision": "reuse_existing_artifact", "reason": "artifact_verified", "semantic_review": "pending", "export_replayed": false}
默认模式只读,不创建接手目录。脚本依次确认:
- 来源是允许的本地运行名,记录中没有本课不支持的符号链接。
- 当前资料与原
context.json的文件指纹一致。 - 原任务、样例、运行名和中断位置符合本课预期。
- 现有
review.md和manifest.json通过原来的独立产物验证。 - 核对期间原记录的文件内容没有变化。
所以 reuse_existing_artifact 是依据现有证据作出的判断,不是“看见一个 Markdown 文件就当它没问题”。它会核对清单、来源、卡片内容和指纹,也会拒绝只有半套文件的目录。
检查仍没有给内容打出语义合格证。pending 继续保留,读者还要对照问题、答案和原文。这里解决的是“能否复用已存在的产物”,不是自动替人决定是否可以发布。
五、续做剩余步骤,另存一份新的判断
确认只读检查结果后,保存本次核验与交接说明。
macOS / Linux:
python3 recover_export.py --run resume-01 --record review-01
python3 verify_recovery.py recovery-records/review-01
Windows PowerShell:
py -3 recover_export.py --run resume-01 --record review-01
py -3 verify_recovery.py recovery-records/review-01
第一条会重新检查现场,不会拿上一条命令的旧判断直接写结果。终端输出与只读检查相同,但这次新增:
recovery-records/review-01/
├── recovery.json
└── HANDOFF.md
第二条独立验证应输出:
{"result": "pass", "status": "needs_review", "original_status": "interrupted", "semantic_review": "pending"}
打开 recovery.json,其中应有 action=verification_only、export_replayed=false,以及原记录中各文件的指纹。再打开 HANDOFF.md,能读到已确认事实、没有做的动作、未解决事项和下一步。
有读者会觉得奇怪:为什么原状态还写着中断,新记录却说可以待审?
因为它们在回答不同问题。原记录描述上次流程在什么位置停了;新记录描述这次接手者重新核对后得到了什么结论。没有必要把旧事故记录改写成一次顺利完成的历史。
本篇也没有补造缺失的旧 01-export.json,仿佛它在中断之前已经存在。补做的是新的验证,不是补写虚假的过去。原运行目录所有文件保持原样,独立验证器会再次核对这些文件与新记录中保存的指纹。
这样,你已经完成了一次有用的续做:复用完成的工作,只做尚需确认的部分,留下新的证据。它没有恢复 Python 当时的内存、调用栈或下一条指令,也没有重新运行导出。
六、不完整的现场,不能靠“应该没问题”通过
再准备两个对照情景。以下为 macOS / Linux 命令;Windows PowerShell 将每行开头的 python3 换成 py -3,其他参数相同。
python3 prepare_handoff.py --run partial-01 --case partial
python3 recover_export.py --run partial-01 --record partial-review
python3 prepare_handoff.py --run empty-01 --case empty
python3 recover_export.py --run empty-01
partial 会在本次新建的教学产物中移除 manifest.json,然后注入中断;empty 则在实际导出前注入中断。两种情况都必须标注为模拟,不能冒充真实事故样本。
两次核查的预期都是:
{"decision": "needs_human", "reason": "evidence_incomplete_or_invalid", "semantic_review": "pending", "export_replayed": false}
核查命令退出1,而且 recovery-records/partial-review 不应被创建。原来的不完整现场保留,不会被清空后重新导出。
| 当前证据 | 本课决定 | 为什么 |
|---|---|---|
| 产物完整、版本吻合、符合固定预期 | 只补核验并另存记录 | 已完成的工作可以复用 |
| 只有资料,没有有效清单 | 交人核查 | 无法按本课标准确认完整性 |
| 没有产物,但记录有执行意图 | 交人核查 | 无法仅凭状态批准自动重放 |
| 输入或代码版本变化 | 停止,比较版本 | 过去的证据不自动适用于现在 |
| 状态不属于本课固定中断模式 | 停止,说明不支持 | 不把一个小示例当通用恢复器 |
在 empty 教学场景中,我们作为构造者知道没有导出。但接手算法故意不把作者的场景标签当完成证据,它依据文件和约定判断。面对真实外部动作,尤其不能把“本地没看到文件”推导为“服务器没执行”。
如果人工最终决定重新开展一次本地导出,应作为明确的新尝试,重新确认输入、范围和预算,使用新运行名并关联旧记录。本篇不会替你自动作这个决定。
七、重复执行和幂等,不要只看命令名称
“再执行一遍应该也没事”有时成立,有时不成立。
读取同一份文件、在稳定版本上重新检查产物,通常适合作为接手的第一步。本篇核验就是这种操作:它不再次创建复习资料,也不改变业务数据。
但保存学习计划、发消息、发布资料等动作,可能每调用一次就产生一个新结果。失败消息还可能发生在动作之后。第11篇中“保存已经成功,后续总结失败”的例子,就属于这种情况。
幂等可以理解为:针对同一个逻辑操作重复执行,最终效果与执行一次相同。给请求起一个新名字并不会自动实现幂等;每次都换一个 ID,反而可能被系统认为是不同任务。
本篇使用独占新目录来拒绝覆盖,用指纹检查现场是否变化。这些有助于保留证据,但不等于给所有外部服务实现了“只执行一次”。真实写操作需要具体系统支持,例如可查询的业务结果、稳定的操作标识、唯一约束或事务;方案要根据动作来设计。参考:AWS 关于幂等 API 与安全重试的工程说明。
不要把恢复流程写成“捕获所有异常,然后从头再来”。越靠近外部副作用,越需要先确认上一次的结果,再决定是否允许重试。
八、一份能交接的记录,应该让下一位少猜什么
打开交接模板。它不是把整段聊天搬进文件,而是整理几类能指导行动的信息。
| 内容 | 应写什么 | 避免怎样写 |
|---|---|---|
| 当前目标与范围 | 只补验证,不重新导出或发布 | “继续完善项目” |
| 版本与入口 | 工作目录、提交或快照、未提交修改、关键路径 | 只写“最新版” |
| 已确认事实 | 实际命令、结果、证据文件和适用版本 | “感觉已经差不多” |
| 不确定事项 | 哪个动作未确认、哪些验证未做 | 把未知写成未执行 |
| 下一步与停止条件 | 先检查什么,只允许补哪一步,何时停 | “一直干到完成” |
本课生成的 HANDOFF.md 是一份具体例子。拿它对照原文件,看每句话能否找到证据:旧状态确实是中断;产物已重新验证;没有再次导出;内容仍需人工审阅。
如果换成实际代码开发,还要补充当前分支、提交和未提交差异。在你自己的已初始化 Git 仓库中,可以用 git status --short、git diff --stat 和 git log -1 --oneline 定位现状。不要在未建仓的练习包里虚构一个提交号,也不要为了让目录“看起来干净”丢掉未提交修改。
测试证据也要带范围:某个提交上的离线测试通过,不代表后续修改也通过;本机通过,不代表已经做过 Windows 或线上验证。交接记录必须让下一位知道哪些结论可以沿用,哪些需要重做。
最后记下预算。换一个聊天不等于任务预算重新满额,旧循环已消耗的尝试、实际调用和未知费用不能被清零。本课续做不调用模型,也不恢复旧 Loop;将来若需要新尝试,应明确剩余额度或新的授权。
九、在 Kimi 中续旧会话,或者用新会话接手
如果你原来就在 Kimi 中工作,可在同一个真实项目目录续最近的会话:
kimi --continue
想自己选择旧会话:
kimi --session
已有明确会话 ID 时,--session 也支持传入 ID。它与 --continue 是两种选择方式,不要一起使用。以上按2026年10月4日官方文档与本机 Kimi Code 2.0.2 的帮助核对。参考:Kimi CLI 会话参数。
本篇新解压目录不一定存在旧 Kimi 会话。没有旧会话、换了工具,或者想让一个全新会话检查交接质量时,可以从计划模式开始:
kimi --plan
给新会话的说明可以是:
先读 AGENTS.md、docs/recovery/TASK.md、recovery-records/review-01/HANDOFF.md 和 recovery-records/review-01/recovery.json,再核对其中引用的 runs/resume-01。先判断当前证据是否支持交接说明,指出尚未确认的事项。不要运行 prepare_handoff,不重放导出或 Loop,不修改旧记录,不读取个人资料、不调用真实应用 API。若证据一致,只提出人工核查内容的下一步;不把待审说成完成。若不一致,说明差异和停止原因。
这里引用的路径以你已完成前面 review-01 练习为前提;换了运行名,要同步修改路径。
旧会话可以提供上下文,新会话可以帮助检验文档是否足够自洽。但两个会话都不应同时修改同一现场。真实 Kimi 交互可能消耗开发额度,作者只核对了文档和帮助,没有启动或读取你的真实会话。
十、完成检查与常见故障
完成本篇后,你应当能自己确认:
- 原
runs/resume-01/state.json仍是interrupted,没有伪造成功回执。 - 原导出仍可读,新
recovery.json明确只补验证,语义仍为pending。 - 独立
verify_recovery.py通过,部分产物和无产物情景都停止。 - 交接说明能指向具体证据,而不是要求下一位相信一句“已经做好”。
| 现象 | 优先检查 | 处理 |
|---|---|---|
| 运行名或记录名已存在 | 是否重复执行了练习 | 保留旧目录,使用新名称;不覆盖 |
context_changed |
README、任务或源码是否被编辑 | 对照对应版本,不刷新旧指纹掩盖变化 |
unsupported_state |
是否拿了其他运行状态来测试 | 本工具只处理指定中断,其他情况人工判断 |
evidence_incomplete_or_invalid |
清单、产物或原记录是否缺失 | 保留现场,不凭猜测补齐 |
| 新记录验证失败 | 原文件是否在接手后改变 | 对比差异,避免沿用过时结论 |
| 续会话找不到预期记录 | 工作目录或选中会话是否正确 | 选择正确会话,或用交接文件开新会话 |
作者验证了三个注入情景、版本变化、未知状态、产物篡改、重名、非法名称和伪造接手结论等边界。测试还把导出函数替换成“一旦被调用就失败”,证明核查和另存记录没有重放它,并逐字节检查原运行不变。网络连接被阻断,没有真实 Kimi / DeepSeek 调用;没有 Windows 实机、浏览器视觉或真实崩溃恢复测试。
自检:为什么一份新的 needs_review 记录可以与旧 interrupted 同时存在?因为前者是本次基于证据作出的新判断,后者保留上次执行事实。为什么不能靠保存聊天保证任务恢复?因为聊天无法代替当前版本、文件完整性与副作用结果的核查。
下一篇并行与多 Agent会把“接手”扩展成“分工”:多个助手各自负责什么,怎样避免互相覆盖,以及如何把结果集成回来。当前练习仍保持单人串行,不提前让多个执行者争用现场。