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

277 lines
5.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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,在恢复点和前置产物都满足时,从最近可恢复点继续执行;否则明确拒绝恢复,不做隐式魔法补救。**