diff --git a/TODO.md b/TODO.md index 5186326..c00481a 100644 --- a/TODO.md +++ b/TODO.md @@ -176,6 +176,9 @@ 说明: - 先做最小可用恢复 - 暂不追求任意 stage 任意重入 +- 设计约束已补充到 `plans/resume-run-minimal-design.md` +- 第一版只支持 freshrss workflow 且仅支持有 `run-state.json` 的 run +- 第一版仅考虑从最近可恢复点继续;`fetch_feed` / `extract_articles` 暂不支持恢复 --- diff --git a/plans/resume-run-minimal-design.md b/plans/resume-run-minimal-design.md new file mode 100644 index 0000000..db5ca93 --- /dev/null +++ b/plans/resume-run-minimal-design.md @@ -0,0 +1,276 @@ +# 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 输入 + +```json +{ + "run_id": "freshrss-pipeline-20260407-010236" +} +``` + +第一版不加 `from_stage`,避免人为覆盖恢复点逻辑。 + +### 7.3 输出 + +建议输出: + +```json +{ + "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 不可恢复时输出 + +```json +{ + "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,在恢复点和前置产物都满足时,从最近可恢复点继续执行;否则明确拒绝恢复,不做隐式魔法补救。**