Commit 8651b84ab0e7d6f3eadd5fdf80437f58a85f5904

Authored by yibo
1 parent 8fb378c8

docs(task-decomposition): 46号文档 Phase 3 真实回归发现两个问题

真实公司走完完整链路(物化→认领CAS→/task/report 状态转正确),机制本身
工作正常。但发现:①认领后没有任何触发源通知 org-coder,纯数据库状态
变更,没人会主动发现/认领可认领子任务;②/task/report 不是干净的状态
回写端点,会自动触发 selfChat 回调唤醒 to 指定的 agent(真实起一个带
文件读写工具的新 CLI 会话),且它是与 org/work graph 完全独立的非 graph
协议(agent-chat.js 的 /task/dispatch↔/task/report),不挂在任何图节点上。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H85SS5hVC4u1PbVhjmrhH8
docs/todo/system-design/46-mandatory-planning-and-task-decomposition.md
... ... @@ -418,9 +418,15 @@ WHERE id=? AND subtask_kind=&#39;claimable_work&#39; AND status=&#39;claimable&#39;
418 418  
419 419 排查一处语法错误(SQL 模板字符串里误用了反引号,把 `db.exec` 的模板字符串提前闭合——`work-graph-db.js` 建表块头部本来就有一条注释警告这件事,这次还是踩上了)时,用了一句 `node -e "require('./server/services/work-graph-db.js')"` 做语法检查——这条命令**没有设 `GRAPH_DB_PATH`**,不是测试进程(没有 `assertNotProdDbInTests` 的拦截),实际对本机 `data/graph.sqlite`(本地开发库,Phase 2 真实回归的测试公司数据也在里面)跑了一次批 6 迁移。核实过:迁移是纯增量的 `ALTER TABLE ADD COLUMN`,102 条既有 task 行原样在、`subtask_kind` 全部是 `NULL`,没有数据损坏。但这本可以用一次性临时库避免,记在这里是给自己留个教训——语法检查也该走隔离环境,不该因为"只是 require 一下"就放松。
420 420  
421   -### 还没做真实端到端回归
  421 +### 真实端到端回归(2026-09-02,已完成,发现两个真实问题)
422 422  
423   -跟 Phase 1/2 一样,机制通不通已经用单测验证(DB 层 CAS、路由层白名单/并发、settleNode 闸门收窄的回归)。**没做的是**:用真实公司走一遍"task 入队 → subtask-planning 产出 steps → 物化出可认领子任务 → org-coder 真的调 `/claim` 认领 → 自己搞定 → `/task/report` 上报 → 状态正确落到终态"这条完整链路——尤其是最后一段(认领后 `org-coder` 自己怎么干),Phase 3 设计里已经写明"这一段完全没有验证质量的机制,跑得糙",真实回归大概率会暴露这一段的具体问题(比如 `org-coder` 会不会真的知道要去查有哪些可认领子任务、认领了之后要不要真的去写代码、写完会不会记得调 `/task/report`——这些都是提示词/流程层面的问题,不是这次数据库+路由层改动能回答的)。留给用户后续决定何时验证。
  423 +用真实公司 `p2verify0902` 走完一次真实链路:真实 goal→task→dispatch → `subtask-planning` 真实产出 4 步分解并物化 → 可认领池查询(`?subtaskKind=claimable_work&status=claimable`)正确返回 4 条,`depends_on` 正确解析成真实 task id(连"done 步骤依赖 list 步骤"这种需要理解语境的依赖关系都推对了)→ 认领 CAS 全部按预期(非白名单角色 403、重复认领 409、放弃 + 重新认领正确)→ `/task/report` 真的把 `claimed → completed` 转正确,`summary`/`finished_at` 落库正确,`description` 原样保留(round-trip 安全)。全程 `ps aux` 确认零残留 CLI 子进程(会话池里保留的常驻进程随服务器关闭一起清零,不是泄漏)。
  424 +
  425 +**发现一:认领之后没有任何机制通知 org-coder。** `claimSubtask` 纯粹是一次数据库状态变更——认领 / 放弃 / 重新认领全程,服务端日志零 agent 活动。也就是说,除非有人或有别的机制手动调用 `/claim`,这些可认领子任务会永远原地躺着,**没有任何东西会主动去发现它们、认领它们**。这是设计阶段"认领后怎么办"一节已经承认的缺口("跑得糙"),真实回归把它坐实成了一个具体、可复现的观察:机制本身工作正常,但**没有触发源**。
  426 +
  427 +**发现二:`/task/report` 不是一条干净的"状态回写"端点,它耦合了一整套遗留的非 graph 回调机制。** 调用 `POST /api/agent/task/report`(`server/routes/agent-chat.js`)之后,除了把 `tasks` 表状态转过去,还会**自动触发一次 `selfChat` 回调**——真实唤醒 `to` 指定的那个 agent,起一个带真实文件读写工具(`allowedTools: bash/read_file/str_replace/write_file`)的新 CLI 会话,提示词是"请检查上述任务的执行结果,然后根据你的 plan.md 继续执行下一步计划"。真实回归时这条回调确实触发了、真的起了一个新 CLI 进程、跑完自己正常退出,没有异常,但这意味着"复用现有 `executor='agent'` 闭环"这个设计判断,代价比设计阶段预想的更高——调 `/task/report` 不是"安静地改一行数据库",是"顺带唤醒另一个 agent 会话"。
  428 +
  429 +**`/task/report` 的执行载体是什么**:它**既不是 org graph 节点,也不是 work graph 节点**——`agent-chat.js` 的 `/task/dispatch` ↔ `/task/report` 是一套完全独立于 `graph-engine.js` 的**非 graph 协议**(`work-graph-db.js` 里的原话:"agent 之间派单(`POST /api/agent/task/dispatch`)与「跑一张 org graph」是两种执行载体")。它是一个普通的 Express 路由处理函数,被 HTTP 请求直接调用,不挂在任何图的节点上、不经过 `graph-engine.settleNode`/`advanceOne` 那套结算逻辑。`claimSubtask` 复用的 `executor='agent'` 状态机和这条路由,是与 org/work graph 完全平行的第二套执行世界。
424 430  
425 431 ### 遗留给 Phase 4 的问题
426 432  
... ...