Files
reader/plans/resume-run-minimal-design.md

5.8 KiB
Raw Permalink Blame History

resume_run 最小恢复语义设计

1. 背景

前面已经完成:

  • run-state 基础设施
  • 状态查询接口
  • 结果读取接口

reader 现在已经具备“运行真相 + 查询 + 结果读取”能力。下一步是补 resume_run,让失败后的 run 可以从已有中间产物继续,而不是完全重跑。

但 resume_run 是第一项真正涉及“重新进入执行流程”的能力,复杂度高于前几轮。因此本轮设计必须进一步收窄范围,避免一次性把任意 stage 重入、复杂后台管理、任务调度全拉进来。


2. 本轮目标

只做 最小可用的 resume_run:

基于已有 run-state.json,让 freshrss pipeline 能从“最近可恢复点”继续执行。

本轮不追求:

  • 任意 stage 任意重入
  • 历史 run 的通用恢复
  • 无 run-state.json run 的恢复
  • 多任务后台管理
  • 队列 / 数据库 / worker

3. 支持范围

3.1 仅支持 workflow

只支持:

  • freshrss_daily_digest
  • 或当前 run-state.workflow 对应的 freshrss pipeline workflow

不支持:

  • article summary 独立恢复
  • 其他未来 workflow

3.2 仅支持有 run-state 的 run

调用 resume_run(run_id=...) 时,必须满足:

  • run 对应目录存在
  • run-state.json 存在
  • run-state.json 可解析

否则直接返回不可恢复错误,而不是尝试猜目录结构。


4. 最近可恢复点定义

本轮采用保守定义:

4.1 恢复依据

优先使用 run-state.recovery.resume_from_stage。

如果没有该字段,使用:

  • state.status == failed 时:失败 stage
  • 否则:拒绝恢复

4.2 只支持以下恢复点

第一版仅支持从下面几类 stage 恢复:

  1. generate_summaries
  2. apply_filters
  3. build_delivery_payload
  4. write_run_report

4.3 明确不支持的恢复点

第一版暂不支持从以下位置恢复:

  1. fetch_feed
  2. extract_articles

原因:

  • 这两个阶段更依赖外部抓取与逐条内容处理过程
  • 恢复语义更复杂
  • 容易和 FreshRSS 读状态、副作用、原始输入不一致问题缠在一起

如果 run 停在这两个阶段:

  • resume_run 返回 resumable=false
  • 并明确建议重新触发新 run,而不是恢复

5. 恢复前置条件

5.1 必须存在的中间产物

按恢复点要求最小前置产物:

从 generate_summaries 恢复

必须至少有:

  • raw output
  • extracted outputs(或足以驱动 summary 的当前输入)

从 apply_filters 恢复

必须至少有:

  • extracted outputs
  • summary outputs

从 build_delivery_payload 恢复

必须至少有:

  • filter / candidate 所需输入已齐备

从 write_run_report 恢复

必须至少有:

  • delivery payload 已存在

5.2 缺失产物处理

如果恢复点所需的关键产物缺失:

  • 直接返回不可恢复
  • 返回字段中注明缺失产物名
  • 不自动降级到更早 stage

原因:

  • 第一版先避免隐式魔法恢复
  • 让行为更可预测

6. 执行语义

6.1 resume_run 的行为

调用 resume_run(run_id) 后:

  1. 读取 run-state.json
  2. 校验 workflow、status、recovery 信息
  3. 判断恢复点是否在本轮支持范围内
  4. 校验该恢复点所需产物是否齐备
  5. 复用现有 freshrss pipeline / runtime,从该恢复点之后继续执行
  6. 更新原 run 的 run-state.json

6.2 不新建 run_id

第一版恢复时:

  • 继续沿用原 run_id
  • 不新建子 run / shadow run / retry run

原因:

  • 保持恢复行为简单直观
  • 避免多 run 关系管理复杂化

6.3 状态更新

恢复开始时:

  • status 置回 running
  • current_stage 置为恢复点
  • error 清理为 null
  • 在必要时更新 recovery 信息

恢复成功时:

  • 正常走到 success

恢复失败时:

  • 正常写入新的失败信息
  • 保留新的 run-state.json

7. MCP 接口建议

7.1 新增 tool

建议新增:

  • resume_run

7.2 输入

{
  "run_id": "freshrss-pipeline-20260407-010236"
}

第一版不加 from_stage,避免人为覆盖恢复点逻辑。

7.3 输出

建议输出:

{
  "run_id": "freshrss-pipeline-20260407-010236",
  "workflow": "freshrss_daily_digest",
  "resumed": true,
  "resume_from_stage": "build_delivery_payload",
  "status": "success",
  "output_dir": "outputs/freshrss/rerun/freshrss-pipeline-20260407-010236",
  "delivery_payload": {...},
  "run_report": {...},
  "message": "Run resumed from build_delivery_payload and completed successfully."
}

7.4 不可恢复时输出

{
  "run_id": "freshrss-pipeline-20260407-010236",
  "resumed": false,
  "status": "failed",
  "resume_from_stage": "extract_articles",
  "message": "This run cannot be resumed from extract_articles in the current minimal implementation.",
  "missing_artifacts": []
}

8. 代码组织建议

优先在现有 runtime 体系内最小扩展:

  • run_store.py

    • 补载入/更新辅助能力(如还缺)
  • query_service.py

    • 可复用读取 run-state / artifact / report / payload 的查询能力
  • 新增或补充 runtime service

    • 例如 resume_service.py 或在现有 runtime 层增加 resume 逻辑
  • server.py

    • 新增 MCP tool resume_run

关键原则:

  • 不要重写整条 freshrss pipeline
  • 应尽量让 pipeline 能接受“从某 stage 之后继续”的最小参数

9. 明确不做的事

本轮明确不做:

  1. rerun_stage
  2. 指定任意 from_stage
  3. 多 workflow 通用恢复框架
  4. 恢复时自动修补缺失产物
  5. 历史无 run-state run 的恢复
  6. 后台异步恢复任务管理

10. 一句话结论

resume_run 第一版只做一件事:

对已有 run-state.json 的 freshrss run,在恢复点和前置产物都满足时,从最近可恢复点继续执行;否则明确拒绝恢复,不做隐式魔法补救。