docs: add reader MCP architecture and implementation plan
This commit is contained in:
@@ -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
|
||||
- 中间产物复用
|
||||
- 分阶段补跑
|
||||
@@ -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/<run_id>/
|
||||
```
|
||||
|
||||
### 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_id>/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_id>/` 下新增 `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 为下游编排层的工作流服务。**
|
||||
@@ -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_id>/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 工作流服务”,再做更多工具;不要反过来先堆接口名。
|
||||
Reference in New Issue
Block a user