From 8f0255ebb35e2c061a284ce4f825ac368c398ba5 Mon Sep 17 00:00:00 2001 From: yibo Date: Sun, 30 Aug 2026 22:55:11 +0800 Subject: [PATCH] docs: 41 号待办文档改名 42 号,避免和并行会话的另一份 41 号撞编号 --- docs/todo/system-design/41-org-work-graph-reliability-followups.md | 93 --------------------------------------------------------------------------------------------- docs/todo/system-design/42-org-work-graph-reliability-followups.md | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 93 deletions(-) delete mode 100644 docs/todo/system-design/41-org-work-graph-reliability-followups.md create mode 100644 docs/todo/system-design/42-org-work-graph-reliability-followups.md diff --git a/docs/todo/system-design/41-org-work-graph-reliability-followups.md b/docs/todo/system-design/41-org-work-graph-reliability-followups.md deleted file mode 100644 index 7f99850..0000000 --- a/docs/todo/system-design/41-org-work-graph-reliability-followups.md +++ /dev/null @@ -1,93 +0,0 @@ -# 41. Org/Work Graph 可靠性收尾 —— 39 号文档真跑复盘的待办 - -> **状态**:📝 待开工,未实现。 -> **日期**:2026-08-30 -> **背景**:[39-boss-agent-task-supervision.md](./39-boss-agent-task-supervision.md) 的 P1-P5 落地后,用它做了一次真实端到端测试(建公司 → 下 goal → `ceo` 监督 → 真实跑出一个可玩的贪吃蛇游戏,Playwright 5/5 验证通过)。**1469 个单测全绿,这次真跑却测出至少 3 个单测完全没覆盖到的真问题**——这份文档是那次复盘沉淀下来的待办,按开工顺序分四个 phase。 -> **读者须知**:每个 phase 的现状描述都来自那次真实测试里实际撞到的场景,不是推测;具体文件/行号落地前按惯例重新核实。 - ---- - -## 一、为什么排这个顺序 - -四个 phase 分两类:**Phase 1-2 是"让已经在跑的东西更可靠"**(不改变行为,只是补上恢复路径和验证),**Phase 3-4 是"补功能缺口"**(新增能力)。前者优先——真跑复盘里代价最大的坑(僵尸节点、鉴权漏挂)都是"东西本来能跑,只是卡住了没人能救"这一类,越早补上,后面几个 phase 开工时越不容易被同样的坑绊到。Phase 5(timeline 承接规划轨迹)单独放最后,因为它是结构性改动,需要先摸清 `org-coder` 之外还有哪些角色也在打那条断线的 `/task/transition`,工作量比其余四个 phase 加起来还大。 - -``` -Phase 1:卡住恢复闭环(僵尸节点检测 + 强制判失败端点)——两个互补的口子一起补 -Phase 2:§四.4 闸门穿透性回归测试 —— 纯测试,零设计风险 -Phase 3:target_dir 的 API/UI 入口 —— 纯加法 -Phase 4:timeline.ndjson 承接 planning/reflection 轨迹 —— 结构性改动,排最后 -``` - ---- - -## 二、Phase 1:卡住恢复闭环 - -### 1a. 服务器重启的僵尸节点检测 - -**现状(真跑撞到的)**:`graph_run_nodes.status='running'` 这一行,没有任何机制确认"背后真的还有个活的进程"。真实测试里为了让 `graph-run.js` 鉴权修复生效重启了一次服务器,正在跑 `org-planner-sdd` 的 Claude CLI 子进程被连带杀掉,但那一行的状态永远停在 `running`——不会自己变成 `blocked`,`ceo` 的两条唤醒路径(事件驱动 + 30 分钟兜底扫描)判据都是 `status='blocked'`,对这个状态完全不可见。 - -查过现成的"重启后清理残留"机制:`server/index.js` 的 `resumeIncompleteTasks()` 只处理 ArenaSK 的 Skill 训练任务(`skill-task-manager`),跟 org/work graph 毫无关系;`shutdown()`(`SIGTERM`/`SIGINT` 处理)只关插件管理器,不碰 `graph_run_nodes`。两处都不覆盖这个场景。 - -**设计**:启动时扫一遍 `graph_run_nodes`,`status='running'` 且 `updated_at`(或 `startedAt`)早于**本次进程启动时间**的行,判定为孤儿(整个进程树都被换过一轮,不可能有真实进程还在跑它)。判定后按 **`infra_flake` 语义**处理,不发明新状态——原地重跑逻辑本来就是为"基础设施打断,跟设计/实现对不对无关"这类场景设计的,服务器重启只是这个桶里更严重的一个成员。复用 `graph-engine.js` 已有的 `routeFailure` 里 `INFRA_FLAKE_REASON` 那条分支,不用另起一套判定/熔断逻辑。 - -**落点**:`server/index.js` 启动流程(挂在 `resumeIncompleteTasks()`/`sweepStuckActiveGoals()` 同一批"开机补一次全量扫描"的地方,跟这些既有钩子形状一致),加一个 `sweepOrphanedRunningNodes()`;判定逻辑放 `graph-engine.js` 或 `work-graph-db.js`(参照 `infra_flake` 现有实现挂在哪个文件)。 - -**验收**:新增测试——起一个 `status='running'`、`startedAt` 早于"当前进程启动时间"的节点,跑一次 `sweepOrphanedRunningNodes()`,断言它被转成 `infra_flake` 路径能处理的状态,且不影响 `startedAt` 晚于启动时间的正常运行节点。 - -### 1b. "强制判失败以便恢复"的 REST 端点 - -**现状**:`POST /:id/nodes/:nodeId/retry` 只接受 `status ∈ {fail, blocked}` 的节点。真实测试里遇到一个卡住的 `running` 节点,唯一的恢复手段是**直接写一段脚本调用 `graph-run-sse.transitionNode` 内部函数**,绕过了任何 REST 接口——这对人类操作者或 `ceo` 都不现实,两者都只能走 HTTP。 - -**设计**:新增 `POST /api/graph-runs/:id/nodes/:nodeId/force-fail`,语义是"我确认这个节点已经不可能再自己完成了,把它判成 `fail`(`failReason` 可选,默认某个通用值如 `operator_intervention`),交给既有的 fail→retry/backward-edge 路径处理"。**只接受 `status='running'` 的节点**(`fail`/`blocked` 已经有 `retry` 端点了,不重复造轮子);调用方是人类还是 `ceo` 不做区分,鉴权走已有的 `requireAuthOrInternal`(跟着 Phase 1a 一起补,`graph-run.js` 这次已经全部换过了)。1a 落地后,这个端点的日常触发频率应该会显著下降(大部分"卡住"场景 1a 已经自动兜底),但保留它作为人工/`ceo` 判断"这个不是重启造成的,就是卡了"时的手动出口。 - -**验收**:新增测试——`running` 节点调 `force-fail` 转 `fail` 且可被 `retry` 端点接上;`fail`/`blocked`/`pending` 状态调用返回 400(沿用 `ensureStatus` 现成的错误形状)。 - ---- - -## 三、Phase 2:§四.4 闸门穿透性回归测试 - -**现状**:39 号文档 §四.4 已拍板"`ceo` 全权自主 `enqueue`,但 `max_parallel_tasks`/`DEFAULT_TASK_BUDGET`/goal 级 `org-validator` 这几道闸门必须照样拦住它",落地时没有补这条测试(§六"已知遗留"记过一笔)。理论上这几道闸门挂在数据层/调度器层,不认"谁创建的",天然覆盖,但没有测试钉死就不能说"已确认"。 - -**设计**:不涉及任何设计决策,纯粹是补测试。新增用例(大致形状,落地时按现有测试文件的辅助函数抄写法): -1. 把公司 `max_parallel_tasks` 设成 1,已有一条 task 占着坑,`ceo` 走 `draft`+`enqueue` 建第二条——断言它排队而不是插队执行。 -2. 把公司 `DEFAULT_TASK_BUDGET` 设小,`ceo` 建的任务跑到预算耗尽——断言按现有 `budget-exhausted` 路径 `blocked`,不会因为"是 agent 建的"就获得额外预算。 -3. `ceo` 建的多 task goal 全部 `completed` 后,断言 goal 级 `org-validator` 照样被调一次(不因创建者是 agent 而跳过收敛验收)。 - -**落点**:新建 `server/tests/unit/ceo-created-task-gates.test.js`,或并入现有 `task-blocked-ceo-wake.test.js`(按当时哪个更顺手)。 - ---- - -## 四、Phase 3:`target_dir` 的 API/UI 入口 - -**现状**:P5 落地的是"配了 `company.json.target_dir` 就生效"这一半,"人怎么把这两个字段填进去"这一半没做——真实测试里是手工编辑公司态目录下的 JSON 文件配的。 - -**设计**: -- **后端**:`PATCH /api/company/:companyId`(如果这个端点已存在就扩展,不存在就新建)接受 `target_dir`/`target_mode`,写回 `company.json`。校验复用 `path-resolver.js` 的 `validateLinkedTarget`(39 号文档 P5 已经抽出来的共享校验函数),配置阶段就能提前报错(不用等到第一次 `ensureProjectRoot` 才发现路径不对)。 -- **前端**:公司设置页面(或 `TaskBoard.vue`/`GraphConsole.vue` 附近,具体挂哪个页面待定)加一个表单,填目录路径 + 选 `linked-new`/`linked-existing`。 - -**验收**:新增测试覆盖 `PATCH` 端点的校验分支(合法路径写入成功、`isSensitivePath` 命中黑名单拒绝、`linked-new` 目录非空拒绝);前端至少过一次真实点击验证表单能提交成功。 - ---- - -## 五、Phase 4:`timeline.ndjson` 承接 planning/reflection 轨迹 - -**现状(真跑撞到的,这是四个 phase 里最大的一个)**:`org-coder` 的四段式(RESEARCH→PLAN→EXECUTE→VERIFY)流转靠调 `bin/lemon.js task transition`,打到 `agent-chat.js` 的 `/task/transition` 端点——这个端点靠自己的 `sessions` Map 找"当前活跃会话",但 `org-coder` 作为 work graph 节点跑的时候走的是 `graph-engine.js` → `AgentManager` → `session-pool.js`,是 CLAUDE.md 里早就记录过的"两个互不相干的会话池"那个坑的又一个受害者。结果:`org-coder` 在 work graph 里调这条流转指令时,`sessions.entries()` 根本找不到它的会话,阶段流转的业务逻辑(P0 审批拦截、OKR 子状态更新)**静默跳过**;它的中间产物(`_research.md`/`_plan.md`/`_lessons.md`)不进 `graph_files` 契约(这个是刻意的,见下"不改的部分"),但也**不进任何跨节点/跨角色共享的轨迹**,人类或 `ceo` 事后想搞清楚"这一步当时是怎么想的",只能去读原始的 agent 会话 transcript(`GET /:id/nodes/:nodeRunId/transcript`),没有结构化的入口。 - -**不改的部分(上一轮已经讨论定案,写在这里防止实现时跑偏)**: -- `_plan.md` 这类中间产物**不进 `needs`/`produces` 契约**,`org-validator` 判定依据仍然只认 `acceptance.md` + `artifact`/`log`。理由:validator 判定"代码对不对",不该依赖"coder 计划写得像不像样",这条边界一旦模糊,两个坏结果都会发生——coder 学会把计划写漂亮而不是把代码写对;`graph_files` 的契约面失控(这个仓库反复栽在"两套体系不统一"上)。 -- 本 phase 只解决**可见性**(人类/`ceo` 事后能不能追溯),不解决**契约**(下游节点该不该依赖)——这是两件事,上一轮讨论已经拆开过。 - -**设计**(需要先做的调研,落地前必做): -1. **调研范围**:`org-coder` 是这次撞到的一个,不代表只有它——扫一遍 `system/agents/*/agent.md` 里还有哪些角色的提示词也在调 `bin/lemon.js task transition`,把范围摸清楚再动手,不要只按 `org-coder` 这一个样本改。 -2. **迁移方向**:把"阶段流转"这个动作,从打 `agent-chat.js` 的 `/task/transition`(legacy,不认 graph run),改成写一条 `timelineNote()`(`graph-engine.js` 已有机制,per-run、append-only、永不改已写行),带上 `phase`(research/plan/execute/verify 或角色自定义的阶段名)+ `artifact` 路径。**具体是改 `bin/lemon.js task transition` 这条命令本身去打新端点,还是给它接一条新命令**,两种做法都要过一遍现有 agent 提示词的改动成本再定——这些提示词文件散在几十个 `agent.md` 里,改一条命令的调用方式等于牵动所有引用它的本体。 -3. **P0 审批拦截 / OKR 子状态更新这两块旧逻辑怎么处理**:`/task/transition` 里现在还挂着 P0 任务拦截、OKR 子状态更新这两段业务逻辑——这两者本身是不是还需要(尤其 OKR 那段,跟 P3C 的新 goal/task 模型是不是还有效对应),需要单独判断,不能假设"迁移流转机制"就等于"这两段逻辑原样照抄"。 - -**验收**(待设计细化后补全,这里先占位):至少要有一条测试证明——`org-coder`(或调研出的任意一个角色)在 work graph 里完成一次阶段流转后,`timeline.ndjson` 里能查到对应的 `phase`+`artifact` 记录,且这条记录不影响 `graph_files`/`needs`/`produces` 的既有行为(回归测试守住"不改的部分"那条边界)。 - ---- - -## 六、开放问题 - -1. Phase 1a 的孤儿判定条件(`startedAt` 早于进程启动时间)在多进程/多副本部署下是否还成立?现在是单进程部署,这条先按当前架构写,多副本场景不在这次范围内。 -2. Phase 3 的 `PATCH /api/company/:companyId` 端点如果本来就不存在,是新建这一个端点,还是这次顺手看一下公司配置类的改动有没有更合适的既有入口——落地前查一遍 `company.js` 现状再定,不在这里预判。 -3. Phase 4 的调研如果发现有 10+ 个角色都在用旧的 `/task/transition`,工作量会明显超出"一个 phase"的量级,届时可能需要再拆细——先按"调研完再评估"处理,不在这里预先估工作量。 diff --git a/docs/todo/system-design/42-org-work-graph-reliability-followups.md b/docs/todo/system-design/42-org-work-graph-reliability-followups.md new file mode 100644 index 0000000..7f99850 --- /dev/null +++ b/docs/todo/system-design/42-org-work-graph-reliability-followups.md @@ -0,0 +1,93 @@ +# 41. Org/Work Graph 可靠性收尾 —— 39 号文档真跑复盘的待办 + +> **状态**:📝 待开工,未实现。 +> **日期**:2026-08-30 +> **背景**:[39-boss-agent-task-supervision.md](./39-boss-agent-task-supervision.md) 的 P1-P5 落地后,用它做了一次真实端到端测试(建公司 → 下 goal → `ceo` 监督 → 真实跑出一个可玩的贪吃蛇游戏,Playwright 5/5 验证通过)。**1469 个单测全绿,这次真跑却测出至少 3 个单测完全没覆盖到的真问题**——这份文档是那次复盘沉淀下来的待办,按开工顺序分四个 phase。 +> **读者须知**:每个 phase 的现状描述都来自那次真实测试里实际撞到的场景,不是推测;具体文件/行号落地前按惯例重新核实。 + +--- + +## 一、为什么排这个顺序 + +四个 phase 分两类:**Phase 1-2 是"让已经在跑的东西更可靠"**(不改变行为,只是补上恢复路径和验证),**Phase 3-4 是"补功能缺口"**(新增能力)。前者优先——真跑复盘里代价最大的坑(僵尸节点、鉴权漏挂)都是"东西本来能跑,只是卡住了没人能救"这一类,越早补上,后面几个 phase 开工时越不容易被同样的坑绊到。Phase 5(timeline 承接规划轨迹)单独放最后,因为它是结构性改动,需要先摸清 `org-coder` 之外还有哪些角色也在打那条断线的 `/task/transition`,工作量比其余四个 phase 加起来还大。 + +``` +Phase 1:卡住恢复闭环(僵尸节点检测 + 强制判失败端点)——两个互补的口子一起补 +Phase 2:§四.4 闸门穿透性回归测试 —— 纯测试,零设计风险 +Phase 3:target_dir 的 API/UI 入口 —— 纯加法 +Phase 4:timeline.ndjson 承接 planning/reflection 轨迹 —— 结构性改动,排最后 +``` + +--- + +## 二、Phase 1:卡住恢复闭环 + +### 1a. 服务器重启的僵尸节点检测 + +**现状(真跑撞到的)**:`graph_run_nodes.status='running'` 这一行,没有任何机制确认"背后真的还有个活的进程"。真实测试里为了让 `graph-run.js` 鉴权修复生效重启了一次服务器,正在跑 `org-planner-sdd` 的 Claude CLI 子进程被连带杀掉,但那一行的状态永远停在 `running`——不会自己变成 `blocked`,`ceo` 的两条唤醒路径(事件驱动 + 30 分钟兜底扫描)判据都是 `status='blocked'`,对这个状态完全不可见。 + +查过现成的"重启后清理残留"机制:`server/index.js` 的 `resumeIncompleteTasks()` 只处理 ArenaSK 的 Skill 训练任务(`skill-task-manager`),跟 org/work graph 毫无关系;`shutdown()`(`SIGTERM`/`SIGINT` 处理)只关插件管理器,不碰 `graph_run_nodes`。两处都不覆盖这个场景。 + +**设计**:启动时扫一遍 `graph_run_nodes`,`status='running'` 且 `updated_at`(或 `startedAt`)早于**本次进程启动时间**的行,判定为孤儿(整个进程树都被换过一轮,不可能有真实进程还在跑它)。判定后按 **`infra_flake` 语义**处理,不发明新状态——原地重跑逻辑本来就是为"基础设施打断,跟设计/实现对不对无关"这类场景设计的,服务器重启只是这个桶里更严重的一个成员。复用 `graph-engine.js` 已有的 `routeFailure` 里 `INFRA_FLAKE_REASON` 那条分支,不用另起一套判定/熔断逻辑。 + +**落点**:`server/index.js` 启动流程(挂在 `resumeIncompleteTasks()`/`sweepStuckActiveGoals()` 同一批"开机补一次全量扫描"的地方,跟这些既有钩子形状一致),加一个 `sweepOrphanedRunningNodes()`;判定逻辑放 `graph-engine.js` 或 `work-graph-db.js`(参照 `infra_flake` 现有实现挂在哪个文件)。 + +**验收**:新增测试——起一个 `status='running'`、`startedAt` 早于"当前进程启动时间"的节点,跑一次 `sweepOrphanedRunningNodes()`,断言它被转成 `infra_flake` 路径能处理的状态,且不影响 `startedAt` 晚于启动时间的正常运行节点。 + +### 1b. "强制判失败以便恢复"的 REST 端点 + +**现状**:`POST /:id/nodes/:nodeId/retry` 只接受 `status ∈ {fail, blocked}` 的节点。真实测试里遇到一个卡住的 `running` 节点,唯一的恢复手段是**直接写一段脚本调用 `graph-run-sse.transitionNode` 内部函数**,绕过了任何 REST 接口——这对人类操作者或 `ceo` 都不现实,两者都只能走 HTTP。 + +**设计**:新增 `POST /api/graph-runs/:id/nodes/:nodeId/force-fail`,语义是"我确认这个节点已经不可能再自己完成了,把它判成 `fail`(`failReason` 可选,默认某个通用值如 `operator_intervention`),交给既有的 fail→retry/backward-edge 路径处理"。**只接受 `status='running'` 的节点**(`fail`/`blocked` 已经有 `retry` 端点了,不重复造轮子);调用方是人类还是 `ceo` 不做区分,鉴权走已有的 `requireAuthOrInternal`(跟着 Phase 1a 一起补,`graph-run.js` 这次已经全部换过了)。1a 落地后,这个端点的日常触发频率应该会显著下降(大部分"卡住"场景 1a 已经自动兜底),但保留它作为人工/`ceo` 判断"这个不是重启造成的,就是卡了"时的手动出口。 + +**验收**:新增测试——`running` 节点调 `force-fail` 转 `fail` 且可被 `retry` 端点接上;`fail`/`blocked`/`pending` 状态调用返回 400(沿用 `ensureStatus` 现成的错误形状)。 + +--- + +## 三、Phase 2:§四.4 闸门穿透性回归测试 + +**现状**:39 号文档 §四.4 已拍板"`ceo` 全权自主 `enqueue`,但 `max_parallel_tasks`/`DEFAULT_TASK_BUDGET`/goal 级 `org-validator` 这几道闸门必须照样拦住它",落地时没有补这条测试(§六"已知遗留"记过一笔)。理论上这几道闸门挂在数据层/调度器层,不认"谁创建的",天然覆盖,但没有测试钉死就不能说"已确认"。 + +**设计**:不涉及任何设计决策,纯粹是补测试。新增用例(大致形状,落地时按现有测试文件的辅助函数抄写法): +1. 把公司 `max_parallel_tasks` 设成 1,已有一条 task 占着坑,`ceo` 走 `draft`+`enqueue` 建第二条——断言它排队而不是插队执行。 +2. 把公司 `DEFAULT_TASK_BUDGET` 设小,`ceo` 建的任务跑到预算耗尽——断言按现有 `budget-exhausted` 路径 `blocked`,不会因为"是 agent 建的"就获得额外预算。 +3. `ceo` 建的多 task goal 全部 `completed` 后,断言 goal 级 `org-validator` 照样被调一次(不因创建者是 agent 而跳过收敛验收)。 + +**落点**:新建 `server/tests/unit/ceo-created-task-gates.test.js`,或并入现有 `task-blocked-ceo-wake.test.js`(按当时哪个更顺手)。 + +--- + +## 四、Phase 3:`target_dir` 的 API/UI 入口 + +**现状**:P5 落地的是"配了 `company.json.target_dir` 就生效"这一半,"人怎么把这两个字段填进去"这一半没做——真实测试里是手工编辑公司态目录下的 JSON 文件配的。 + +**设计**: +- **后端**:`PATCH /api/company/:companyId`(如果这个端点已存在就扩展,不存在就新建)接受 `target_dir`/`target_mode`,写回 `company.json`。校验复用 `path-resolver.js` 的 `validateLinkedTarget`(39 号文档 P5 已经抽出来的共享校验函数),配置阶段就能提前报错(不用等到第一次 `ensureProjectRoot` 才发现路径不对)。 +- **前端**:公司设置页面(或 `TaskBoard.vue`/`GraphConsole.vue` 附近,具体挂哪个页面待定)加一个表单,填目录路径 + 选 `linked-new`/`linked-existing`。 + +**验收**:新增测试覆盖 `PATCH` 端点的校验分支(合法路径写入成功、`isSensitivePath` 命中黑名单拒绝、`linked-new` 目录非空拒绝);前端至少过一次真实点击验证表单能提交成功。 + +--- + +## 五、Phase 4:`timeline.ndjson` 承接 planning/reflection 轨迹 + +**现状(真跑撞到的,这是四个 phase 里最大的一个)**:`org-coder` 的四段式(RESEARCH→PLAN→EXECUTE→VERIFY)流转靠调 `bin/lemon.js task transition`,打到 `agent-chat.js` 的 `/task/transition` 端点——这个端点靠自己的 `sessions` Map 找"当前活跃会话",但 `org-coder` 作为 work graph 节点跑的时候走的是 `graph-engine.js` → `AgentManager` → `session-pool.js`,是 CLAUDE.md 里早就记录过的"两个互不相干的会话池"那个坑的又一个受害者。结果:`org-coder` 在 work graph 里调这条流转指令时,`sessions.entries()` 根本找不到它的会话,阶段流转的业务逻辑(P0 审批拦截、OKR 子状态更新)**静默跳过**;它的中间产物(`_research.md`/`_plan.md`/`_lessons.md`)不进 `graph_files` 契约(这个是刻意的,见下"不改的部分"),但也**不进任何跨节点/跨角色共享的轨迹**,人类或 `ceo` 事后想搞清楚"这一步当时是怎么想的",只能去读原始的 agent 会话 transcript(`GET /:id/nodes/:nodeRunId/transcript`),没有结构化的入口。 + +**不改的部分(上一轮已经讨论定案,写在这里防止实现时跑偏)**: +- `_plan.md` 这类中间产物**不进 `needs`/`produces` 契约**,`org-validator` 判定依据仍然只认 `acceptance.md` + `artifact`/`log`。理由:validator 判定"代码对不对",不该依赖"coder 计划写得像不像样",这条边界一旦模糊,两个坏结果都会发生——coder 学会把计划写漂亮而不是把代码写对;`graph_files` 的契约面失控(这个仓库反复栽在"两套体系不统一"上)。 +- 本 phase 只解决**可见性**(人类/`ceo` 事后能不能追溯),不解决**契约**(下游节点该不该依赖)——这是两件事,上一轮讨论已经拆开过。 + +**设计**(需要先做的调研,落地前必做): +1. **调研范围**:`org-coder` 是这次撞到的一个,不代表只有它——扫一遍 `system/agents/*/agent.md` 里还有哪些角色的提示词也在调 `bin/lemon.js task transition`,把范围摸清楚再动手,不要只按 `org-coder` 这一个样本改。 +2. **迁移方向**:把"阶段流转"这个动作,从打 `agent-chat.js` 的 `/task/transition`(legacy,不认 graph run),改成写一条 `timelineNote()`(`graph-engine.js` 已有机制,per-run、append-only、永不改已写行),带上 `phase`(research/plan/execute/verify 或角色自定义的阶段名)+ `artifact` 路径。**具体是改 `bin/lemon.js task transition` 这条命令本身去打新端点,还是给它接一条新命令**,两种做法都要过一遍现有 agent 提示词的改动成本再定——这些提示词文件散在几十个 `agent.md` 里,改一条命令的调用方式等于牵动所有引用它的本体。 +3. **P0 审批拦截 / OKR 子状态更新这两块旧逻辑怎么处理**:`/task/transition` 里现在还挂着 P0 任务拦截、OKR 子状态更新这两段业务逻辑——这两者本身是不是还需要(尤其 OKR 那段,跟 P3C 的新 goal/task 模型是不是还有效对应),需要单独判断,不能假设"迁移流转机制"就等于"这两段逻辑原样照抄"。 + +**验收**(待设计细化后补全,这里先占位):至少要有一条测试证明——`org-coder`(或调研出的任意一个角色)在 work graph 里完成一次阶段流转后,`timeline.ndjson` 里能查到对应的 `phase`+`artifact` 记录,且这条记录不影响 `graph_files`/`needs`/`produces` 的既有行为(回归测试守住"不改的部分"那条边界)。 + +--- + +## 六、开放问题 + +1. Phase 1a 的孤儿判定条件(`startedAt` 早于进程启动时间)在多进程/多副本部署下是否还成立?现在是单进程部署,这条先按当前架构写,多副本场景不在这次范围内。 +2. Phase 3 的 `PATCH /api/company/:companyId` 端点如果本来就不存在,是新建这一个端点,还是这次顺手看一下公司配置类的改动有没有更合适的既有入口——落地前查一遍 `company.js` 现状再定,不在这里预判。 +3. Phase 4 的调研如果发现有 10+ 个角色都在用旧的 `/task/transition`,工作量会明显超出"一个 phase"的量级,届时可能需要再拆细——先按"调研完再评估"处理,不在这里预先估工作量。 -- libgit2 0.21.4