From 7563aa8fea45bedf2e0068d53fb5d17523aa985b Mon Sep 17 00:00:00 2001 From: root Date: Tue, 7 Apr 2026 10:57:46 +0800 Subject: [PATCH] docs: add reader MCP architecture and implementation plan --- .../2026-04-06-reader-digest-sigterm.md | 112 +++ plans/reader-mcp-architecture-design.md | 766 ++++++++++++++++++ plans/reader-mcp-implementation-plan.md | 209 +++++ 3 files changed, 1087 insertions(+) create mode 100644 plans/issues/2026-04-06-reader-digest-sigterm.md create mode 100644 plans/reader-mcp-architecture-design.md create mode 100644 plans/reader-mcp-implementation-plan.md diff --git a/plans/issues/2026-04-06-reader-digest-sigterm.md b/plans/issues/2026-04-06-reader-digest-sigterm.md new file mode 100644 index 0000000..08ff7dd --- /dev/null +++ b/plans/issues/2026-04-06-reader-digest-sigterm.md @@ -0,0 +1,112 @@ +# reader 日报链路在 OpenClaw/Feishu 外层执行中被 SIGTERM 截断 + +## 背景 +2026-04-06 在 OpenClaw 中执行 reader 日报流程时,出现多次“前半段有产物、最终产物缺失”的现象。 + +典型表现: +- 能生成 `raw/freshrss.raw.json` +- 能部分生成 `extracted/item-xx.extracted.json` +- 但经常拿不到: + - `run-report.json` + - `candidates/openclaw-delivery-payload.json` + - `candidates/digest-brief.json` +- 外层日志多次出现 `Exec failed (..., signal SIGTERM)` + +## 已确认结论 + +### 1. RSS 抓取与排序正常 +已确认: +- FreshRSS API 正常 +- 原始 raw 数据能拉到 +- 默认排序正常(默认相当于 `r=d`,新到旧) + +因此问题不在: +- RSS 接口 +- 鉴权 +- 排序规则 + +### 2. reader 前半段模块正常 +已确认: +- 单篇 summary 能成功 +- 单篇 filter + candidate 构建能成功 + +因此问题不在: +- summary 模块整体损坏 +- candidate 构建整体损坏 + +### 3. 真正问题在长链路执行方式 +更合理的判断是: + +> 整条 reader 日报 pipeline 是长串行任务,在 OpenClaw / Feishu 当前这条外层执行链路里,容易被外层执行环境提前 `SIGTERM`。 + +也就是说: +- 不是 reader 总是自己抛 Python 异常退出 +- 更多是脚本尚未跑完,外层执行会话先被终止 + +## 关键认知 +当天很多排障与补跑步骤,实际不是通过稳定常驻的 MCP 服务在跑,而是直接执行 reader 仓库里的 Python 脚本: + +- `python scripts/run_freshrss_pipeline.py` +- `python scripts/run_article_summaries.py` + +因此更准确地说: + +> 当前 reader 正式日报运行入口偏 CLI/脚本模式,而不是稳定 MCP 服务调用模式。 + +这也是为什么长任务更容易受外层 exec 生命周期影响。 + +## 为什么前几次没问题 +可能原因: +1. 之前任务更短、内容更轻,刚好能在外层执行环境截断前跑完 +2. 之前不是链路天然稳,而是还没撞上边界条件 +3. 当前正式链路缺少稳健的断点恢复能力,因此一旦遇到较重任务,就暴露出问题 + +## 当天动作记录 + +### 做过的排查 +- 确认 FreshRSS 默认排序 +- 确认 raw 文件能生成 +- 确认 extracted 文件能部分生成 +- 单独验证单篇 summary 成功 +- 单独验证单篇 filter / candidate 成功 + +### 做过的临时修复 +当天曾尝试加入: +- `--resume` +- 中间产物复用 +- 断点恢复思路 + +目的是降低 SIGTERM 后的损失。 + +### 当前状态 +这些临时代码修改已全部回滚,reader 工作区已恢复干净。 + +## 当天结果 +虽然正式链路异常,但通过手工恢复推进,最终仍补齐了: +- `candidates/openclaw-delivery-payload.json` +- `candidates/digest-brief.json` +- `run-report.json` + +当天最终保留文章: +- 1 +- 3 + +## 后续建议 + +### 短期 +- CLI 仅保留为 debug / fallback +- 不再把正式生产日报流主要建立在长脚本入口上 + +### 中期 +- 将 reader 作为正式 MCP 服务 +- OpenClaw 正式通过 MCP tool 调用: + - `run_freshrss_openclaw_pipeline` + - `generate_article_summaries` + +### 长期 +让 reader 的正式能力具备: +- run_id +- status 查询 +- resume +- 中间产物复用 +- 分阶段补跑 diff --git a/plans/reader-mcp-architecture-design.md b/plans/reader-mcp-architecture-design.md new file mode 100644 index 0000000..eec87dc --- /dev/null +++ b/plans/reader-mcp-architecture-design.md @@ -0,0 +1,766 @@ +# Reader 正式 MCP 服务架构设计 + +## 1. 背景 + +当前 reader 仓库已经具备 MCP 服务入口(`src/summary_mcp/server.py`),并暴露了: + +- `extract_url_content` +- `extract_item_content` +- `filter_summary_result` +- `run_freshrss_openclaw_pipeline` +- `generate_article_summaries` + +但从实际生产使用方式看,reader 的正式日报链路仍然**偏向 CLI/脚本模式**,尤其是: + +- `python scripts/run_freshrss_pipeline.py` +- `python scripts/run_article_summaries.py` + +这导致在 OpenClaw / Feishu 外层执行环境下,长链路任务容易因为 exec 生命周期、超时或外层中断而失败。 + +2026-04-06 的事故已经说明: + +- `raw` 能产出 +- `extracted` 能部分产出 +- 但 `run-report.json` / `openclaw-delivery-payload.json` / `digest-brief.json` 经常缺失 +- 外层日志出现 `signal SIGTERM` + +因此,reader 需要从“可被 exec 调起的仓库”升级为“**正式的 MCP 工作流服务**”。 + +--- + +## 2. 设计目标 + +本次架构设计的目标不是重做 reader 的业务逻辑,而是把现有能力收口为稳定的服务接口。 + +目标如下: + +1. **MCP 成为正式入口** + - OpenClaw 与上层编排默认通过 MCP tool 调用 reader + - CLI 降级为 debug / fallback 入口 + +2. **引入稳定的运行态抽象** + - 统一 `run_id` + - 统一 `status` + - 统一 `stage` + - 统一 `artifacts` + +3. **支持长链路可观测与恢复** + - 可查询当前运行状态 + - 可查询失败阶段 + - 可基于已有中间产物 resume / rerun + +4. **让 OpenClaw 消费结构化结果,而不是硬编码目录细节** + - OpenClaw 不再依赖 reader 的脚本 stdout 作为唯一信号 + - OpenClaw 尽量不直接拼接 reader 的输出目录路径 + +5. **保持最小重构成本** + - 先用文件系统持久化 run state + - 先不引入复杂任务队列 / 数据库 / 多 worker 平台 + - 先让单机、单实例、顺序执行场景稳定起来 + +--- + +## 3. 核心结论 + +一句话总结: + +> Reader 应该被设计为“有状态的工作流 MCP 服务”,而不是“包了一层 MCP 壳的长 CLI 命令”。 + +换句话说: + +- **错误方向**:`reader_run(command="python scripts/run_xxx.py ...")` +- **正确方向**:`start_run` + `get_run_status` + `get_artifact` + `resume_run` + +协议层只是入口,真正的关键是把 reader 内部抽象成: + +- run +- stage +- status +- artifact +- recovery + +--- + +## 4. 服务定位 + +### 4.1 Reader 负责什么 + +reader 作为 MCP 服务,负责上游阅读工作流本身: + +- FreshRSS 拉取 +- 内容提取 +- LLM 摘要 +- 规则过滤 +- candidate / delivery payload 生成 +- 单篇总结生成 +- 中间产物保存 +- 运行状态记录 +- 恢复与补跑 + +### 4.2 Reader 不负责什么 + +以下继续由 OpenClaw / skill 层负责: + +- Hugo 发布 +- 对话汇报 +- 用户确认精选 +- IMA 上传编排 +- 最终对人类的日常交互 + +### 4.3 边界结论 + +- **reader 是上游引擎 / workflow service** +- **OpenClaw 是下游编排层 / orchestration layer** + +这个边界与当前 `docs/openclaw/openclaw-handoff.md` 的原则保持一致,但会进一步强化“reader 通过正式 MCP 接口暴露工作流状态”,而不是只暴露“跑完后的结果”。 + +--- + +## 5. 当前问题分析 + +### 5.1 现状问题 + +当前 reader 的 MCP 工具里虽然已经有 `run_freshrss_openclaw_pipeline`,但其调用语义仍然更像: + +- 一次性同步执行整个长链路 +- 直接返回最终结果 +- 对外隐藏中间状态 + +这会带来几个问题: + +1. 长任务运行时,外层必须一直等 +2. 一旦外层中断,状态观测困难 +3. 难以精确判断失败发生在哪个阶段 +4. resume / rerun 只能靠脚本层补丁式处理 +5. OpenClaw 不容易构建“先触发,后查询,再消费”的稳定编排流 + +### 5.2 根本问题 + +根本问题不是“有没有 MCP server”,而是: + +> **reader 还没有被真正建模成一个有状态的 workflow service。** + +当前更接近: + +- MCP 暴露了几个函数 +- 但长链路执行模型仍是 CLI thinking + +因此改造重点不应放在“多加几个 tool 名字”,而应该放在: + +- 状态机 +- 运行记录 +- 恢复机制 +- 标准产物注册 + +--- + +## 6. 目标架构 + +建议的正式架构如下: + +```text +OpenClaw / Skill Layer + -> MCP Client Calls + -> Reader MCP Server + -> Workflow Service Layer + -> Workflow Runtime / Stage Engine + -> Existing Reader Core Modules + - FreshRSS pull + - extraction + - summary + - filter + - candidate builder + - article summary + -> Run Store (filesystem-backed) + -> Artifact Store (existing outputs directory) +``` + +### 6.1 分层说明 + +#### A. MCP Server Layer +职责: +- tool 注册 +- 输入校验 +- 输出包装 +- 对外暴露稳定 API + +不负责: +- 大段业务逻辑 +- 状态推进细节 +- 复杂流程编排 + +#### B. Workflow Service Layer +职责: +- 接收“启动日报任务”“恢复任务”“查询状态”等请求 +- 管理 run 生命周期 +- 将调用分发给 runtime/stage engine + +#### C. Workflow Runtime / Stage Engine +职责: +- 执行各阶段 +- 记录阶段状态 +- 收集中间产物 +- 更新运行态文件 +- 支持 resume / rerun + +#### D. Reader Core Modules +继续复用现有业务能力: +- `summary_mcp.core.*` +- `summary_mcp.workflows.*` +- 现有脚本中成熟的逻辑 + +这层应该尽量保持“业务逻辑纯净”,不要耦合 MCP 协议。 + +--- + +## 7. 核心抽象 + +### 7.1 Run + +每一次完整的 reader 工作流执行,都对应一个 `run_id`。 + +建议语义: + +- `run_id` 是 reader 工作流的一等公民 +- 所有状态、产物、恢复都围绕 `run_id` 建立 +- 所有下游消费都优先通过 `run_id` 取结果,而不是手拼路径 + +建议输出目录继续沿用现有结构: + +```text +outputs/freshrss/rerun// +``` + +### 7.2 Stage + +建议将 reader 的正式工作流拆为明确阶段: + +1. `fetch_feed` +2. `extract_articles` +3. `generate_summaries` +4. `apply_filters` +5. `build_delivery_payload` +6. `write_run_report` +7. `completed` + +对于单篇总结链路,可单独建另一类 workflow,或者作为独立 run type。 + +### 7.3 Status + +建议统一使用: + +- `queued` +- `running` +- `partial` +- `success` +- `failed` +- `cancelled` + +其中: + +- `partial` 表示已有阶段成功,但整体尚未完成或部分失败 +- `failed` 表示本次 run 已终止且未达到最终成功 + +### 7.4 Artifact + +所有关键输出都应被注册为 artifact,而不只是“写到了某个目录”。 + +artifact 至少应包含: + +- `name` +- `path` +- `kind` +- `stage` +- `exists` +- `created_at` +- `metadata` + +关键 artifact 示例: + +- `raw_output` +- `extracted_dir` +- `delivery_payload` +- `digest_brief` +- `run_report` +- `single_summary_dir` + +--- + +## 8. 运行态持久化设计 + +### 8.1 为什么要单独持久化 run state + +仅靠目录中是否存在某些 json 文件,不足以稳定表达: + +- 当前是否还在跑 +- 跑到了哪个阶段 +- 哪个阶段失败 +- 是否可以 resume +- 哪些 artifact 已确认可用 + +因此必须增加显式的运行态文件。 + +### 8.2 建议文件 + +建议在每个 run 目录下引入: + +```text +outputs/freshrss/rerun//run-state.json +``` + +### 8.3 run-state.json 建议结构 + +```json +{ + "run_id": "20260407-093000", + "workflow": "freshrss_daily_digest", + "run_type": "daily_digest", + "status": "running", + "current_stage": "extract_articles", + "started_at": "2026-04-07T09:30:00+08:00", + "updated_at": "2026-04-07T09:31:10+08:00", + "finished_at": null, + "input": { + "limit": 5, + "mark_read": true, + "include_read": false, + "debug_artifacts": false + }, + "stages": [ + { + "name": "fetch_feed", + "status": "success", + "started_at": "2026-04-07T09:30:00+08:00", + "finished_at": "2026-04-07T09:30:05+08:00", + "outputs": { + "pulled_count": 5, + "raw_output": "outputs/freshrss/rerun/20260407-093000/raw/freshrss.raw.json" + }, + "error": null + }, + { + "name": "extract_articles", + "status": "running", + "started_at": "2026-04-07T09:30:05+08:00", + "finished_at": null, + "outputs": { + "completed_items": 3, + "expected_items": 5 + }, + "error": null + } + ], + "artifacts": [ + { + "name": "raw_output", + "kind": "json", + "stage": "fetch_feed", + "path": "outputs/freshrss/rerun/20260407-093000/raw/freshrss.raw.json", + "exists": true + } + ], + "error": null, + "recovery": { + "resumable": true, + "resume_from_stage": "extract_articles", + "last_success_stage": "fetch_feed" + } +} +``` + +### 8.4 设计原则 + +- 运行中每完成一个 stage,就更新一次 `run-state.json` +- 不要求数据库,先以文件系统为准 +- 所有对外 status 查询优先读 `run-state.json` +- 其他 output 文件仍然可以保留现有格式和目录结构 + +--- + +## 9. MCP Tool 设计 + +不建议继续把正式能力收口成一个“巨型同步工具”。 + +建议拆成以下 MCP tools。 + +### 9.1 任务启动类 + +#### `start_freshrss_digest_run` + +作用: +- 启动一条新的日报工作流 +- 默认返回 `run_id`,而不是要求调用方一直同步等待到最后 + +输入示例: + +```json +{ + "limit": 5, + "mark_read": true, + "include_read": false, + "debug_artifacts": false, + "timeout_seconds": 60, + "max_retries": 2 +} +``` + +输出示例: + +```json +{ + "run_id": "20260407-093000", + "status": "queued", + "workflow": "freshrss_daily_digest", + "output_dir": "outputs/freshrss/rerun/20260407-093000" +} +``` + +#### 兼容策略 + +第一阶段可保留现有 `run_freshrss_openclaw_pipeline`,但其语义逐步调整为: + +- 内部复用新的 workflow runtime +- 可选 `wait=true/false` +- 默认建议 `wait=false` + +--- + +### 9.2 状态查询类 + +#### `get_run_status` + +作用: +- 查询 run 当前状态 +- 返回 current stage、completed stages、关键 artifact、失败信息 + +输入: + +```json +{ + "run_id": "20260407-093000" +} +``` + +输出字段建议: + +- `run_id` +- `workflow` +- `status` +- `current_stage` +- `started_at` +- `updated_at` +- `finished_at` +- `progress` +- `completed_stages` +- `failed_stage` +- `error_summary` +- `artifacts` +- `recovery` + +#### `list_runs` + +作用: +- 支持按日期、状态、workflow 类型查看最近 runs + +输入示例: + +```json +{ + "workflow": "freshrss_daily_digest", + "status": "failed", + "latest_n": 10 +} +``` + +--- + +### 9.3 结果读取类 + +#### `get_delivery_payload` + +作用: +- 按 `run_id` 获取 delivery payload +- 返回结构化对象,不要求调用方自己读文件 + +#### `get_digest_brief` + +作用: +- 按 `run_id` 获取 digest brief +- 可支持 `mode=public|internal` + +#### `get_run_report` + +作用: +- 返回 run-report 内容 +- 供 OpenClaw / 调试 / 运维查看 + +#### `get_extracted_article` + +作用: +- 获取单篇 extracted 内容 +- 输入:`run_id + item_id` 或 `run_id + item_index` + +#### `list_run_artifacts` + +作用: +- 统一列出 run 下当前注册的 artifact + +--- + +### 9.4 恢复与补跑类 + +#### `resume_run` + +作用: +- 基于已有 `run-state.json` 和中间产物,从可恢复点继续 + +输入示例: + +```json +{ + "run_id": "20260407-093000" +} +``` + +#### `rerun_stage` + +作用: +- 指定从某个阶段开始重跑 + +输入示例: + +```json +{ + "run_id": "20260407-093000", + "stage": "generate_summaries", + "force": true +} +``` + +#### `explain_run_failure` + +作用: +- 给出适合 OpenClaw / 人类阅读的失败解释 +- 不只是 Python stacktrace + +--- + +### 9.5 单篇总结相关工具 + +现有 `generate_article_summaries` 可继续保留,但建议长期也纳入 run 模型。 + +后续可扩展为: + +- `start_article_summary_run` +- `get_article_summary_status` +- `get_article_summary_outputs` + +短期内,如果单篇总结执行耗时可控,也可以先保持同步接口。 + +--- + +## 10. 向后兼容策略 + +为了降低迁移成本,不建议一次性砍掉现有接口。 + +### 10.1 保留现有 MCP 工具 + +短期保留: + +- `run_freshrss_openclaw_pipeline` +- `generate_article_summaries` + +但内部逐步改为调用新的 workflow runtime。 + +### 10.2 调整 `run_freshrss_openclaw_pipeline` 语义 + +建议演进成: + +- `wait=true` 时:保留当前类似同步行为 +- `wait=false` 时:只返回 `run_id` +- 若未显式指定,生产建议默认 `wait=false` + +### 10.3 CLI 的新定位 + +CLI 继续保留,但只作为: + +- debug +- fallback +- 本地排障 +- 开发验证 + +CLI 最好只是新 runtime 的薄包装,而不是另一套独立实现。 + +--- + +## 11. 与 OpenClaw 的集成方式 + +### 11.1 旧模式 + +OpenClaw: +- 直接 exec reader 脚本 +- 等待整条 CLI 跑完 +- 根据 stdout 或目录文件判断是否成功 + +### 11.2 新模式 + +OpenClaw: +1. 调用 `start_freshrss_digest_run` +2. 获得 `run_id` +3. 周期性调用 `get_run_status` +4. status = `success` 后调用 `get_delivery_payload` +5. 再执行下游 Hugo / chat / IMA 编排 + +这样会有几个明显好处: + +- 上游 reader 运行态可观察 +- OpenClaw 不必绑死在长 CLI 会话上 +- 中途失败可以明确知道失败位置 +- 下游编排只依赖结构化结果 + +--- + +## 12. 最小实现方案(MVP) + +为了尽快落地,不建议一次做到最重。 + +### Phase 1:先做内部状态化 + +目标: +- 在现有 pipeline 内引入 `run_id` +- 新增 `run-state.json` +- 固化 stage 切分 +- 所有关键输出注册成 artifact +- 将 resume 所需信息写入 recovery 字段 + +这一步完成后,即使对外接口还没完全变化,内部也已经不再是“黑箱长函数”。 + +### Phase 2:新增 MCP 状态查询接口 + +目标: +- 新增 `get_run_status` +- 新增 `get_delivery_payload` +- 新增 `list_runs` +- 新增 `resume_run` + +这一步完成后,OpenClaw 就可以逐步改走正式服务调用。 + +### Phase 3:调整现有生产接入 + +目标: +- OpenClaw 默认不再 exec `scripts/run_freshrss_pipeline.py` +- OpenClaw 默认走 MCP run + status + payload 模式 +- 将 CLI 降级为 debug/fallback + +--- + +## 13. 目录与模块建议 + +建议新增一层 workflow runtime 模块,例如: + +```text +src/summary_mcp/ + server.py + runtime/ + run_store.py + artifact_store.py + state_models.py + workflow_service.py + stage_runner.py + workflows/ + freshrss_pipeline.py + article_summary.py + core/ + ... +``` + +### 建议职责 + +- `runtime/state_models.py` + - RunState / StageState / Artifact models + +- `runtime/run_store.py` + - 读写 `run-state.json` + +- `runtime/artifact_store.py` + - artifact 注册与查询 + +- `runtime/workflow_service.py` + - start / status / resume / rerun 核心服务 + +- `runtime/stage_runner.py` + - 阶段推进与失败捕获 + +这样可以保持: + +- 协议层清晰 +- 运行态管理独立 +- 业务逻辑复用现有 workflows + +--- + +## 14. 风险与注意事项 + +### 14.1 不要把“异步”理解成“一定要上复杂队列” + +当前阶段,异步的核心不是上 Redis/Celery,而是: + +- 有 `run_id` +- 有状态文件 +- 可以先启动、后查询 + +单机单进程也完全可以做到。 + +### 14.2 不要让 OpenClaw 继续依赖 reader 内部目录细节 + +OpenClaw 可以知道输出目录存在,但不应继续以“自己拼 `outputs/.../*.json`”作为主交互方式。 + +正式模式下,优先通过 MCP 取: + +- status +- payload +- report +- artifact list + +### 14.3 不要保留两套行为漂移的实现 + +如果 CLI 和 MCP 背后各跑各的逻辑,后续一定会漂移。 + +正确做法: +- 先统一 runtime +- 再让 CLI / MCP 都调用同一套 runtime + +--- + +## 15. 最终建议 + +### 架构判断 + +Reader 现在已经不再是一个简单脚本仓库,而是: + +- 有明确上游输入 +- 有稳定工作流 +- 有中间产物 +- 有下游消费者 +- 有恢复与补跑需求 + +因此它应该正式升级为: + +> **Reader Workflow MCP Service** + +### 最终建议清单 + +1. 把 `run_id / stage / status / artifact / recovery` 作为正式核心抽象 +2. 在 `outputs/freshrss/rerun//` 下新增 `run-state.json` +3. 新增 `get_run_status / list_runs / get_delivery_payload / resume_run` +4. 让现有 `run_freshrss_openclaw_pipeline` 内部复用新 runtime +5. 让 OpenClaw 逐步从 exec 切到 MCP 调用 +6. CLI 保留,但降级为 debug/fallback + +--- + +## 16. 一句话结论 + +Reader 的正式生产能力不应再主要依赖长 CLI exec,而应演进为: + +**以 MCP 为正式入口、以 run-state 为运行真相、以 artifact 为交付契约、以 OpenClaw 为下游编排层的工作流服务。** diff --git a/plans/reader-mcp-implementation-plan.md b/plans/reader-mcp-implementation-plan.md new file mode 100644 index 0000000..7c2049d --- /dev/null +++ b/plans/reader-mcp-implementation-plan.md @@ -0,0 +1,209 @@ +# Reader MCP 实施计划 + +## 目标 + +把 reader 从“生产上主要依赖长 CLI/exec”推进到“以 MCP 为正式入口的工作流服务”。 + +## 协作分工 + +- **架构与方向**:由我负责 +- **编码实现**:由 Codex 负责 +- **同步机制**:通过 `plans/` 下规划文档 + `TODO.md` 保持进度与方向一致 + +## 当前权威文档 + +实现前,必须先读: + +1. `plans/reader-mcp-architecture-design.md` +2. `plans/reader-mcp-implementation-plan.md` +3. `TODO.md` +4. `plans/issues/2026-04-06-reader-digest-sigterm.md` +5. `docs/openclaw/openclaw-handoff.md` + +如实现细节与旧文档冲突,以: + +1. 最新架构设计文档 +2. 最新实施计划 +3. TODO 当前项 + +为准。 + +--- + +## 里程碑 + +### M1:运行态落地(最优先) + +目标:让现有 freshrss pipeline 拥有明确 run state。 + +交付: +- 新增 `run-state.json` 持久化能力 +- 定义 `RunState / StageState / ArtifactRecord` 模型 +- 现有 pipeline 按 stage 更新状态 +- 保持现有产物目录兼容 + +完成标准: +- 任意一次 run 都能产出 `outputs/freshrss/rerun//run-state.json` +- 中途失败时也能看到失败阶段和已有 artifacts + +--- + +### M2:状态查询接口 + +目标:通过 MCP 查询 run 状态,不再只靠 CLI 或手看目录。 + +交付: +- 新增 `get_run_status` +- 新增 `list_runs` +- 新增 `list_run_artifacts` + +完成标准: +- OpenClaw 可通过 MCP 查询 run 当前状态 +- 不需要直接读磁盘路径判断是否成功 + +--- + +### M3:结果读取接口 + +目标:通过 MCP 获取结果内容,而不是自己拼文件路径。 + +交付: +- 新增 `get_delivery_payload` +- 新增 `get_run_report` +- 视情况新增 `get_digest_brief` +- 视情况新增 `get_extracted_article` + +完成标准: +- OpenClaw 只要知道 `run_id`,就能获取关键结果 + +--- + +### M4:恢复与补跑 + +目标:reader 具备正式的恢复机制。 + +交付: +- 新增 `resume_run` +- 视情况新增 `rerun_stage` +- 在 `run-state.json` 中记录 recovery 信息 + +完成标准: +- 至少支持从最近成功 stage 之后继续执行 +- 能给出可恢复/不可恢复的明确判断 + +--- + +### M5:生产入口切换 + +目标:OpenClaw 正式从 exec 模式切到 MCP 模式。 + +交付: +- 保留 CLI 作为 debug/fallback +- 生产推荐入口改为 MCP run + status + payload +- 更新 handoff / README / docs + +完成标准: +- 正式流程默认不再依赖长 CLI exec + +--- + +## 编码原则 + +1. **优先复用现有业务逻辑** + - 不要重写成熟的 FreshRSS / extract / summary / filter 逻辑 + - 优先抽 runtime 层把现有逻辑包起来 + +2. **先状态化,再协议扩展** + - 先把 run-state 跑通 + - 再补 MCP tools + +3. **保持向后兼容** + - `run_freshrss_openclaw_pipeline` 先保留 + - 内部逐步改为调用新的 runtime + +4. **CLI 降级,不删除** + - 仍保留 debug/fallback 价值 + - 但不要让 CLI 和 MCP 背后出现两套逻辑 + +5. **小步提交** + - 每完成一个可验证的小目标就提交 + - 不要攒一个超大变更 + +--- + +## 建议目录改造 + +建议新增: + +```text +src/summary_mcp/runtime/ + state_models.py + run_store.py + artifact_store.py + workflow_service.py + stage_runner.py +``` + +说明: +- 目录名可微调 +- 但必须把“运行态管理”从现有 workflows 中抽出来,避免继续黑箱化 + +--- + +## Codex 工作方式要求 + +Codex 每次开始前: + +1. 先读 `plans/reader-mcp-architecture-design.md` +2. 再读本文件 +3. 再读 `TODO.md` +4. 只处理 TODO 中 `TODO` 状态的当前优先项 + +Codex 每完成一项后: + +1. 更新 `TODO.md` +2. 在 TODO 对应项下补: + - 完成情况 + - 改动文件 + - 遗留风险 +3. 如实现偏离原架构,必须先更新 `plans/` 文档,再继续代码 + +--- + +## 决策规则 + +如果出现以下情况: + +- 需要新增 MCP tool,但架构设计中未定义 +- 需要改动现有 pipeline 关键语义 +- 需要引入数据库 / 队列 / 线程池 / 后台 worker +- 需要改变 OpenClaw 与 reader 的边界 + +则不允许 Codex自行拍板,必须先回写到: + +- `plans/reader-mcp-architecture-design.md` +- 或新增 `plans/issues/*.md` + +由架构层确认后再继续。 + +--- + +## 当前实现顺序(强约束) + +按以下顺序推进: + +1. `run-state.json` 模型与持久化 +2. pipeline 中 stage 状态更新 +3. `get_run_status` +4. `list_runs` / `list_run_artifacts` +5. `get_delivery_payload` / `get_run_report` +6. `resume_run` +7. 再考虑 `rerun_stage` + +不要一上来就做复杂异步后台队列。 + +--- + +## 一句话执行口径 + +先把 reader 做成“有运行真相的 MCP 工作流服务”,再做更多工具;不要反过来先堆接口名。