# FreshRSS 主日报异步 job 方案 ## 背景 当前 `run_freshrss_openclaw_pipeline` 虽然已经作为正式 MCP workflow 入口存在,但执行模型仍是**同步 MCP 调用**。这会带来几个现实问题: 1. OpenClaw / MCP wrapper 存在超时风险,尤其是 5-10 篇的正式日报批次。 2. wrapper timeout 与真实 run 是否已落地,容易出现语义分离。 3. 当前已有 `run-state.json`、`get_run_status`、`get_run_report`、`get_delivery_payload`,但**启动层**仍然是同步调用,不利于正式生产链路稳定运行。 4. 单篇总结已经验证了“最小 async job + 轮询状态 + 读取结果”模型可行,主日报 run 应收敛到同一套运行模式。 ## 目标 将 FreshRSS 主日报 run 改造成与 article-summary 类似的**最小真异步 job**: - 启动即返回 `job_id` - 真正执行由后台子进程完成 - 状态可轮询 - 成功后可读取结构化结果 - 业务逻辑继续复用既有 `run_freshrss_pipeline(...)` - 不推翻现有 run-state / result query 能力 ## 非目标 本阶段不做: - 分布式任务队列 - 多 worker 调度 - 任意 stage 的后台恢复编排 - 并发控制中心 - 主流程与 article-summary job 的通用抽象框架一次性大重构 先做最小可用。 ## 设计原则 1. **启动层异步化,执行核心不重写** - `run_freshrss_pipeline(...)` 继续是主业务逻辑真相。 - async job 只负责启动、状态持久化、结果回读。 2. **run truth 与 job truth 分层** - job truth:这次异步任务有没有启动、运行到哪一步、是否成功。 - run truth:真正的 freshrss workflow 输出与 `run-state.json`。 3. **OpenClaw 正式生产默认改为 async start path** - 启动走 async job - 状态和结果优先先看 job - 真正业务产物仍由现有 run 查询工具承接 4. **与 article-summary job 尽量同构** - 目录结构 - `run-state.json` / `input.json` / `result.json` / `job-report.json` - 后台 runner 脚本 ## 拟新增能力 ### MCP tools 新增 3 个工具: - `start_freshrss_pipeline_job` - `get_freshrss_pipeline_job_status` - `get_freshrss_pipeline_job_result` ### job 目录 固定目录: `outputs/freshrss/pipeline_jobs//` 至少包含: - `run-state.json` - `input.json` - `result.json`(成功时) - `job-report.json` ### 执行模型 - `start_freshrss_pipeline_job` 写入 input + 初始化 job state - 后台 `subprocess.Popen(...)` 启动 runner - runner 内部调用 `run_freshrss_pipeline(...)` - 成功后把 `run_id`、核心产物路径、关键计数写入 `result.json` ## job 输入参数 与现有 `run_freshrss_openclaw_pipeline` 尽量对齐: - `limit` - `mark_read` - `include_read` - `debug_artifacts` - `continuation` - `timeout_seconds` - `max_retries` - `stream_id` - `api_base_url` - `username` - `api_password` - `llm_api_key` - `llm_model` - `llm_api_url` - `context` - `run_id` - `date_value` - `output_dir` - `include_item_reports` ## 返回语义 ### start 返回: - `job_id` - `workflow` - `run_type` - `status=running` - `output_dir` - `message` ### status 返回: - `job_id` - `status` - `current_stage` - `started_at` / `updated_at` / `finished_at` - `progress` - `artifacts` - `error_summary` - 若主 run 已创建,可附带 `linked_run_id` ### result 成功时返回: - `job_id` - `status=success` - `run_id` - `delivery_output` - `report_output` - `digest_brief_output` - `pulled_count` - `delivered_count` - `marked_read_count` - `artifact` - `result` ## stages 建议 最小 job stages: 1. `prepare_job` 2. `load_input` 3. `run_pipeline` 4. `write_result` 其中 `run_pipeline` 内部仍由现有 freshrss workflow 自己写它的 run-state。 ## 与现有同步入口的关系 ### 保留 `run_freshrss_openclaw_pipeline` 暂时保留,作为: - debug / light path - 本地调试工具 - 向后兼容路径 ### 正式语义调整 文档与 OpenClaw handoff 中,主日报正式生产默认启动入口改为: - `start_freshrss_pipeline_job` 同步入口降级为: - debug / fallback - 小批量验证 ## OpenClaw 编排建议 新的推荐路径: 1. `start_freshrss_pipeline_job` 2. `get_freshrss_pipeline_job_status` 3. 成功后 `get_freshrss_pipeline_job_result` 4. 后续仍用: - `get_run_status` - `get_delivery_payload` - `get_run_report` - `list_run_artifacts` ## 风险点 1. **job 成功但 run 部分失败** - 允许,job 结果应以真实 `run_freshrss_pipeline(...)` 返回为准。 - `run_id` + `report_output` 仍是最终真相。 2. **runner 崩溃但来不及写 result** - 需保证 `job-report.json` 至少能写下失败摘要。 3. **重复状态源导致混淆** - 文档必须明确: - job state 管“启动任务” - run state 管“业务工作流真相” 4. **同步 / 异步双入口长期漂移** - 必须要求 async job 内部直接复用 `run_freshrss_pipeline(...)` - 禁止再实现一套平行主流程 ## 验收标准 1. 能通过 MCP 启动一个主日报 async job 并立即返回 `job_id` 2. 能轮询到 `running -> success/failed` 3. 成功后 `result.json` 含 `run_id` 与核心产物路径 4. 对应 run 仍能通过既有 `get_run_status` / `get_run_report` / `get_delivery_payload` 正常读取 5. README / handoff / TODO / plans 同步更新 ## 建议实施顺序 1. 复制 article-summary job 骨架到 freshrss pipeline job 2. 新增 runner 脚本 3. server.py 暴露 3 个新工具 4. 补 query/result 读法 5. 更新 README / handoff 6. 将 TODO 主任务切到“主日报 async job”