From 407bef7babf77d93de6dd27d218a5bbcba6f1eba Mon Sep 17 00:00:00 2001 From: yibo Date: Mon, 7 Sep 2026 17:08:37 +0800 Subject: [PATCH] chore(agents): 生产环境 dream 自动更新(ceo 经验区块,2026-09-07) --- docs/done/system-design/46-mandatory-planning-and-task-decomposition.md | 490 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ docs/todo/system-design/46-mandatory-planning-and-task-decomposition.md | 490 ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- system/agents/ceo/agent.md | 17 ++++++++--------- system/agents/ceo/soul.md | 17 ++++++++--------- 4 files changed, 506 insertions(+), 508 deletions(-) create mode 100644 docs/done/system-design/46-mandatory-planning-and-task-decomposition.md delete mode 100644 docs/todo/system-design/46-mandatory-planning-and-task-decomposition.md diff --git a/docs/done/system-design/46-mandatory-planning-and-task-decomposition.md b/docs/done/system-design/46-mandatory-planning-and-task-decomposition.md new file mode 100644 index 0000000..419c44e --- /dev/null +++ b/docs/done/system-design/46-mandatory-planning-and-task-decomposition.md @@ -0,0 +1,490 @@ +# 46 · Planning 应作为每个任务的默认前置步骤,覆盖三类信息来源 + +> **状态**: 📝 设计中——四个关键决策已拍板。**Phase 0 已完成**(2026-09-01 真实回归验证):链路能收敛、44 号文档修复确认生效,发现的独立缺陷已拆到 [47 号文档](../../done/system-design/47-itemized-subtask-materialization-goal-benchmark-fallback.md) 并修复。**Phase 1 代码已实现**(2026-09-02,见「Phase 1 实现记录」):新 agent `subtask-planning` + `enqueueTaskRun` 挂钩(收窄为显式 opt-in)+ 复用 R-9.3 任务级共享索引对接下游,单测覆盖,**但还没做真实端到端回归**(且过程中出过一次真实 CLI 子进程泄漏事故,已修复并加了测试环境防护,见该节)。**Phase 2 已实现且已做真实端到端回归(收窄版,2026-09-02,见「Phase 2 实现记录」)**:经验检索(新查询能力,扫已雇 agent 的 dream 沉淀)+ 按需 deep research(图外调用 org-hunt,默认关闭)。真实回归第一次就跑出一个真缺陷(关键词打分噪音压过信号,导致"经验库有答案"分支误触发)并已修复验证;两条真实任务的分解内容质量都好,但 deep research 分支目前更接近"多问一个角色的第二意见"而非真的做了外部检索(org-hunt 那次调用没有工具调用痕迹),如实记在实现记录里。"历史同类 goal 检索"这条来源本轮明确收窄未做。**Phase 3 走过一轮推翻重来(2026-09-02)**:v1("认领"pull 语义 + CAS)已实现并做过真实回归,但回归本身就暴露出"认领后没有触发源"的真问题,讨论后发现"认领"要解决的"多方竞争"场景根本不存在(只有 CEO 一人拍板要不要派)——**v2 改成 CEO 心跳直接发现+派发(push),删掉了 v1 的整套认领机制**,改为 CEO 判断节奏后调现成的 `/enqueue`,子任务默认走一张现造的轻量 work graph(`org-coder` 实现 + `org-validator` 验证两个节点,跳过公司完整 org graph 的规划仪式),效果不理想再升级成完整 org graph(见「Phase 3 v2」)。v1 设计和实现记录原样保留、标了废弃(留作决策过程的第一手记录,不是白做)。**v2 已做真实端到端回归**:真实 CEO 心跳确实发现并派发了子任务(顺手还修了一个真实的、影响面更大的 bug——两套心跳系统的自环调用一直缺内部令牌、暗中 401),递归防护、依赖阻塞都验证生效;`org-coder` 在没有 brd/prd/sdd 的轻量图里产出质量真实达标,但 `org-validator` 卡在硬依赖的 `acceptance.json`(只有 org-planner 会产出)上——**轻量图的执行没问题,验证这一半有真实的结构性缺口**,需要另外决定怎么补(见「Phase 3 v2」结尾三个方向)。Phase 4(独立开发验证循环)、Phase 5(看板 UI)仍未设计,留给后续 +> **日期**: 2026-09-01 +> **触发场景**: 用户复盘 [43 号文档](../../done/system-design/43-task-decomposition-granularity.md)(goal 分解粒度)时,追问"为什么本地和线上所有公司的任务看板都没有子任务分解"。核实后发现:不是巧合或配置遗漏,`acceptance_mode='itemized'` 在当前系统里**从未被真正触发并落库过一次**——追问引出了比 43 号文档更大的架构缺口:43 号文档只解决"验收要不要拆细",而用户认为**规划(把一件事拆成第一步/第二步/第三步)本身应该是每个任务默认必经的前置步骤,不是一个可选的、可以被判"不需要"的例外**。 +> **格式对齐** `27-requirement.md`/[`43-task-decomposition-granularity.md`](../../done/system-design/43-task-decomposition-granularity.md):原话照搬 + 结构化复述,现状调研另起一节。 + +--- + +## 原始需求(用户原话,一字不改) + +> 所以,拆解任务计划这件事情是默认要做的。也就是说,我们做任何一个任务,其实都要有一个 planning(规划)的过程。 +> +> Planning 过程就是明确这个事情怎么做:第一步干什么,第二步干什么,第三步干什么。这个是一定要规划出来的。而这种规划来源于以下三个地方: +> +> 1. 模型本身有规划能力。 +> 2. 来源于我们的知识库、经验库、memory 或 history。 +> 3. 来源于我们去做线上的 Deep Research。去研究怎么更好地实现这个过程,别人是怎么做的,线上有什么 know-how、有什么方法论,怎么做这件事情才是更优解。 +> +> 比如说,我们怎么做一个视频:视频要不要生成分镜、生成脚本、修改编剧台词?最后怎么视频合成、怎么验证?这些方法论是需要有一个 planning 过程的,这个过程一定是要有的。所以,我们之前没有做任务分解,但并不代表这个事情在现实生活中不存在。既然我们之前的架构上有这个问题,那我们就必须要改变它。 +> +> 也就是说,我们做任何一个 ORG graphy 或者是 work graphy 的时候,就应该先有 planning 的过程: +> 1. 分解任务; +> 2. 所有的 agents 围绕这个任务看板来工作,一层一层地接子任务; +> 3. 子任务做完之后,再去做主任务的 validate。 +> +> 人的工作是这样的,那多 Agent 的协同工作也应该是这样的。 + +--- + +## 结构化复述 + +拆成三条独立主张: + +1. **规划是默认前置步骤,不是可选判断**——任何一个任务(goal / org graph / work graph 层面)在真正执行前,都必须先产出一份"第一步干什么、第二步干什么"的分解结果。当前架构里"要不要拆"是一次可以判"不需要"的例外判断,这一点本身就是缺陷。 +2. **规划的信息来源要显式覆盖三类**:① 模型自身推理能力,② 内部知识库/经验库/memory/history,③ 外部 deep research(别人怎么做、行业 know-how、方法论)。三者不是互斥,是应该都被用上的三个输入源。 +3. **执行模型要围绕"任务看板"重新组织**:分解出的子任务应该是 agent 们认领、逐层完成的对象;子任务做完之后再做主任务的 validate(先子后主,不是先给主任务判总分)。 + +--- + +## 现状调研(本次会话核实,基于 43/44 号文档 + 本次代码交叉核对) + +结论:**当前架构在这三条主张上都有真实的、具体的缺口**,不是风格问题。逐条列证据: + +### 1. 规划目前是"要不要拆"的保守判断,默认是不拆 + +`system/agents/task-planning/agent.md` Step 2bis(决定 `acceptance_mode` 的地方)原文态度: + +> 判断错的代价是不对称的:错误地写 `"itemized"`(验收标准其实互相依赖)会让后续验收拆出没法独立成立的子任务,比漏写、保守选 `"holistic"` 的代价更高。**拿不准就留 `"holistic"`**。 + +这与"规划是默认动作"直接相反——现在的默认值是"不规划到子任务颗粒度",只有信心足够时才拆。而且这一步只回答"验收要不要拆",从不回答"这件事第一步干什么、第二步干什么"这种通用意义上的 planning。 + +### 2. 规划阶段被显式禁止使用知识库/深度调研 + +`system/agents/task-planning/tools.md`「你**不该**用的工具」表格原文: + +> | 联网搜索、读第三方资料 | 拆分的唯一真相来源是 goal 描述原文。去外面找依据,等于给自己发明需求(违反 `agent.md` 硬约束 8) | + +这条规则本身有合理的历史动机(防止 LLM 脱离用户原话臆造需求),但结果是:用户设想的三条规划来源里,**第 2 条(知识库/经验库/memory/history)和第 3 条(deep research)在当前唯一的"goal → task"规划入口上被明文禁止**,规划只能来自第 1 条(模型自身能力)。 + +值得注意:知识库/经验库这条路径**不是完全空白**——`dream` 机制(29 号文档)会把各 agent 的运行经验提炼后写回 `agent.md`/`soul.md` 的专属区块(本对话前半段刚看过 CEO 的这次刷新)。但这是**事后单向沉淀**:写给 agent 在被调用时"顺带看到",不是规划阶段主动查询的机制——task-planning 判断"要不要拆、怎么拆"时,完全不会去读任何 agent 的 dream 沉淀经验。 + +### 3. 已经存在一套真正做"deep research"的机制,但没有接入日常任务规划 + +`system/agents/org-hunt`(+ `org-digest`/`org-evolve`)是 41 号文档验证过的一套真实调研机制:用 `l1-web-fetch` 抓取网页、评估数据源权威性、产出方法论。这正是用户设想里"第 3 条来源"的现成实现,而且经过真实跑通验证(41 号文档"11 轮真跑挖出并修复 5 个独立缺陷")。 + +但它**当前只服务于 FDE 方法论训练场景**(`org-innovator` 评估/训练某个角色的专业能力),只在 `deliverable.json` 这一种 work-graph-template 骨架里被引用,**从未被接入 task-planning 或 org-planner 的常规执行路径**。换句话说:deep research 的能力已经造好了,只是没有被接到"用户下一个任务 → 系统怎么规划"这条主路径上。 + +### 4. 子任务目前只影响"验收",不影响"执行分工" + +43 号文档的落地范围里明确记录了这条设计决策: + +> 子任务要不要独立开发+验收循环 | **先只拆验收,开发仍整体完成**——org-coder 一次性写完整个交付物,验收侧改成按子任务(验收条目)逐条判,而不是给总分 | 改动小得多……**独立开发循环(每个子任务各起一个 work-graph run)留作后续再评估** + +用户这次的第 2 条主张——"所有的 agents 围绕这个任务看板来工作,一层一层地接子任务"——正是 43 号文档当时明确放弃、留待后续评估的那部分。现在看,"后续"就是现在。 + +### 5. org/work graph 是预先铺好的固定拓扑,不是"任务池、谁有空谁认领" + +现有 org graph / work graph 是建图阶段(`orggraph-planning`/`workgraph-planning`)一次性规划出的 DAG,节点和边在执行前就已固定,执行时严格按拓扑推进。`org-planner` 没有任何"创建 task"的工具(43 号文档已核实),子任务是靠服务端在它产出 `acceptance.md` 之后**物化**出来的,不是 agent 从一个共享任务池里"认领"的。 + +用户"agents 围绕任务看板逐层接子任务"这句话,字面上更接近一个**动态任务池 + 认领机制**,跟"图在建图那一刻就已经画死"的现有模型有结构性差异——这不是一个小改动,需要先确认要不要走这个方向(见下节问题 C)。 + +### 6. 即便是现有最小范围的子任务机制,可靠性也还没被坐实 + +上一轮对话已核实:当前 `data/graph.sqlite` 里 `acceptance_mode='itemized'` 的 task 零条,所有公司运行态目录里都搜不到 `itemized` 字样——43/44 号文档做的那次真实验证用的测试公司数据也已被清理。也就是说,在讨论"要不要扩大规划范围"之前,现有范围内的东西本身还没有一次成功的端到端确认。 + +--- + +## 设计决策(2026-09-01 定案) + +| 问题 | 决定 | 直接后果 | +|------|------|----------| +| A:规划打通到哪一层 | **新增独立的"深度规划"阶段**,插在 `task-planning`(goal→task,保持极简不变)和现有 org-planner 之间 | `task-planning` 职责不变(仍是"要不要多条 task"的粗粒度判断,3 轮内、不碰磁盘)。新阶段专门承接"这件事第一步/第二步/第三步怎么做"+ deep research + 查经验库,产出结构化的分解结果,**取代**现有"org-planner 收尾后才物化子任务"的时序——分解结果要在执行开始前就产出,而不是brd/prd/sdd写完后才回头拆 | +| B:Deep Research 触发时机 | **规划环节自行判断**——先查经验库/memory/dream 沉淀,查不到或置信度低时才触发调研(复用 `org-hunt`/`org-digest`) | 新阶段需要两类工具权限:① 读 agent 的 `soul.md`/`agent.md` dream 区块 + 跨公司经验(目前没有现成的"查经验库"检索接口,需要新建);② 有条件触发 `org-hunt`(目前只能被 workgraph-planning 挑进固定模板,需要开放为"新阶段可按需插入") | +| C:执行模型 | **引入任务池 + 认领机制**,替代当前"建图时固定 DAG、严格拓扑推进"的执行方式 | 这是四条里改动面最大的一条:`graph-engine.js` 当前是纯拓扑驱动(`readDefGraph` 一次性给出全部节点和边),需要新增"子任务作为可认领工作项"的调度层——agent(或调度器代 agent)从共享任务看板拉取匹配自己角色、状态为"可认领"的子任务,而不是等图上游节点产出后按边推给固定下游 | +| D:验证时机 | **子任务拥有独立的开发+验证生命周期**(这次和 A/C 一起做,不再分阶段) | 每个子任务对应一次独立的开发→验证循环(而不是 43 号文档现状的"整体开发、分子验证"),意味着每个被认领的子任务大概率各自触发一次工作流实例(起类似 work-graph run 的调用),成本和并发编排复杂度显著上升,需要有预算/并发槽位控制(可复用 27-P3C 已实现的队列/并行槽位机制) | + +四条决策合起来的完整流程: + +``` +goal 创建 + │ + ▼ task-planning(不变,粗粒度) + 产出 1..N 条 task(draft) + │ + ▼ 人工"全部下达"(不变) + │ + ▼ 【新】深度规划阶段 + 1. 查经验库/memory/dream 沉淀,看有没有可复用的方法论 + 2. 置信度不够 → 触发 org-hunt/org-digest 做 deep research + 3. 结合"模型自身规划能力 + 查到的经验/调研结果",产出结构化分解 + ("这件事第一步做什么、第二步做什么…",形态类似 43 号文档的 acceptance.md + 但发生在执行前而不是收尾后,且不仅是验收条目,是完整的步骤清单) + 4. 把分解结果物化成任务看板上的一批"可认领子任务"(不是固定图节点) + │ + ▼ 【新】任务池认领执行 + 各角色 agent(org-coder / org-validator / …)按自己的角色,从共享看板拉取 + 下一条匹配、状态为"可认领"的子任务 → 独立开发 → 独立验证 → 回写子任务终态 + │ + ▼ 汇总收敛 + 全部子任务 completed → 父 task success;任一子任务 failed → 父 task fail + (沿用 43 号文档已实现的父子终态汇总规则,未变) +``` + +--- + +## 已知的高风险点(老实写,别假装这是小改动) + +- **任务池认领是对现有图执行引擎(`graph-engine.js`)的范式级改动**,不是加字段/加钩子能做到的。现有引擎的核心心智模型是"边决定谁在什么时候跑",任务池模型的心智模型是"角色决定谁能跑什么,时机由认领动作决定"——两者要并存(company 结构层的 org graph 显然还是要保留固定角色拓扑,不可能把 CEO/CTO 汇报关系也做成认领制),需要先明确**任务池模型的作用域边界**:只用于"深度规划产出的子任务"这一层,还是要往上影响到 work-graph 本身怎么跑。这个边界目前还没有定案,是 Phase 1 设计阶段必须先钉死的第一件事。 +- **43/44 号文档已经踩过一次"新机制先天有可靠性缺口"的坑**(`acceptance.md` 未必被稳定产出,闸门只查私有目录不查共享区),且现有最小范围的子任务机制(43 号文档)在生产数据里至今零命中。在没有把现有范围坐实之前就叠加更大的改动,风险会累加而不是替换。 +- **"每个子任务独立开发+验证循环"直接推高成本**——一个 goal 可能因此从"1 次 work-graph run"变成"N 次子任务级 run",需要先有预算模型(每个子任务分到多少预算、超预算怎么办)才能不失控。 +- **Deep research 的调用成本和可靠性**——`org-hunt` 目前只在方法论训练场景验证过(41 号文档),没有在"日常任务规划、时间敏感"场景下验证过延迟和成功率。 + +--- + +## 技术实施计划(架构级阶段划分,未细化到代码行) + +建议顺序:**Phase 0(先坐实现有范围)→ Phase 1(深度规划阶段骨架)→ Phase 2(经验库检索 + deep research 接入)→ Phase 3(任务池认领)→ Phase 4(子任务独立开发验证循环)→ Phase 5(看板 UI)**。理由:每一阶段都在前一阶段的地基上加一层能力,且允许在任何一个阶段暂停评估效果/成本,不是一次性大爆炸式改造。 + +### Phase 0 · 坐实现有范围(前置条件,不是本文档新增范围) +在叠加新机制前,先补一次 43/44 号文档遗留的端到端确认跑——用真实公司走一遍现有 `acceptance_mode='itemized'` 全链路,确认它能稳定收敛。当前生产数据零命中,不能假设地基是稳的。 + +### Phase 1 · 深度规划阶段骨架 +- 新增独立 agent(暂定 `deep-planning`,命名需过 27 号命名规范),接在 task-planning 产出 task、人工下达之后,org 图真正执行之前。 +- 先只做"模型自身规划能力"这一路(用户三条来源里的第 1 条):产出结构化步骤分解,格式参考 org-planner 现有 `acceptance.md` 表格惯例,但覆盖"怎么做"而不仅是"怎么验收"。 +- 暂不接经验库/deep research,暂不接任务池——先验证"分解结果能不能被下游正确消费"这一步。 +- 明确任务池模型的作用域边界(上面"高风险点"第一条),写成本文档的补充小节,动 Phase 3 之前必须先定案。 + +### Phase 2 · 经验库检索 + Deep Research 接入 +- 新建一个"查经验库"的检索能力——扫描相关 agent 的 dream 沉淀区块 + 历史同类 goal 的执行记录,给深度规划阶段用。这是全新能力,当前没有任何 agent 具备"主动查别的 agent 经验"的工具。 +- 打通深度规划阶段按需触发 `org-hunt`/`org-digest` 的路径(当前它们只能被 workgraph-planning 挑进固定模板,需要开放"按需调用"的口子)。 +- 验收:给一个"经验库有答案"的任务和一个"经验库没答案、需要调研"的任务各跑一次,确认分支判断正确、调研产出确实被规划阶段消费。 + +### Phase 3 · 任务池认领机制 +- 范围先限定在"深度规划产出的子任务"这一层(不动 company 结构层的 org graph 固定拓扑)。 +- 新增"可认领子任务"的状态位与认领接口;调度器或各角色 agent 心跳时拉取匹配自己角色的可认领项。 +- 需要复用/扩展 27-P3C 已实现的队列与并行槽位机制,避免重新发明并发控制。 + +### Phase 4 · 子任务独立开发+验证循环 +- 每条被认领的子任务触发独立的开发→验证生命周期(复用 work-graph run 的既有机制,而不是新发明一套)。 +- 预算模型:子任务预算怎么从父 task 预算里切分、超支怎么处理,需要单独设计(可能复用现有 wallclock/token 预算机制的分配逻辑)。 +- 父子终态汇总规则沿用 43 号文档已实现的逻辑(全 success 才 success)。 + +### Phase 5 · 看板 UI +- 任务看板要能展示"可认领池"状态(谁在等认领、谁已认领在跑),不只是现有的父子树形/表格展示。 + +--- + +## Phase 0 实现记录(2026-09-01,真实回归已完成) + +用真实(非 mock)测试公司 `p0verify0901` 走完一次完整的 goal→task→org 执行链路。**测试公司数据未清理,留作证据**(`data/companies/p0verify0901/`)。 + +**结论**:链路能收敛,44 号文档的修复确认生效,但发现一个新的、独立的真实缺口——**`acceptance_mode='itemized'` 在当前 org-planner 子图形状下永远物化不出子任务,不是概率问题,是结构性打不通**。这解释了 46 号文档「现状调研」第 6 条记录的"生产库零命中"现象:不是运气差,是这条路径已经失效。 + +### 验证结果 + +| 检查项 | 结果 | +|---|---| +| task-planning 真实判定 | `itemized`(未诱导、未重试,`T-mtis98aq-8c7f`) | +| `goal-benchmark`/`planner-validator` 是否真正跑完(44 号文档修复验证) | ✅ 两者都 success,不再被误判 skip;`contract.json` 声明 `produces.acceptance` 指向共享 `artifacts/` 绝对路径,闸门正确识别 | +| `acceptance.md` 是否产出 | ❌ 全程未产出,四个 org-planner 节点私有目录搜遍为空 | +| 子任务是否物化(`parent_task_id` 指向该 task) | ❌ 0 条 | +| `coder-validator` 是否逐条上报子任务结果 | ❌ 未调用,走的是整体 holistic 判定(`SUBTASKS_INCOMPLETE` 闸门因为子任务本来就是 0 条而天然放行) | +| 父 task 终态 | `completed`(按 holistic 规则收敛,不是 43 号文档设计的"全子任务 completed 才 success"规则——因为没有子任务可供该规则生效) | +| 回退/报错 | 零回退(`backward_count` 全程 0),零 `fail_reason`,全程 18 分 55 秒 | + +### 根因 + +`org-planner` 名义上(`agent.md`/`tools.md` 文档)要交 `brd.md/prd.md/sdd.md/acceptance.md` 四份产物,但它是 `nodeKind='subgraph'`,真正执行靠 `workgraph-planning` 现场规划的子图。这次真实规划出的子图是 `org-planner-prd → org-planner-sdd → org-planner-benchmark → org-planner-validator`(没有 brd-planning,也没有任何节点职责是写 `acceptance.md`)——`org-planner-benchmark` 产出的是 `goal-benchmark.json`(R-19-B 契约的 `benchmark` 段,44 号文档那套新机制),跟"子任务物化"依赖的 `acceptance.md` 是两套不同产物,已经对不上。`graph-engine.js` 里 `maybeMaterializeItemizedSubtasks`(约 2372-2417 行)扫不到 `acceptance.md` 时会静默降级为 holistic(设计上是"软失败不拖垮硬产出",出发点合理),但代价是这条路径悄无声息地形同虚设,没人会发现。 + +`org-planner/agent.md`、`org-planner/tools.md` 里"四份产物含 acceptance.md"的文档描述本身可能已经过时,没跟上 `goal-benchmark.json`/R-19-B 契约这套后来的机制。 + +### 遗留决策:这个新发现的 bug 怎么处理 + +已拆分成独立文档处理,**已修复**:见 [47 号文档](../../done/system-design/47-itemized-subtask-materialization-goal-benchmark-fallback.md)(`acceptance.md` 缺失时物化逻辑改读 `goal-benchmark.json` 的 `dimensions[]`,2026-09-02,单测 5 条全过)。47 号文档末尾也记了一条需要 Phase 1 设计时想清楚的遗留点:这条兜底修的是**旧的(43 号文档设计的)物化机制**,Phase 1 打算把分解提前到执行开始之前,旧机制届时可能降级为安全网而不是主路径——不影响当下必要性,但 Phase 1 设计阶段要显式决定要不要保留它。 + +--- + +## Phase 1 详细设计(2026-09-02,设计已细化,未实现) + +### 命名 + +按 [27-naming-conventions.md](./27-naming-conventions.md) §二「规划类 agent —— `{产出物}-planning`」的既有惯例(`brd-planning`/`prd-planning`/`sdd-planning` 都按自己产出什么命名),新 agent 命名为 **`subtask-planning`**——它的产出物就是 `subtask-goal`(该规范 §一 作用域表里已经预留了这一级:"子任务的目标(一个 task-goal 分解出多个)"),命名轴完全对上,不用新造概念。 + +与 `task-planning` 的关系:`task-planning` 把 `company-goal` 拆成多条 `task`(= `task-goal`);`subtask-planning` 把一条 `task-goal` 拆成多条 `subtask-goal`——同一条命名轴上相邻的两级,职责边界清楚。 + +### 触发点:`enqueueTaskRun`(不改 task-scheduler 的排队逻辑) + +`server/routes/company.js:1099 enqueueTaskRun()` 是 dispatch 时真正创建 org run 并把 task 标记 `queued` 的地方,紧接着会调用 `writeTaskGoalArtifact()` 写下这条 task 的验收边界产物。**这是最合适的钩子位置**——原因: + +- **不碰 `task-scheduler.js`**:该文件的排队/占坑逻辑充满精细的并发不变式(本次设计阶段读代码时看到的注释密度就是证据),新增一道"等 subtask-planning 跑完才能出队"的阻塞闸门风险很高,且违反 33/44/47 号文档反复验证过的教训——**软数据不能当硬闸门**。 +- **异步、不阻塞**:`subtask-planning` 在 `enqueueTaskRun` 内用 `setImmediate` 触发(照抄 `runGoalSplit` 现成的异步模式),跟 org run 的排队/起跑并行,不等它。多数情况下 `subtask-planning` 只是一次单轮 LLM 调用,会比 org run 真正排到执行位、org-planner 开始跑之前完成;即使没赶上,也只是这一轮 org-planner 拿不到分解方案参考,不是失败。 + +### 产出物:写共享 `artifacts/` 区,不是私有目录 + +吸取 44/47 号文档的教训——产物必须落在**下游查得到的地方**,且格式要对已经证实稳定工作的机制友好。写入 `runRootDir(run)/artifacts/subtask-plan.json`,形状复用 47 号文档刚验证过的 `goal-benchmark.json` 风格(结构化、非 Markdown 表格,避免重蹈 `acceptance.md` 那种"文档说要写、实际没人写"的覆辙): + +```json +{ + "steps": [ + { "seq": 1, "title": "...", "why": "...", "depends_on_hint": null }, + { "seq": 2, "title": "...", "why": "...", "depends_on_hint": [1] } + ], + "reasoning_source": "model_only" +} +``` + +`reasoning_source` 字段是为 Phase 2 预留的("model_only" / "memory" / "deep_research"),Phase 1 恒为 `"model_only"`。 + +### 与 org-planner 的衔接点:软注入,不建硬依赖 + +`org-planner` 现在读的输入里已经有一套"P1 虚拟引用"注入模式(`company-context.md`,prompt-builder 直接拼进提示词,见 27-P1 文档)。`subtask-plan.json` 走同一种模式:prompt-builder 组装 org-planner 任务提示词时,检查这条 task 对应 run 的共享 `artifacts/subtask-plan.json` 是否存在,存在就拼进提示词一段"已有的分解参考(来自 subtask-planning,仅供参考,不是必须遵守的清单)",不存在就跟现在完全一样,什么都不加。**不改 org-planner 的硬约束、不要求它必须使用这份参考**——Phase 1 的验收标准只是"分解结果能被下游正确读到",不是"下游行为因此改变"(那是 Phase 1 完成后才评估的效果问题,不是这一步的实现范围)。 + +### 工具权限 + +比照 `task-planning` 的极简先例(该 agent 现在"大概率一个工具都不需要用"):v1 只给读权限(`bash`/`read_file`,无 `str_replace`),能读的输入是任务描述 + 公司 org graph 摘要(跟 task-planning 拿到的是同一批,首轮消息里给)。**明确不给**:创建/修改 task 的 API、创建/下达 run 的 API、招聘/改 org graph 的 API(原因与 task-planning 工具箱里那张"不该用的工具"表完全一致——落库是服务端的活,agent 只交结构化产出)。联网调研(Phase 2 才接)本阶段也不给。 + +### Phase 3 前必须先定案的边界(呼应「已知的高风险点」第一条) + +任务池认领机制的作用域明确限定为:**`subtask-planning` 产出的 `subtask-plan.json` 里的 `steps[]`,物化成 `tasks` 表里 `parent_task_id` 指向该 task、可被认领的行**。不影响: + +- company 级 org graph(`ceo`/`cto`/`org-planner`/`org-coder` 这套角色拓扑,27-P2 已实现,继续保持人工固定/慢变/可审计); +- `workgraph-planning` 现场规划出的 org node 内部 work graph(`brd-planning → prd-planning → sdd-planning → ...` 这类,继续保持固定 DAG)。 + +任务池只是在"task"和"当前这套固定的 org/work graph 执行"之间新插了一层——这层里的东西(subtask)才能被认领,图本身怎么跑不变。这条边界写在这里,是为了 Phase 3 设计时不用重新讨论一遍。 + +### Phase 1 验收标准(后续真实回归要检查的) + +1. dispatch 一条 task 后,`subtask-planning` 是否被异步触发、是否产出了 `subtask-plan.json`? +2. `org-planner` 的任务提示词里是否真的拼进了这段参考(不是"写了注入逻辑但没生效")? +3. `subtask-planning` 没跑完/失败时,org-planner 这一轮是否完全不受影响(确认软注入的降级路径成立)? + +--- + +## Phase 1 实现记录(2026-09-02,代码已实现,未做真实端到端回归) + +### 落地清单 + +| 内容 | 位置 | +|---|---| +| 新 agent 本体 `subtask-planning` | `system/agents/subtask-planning/{config.json,agent.md,identity.md,soul.md,tools.md}`,`config.type='meta-planning'`(复用 `task-planning` 同款分类,不进 org graph) | +| 规划服务(含 CLI 调用 + 宽松解析归一化) | `server/services/subtask-planning.js`——单轮调用、无重试、单条不合法数据丢弃不作废整批(与 task-planning 的严格写入前校验刻意不同,见 agent.md「你的产出是建议,不是指令」) | +| 触发点 | `enqueueTaskRun`(`server/routes/company.js`)新增 `withSubtaskPlanning` 参数,**默认 `false`**,只在两处显式传 `true`:`/goals/:goalId/dispatch`(批量下达)、`/tasks/:taskId/enqueue`(单条下达)。`POST /:companyId/tasks`(quick task,外部 agent 自主派发)与 `/rerun` 保持默认不触发——理由见下「设计比原计划收窄的一处」 | +| 产物 | `runRootDir(run)/artifacts/subtask-plan.json`(共享区,47 号文档同款教训:不写节点私有目录) | +| 与下游的衔接 | 登记进 `graph_files`(`kind='subtask_plan'`),纳入 `graph-engine.TASK_SHARED_KINDS`——复用 R-9.3 任务级共享机制自动出现在该 task 下**任意**节点的「上游输入」提示词段落,**没有另写 prompt-builder 注入逻辑**(比原设计「衔接点」一节设想的软注入方案更省事,见下) | +| 测试 | `subtask-planning.test.js`(12 条,纯函数解析/归一化)+ `subtask-planning-materialize.test.js`(4 条,共享区落地 + `describeInputs` 真的能读到)。全量单测 1558 passed,唯二失败与本次改动无关(改动前即存在) | + +### 与原设计的两处出入 + +1. **触发点从"`enqueueTaskRun` 内部无条件触发"收窄成"调用方显式 opt-in"**。原设计(本文档上一版)打算让 `enqueueTaskRun` 无条件触发,动手时撞上 `goal-task-acceptance.test.js` 的「验收 22」——它守着一条真实存在的产品保证:`POST /:companyId/tasks`(外部 agent curl 自主派发的快速任务,例子就是"把周报发出去"这种行政指令)必须**零 LLM 调用**,这是给 agent 间快速委派留的零成本通道。subtask-planning 无差别触发会直接击穿这条保证。改法:`enqueueTaskRun` 新增 `withSubtaskPlanning` 参数,默认 `false`,只在"goal 批量下达"和"单条 task 显式下达"这两个真正对应"人/系统郑重下达一条任务"的语义上打开。 + +2. **与 org-planner 的衔接从"prompt-builder 软注入"改成"复用 R-19.3 任务级共享索引"**。原设计打算专门给 org-planner 写一段"检查 subtask-plan.json 存在就拼进提示词"的注入逻辑。动手核实 `graph-engine.describeInputs` 的实现后发现:这个函数**在 subgraph 节点上直接跳过**(`org-planner` 本身是 subgraph),真正的消费者应该是它展开出的子图里那些 `agent_call` 节点(`org-planner-prd`/`org-planner-sdd`/…)——而这些节点已经有一套现成机制(`TASK_SHARED_KINDS`,任务级共享产物索引,`acceptance`/`contract` 已经在用)自动把"这个 task 下的共享产物"塞进**任意**节点的提示词,不受拓扑相邻性限制。把 `subtask_plan` 加进这个白名单,比另写一段注入逻辑更省代码、更少一个新的失败模式,而且覆盖面更广(org-coder 阶段的节点也能看到,不只 org-planner)。 + +### ⚠️ 一次真实的安全事故(写实话,别再犯) + +第一版实现(`enqueueTaskRun` 内部无条件触发,未做任何测试环境防护)在跑 `npx vitest run server/tests/unit` 时,**真实开出了 44 个 `claude` CLI 子进程**挂在后台(`ps aux` 现场抓到,手动 `kill` 清理干净,未产生数据损坏,但存在真实 API 调用成本)。根因:`task-planning`/`workgraph-planning` 那套"默认走真实实现,测试要显式注入桩才安全"的设计前提,建立在"只有专门测规划逻辑的测试才会碰到这条触发路径"之上;subtask-planning 的触发点(`enqueueTaskRun`)被全仓库大量跟规划毫不相关的单测(测排队、测预算、测状态流转)顺带路过,这些测试从没想过要为一个新加的旁支功能注入桩。 + +修法:`subtask-planning.js` 的 `planSubtasks()` 新增一道防护——`process.env.VITEST`(vitest 对所有测试进程自动置位,通用判据不是我们发明的)为真、且没有人显式调用过 `setPlanSubtasks()` 注入时,直接跳过真实调用,返回空结果(等价于 47 号文档反复用到的软失败路径)。这是 `subtask-planning.js` 独有的防护,`task-planning.js` 没有、也不需要——它的触发面窄得多,从未出现过这个问题。加完防护后,全量测试(1558 条,含 integration/devloop)跑三遍,`ps aux` 确认零残留子进程。 + +### 遗留:还没做真实端到端回归 + +设计文档「Phase 1 验收标准」一节列的三条(① dispatch 后 subtask-planning 真的被触发并产出 artifact;② org-planner 展开的子图节点提示词里真的能读到这段参考;③ subtask-planning 没跑完时 org-planner 完全不受影响)——**①③ 已经被集成测试间接覆盖**(用注入桩验证了机制本身是对的),**②的"提示词真的拼上了共享索引这一段"也已经用集成测试直接验证**(`describeInputs` 真实读到 `subtask_plan` 并列进提示词)。**没做的是**:用真实公司、真实 goal、真实 Claude CLI 走一遍全链路,确认 subtask-planning 在真实调用下真的产出有意义的分解(不是机制通不通,是内容有没有用)——考虑到刚踩过的真实 CLI 子进程事故,这次真跑验证需要更谨慎地控制范围(单个任务、有人盯着、跑完立刻检查有没有残留进程),不建议顺手跑,值得单独作为一步来做。 + +--- + +## Phase 2 实现记录(2026-09-02,收窄版,代码已实现,未做真实端到端回归) + +用户原话要求"把 Phase 2-5 一次性做完"。动手前先做了一轮代码调研,发现文档字面描述掩盖了真实工作量落差:dream(29-DREAM)只**单向写回**经验,没有任何可查询索引;"R-19.3 共享索引"是笔误(实指 `TASK_SHARED_KINDS`/R-9.3,跟经验库无关);org-hunt 只能作为图节点被 `workgraph-planning` 动态选中,没有图外可调用入口;Phase 3(任务池认领)是"改动面最大的一条",要给纯拓扑驱动的 `graph-engine.js` 并行引入认领制调度层;Phase 4 需要全新的子任务级 run + 预算模型;Phase 5 在 3/4 落地前没有真实"可认领池"状态可展示。跟用户对齐后,**这一轮只做 Phase 2(收窄版)**,Phase 3-5 涉及核心调度引擎范式级改动 + 全新预算模型,风险量级不同,另起设计讨论。 + +### 落地清单 + +| 内容 | 位置 | +|---|---| +| 经验检索(新模块) | `server/services/experience-lookup.js`——`queryExperience({companyId, task})`:查这家公司已雇佣 agent 的 `memory/experience.md`(dream 沉淀),关键词重叠打分(中文用双字滑窗近似分词,不引依赖)。命中为零是真实信号("查不到"),不是异常 | +| 按需 Deep Research(新模块) | `server/services/deep-research.js`——`triggerDeepResearch({companyId, task, topic})`:图外直接 `AgentManager.callAgent('org-hunt', ...)`,轻量提示词,不套用 org-hunt 训练场景的三件套契约。**默认关闭**(`ENABLE_DEEP_RESEARCH` 环境变量,不为 `'1'` 直接返回 `null`,连注入的假实现都不调用) | +| `subtask-planning.js` 两段式改造 | `defaultPlanSubtasks`:先查经验库拼进提示词 → 调一次 agent,agent 自评 `confidence` → 经验为空或 `confidence:"low"` 就触发调研 → 有结果就带着调研结果再调一次取代第一轮。`reasoning_source`(`model_only`/`memory`/`deep_research`)由服务端根据实际发生的分支算出,不是 agent 自报的。新增更细粒度注入点 `setSubtaskAgentCaller`(仿 `dream-service.setDreamAgentCaller`),能测到这段编排逻辑本身,不只是测"落盘"那一层 | +| `subtask-plan.json` 增补字段 | `reasoning_source`/`experience_refs`/`research_used`,纯增量、向后兼容旧的注入桩(缺失时兜底 `model_only`/`[]`/`false`) | +| `system/agents/subtask-planning/agent.md` | 输出契约加 `confidence` 字段(硬约束 9),补充"你可能会看到两种提示词"一节 | +| 测试 | `experience-lookup.test.js`(6 条)、`deep-research.test.js`(6 条,含"关闭开关时哪怕注入了假实现也不调用"这条最重要的安全阀)、`subtask-planning.test.js` 扩展(`extractConfidence` + `buildSubtaskPlanningPrompt` 的经验/调研小节,7 条新增)、`subtask-planning-materialize.test.js` 扩展(新增字段落盘 2 条 + `defaultPlanSubtasks` 三条分支编排 4 条)。全量单测 1177 条,1175 passed,唯二失败(`.env.example` 覆盖 + 测试 id 稳定性)与本次改动无关、改动前即存在。测试全程 `ps aux` 确认零残留 CLI 子进程 | + +### 与原设计的出入 + +1. **"历史同类 goal 执行记录"这条来源本轮不做**——只做 agent 经验区块检索。原设计的用户原话把"经验库/memory/dream 沉淀"和"历史同类 goal 检索"并列成一条来源,但后者需要语义检索基础设施(当前没有),贸然上关键词匹配对"同类"这种判断噪音会很大。这是范围收窄,不是遗忘,留给以后需要时再评估怎么做语义检索。 +2. **Deep research 复用 org-hunt,但用轻量提示词,不套用它的训练场景三件套契约**(`knowhow_raw.md`/`crawl_config.json`/`seed_data.json` ≥50 条)。原因:那套契约是为 FDE 训练场景校准的,直接套用在"人在等一条 task 入队"这种时间敏感场景会不可控地慢。org-hunt 本身的图节点契约、`deliverable.json` 模板都没动。 +3. **Deep research 触发默认关闭**(`ENABLE_DEEP_RESEARCH` 环境变量)。org-hunt 的延迟/成功率在"日常任务规划、时间敏感"场景下从未验证过,且 Phase 1 已经因为一个新触发点疏于隔离出过真事故。这跟 Phase 1 `withSubtaskPlanning` 默认 `false` 是同一个谨慎级别——机制已实现且单测覆盖,但真实调用需要用户手动开关、挑单个任务验证。 + +### 真实端到端回归(2026-09-02,已完成) + +用真实测试公司 `p2verify0902`(真实 org graph、真实 goal→task→dispatch)+ `ENABLE_DEEP_RESEARCH=1` 跑了两条真实任务,全程 `ps aux` 确认零残留 CLI 子进程。 + +**发现一个真实缺陷(已修复)**:第一条真实任务(贪吃蛇小游戏)跑完,`reasoning_source` 落成 `"memory"`,但 `experience_refs` 列出的命中全是 `ceo`/`orggraph-planning` 关于"校验失败怎么兜底路由""修复要重新触发验证"这类**跟游戏开发毫无关系**的工程流程经验——查了实际打分才发现根因:原始实现"关键词交集计数、只要 >0 就算命中",一条 100+ 词的任务描述几乎必然跟经验库里的高频工程词汇("验证""检测""流程")偶然重合 1-3 个 bigram,噪音就压过了信号,**直接吃掉了本该触发的调研分支**——这正是 Phase 2 acceptance 标准要求确认的"分支判断正确",第一次真跑就没通过。 + +修法:打分改成"这条经验自己的词里有多少比例被 task 提到"(对经验块词数归一化的 containment ratio,不是对查询词数),阈值(命中 ≥2 词且比例 ≥12%)拿真实噪音案例(约 3%-4%)和合成的真正相关案例(约 16%-32%)校准。补了一条回归测试固定复现这个真事故场景。修完重启服务、用第二条真实任务(Markdown 转 HTML 工具)重跑:`reasoning_source` 正确变成 `"deep_research"`、`experience_refs` 正确为空、`research_used: true`——分支判断修复生效,且日志确认真的走了两轮调用(subtask-planning → org-hunt → subtask-planning)。 + +**两条真实任务的分解内容质量都相当好**:贪吃蛇任务给出"先定义状态 → 渲染循环 → 输入监听 → 移动逻辑 → 吃食物 → 碰撞检测"六步,依赖关系合理;Markdown 工具任务甚至体现出"代码块围栏必须最先扫描保护,否则块内的 `#`/`**`/`[]` 会被后续正则误转换"这种真实的解析器实现顺序知识。这直接回答了 Phase 1/2 一直悬而未决的"内容有没有用"——回答是有用。 + +**一个诚实的局限,没有掩盖**:查了 org-hunt 那次调用的日志,全程没有 `l1-web-fetch` 工具调用痕迹——它大概率是直接用自己训练知识回答的,没有真的去查外部资料。轻量提示词只要求"给一段总结",没有强制要求引用可验证来源、没有强制要求真的调用检索工具。这意味着当前的 `"deep_research"` 分支实际提供的价值更接近"多问一个角色的第二意见",不是名副其实的外部调研——如果以后要坐实"真的查了外部资料"这件事,需要在提示词里明确要求工具调用证据(比如强制要求 sources 是真实可验证 URL),这次没有额外去做,留给以后需要更强调研可信度时再补。 + +--- + +## Phase 3 详细设计(v1,2026-09-02,⚠️ 已废弃——见下方「Phase 3 v2」) + +> **这一版设计和它对应的实现记录整段废弃,但原样保留在下面**(不删):真实回归发现"认领后没有触发源"这个真问题之后,跟用户讨论出的根本原因是——"认领"(pull,CAS 抢占)解决的是"多方竞争同一条任务",但实际场景里从头到尾只有 CEO 一个人拍板要不要派、派给谁,压根不存在竞争,CAS 是在给不存在的问题找解。v2(本文档更后面)改成 CEO 直接派发(push),砍掉了 `claim`/`unclaim` 两个端点和 `claimable`/`claimed` 两个状态。留着 v1 不删是因为这段"先做认领、跑真实回归、发现问题、推翻重来"的过程本身有价值——**它是这次为什么改主意的第一手证据**,删掉只留结论会让人以为 v2 是一次性设计对的。 + +### 前置决策(跟用户对齐) + +Phase 3 最关键的架构分叉——可认领子任务要不要复用现有 `tasks` 表的 `parent_task_id` 机制——**已跟用户对齐:复用 `tasks` 表,加字段区分类型**。原因见下节,这条决定直接决定了后面所有设计。 + +### 数据模型:新增 `subtask_kind` 字段 + 两个新 `status` 值 + +`tasks` 表新增列 `subtask_kind`(`NULL` | `'acceptance_item'` | `'claimable_work'`),CHECK 约束仿 `acceptance_mode` 那次迁移的写法(batch 迁移): + +- `NULL`:普通 task(现状不变,绝大多数行)。 +- `'acceptance_item'`:43 号文档现有机制——`maybeMaterializeItemizedSubtasks` 从 `acceptance.md`/`goal-benchmark.json` 物化出的验收条目,**行为完全不变**,只是把它们打上一个明确的类型标签(之前隐含在"parent_task_id 非空"里,现在显式区分)。 +- `'claimable_work'`:Phase 3 新增——`subtask-planning` 产出的 `subtask-plan.json` 里 `steps[]` 物化出的可认领工作项。 + +两者都用 `parent_task_id` 指向同一条父 task,语义/生命周期完全独立,靠 `subtask_kind` 分流——这是为什么不需要开新表:现有 `tasks` 表本来就是"通用工作项容器",`subtask_kind` 只是给容器里已经混杂的两种东西正名,不是往一个干净的模型里硬塞第二套语义。 + +`status` 枚举新增两个值:`'claimable'`(已物化、开放认领、还没人要)、`'claimed'`(已被认领,等待被处理——处理方式见下"认领后怎么办"一节)。只对 `subtask_kind='claimable_work'` 的行有意义,其余行继续用现有 `draft|queued|running|completed|failed|blocked|cancelled` 六态,不受影响。新增 `claimed_at` 时间戳列(认领发生的时间,用于超时/僵尸认领巡检——复用心跳巡检已经在用的"时间戳 + 巡检" 模式,不新造一套监控)。 + +### 必须同步改的一处:`settleNode` 的 `SUBTASKS_INCOMPLETE` 硬闸门要收窄 + +`graph-engine.js:1881-1889`(43 号文档实现)现在是: + +```js +// coder-validator 报 success 时,查 parent_task_id 指向该 task 的所有行是否 completed +SELECT id, status FROM tasks WHERE parent_task_id = ? +``` + +Phase 3 一旦开始物化 `claimable_work` 行,这条查询会立刻把它们也扫进去——一堆天然不会是 `completed`(它们的终态是 Phase 4 才要定义的东西)的 `claimable_work` 行会让这条闸门对**所有**开了 `subtask-planning` 的 task 都误判 `SUBTASKS_INCOMPLETE`,把本该正常通过的验收打成失败。**这是实现时第一件必须做的事,不是可选项**:查询加一条 `AND subtask_kind = 'acceptance_item'`,把两套子任务的完成判定彻底分开。 + +### 触发/物化时机:跟 `subtask-planning` 同一时刻,不等 org-planner + +43 号文档的验收条目物化时机是"org-planner 完成后"(依赖它的产出)。Phase 3 的物化对象不一样——`subtask-plan.json` 在 `enqueueTaskRun` 时就异步产出了(`triggerSubtaskPlanning`),比 org-planner 开始规划早得多。**物化时机定在 `triggerSubtaskPlanning` 成功写完 `subtask-plan.json` 之后立刻做**,不拖到 org-planner 跑完: + +- 呼应用户原始诉求"分解任务 → 所有 agents 围绕这个任务看板来工作,一层一层地接子任务"——如果拖到 org-planner 整个流程跑完才有"看板","围绕看板协作"这句话在 org-planner 执行期间根本无从谈起。 +- 技术上更省事:`triggerSubtaskPlanning` 已经是 best-effort、绝不 throw 的收尾逻辑,加一步物化不引入新的失败模式(复用同一套"软失败,只打日志"的处理)。 + +具体实现:`steps[].depends_on_hint` 是这批 steps 内部的 1-based `seq` 引用,物化时先建好全部行拿到真实 `task.id`,再回填 `depends_on`(复用 `tasks.depends_on` 现有的"JSON 数组存 task id"格式,不新造依赖表示法)——`depends_on_hint` 本身"是提示不是硬依赖"这条设计(Phase 1 agent.md 就这么写的)延续下来:Phase 3 物化后的 `depends_on` 同样只做展示,**不做认领前置校验**(见下"明确不做")。 + +### 认领 API:这是本轮唯一真正的新机制 + +调研确认:**全仓库目前没有任何"认领/接单(pull)"语义的端点**——`executor='agent'` 那条路径是纯粹的"派发(push)":发送方在 `/task/dispatch` 请求体里显式指定 `to`,从没有过"多方竞争同一条任务,谁先抢到算谁的"这种场景,所以也没有任何 CAS 并发保护先例可抄。这是 Phase 3 唯一必须新写的东西: + +``` +POST /api/company/:companyId/tasks/:taskId/claim {agentId} +``` + +服务端逻辑(仿 `claimQueuedTask` 的 CAS 写法,`work-graph-db.js:2209`): + +```sql +UPDATE tasks SET status='claimed', assignee_agent_id=?, claimed_at=? +WHERE id=? AND subtask_kind='claimable_work' AND status='claimable' +``` + +`changes === 0` 就是"没抢到"(要么已被别人认领,要么状态不对),返回 409,不是 500——这是预期会发生的正常竞争结果,不是错误。对称补一个 `POST .../unclaim`(`status='claimed' → 'claimable'`,`assignee_agent_id` 清空)——认领了发现做不了要能退回去,成本很低,没理由不做。 + +**认领角色范围**:不引入能力匹配(调研确认 `system_agents`/`company_agents` 表没有任何结构化角色能力字段,能不能干一件事完全靠 agent 自己读 `agent.md` 判断,代码层不做匹配)。Phase 3 用最简单的白名单:`claimable_work` 行默认只对 `role_name='org-coder'` 开放认领(当前系统唯一的通用"写代码"角色,且物化出的 steps 基本都是实现类工作),认领 API 校验调用者的 `role_name` 是否在白名单里,不在直接拒绝。这个白名单先写死,不是留数据库字段——真需要扩展到多角色再加,不为假想的未来预先建模。 + +### 认领后怎么办:复用现成的 agent 派单路径,不新起执行机制 + +这是这轮设计里最重要的一处化简,值得单独说清楚:最初以为"认领后要真正驱动执行"必然要碰 `graph-engine.js`(比如给每个 `claimed` 的子任务起一次独立 work-graph run),那就是 Phase 4 的范畴,而且是范式级改动。但调研 `executor='agent'` 那条路径后发现——**它本来就是"一条 task 归某个 agent 负责、这个 agent 自己想办法搞定、搞定后自己调 `/task/report` 上报"的完整闭环**,跟"认领了之后谁去干"这个问题的答案高度重合。 + +所以 Phase 3 的落地方案是:`claim` 成功后,除了状态转成 `claimed`,**同时把这条行的 `executor` 设成 `'agent'`**——这样它立刻变成一条普通的"agent 自主任务",认领它的 agent(多半是 org-coder)用它已经在用的既有机制(自己决定怎么干、干完调 `/task/report`)走完剩下的生命周期,`META_TO_TASK_STATUS` 状态机(`agent-chat.js:1795`)直接接管后续的 `dispatched → done/failed/blocked/cancelled` 转移,**不需要 Phase 3 再发明一套新的执行触发逻辑**。`task-scheduler.js` 明确只认 `executor='graph'`(367 行注释已经写了这条判据),所以这条行天然不会被现有的图排队/并发逻辑碰到,两套系统互不干扰。 + +这个方案的好处:Phase 3 做完,"认领"不是一个没人会因此真正干活的静态标记,是一个立刻能跑的真实闭环,而且**零新增执行机制、不碰 graph-engine 一行代码**,完全落在 Phase 1 已经定案的边界内("图本身怎么跑不变")。代价是诚实的:`org-coder` 认领到的这一步"怎么干"完全靠它自己(可能是直接写代码、可能是走它熟悉的其他工具),没有 Phase 4 设想的那种"独立开发 + 独立验证"的正式生命周期(写代码有没有经过验证、有没有走测试,全凭 org-coder 自己负责)——这正是 Phase 4 真正要解决的问题:现在这条最小可用路径能跑,但"跑得糙",要不要、以及怎么给它补上正式的验证循环,留给 Phase 4 设计时决定。 + +### 明确不做(本轮范围边界) + +- **不做依赖顺序强制**:`depends_on` 只展示,不阻止认领——判断"前一步是否真的做完了"需要一个可靠的完成判据,Phase 3 阶段(`executor='agent'` 自主执行)没有这个判据,强行卡会卡出一堆认领不了的死锁。 +- **不做能力匹配**:认领角色白名单写死 `['org-coder']`,不引入任何"这一步该给谁"的推断逻辑。 +- **不碰 `graph-engine.js` 执行/调度模型**:认领这件事完全在 `tasks` 表 + 一个新端点里完成,不新增图节点类型、不改拓扑推进逻辑。 +- **不做 Phase 4 设想的"独立开发+验证循环"**:认领后的执行完全复用现有 `executor='agent'` 闭环,`org-coder` 自己对交付质量负责,没有独立验证阶段。 +- **不做看板 UI(Phase 5)**:可认领池先靠 `GET /:companyId/tasks?subtaskKind=claimable_work&status=claimable` 这类查询参数(复用现有 `parentTaskId` 过滤模式加一个新参数)暴露数据,UI 展示留给 Phase 5。 + +### Phase 3 验收标准(后续实现完要检查的) + +1. `subtask-plan.json` 产出后,`claimable_work` 子任务是否正确物化(`depends_on` 回填正确、`status='claimable'`)? +2. 有 `subtask-planning` 挂钩的 task,`coder-validator` 的验收判定是否完全不受这些新增 `claimable_work` 行影响(`SUBTASKS_INCOMPLETE` 闸门收窄生效)? +3. 并发认领同一条子任务,是否只有一方成功(CAS 生效,409 不是 500)? +4. 认领成功后,这条行是否真的能走完 `executor='agent'` 的既有生命周期(`org-coder` 自己完成、`/task/report` 上报、状态正确落到终态)? +5. 非白名单角色尝试认领,是否被正确拒绝? + +## Phase 3 实现记录(v1,2026-09-02,⚠️ 代码已被 v2 替换/删除——见「Phase 3 v2」) + +> 本节记录的代码(`claimSubtask`/`unclaimSubtask`/`/claim`/`/unclaim`/`claimable`/`claimed`)**已经不在仓库里**。保留这份记录是因为下面的"真实端到端回归"小节记录了促成 v2 拍板的两个关键发现(认领没有触发源、`/task/report` 耦合了非 graph 回调),这两个发现本身仍然成立、仍然是 v2 存在的理由,不能删。 + +### 落地清单 + +| 内容 | 位置 | +|---|---| +| schema:`subtask_kind` + `claimed_at` | `work-graph-db.js`(批 6 迁移,`ALTER TABLE ADD COLUMN`,纯增量向后兼容;`subtask_kind` 取值域 `'acceptance_item'|'claimable_work'`,只有新建库有 CHECK,存量库应用层守,跟 `acceptance_mode` 那次迁移同一套限制) | +| `createTask` 校验 | 新增 `subtaskKind`/`status` 参数:`subtaskKind` 非法值拒、没有 `parentTaskId` 拒;`status` 只放开 `'draft'`/`'claimable'` 两个初始值,`'claimable'` 只能配 `subtaskKind='claimable_work'` | +| 认领 CAS | `claimSubtask`/`unclaimSubtask`(`work-graph-db.js`)——全仓库第一个"认领(pull)"语义写入,照抄 `claimQueuedTask` 的 `changes` 判据。认领成功同时把 `executor` 改成 `'agent'`,复用既有 `executor='agent'` 派单闭环(`/task/report` 上报),**不碰 `graph-engine.js` 一行调度逻辑**,跟设计阶段的判断一致 | +| 物化 | `materializeClaimableSubtasks`(`work-graph-db.js`)——两阶段建法处理 `depends_on_hint` 前向引用,幂等(按 `subtask_kind='claimable_work'` 计数),从 `subtask-planning.triggerSubtaskPlanning` 写完 artifact 后立刻调用,不等 org-planner | +| **必须同步改的坑,已修** | `graph-engine.js` 的 `settleNode` `SUBTASKS_INCOMPLETE` 闸门查询收窄成 `AND subtask_kind = 'acceptance_item'`;`maybeMaterializeItemizedSubtasks` 的幂等判据和 `createTask` 调用同步打上 `subtask_kind='acceptance_item'`。两处都补了回归测试(见下) | +| 路由 | `POST /:companyId/tasks/:taskId/claim`、`.../unclaim`;`taskView` 补 `subtaskKind`/`claimedAt`;`GET /:companyId/tasks` 加 `subtaskKind` 过滤(可认领池查询=`?subtaskKind=claimable_work&status=claimable`,不用专门开池子端点) | +| 角色白名单 | 写死 `CLAIMABLE_WORK_ROLES = ['org-coder']`(`company.js`),不做能力匹配 | +| 测试 | 新增 `task-pool-claim.test.js`(23 条:DB 层 `createTask` 校验/`materializeClaimableSubtasks`/CAS + 路由层白名单/并发/放弃);`task-decomposition-validator-gate.test.js` 补 1 条回归测试(`claimable_work` 子任务不影响 `acceptance_item` 验收判定)+ 更新既有用例显式打 `subtaskKind`(闸门收窄后这些用例如果不补 `subtaskKind: 'acceptance_item'` 会全部失效——查询看不到没打标的行,等于闸门形同虚设,真被这次改动摸到了);`task-decomposition-materialize.test.js` 补 `subtask_kind` 落库断言。全量单测 1202 条,1200 passed,唯二失败与本次改动无关、改动前即存在 | + +### 一次诚实的记录:本地库被顺手迁移了一次 + +排查一处语法错误(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 一下"就放松。 + +### 真实端到端回归(2026-09-02,已完成,发现两个真实问题) + +用真实公司 `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 子进程(会话池里保留的常驻进程随服务器关闭一起清零,不是泄漏)。 + +**发现一:认领之后没有任何机制通知 org-coder。** `claimSubtask` 纯粹是一次数据库状态变更——认领 / 放弃 / 重新认领全程,服务端日志零 agent 活动。也就是说,除非有人或有别的机制手动调用 `/claim`,这些可认领子任务会永远原地躺着,**没有任何东西会主动去发现它们、认领它们**。这是设计阶段"认领后怎么办"一节已经承认的缺口("跑得糙"),真实回归把它坐实成了一个具体、可复现的观察:机制本身工作正常,但**没有触发源**。 + +**发现二:`/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 会话"。 + +**`/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 完全平行的第二套执行世界。 + +### 遗留给 Phase 4 的问题 + +- 现在"认领后怎么干"完全靠 `org-coder` 自主决定,质量没有保障——要不要给 `claimed` 状态补一段正式的"开发 + 验证"生命周期?如果要,是新起一次简化版 work-graph run,还是在现有 `executor='agent'` 闭环里加一道可选的验证钩子(成本更低,但仍然不是"独立 run")?两个方向哪个都不是这次 Phase 3 该决定的事,需要单独评估。 +- 子任务级预算:`executor='agent'` 现有闭环目前完全没有预算概念(`budget_json` 只在 `executor='graph'` 的 run 上有意义)——如果 Phase 4 决定要给认领的子任务设预算,需要先补这个空白。 + +--- + +## Phase 3 v2:CEO 直接派发(2026-09-02,代码已实现,未做真实端到端回归) + +### 为什么推翻 v1(讨论过程,不是事后诸葛亮) + +v1 真实回归跑完,用户追问"认领后没有触发源,谁来触发"——讨论过程: + +1. 用户最初设想:每个候选角色(`org-coder`)自己开心跳,扫描"有没有可认领的、匹配我角色的子任务",够条件自己 `/claim`。 +2. 反对意见(用户自己提出):心跳生命周期管理的开销是真实问题——几百个 agent 都开心跳,谁来决定什么时候启、什么时候停?如果还是需要 CEO 来管这些心跳的启停,那跟 CEO 直接扫一遍指派没有区别,等于多绕了一层。 +3. 拍板:**CEO 心跳统一发现 + 直接指派(push),不需要认领机制**。CEO 本来就有心跳、本来就在盯 task 板,成本最低;CEO 还能顺带控制派发节奏(先放哪批、等哪批)。 +4. 顺着这条拍板往下查:现有 `enqueueTaskRun`(起 org/work graph run 的唯一入口)默认假设自己服务顶层 task(硬要求 `status==='draft'`,起 run 时把 status 强改成 `'queued'`)——这个状态转移**跟 v1 的 `claimable`/`claimed` 状态机直接冲突**(CAS 会静默失败)。查证过程中发现:v1 设计"认领"要解决的"多方竞争"场景,在"CEO 一人拍板"的模型下根本不存在——CAS 是在给不存在的问题找解,不是保留价值、只是不再需要的复杂度。**拍板删除 `claim`/`unclaim` 和相关状态**,改成"物化即普通 draft task,CEO 判断节奏后调现成的 `/enqueue`"。 +5. 顺带定了执行载体:"先跑一个轻量的子任务图看效果,遇到质量问题再升级成完整 org graph"——不是先验地假设哪个对,是留一个可以真实观察、按需升级的路径。 + +### 落地清单 + +| 内容 | 位置 | +|---|---| +| 删除的代码 | `claimSubtask`/`unclaimSubtask`(`work-graph-db.js`)、`POST /claim`/`POST /unclaim`(`company.js`)、`CLAIMABLE_WORK_ROLES` 白名单、`claimable`/`claimed` 两个 status 值、`claimed_at` 列(连同批 6 迁移里加它的那部分) | +| 改名 | `subtask_kind` 的枚举值 `'claimable_work'` → `'decomposed_work'`(连同 `materializeClaimableSubtasks` → `materializeDecomposedSubtasks`)——"认领"这个词不再准确,趁还没发布改掉比以后改省事 | +| 物化行为简化 | `materializeDecomposedSubtasks` 物化出的行现在就是普通 `status='draft'` task(不再有专属初始状态),`executionMode='auto'`(CEO 一旦 enqueue 就该一路跑完,不停在入口等人工 `/next`) | +| **新增:递归防护** | `triggerSubtaskPlanning`(`subtask-planning.js`)新增 `if (task.parent_task_id) return`——这是设计 v2 时才发现的真实漏洞:CEO 对 `decomposed_work` 子任务调 `/enqueue` 时,该端点默认 `withSubtaskPlanning:true`,不挡的话会对一条已经是"第几步怎么做"粒度的子任务再拆一层,浪费调用还可能无限递归。已用测试验证挡住 | +| **新增:轻量子任务图** | `createDecomposedWorkGraph`(`work-graph-db.js`)——现造一张两节点 work graph(`impl`=`org-coder`、`validate`=`org-validator`,都是 `agent_call` 不是 `subgraph`),跳过公司完整 org graph 的 ceo→cto→org-planner→org-coder→org-validator 规划仪式。理由:`subtask-planning` 已经把"怎么做"拆到这一步的粒度了,再走一遍 org-planner 的 brd/prd/sdd 纯属重复劳动 | +| `enqueueTaskRun` 图选择 | `company.js`:`taskRow.subtask_kind === 'decomposed_work'` 时用 `graphKind:'work'` + 现造的图;否则行为完全不变(`graphKind:'org'` + `companyId`),回归测试覆盖 | +| CEO 心跳/工具 | `system/agents/ceo/heartbeat.md`、`tools.md` 补了"查 `?subtaskKind=decomposed_work&status=draft`、按节奏 `/enqueue`"的指令——`enqueue` 是 CEO 已经被授权自主调用的现成端点,子任务用同一条权限,不用新开一条 | +| 测试 | 删除 `task-pool-claim.test.js`(v1 的认领测试,随代码一起废弃),新增 `task-decomposition-dispatch.test.js`(12 条:DB 层 `createTask` 校验/`materializeDecomposedSubtasks`/`createDecomposedWorkGraph`,路由层 `enqueueTaskRun` 图选择回归 + decomposed_work 分支),更新 `task-decomposition-validator-gate.test.js`/`task-decomposition-materialize.test.js` 的措辞。全量单测 1191 条,1189 passed(唯二失败与本次改动无关、改动前即存在),零残留 CLI 子进程 | + +### 一处没有真实验证过的假设,如实写在这里 + +`org-coder`/`org-validator` 平时**只在"公司完整 org graph 的 subgraph 节点"这个上下文里被调用过**——它们的 `agent.md`/`tools.md` 有没有隐含"前面一定有 brd/prd/sdd 产物"这种假设,直接当轻量图里的裸 `agent_call` 节点调用,会不会读不到预期上下文、表现异常——**没有实测过**。图的机制本身(建图、起 run、CAS 判据不冲突)单测覆盖了,但"跑起来输出好不好"是内容问题,机制通不通答不了这个问题。这正是用户拍板"先跑轻量版看效果"这句话里"看效果"的意思——下一步真实回归要验的就是这个。 + +### 真实端到端回归(2026-09-02,已完成,机制全部验证通过,效果发现一个真实结构性缺口) + +用真实公司 `p2verify0902` 走完一次真实链路——真实 goal→task→dispatch → `subtask-planning` 产出 2 步分解并物化成 `decomposed_work` 草稿 → **真实 CEO 心跳**(不是模拟)读到 `heartbeat.md` 新加的指令后,真的查了 `?subtaskKind=decomposed_work&status=draft`、真的判断了节奏、真的调了 `/enqueue`——不只是这两条,CEO 还顺带自己发现并处理了一条与本次测试无关的历史遗留卡住的 run(证明心跳修复后其余巡检逻辑也正常了)。触发过程中确认 `triggerSubtaskPlanning` 的递归防护在真实场景下正确拦下了对子任务本身再拆一层的尝试(8 条 `decomposed_work` 行全部被正确跳过)。 + +**一处真实的额外发现(与 Phase 3 无关,但直接堵住了这次验证,顺手修了)**:触发 CEO 心跳时先撞上 401——`company-heartbeat-service.js`/`heartbeat-service.js` 里所有自环调用 `/api/agent/:agentId/chat`(`agent-chat.js` 挂了 `requireAuthOrInternal`)**都没有带内部令牌**,而 `company.js`/`task-settle.js` 的同类自环调用早就在带。也就是说**心跳系统的两条自触发路径(含 CEO 心跳)一直在暗中 401**,失败只落一行 `console.warn`,界面上什么都看不出来。已按现成模式补上 `INTERNAL_TOKEN_HEADER`(两个文件),修完确认心跳能正常跑完整段推理会话。这条影响面比 Phase 3 大得多,值得单独跟进核实生产环境是否也受影响。 + +**"看效果"的真实结果——机制通,内容质量分裂**: + +- `org-coder`(impl 节点)**表现完全达标**:给单位换算任务真实产出了 `src/convert.js`(正确的换算因子:km=1000/m=1/mi=1609.344/ft=0.3048)+ 一份质量相当好的测试文件(含同单位恒等、非法单位报错、往返精度、浮点容差等边界情况),**完全没有 brd/prd/sdd 上游产物,产出依然靠谱**——这条验证了"轻量图跳过 org-planner 规划仪式"这个核心假设成立。 +- `org-validator`(validate 节点)**卡死,`input_underspecified`**:它调用的 `goal-contract.js validate --run-dir` 硬性要求 `artifacts/acceptance.json` 存在,而这份文件只由 org-planner 的 work graph(`goal-benchmark`/`goal-test-data`/`goal-test-script` 三个节点)产出——轻量图完全跳过了 org-planner,这份文件天然不存在。**org-validator 自己给出的诊断相当精确**:明确写"本次上游 coder 节点已产出 src/convert.js + test/convert.test.js,属正常交付,不是本次失败的责任方",把根因准确指向 org-planner 缺位,`target_org_node` 字段也写对了。 +- 依赖阻塞也验证了:第二个子任务(依赖第一个)在第一个卡在 `blocked`(不是 `completed`)后,正确地没有被调度器放行,一直停在 `queued`——`task-scheduler` 的既有依赖推导对 `decomposed_work` 子任务同样生效,不需要 CEO 自己算顺序,讨论时的判断成立。 + +### 结论:轻量图的"实现"这一半跑通了,"验证"这一半有一个真实的结构性缺口 + +不是 agent 表现不好,是图的构成本身缺一块——`org-validator` 依赖的验收契约只能来自 org-planner,轻量图里没有任何节点能产出它。**这不是"再调一下提示词"能解决的**,是"要不要跳过 org-planner"这条设计决策本身的直接代价。摆几个方向留给下一步讨论: +1. 轻量图加一个第三节点,专门产出一份最小的 `acceptance.json`(不跑 org-planner 整套 brd/prd/sdd,只把 `subtask-planning` 已经写好的"这一步要做什么"转成 goal-contract 认的验收格式); +2. `org-validator` 之外找一个不依赖 `goal-contract.js`/`acceptance.json` 的轻量验证方式(比如直接跑 `org-coder` 自己写的测试文件、看退出码); +3. 按用户原话的退路——这条子任务级别就不追求跑通 `org-validator`,直接升级成完整 org graph(这就是当初讨论"先跑轻量版看效果,不行再走完整 org graph"里"不行"的具体触发条件)。 + +**后续讨论收敛到方向 1 的一个更彻底的版本**:不是"轻量图加一个专门产契约的节点",而是"每个 task/subtask 创建时就必须 1:1 带一份验收契约"——把约束点从"某张图怎么搭"提到"task 这个实体本身"。完整讨论过程、方案取舍、以及需要改动的 agent/代码盘点见 [48 号文档](./48-task-acceptance-contract-binding.md)。 diff --git a/docs/todo/system-design/46-mandatory-planning-and-task-decomposition.md b/docs/todo/system-design/46-mandatory-planning-and-task-decomposition.md deleted file mode 100644 index 419c44e..0000000 --- a/docs/todo/system-design/46-mandatory-planning-and-task-decomposition.md +++ /dev/null @@ -1,490 +0,0 @@ -# 46 · Planning 应作为每个任务的默认前置步骤,覆盖三类信息来源 - -> **状态**: 📝 设计中——四个关键决策已拍板。**Phase 0 已完成**(2026-09-01 真实回归验证):链路能收敛、44 号文档修复确认生效,发现的独立缺陷已拆到 [47 号文档](../../done/system-design/47-itemized-subtask-materialization-goal-benchmark-fallback.md) 并修复。**Phase 1 代码已实现**(2026-09-02,见「Phase 1 实现记录」):新 agent `subtask-planning` + `enqueueTaskRun` 挂钩(收窄为显式 opt-in)+ 复用 R-9.3 任务级共享索引对接下游,单测覆盖,**但还没做真实端到端回归**(且过程中出过一次真实 CLI 子进程泄漏事故,已修复并加了测试环境防护,见该节)。**Phase 2 已实现且已做真实端到端回归(收窄版,2026-09-02,见「Phase 2 实现记录」)**:经验检索(新查询能力,扫已雇 agent 的 dream 沉淀)+ 按需 deep research(图外调用 org-hunt,默认关闭)。真实回归第一次就跑出一个真缺陷(关键词打分噪音压过信号,导致"经验库有答案"分支误触发)并已修复验证;两条真实任务的分解内容质量都好,但 deep research 分支目前更接近"多问一个角色的第二意见"而非真的做了外部检索(org-hunt 那次调用没有工具调用痕迹),如实记在实现记录里。"历史同类 goal 检索"这条来源本轮明确收窄未做。**Phase 3 走过一轮推翻重来(2026-09-02)**:v1("认领"pull 语义 + CAS)已实现并做过真实回归,但回归本身就暴露出"认领后没有触发源"的真问题,讨论后发现"认领"要解决的"多方竞争"场景根本不存在(只有 CEO 一人拍板要不要派)——**v2 改成 CEO 心跳直接发现+派发(push),删掉了 v1 的整套认领机制**,改为 CEO 判断节奏后调现成的 `/enqueue`,子任务默认走一张现造的轻量 work graph(`org-coder` 实现 + `org-validator` 验证两个节点,跳过公司完整 org graph 的规划仪式),效果不理想再升级成完整 org graph(见「Phase 3 v2」)。v1 设计和实现记录原样保留、标了废弃(留作决策过程的第一手记录,不是白做)。**v2 已做真实端到端回归**:真实 CEO 心跳确实发现并派发了子任务(顺手还修了一个真实的、影响面更大的 bug——两套心跳系统的自环调用一直缺内部令牌、暗中 401),递归防护、依赖阻塞都验证生效;`org-coder` 在没有 brd/prd/sdd 的轻量图里产出质量真实达标,但 `org-validator` 卡在硬依赖的 `acceptance.json`(只有 org-planner 会产出)上——**轻量图的执行没问题,验证这一半有真实的结构性缺口**,需要另外决定怎么补(见「Phase 3 v2」结尾三个方向)。Phase 4(独立开发验证循环)、Phase 5(看板 UI)仍未设计,留给后续 -> **日期**: 2026-09-01 -> **触发场景**: 用户复盘 [43 号文档](../../done/system-design/43-task-decomposition-granularity.md)(goal 分解粒度)时,追问"为什么本地和线上所有公司的任务看板都没有子任务分解"。核实后发现:不是巧合或配置遗漏,`acceptance_mode='itemized'` 在当前系统里**从未被真正触发并落库过一次**——追问引出了比 43 号文档更大的架构缺口:43 号文档只解决"验收要不要拆细",而用户认为**规划(把一件事拆成第一步/第二步/第三步)本身应该是每个任务默认必经的前置步骤,不是一个可选的、可以被判"不需要"的例外**。 -> **格式对齐** `27-requirement.md`/[`43-task-decomposition-granularity.md`](../../done/system-design/43-task-decomposition-granularity.md):原话照搬 + 结构化复述,现状调研另起一节。 - ---- - -## 原始需求(用户原话,一字不改) - -> 所以,拆解任务计划这件事情是默认要做的。也就是说,我们做任何一个任务,其实都要有一个 planning(规划)的过程。 -> -> Planning 过程就是明确这个事情怎么做:第一步干什么,第二步干什么,第三步干什么。这个是一定要规划出来的。而这种规划来源于以下三个地方: -> -> 1. 模型本身有规划能力。 -> 2. 来源于我们的知识库、经验库、memory 或 history。 -> 3. 来源于我们去做线上的 Deep Research。去研究怎么更好地实现这个过程,别人是怎么做的,线上有什么 know-how、有什么方法论,怎么做这件事情才是更优解。 -> -> 比如说,我们怎么做一个视频:视频要不要生成分镜、生成脚本、修改编剧台词?最后怎么视频合成、怎么验证?这些方法论是需要有一个 planning 过程的,这个过程一定是要有的。所以,我们之前没有做任务分解,但并不代表这个事情在现实生活中不存在。既然我们之前的架构上有这个问题,那我们就必须要改变它。 -> -> 也就是说,我们做任何一个 ORG graphy 或者是 work graphy 的时候,就应该先有 planning 的过程: -> 1. 分解任务; -> 2. 所有的 agents 围绕这个任务看板来工作,一层一层地接子任务; -> 3. 子任务做完之后,再去做主任务的 validate。 -> -> 人的工作是这样的,那多 Agent 的协同工作也应该是这样的。 - ---- - -## 结构化复述 - -拆成三条独立主张: - -1. **规划是默认前置步骤,不是可选判断**——任何一个任务(goal / org graph / work graph 层面)在真正执行前,都必须先产出一份"第一步干什么、第二步干什么"的分解结果。当前架构里"要不要拆"是一次可以判"不需要"的例外判断,这一点本身就是缺陷。 -2. **规划的信息来源要显式覆盖三类**:① 模型自身推理能力,② 内部知识库/经验库/memory/history,③ 外部 deep research(别人怎么做、行业 know-how、方法论)。三者不是互斥,是应该都被用上的三个输入源。 -3. **执行模型要围绕"任务看板"重新组织**:分解出的子任务应该是 agent 们认领、逐层完成的对象;子任务做完之后再做主任务的 validate(先子后主,不是先给主任务判总分)。 - ---- - -## 现状调研(本次会话核实,基于 43/44 号文档 + 本次代码交叉核对) - -结论:**当前架构在这三条主张上都有真实的、具体的缺口**,不是风格问题。逐条列证据: - -### 1. 规划目前是"要不要拆"的保守判断,默认是不拆 - -`system/agents/task-planning/agent.md` Step 2bis(决定 `acceptance_mode` 的地方)原文态度: - -> 判断错的代价是不对称的:错误地写 `"itemized"`(验收标准其实互相依赖)会让后续验收拆出没法独立成立的子任务,比漏写、保守选 `"holistic"` 的代价更高。**拿不准就留 `"holistic"`**。 - -这与"规划是默认动作"直接相反——现在的默认值是"不规划到子任务颗粒度",只有信心足够时才拆。而且这一步只回答"验收要不要拆",从不回答"这件事第一步干什么、第二步干什么"这种通用意义上的 planning。 - -### 2. 规划阶段被显式禁止使用知识库/深度调研 - -`system/agents/task-planning/tools.md`「你**不该**用的工具」表格原文: - -> | 联网搜索、读第三方资料 | 拆分的唯一真相来源是 goal 描述原文。去外面找依据,等于给自己发明需求(违反 `agent.md` 硬约束 8) | - -这条规则本身有合理的历史动机(防止 LLM 脱离用户原话臆造需求),但结果是:用户设想的三条规划来源里,**第 2 条(知识库/经验库/memory/history)和第 3 条(deep research)在当前唯一的"goal → task"规划入口上被明文禁止**,规划只能来自第 1 条(模型自身能力)。 - -值得注意:知识库/经验库这条路径**不是完全空白**——`dream` 机制(29 号文档)会把各 agent 的运行经验提炼后写回 `agent.md`/`soul.md` 的专属区块(本对话前半段刚看过 CEO 的这次刷新)。但这是**事后单向沉淀**:写给 agent 在被调用时"顺带看到",不是规划阶段主动查询的机制——task-planning 判断"要不要拆、怎么拆"时,完全不会去读任何 agent 的 dream 沉淀经验。 - -### 3. 已经存在一套真正做"deep research"的机制,但没有接入日常任务规划 - -`system/agents/org-hunt`(+ `org-digest`/`org-evolve`)是 41 号文档验证过的一套真实调研机制:用 `l1-web-fetch` 抓取网页、评估数据源权威性、产出方法论。这正是用户设想里"第 3 条来源"的现成实现,而且经过真实跑通验证(41 号文档"11 轮真跑挖出并修复 5 个独立缺陷")。 - -但它**当前只服务于 FDE 方法论训练场景**(`org-innovator` 评估/训练某个角色的专业能力),只在 `deliverable.json` 这一种 work-graph-template 骨架里被引用,**从未被接入 task-planning 或 org-planner 的常规执行路径**。换句话说:deep research 的能力已经造好了,只是没有被接到"用户下一个任务 → 系统怎么规划"这条主路径上。 - -### 4. 子任务目前只影响"验收",不影响"执行分工" - -43 号文档的落地范围里明确记录了这条设计决策: - -> 子任务要不要独立开发+验收循环 | **先只拆验收,开发仍整体完成**——org-coder 一次性写完整个交付物,验收侧改成按子任务(验收条目)逐条判,而不是给总分 | 改动小得多……**独立开发循环(每个子任务各起一个 work-graph run)留作后续再评估** - -用户这次的第 2 条主张——"所有的 agents 围绕这个任务看板来工作,一层一层地接子任务"——正是 43 号文档当时明确放弃、留待后续评估的那部分。现在看,"后续"就是现在。 - -### 5. org/work graph 是预先铺好的固定拓扑,不是"任务池、谁有空谁认领" - -现有 org graph / work graph 是建图阶段(`orggraph-planning`/`workgraph-planning`)一次性规划出的 DAG,节点和边在执行前就已固定,执行时严格按拓扑推进。`org-planner` 没有任何"创建 task"的工具(43 号文档已核实),子任务是靠服务端在它产出 `acceptance.md` 之后**物化**出来的,不是 agent 从一个共享任务池里"认领"的。 - -用户"agents 围绕任务看板逐层接子任务"这句话,字面上更接近一个**动态任务池 + 认领机制**,跟"图在建图那一刻就已经画死"的现有模型有结构性差异——这不是一个小改动,需要先确认要不要走这个方向(见下节问题 C)。 - -### 6. 即便是现有最小范围的子任务机制,可靠性也还没被坐实 - -上一轮对话已核实:当前 `data/graph.sqlite` 里 `acceptance_mode='itemized'` 的 task 零条,所有公司运行态目录里都搜不到 `itemized` 字样——43/44 号文档做的那次真实验证用的测试公司数据也已被清理。也就是说,在讨论"要不要扩大规划范围"之前,现有范围内的东西本身还没有一次成功的端到端确认。 - ---- - -## 设计决策(2026-09-01 定案) - -| 问题 | 决定 | 直接后果 | -|------|------|----------| -| A:规划打通到哪一层 | **新增独立的"深度规划"阶段**,插在 `task-planning`(goal→task,保持极简不变)和现有 org-planner 之间 | `task-planning` 职责不变(仍是"要不要多条 task"的粗粒度判断,3 轮内、不碰磁盘)。新阶段专门承接"这件事第一步/第二步/第三步怎么做"+ deep research + 查经验库,产出结构化的分解结果,**取代**现有"org-planner 收尾后才物化子任务"的时序——分解结果要在执行开始前就产出,而不是brd/prd/sdd写完后才回头拆 | -| B:Deep Research 触发时机 | **规划环节自行判断**——先查经验库/memory/dream 沉淀,查不到或置信度低时才触发调研(复用 `org-hunt`/`org-digest`) | 新阶段需要两类工具权限:① 读 agent 的 `soul.md`/`agent.md` dream 区块 + 跨公司经验(目前没有现成的"查经验库"检索接口,需要新建);② 有条件触发 `org-hunt`(目前只能被 workgraph-planning 挑进固定模板,需要开放为"新阶段可按需插入") | -| C:执行模型 | **引入任务池 + 认领机制**,替代当前"建图时固定 DAG、严格拓扑推进"的执行方式 | 这是四条里改动面最大的一条:`graph-engine.js` 当前是纯拓扑驱动(`readDefGraph` 一次性给出全部节点和边),需要新增"子任务作为可认领工作项"的调度层——agent(或调度器代 agent)从共享任务看板拉取匹配自己角色、状态为"可认领"的子任务,而不是等图上游节点产出后按边推给固定下游 | -| D:验证时机 | **子任务拥有独立的开发+验证生命周期**(这次和 A/C 一起做,不再分阶段) | 每个子任务对应一次独立的开发→验证循环(而不是 43 号文档现状的"整体开发、分子验证"),意味着每个被认领的子任务大概率各自触发一次工作流实例(起类似 work-graph run 的调用),成本和并发编排复杂度显著上升,需要有预算/并发槽位控制(可复用 27-P3C 已实现的队列/并行槽位机制) | - -四条决策合起来的完整流程: - -``` -goal 创建 - │ - ▼ task-planning(不变,粗粒度) - 产出 1..N 条 task(draft) - │ - ▼ 人工"全部下达"(不变) - │ - ▼ 【新】深度规划阶段 - 1. 查经验库/memory/dream 沉淀,看有没有可复用的方法论 - 2. 置信度不够 → 触发 org-hunt/org-digest 做 deep research - 3. 结合"模型自身规划能力 + 查到的经验/调研结果",产出结构化分解 - ("这件事第一步做什么、第二步做什么…",形态类似 43 号文档的 acceptance.md - 但发生在执行前而不是收尾后,且不仅是验收条目,是完整的步骤清单) - 4. 把分解结果物化成任务看板上的一批"可认领子任务"(不是固定图节点) - │ - ▼ 【新】任务池认领执行 - 各角色 agent(org-coder / org-validator / …)按自己的角色,从共享看板拉取 - 下一条匹配、状态为"可认领"的子任务 → 独立开发 → 独立验证 → 回写子任务终态 - │ - ▼ 汇总收敛 - 全部子任务 completed → 父 task success;任一子任务 failed → 父 task fail - (沿用 43 号文档已实现的父子终态汇总规则,未变) -``` - ---- - -## 已知的高风险点(老实写,别假装这是小改动) - -- **任务池认领是对现有图执行引擎(`graph-engine.js`)的范式级改动**,不是加字段/加钩子能做到的。现有引擎的核心心智模型是"边决定谁在什么时候跑",任务池模型的心智模型是"角色决定谁能跑什么,时机由认领动作决定"——两者要并存(company 结构层的 org graph 显然还是要保留固定角色拓扑,不可能把 CEO/CTO 汇报关系也做成认领制),需要先明确**任务池模型的作用域边界**:只用于"深度规划产出的子任务"这一层,还是要往上影响到 work-graph 本身怎么跑。这个边界目前还没有定案,是 Phase 1 设计阶段必须先钉死的第一件事。 -- **43/44 号文档已经踩过一次"新机制先天有可靠性缺口"的坑**(`acceptance.md` 未必被稳定产出,闸门只查私有目录不查共享区),且现有最小范围的子任务机制(43 号文档)在生产数据里至今零命中。在没有把现有范围坐实之前就叠加更大的改动,风险会累加而不是替换。 -- **"每个子任务独立开发+验证循环"直接推高成本**——一个 goal 可能因此从"1 次 work-graph run"变成"N 次子任务级 run",需要先有预算模型(每个子任务分到多少预算、超预算怎么办)才能不失控。 -- **Deep research 的调用成本和可靠性**——`org-hunt` 目前只在方法论训练场景验证过(41 号文档),没有在"日常任务规划、时间敏感"场景下验证过延迟和成功率。 - ---- - -## 技术实施计划(架构级阶段划分,未细化到代码行) - -建议顺序:**Phase 0(先坐实现有范围)→ Phase 1(深度规划阶段骨架)→ Phase 2(经验库检索 + deep research 接入)→ Phase 3(任务池认领)→ Phase 4(子任务独立开发验证循环)→ Phase 5(看板 UI)**。理由:每一阶段都在前一阶段的地基上加一层能力,且允许在任何一个阶段暂停评估效果/成本,不是一次性大爆炸式改造。 - -### Phase 0 · 坐实现有范围(前置条件,不是本文档新增范围) -在叠加新机制前,先补一次 43/44 号文档遗留的端到端确认跑——用真实公司走一遍现有 `acceptance_mode='itemized'` 全链路,确认它能稳定收敛。当前生产数据零命中,不能假设地基是稳的。 - -### Phase 1 · 深度规划阶段骨架 -- 新增独立 agent(暂定 `deep-planning`,命名需过 27 号命名规范),接在 task-planning 产出 task、人工下达之后,org 图真正执行之前。 -- 先只做"模型自身规划能力"这一路(用户三条来源里的第 1 条):产出结构化步骤分解,格式参考 org-planner 现有 `acceptance.md` 表格惯例,但覆盖"怎么做"而不仅是"怎么验收"。 -- 暂不接经验库/deep research,暂不接任务池——先验证"分解结果能不能被下游正确消费"这一步。 -- 明确任务池模型的作用域边界(上面"高风险点"第一条),写成本文档的补充小节,动 Phase 3 之前必须先定案。 - -### Phase 2 · 经验库检索 + Deep Research 接入 -- 新建一个"查经验库"的检索能力——扫描相关 agent 的 dream 沉淀区块 + 历史同类 goal 的执行记录,给深度规划阶段用。这是全新能力,当前没有任何 agent 具备"主动查别的 agent 经验"的工具。 -- 打通深度规划阶段按需触发 `org-hunt`/`org-digest` 的路径(当前它们只能被 workgraph-planning 挑进固定模板,需要开放"按需调用"的口子)。 -- 验收:给一个"经验库有答案"的任务和一个"经验库没答案、需要调研"的任务各跑一次,确认分支判断正确、调研产出确实被规划阶段消费。 - -### Phase 3 · 任务池认领机制 -- 范围先限定在"深度规划产出的子任务"这一层(不动 company 结构层的 org graph 固定拓扑)。 -- 新增"可认领子任务"的状态位与认领接口;调度器或各角色 agent 心跳时拉取匹配自己角色的可认领项。 -- 需要复用/扩展 27-P3C 已实现的队列与并行槽位机制,避免重新发明并发控制。 - -### Phase 4 · 子任务独立开发+验证循环 -- 每条被认领的子任务触发独立的开发→验证生命周期(复用 work-graph run 的既有机制,而不是新发明一套)。 -- 预算模型:子任务预算怎么从父 task 预算里切分、超支怎么处理,需要单独设计(可能复用现有 wallclock/token 预算机制的分配逻辑)。 -- 父子终态汇总规则沿用 43 号文档已实现的逻辑(全 success 才 success)。 - -### Phase 5 · 看板 UI -- 任务看板要能展示"可认领池"状态(谁在等认领、谁已认领在跑),不只是现有的父子树形/表格展示。 - ---- - -## Phase 0 实现记录(2026-09-01,真实回归已完成) - -用真实(非 mock)测试公司 `p0verify0901` 走完一次完整的 goal→task→org 执行链路。**测试公司数据未清理,留作证据**(`data/companies/p0verify0901/`)。 - -**结论**:链路能收敛,44 号文档的修复确认生效,但发现一个新的、独立的真实缺口——**`acceptance_mode='itemized'` 在当前 org-planner 子图形状下永远物化不出子任务,不是概率问题,是结构性打不通**。这解释了 46 号文档「现状调研」第 6 条记录的"生产库零命中"现象:不是运气差,是这条路径已经失效。 - -### 验证结果 - -| 检查项 | 结果 | -|---|---| -| task-planning 真实判定 | `itemized`(未诱导、未重试,`T-mtis98aq-8c7f`) | -| `goal-benchmark`/`planner-validator` 是否真正跑完(44 号文档修复验证) | ✅ 两者都 success,不再被误判 skip;`contract.json` 声明 `produces.acceptance` 指向共享 `artifacts/` 绝对路径,闸门正确识别 | -| `acceptance.md` 是否产出 | ❌ 全程未产出,四个 org-planner 节点私有目录搜遍为空 | -| 子任务是否物化(`parent_task_id` 指向该 task) | ❌ 0 条 | -| `coder-validator` 是否逐条上报子任务结果 | ❌ 未调用,走的是整体 holistic 判定(`SUBTASKS_INCOMPLETE` 闸门因为子任务本来就是 0 条而天然放行) | -| 父 task 终态 | `completed`(按 holistic 规则收敛,不是 43 号文档设计的"全子任务 completed 才 success"规则——因为没有子任务可供该规则生效) | -| 回退/报错 | 零回退(`backward_count` 全程 0),零 `fail_reason`,全程 18 分 55 秒 | - -### 根因 - -`org-planner` 名义上(`agent.md`/`tools.md` 文档)要交 `brd.md/prd.md/sdd.md/acceptance.md` 四份产物,但它是 `nodeKind='subgraph'`,真正执行靠 `workgraph-planning` 现场规划的子图。这次真实规划出的子图是 `org-planner-prd → org-planner-sdd → org-planner-benchmark → org-planner-validator`(没有 brd-planning,也没有任何节点职责是写 `acceptance.md`)——`org-planner-benchmark` 产出的是 `goal-benchmark.json`(R-19-B 契约的 `benchmark` 段,44 号文档那套新机制),跟"子任务物化"依赖的 `acceptance.md` 是两套不同产物,已经对不上。`graph-engine.js` 里 `maybeMaterializeItemizedSubtasks`(约 2372-2417 行)扫不到 `acceptance.md` 时会静默降级为 holistic(设计上是"软失败不拖垮硬产出",出发点合理),但代价是这条路径悄无声息地形同虚设,没人会发现。 - -`org-planner/agent.md`、`org-planner/tools.md` 里"四份产物含 acceptance.md"的文档描述本身可能已经过时,没跟上 `goal-benchmark.json`/R-19-B 契约这套后来的机制。 - -### 遗留决策:这个新发现的 bug 怎么处理 - -已拆分成独立文档处理,**已修复**:见 [47 号文档](../../done/system-design/47-itemized-subtask-materialization-goal-benchmark-fallback.md)(`acceptance.md` 缺失时物化逻辑改读 `goal-benchmark.json` 的 `dimensions[]`,2026-09-02,单测 5 条全过)。47 号文档末尾也记了一条需要 Phase 1 设计时想清楚的遗留点:这条兜底修的是**旧的(43 号文档设计的)物化机制**,Phase 1 打算把分解提前到执行开始之前,旧机制届时可能降级为安全网而不是主路径——不影响当下必要性,但 Phase 1 设计阶段要显式决定要不要保留它。 - ---- - -## Phase 1 详细设计(2026-09-02,设计已细化,未实现) - -### 命名 - -按 [27-naming-conventions.md](./27-naming-conventions.md) §二「规划类 agent —— `{产出物}-planning`」的既有惯例(`brd-planning`/`prd-planning`/`sdd-planning` 都按自己产出什么命名),新 agent 命名为 **`subtask-planning`**——它的产出物就是 `subtask-goal`(该规范 §一 作用域表里已经预留了这一级:"子任务的目标(一个 task-goal 分解出多个)"),命名轴完全对上,不用新造概念。 - -与 `task-planning` 的关系:`task-planning` 把 `company-goal` 拆成多条 `task`(= `task-goal`);`subtask-planning` 把一条 `task-goal` 拆成多条 `subtask-goal`——同一条命名轴上相邻的两级,职责边界清楚。 - -### 触发点:`enqueueTaskRun`(不改 task-scheduler 的排队逻辑) - -`server/routes/company.js:1099 enqueueTaskRun()` 是 dispatch 时真正创建 org run 并把 task 标记 `queued` 的地方,紧接着会调用 `writeTaskGoalArtifact()` 写下这条 task 的验收边界产物。**这是最合适的钩子位置**——原因: - -- **不碰 `task-scheduler.js`**:该文件的排队/占坑逻辑充满精细的并发不变式(本次设计阶段读代码时看到的注释密度就是证据),新增一道"等 subtask-planning 跑完才能出队"的阻塞闸门风险很高,且违反 33/44/47 号文档反复验证过的教训——**软数据不能当硬闸门**。 -- **异步、不阻塞**:`subtask-planning` 在 `enqueueTaskRun` 内用 `setImmediate` 触发(照抄 `runGoalSplit` 现成的异步模式),跟 org run 的排队/起跑并行,不等它。多数情况下 `subtask-planning` 只是一次单轮 LLM 调用,会比 org run 真正排到执行位、org-planner 开始跑之前完成;即使没赶上,也只是这一轮 org-planner 拿不到分解方案参考,不是失败。 - -### 产出物:写共享 `artifacts/` 区,不是私有目录 - -吸取 44/47 号文档的教训——产物必须落在**下游查得到的地方**,且格式要对已经证实稳定工作的机制友好。写入 `runRootDir(run)/artifacts/subtask-plan.json`,形状复用 47 号文档刚验证过的 `goal-benchmark.json` 风格(结构化、非 Markdown 表格,避免重蹈 `acceptance.md` 那种"文档说要写、实际没人写"的覆辙): - -```json -{ - "steps": [ - { "seq": 1, "title": "...", "why": "...", "depends_on_hint": null }, - { "seq": 2, "title": "...", "why": "...", "depends_on_hint": [1] } - ], - "reasoning_source": "model_only" -} -``` - -`reasoning_source` 字段是为 Phase 2 预留的("model_only" / "memory" / "deep_research"),Phase 1 恒为 `"model_only"`。 - -### 与 org-planner 的衔接点:软注入,不建硬依赖 - -`org-planner` 现在读的输入里已经有一套"P1 虚拟引用"注入模式(`company-context.md`,prompt-builder 直接拼进提示词,见 27-P1 文档)。`subtask-plan.json` 走同一种模式:prompt-builder 组装 org-planner 任务提示词时,检查这条 task 对应 run 的共享 `artifacts/subtask-plan.json` 是否存在,存在就拼进提示词一段"已有的分解参考(来自 subtask-planning,仅供参考,不是必须遵守的清单)",不存在就跟现在完全一样,什么都不加。**不改 org-planner 的硬约束、不要求它必须使用这份参考**——Phase 1 的验收标准只是"分解结果能被下游正确读到",不是"下游行为因此改变"(那是 Phase 1 完成后才评估的效果问题,不是这一步的实现范围)。 - -### 工具权限 - -比照 `task-planning` 的极简先例(该 agent 现在"大概率一个工具都不需要用"):v1 只给读权限(`bash`/`read_file`,无 `str_replace`),能读的输入是任务描述 + 公司 org graph 摘要(跟 task-planning 拿到的是同一批,首轮消息里给)。**明确不给**:创建/修改 task 的 API、创建/下达 run 的 API、招聘/改 org graph 的 API(原因与 task-planning 工具箱里那张"不该用的工具"表完全一致——落库是服务端的活,agent 只交结构化产出)。联网调研(Phase 2 才接)本阶段也不给。 - -### Phase 3 前必须先定案的边界(呼应「已知的高风险点」第一条) - -任务池认领机制的作用域明确限定为:**`subtask-planning` 产出的 `subtask-plan.json` 里的 `steps[]`,物化成 `tasks` 表里 `parent_task_id` 指向该 task、可被认领的行**。不影响: - -- company 级 org graph(`ceo`/`cto`/`org-planner`/`org-coder` 这套角色拓扑,27-P2 已实现,继续保持人工固定/慢变/可审计); -- `workgraph-planning` 现场规划出的 org node 内部 work graph(`brd-planning → prd-planning → sdd-planning → ...` 这类,继续保持固定 DAG)。 - -任务池只是在"task"和"当前这套固定的 org/work graph 执行"之间新插了一层——这层里的东西(subtask)才能被认领,图本身怎么跑不变。这条边界写在这里,是为了 Phase 3 设计时不用重新讨论一遍。 - -### Phase 1 验收标准(后续真实回归要检查的) - -1. dispatch 一条 task 后,`subtask-planning` 是否被异步触发、是否产出了 `subtask-plan.json`? -2. `org-planner` 的任务提示词里是否真的拼进了这段参考(不是"写了注入逻辑但没生效")? -3. `subtask-planning` 没跑完/失败时,org-planner 这一轮是否完全不受影响(确认软注入的降级路径成立)? - ---- - -## Phase 1 实现记录(2026-09-02,代码已实现,未做真实端到端回归) - -### 落地清单 - -| 内容 | 位置 | -|---|---| -| 新 agent 本体 `subtask-planning` | `system/agents/subtask-planning/{config.json,agent.md,identity.md,soul.md,tools.md}`,`config.type='meta-planning'`(复用 `task-planning` 同款分类,不进 org graph) | -| 规划服务(含 CLI 调用 + 宽松解析归一化) | `server/services/subtask-planning.js`——单轮调用、无重试、单条不合法数据丢弃不作废整批(与 task-planning 的严格写入前校验刻意不同,见 agent.md「你的产出是建议,不是指令」) | -| 触发点 | `enqueueTaskRun`(`server/routes/company.js`)新增 `withSubtaskPlanning` 参数,**默认 `false`**,只在两处显式传 `true`:`/goals/:goalId/dispatch`(批量下达)、`/tasks/:taskId/enqueue`(单条下达)。`POST /:companyId/tasks`(quick task,外部 agent 自主派发)与 `/rerun` 保持默认不触发——理由见下「设计比原计划收窄的一处」 | -| 产物 | `runRootDir(run)/artifacts/subtask-plan.json`(共享区,47 号文档同款教训:不写节点私有目录) | -| 与下游的衔接 | 登记进 `graph_files`(`kind='subtask_plan'`),纳入 `graph-engine.TASK_SHARED_KINDS`——复用 R-9.3 任务级共享机制自动出现在该 task 下**任意**节点的「上游输入」提示词段落,**没有另写 prompt-builder 注入逻辑**(比原设计「衔接点」一节设想的软注入方案更省事,见下) | -| 测试 | `subtask-planning.test.js`(12 条,纯函数解析/归一化)+ `subtask-planning-materialize.test.js`(4 条,共享区落地 + `describeInputs` 真的能读到)。全量单测 1558 passed,唯二失败与本次改动无关(改动前即存在) | - -### 与原设计的两处出入 - -1. **触发点从"`enqueueTaskRun` 内部无条件触发"收窄成"调用方显式 opt-in"**。原设计(本文档上一版)打算让 `enqueueTaskRun` 无条件触发,动手时撞上 `goal-task-acceptance.test.js` 的「验收 22」——它守着一条真实存在的产品保证:`POST /:companyId/tasks`(外部 agent curl 自主派发的快速任务,例子就是"把周报发出去"这种行政指令)必须**零 LLM 调用**,这是给 agent 间快速委派留的零成本通道。subtask-planning 无差别触发会直接击穿这条保证。改法:`enqueueTaskRun` 新增 `withSubtaskPlanning` 参数,默认 `false`,只在"goal 批量下达"和"单条 task 显式下达"这两个真正对应"人/系统郑重下达一条任务"的语义上打开。 - -2. **与 org-planner 的衔接从"prompt-builder 软注入"改成"复用 R-19.3 任务级共享索引"**。原设计打算专门给 org-planner 写一段"检查 subtask-plan.json 存在就拼进提示词"的注入逻辑。动手核实 `graph-engine.describeInputs` 的实现后发现:这个函数**在 subgraph 节点上直接跳过**(`org-planner` 本身是 subgraph),真正的消费者应该是它展开出的子图里那些 `agent_call` 节点(`org-planner-prd`/`org-planner-sdd`/…)——而这些节点已经有一套现成机制(`TASK_SHARED_KINDS`,任务级共享产物索引,`acceptance`/`contract` 已经在用)自动把"这个 task 下的共享产物"塞进**任意**节点的提示词,不受拓扑相邻性限制。把 `subtask_plan` 加进这个白名单,比另写一段注入逻辑更省代码、更少一个新的失败模式,而且覆盖面更广(org-coder 阶段的节点也能看到,不只 org-planner)。 - -### ⚠️ 一次真实的安全事故(写实话,别再犯) - -第一版实现(`enqueueTaskRun` 内部无条件触发,未做任何测试环境防护)在跑 `npx vitest run server/tests/unit` 时,**真实开出了 44 个 `claude` CLI 子进程**挂在后台(`ps aux` 现场抓到,手动 `kill` 清理干净,未产生数据损坏,但存在真实 API 调用成本)。根因:`task-planning`/`workgraph-planning` 那套"默认走真实实现,测试要显式注入桩才安全"的设计前提,建立在"只有专门测规划逻辑的测试才会碰到这条触发路径"之上;subtask-planning 的触发点(`enqueueTaskRun`)被全仓库大量跟规划毫不相关的单测(测排队、测预算、测状态流转)顺带路过,这些测试从没想过要为一个新加的旁支功能注入桩。 - -修法:`subtask-planning.js` 的 `planSubtasks()` 新增一道防护——`process.env.VITEST`(vitest 对所有测试进程自动置位,通用判据不是我们发明的)为真、且没有人显式调用过 `setPlanSubtasks()` 注入时,直接跳过真实调用,返回空结果(等价于 47 号文档反复用到的软失败路径)。这是 `subtask-planning.js` 独有的防护,`task-planning.js` 没有、也不需要——它的触发面窄得多,从未出现过这个问题。加完防护后,全量测试(1558 条,含 integration/devloop)跑三遍,`ps aux` 确认零残留子进程。 - -### 遗留:还没做真实端到端回归 - -设计文档「Phase 1 验收标准」一节列的三条(① dispatch 后 subtask-planning 真的被触发并产出 artifact;② org-planner 展开的子图节点提示词里真的能读到这段参考;③ subtask-planning 没跑完时 org-planner 完全不受影响)——**①③ 已经被集成测试间接覆盖**(用注入桩验证了机制本身是对的),**②的"提示词真的拼上了共享索引这一段"也已经用集成测试直接验证**(`describeInputs` 真实读到 `subtask_plan` 并列进提示词)。**没做的是**:用真实公司、真实 goal、真实 Claude CLI 走一遍全链路,确认 subtask-planning 在真实调用下真的产出有意义的分解(不是机制通不通,是内容有没有用)——考虑到刚踩过的真实 CLI 子进程事故,这次真跑验证需要更谨慎地控制范围(单个任务、有人盯着、跑完立刻检查有没有残留进程),不建议顺手跑,值得单独作为一步来做。 - ---- - -## Phase 2 实现记录(2026-09-02,收窄版,代码已实现,未做真实端到端回归) - -用户原话要求"把 Phase 2-5 一次性做完"。动手前先做了一轮代码调研,发现文档字面描述掩盖了真实工作量落差:dream(29-DREAM)只**单向写回**经验,没有任何可查询索引;"R-19.3 共享索引"是笔误(实指 `TASK_SHARED_KINDS`/R-9.3,跟经验库无关);org-hunt 只能作为图节点被 `workgraph-planning` 动态选中,没有图外可调用入口;Phase 3(任务池认领)是"改动面最大的一条",要给纯拓扑驱动的 `graph-engine.js` 并行引入认领制调度层;Phase 4 需要全新的子任务级 run + 预算模型;Phase 5 在 3/4 落地前没有真实"可认领池"状态可展示。跟用户对齐后,**这一轮只做 Phase 2(收窄版)**,Phase 3-5 涉及核心调度引擎范式级改动 + 全新预算模型,风险量级不同,另起设计讨论。 - -### 落地清单 - -| 内容 | 位置 | -|---|---| -| 经验检索(新模块) | `server/services/experience-lookup.js`——`queryExperience({companyId, task})`:查这家公司已雇佣 agent 的 `memory/experience.md`(dream 沉淀),关键词重叠打分(中文用双字滑窗近似分词,不引依赖)。命中为零是真实信号("查不到"),不是异常 | -| 按需 Deep Research(新模块) | `server/services/deep-research.js`——`triggerDeepResearch({companyId, task, topic})`:图外直接 `AgentManager.callAgent('org-hunt', ...)`,轻量提示词,不套用 org-hunt 训练场景的三件套契约。**默认关闭**(`ENABLE_DEEP_RESEARCH` 环境变量,不为 `'1'` 直接返回 `null`,连注入的假实现都不调用) | -| `subtask-planning.js` 两段式改造 | `defaultPlanSubtasks`:先查经验库拼进提示词 → 调一次 agent,agent 自评 `confidence` → 经验为空或 `confidence:"low"` 就触发调研 → 有结果就带着调研结果再调一次取代第一轮。`reasoning_source`(`model_only`/`memory`/`deep_research`)由服务端根据实际发生的分支算出,不是 agent 自报的。新增更细粒度注入点 `setSubtaskAgentCaller`(仿 `dream-service.setDreamAgentCaller`),能测到这段编排逻辑本身,不只是测"落盘"那一层 | -| `subtask-plan.json` 增补字段 | `reasoning_source`/`experience_refs`/`research_used`,纯增量、向后兼容旧的注入桩(缺失时兜底 `model_only`/`[]`/`false`) | -| `system/agents/subtask-planning/agent.md` | 输出契约加 `confidence` 字段(硬约束 9),补充"你可能会看到两种提示词"一节 | -| 测试 | `experience-lookup.test.js`(6 条)、`deep-research.test.js`(6 条,含"关闭开关时哪怕注入了假实现也不调用"这条最重要的安全阀)、`subtask-planning.test.js` 扩展(`extractConfidence` + `buildSubtaskPlanningPrompt` 的经验/调研小节,7 条新增)、`subtask-planning-materialize.test.js` 扩展(新增字段落盘 2 条 + `defaultPlanSubtasks` 三条分支编排 4 条)。全量单测 1177 条,1175 passed,唯二失败(`.env.example` 覆盖 + 测试 id 稳定性)与本次改动无关、改动前即存在。测试全程 `ps aux` 确认零残留 CLI 子进程 | - -### 与原设计的出入 - -1. **"历史同类 goal 执行记录"这条来源本轮不做**——只做 agent 经验区块检索。原设计的用户原话把"经验库/memory/dream 沉淀"和"历史同类 goal 检索"并列成一条来源,但后者需要语义检索基础设施(当前没有),贸然上关键词匹配对"同类"这种判断噪音会很大。这是范围收窄,不是遗忘,留给以后需要时再评估怎么做语义检索。 -2. **Deep research 复用 org-hunt,但用轻量提示词,不套用它的训练场景三件套契约**(`knowhow_raw.md`/`crawl_config.json`/`seed_data.json` ≥50 条)。原因:那套契约是为 FDE 训练场景校准的,直接套用在"人在等一条 task 入队"这种时间敏感场景会不可控地慢。org-hunt 本身的图节点契约、`deliverable.json` 模板都没动。 -3. **Deep research 触发默认关闭**(`ENABLE_DEEP_RESEARCH` 环境变量)。org-hunt 的延迟/成功率在"日常任务规划、时间敏感"场景下从未验证过,且 Phase 1 已经因为一个新触发点疏于隔离出过真事故。这跟 Phase 1 `withSubtaskPlanning` 默认 `false` 是同一个谨慎级别——机制已实现且单测覆盖,但真实调用需要用户手动开关、挑单个任务验证。 - -### 真实端到端回归(2026-09-02,已完成) - -用真实测试公司 `p2verify0902`(真实 org graph、真实 goal→task→dispatch)+ `ENABLE_DEEP_RESEARCH=1` 跑了两条真实任务,全程 `ps aux` 确认零残留 CLI 子进程。 - -**发现一个真实缺陷(已修复)**:第一条真实任务(贪吃蛇小游戏)跑完,`reasoning_source` 落成 `"memory"`,但 `experience_refs` 列出的命中全是 `ceo`/`orggraph-planning` 关于"校验失败怎么兜底路由""修复要重新触发验证"这类**跟游戏开发毫无关系**的工程流程经验——查了实际打分才发现根因:原始实现"关键词交集计数、只要 >0 就算命中",一条 100+ 词的任务描述几乎必然跟经验库里的高频工程词汇("验证""检测""流程")偶然重合 1-3 个 bigram,噪音就压过了信号,**直接吃掉了本该触发的调研分支**——这正是 Phase 2 acceptance 标准要求确认的"分支判断正确",第一次真跑就没通过。 - -修法:打分改成"这条经验自己的词里有多少比例被 task 提到"(对经验块词数归一化的 containment ratio,不是对查询词数),阈值(命中 ≥2 词且比例 ≥12%)拿真实噪音案例(约 3%-4%)和合成的真正相关案例(约 16%-32%)校准。补了一条回归测试固定复现这个真事故场景。修完重启服务、用第二条真实任务(Markdown 转 HTML 工具)重跑:`reasoning_source` 正确变成 `"deep_research"`、`experience_refs` 正确为空、`research_used: true`——分支判断修复生效,且日志确认真的走了两轮调用(subtask-planning → org-hunt → subtask-planning)。 - -**两条真实任务的分解内容质量都相当好**:贪吃蛇任务给出"先定义状态 → 渲染循环 → 输入监听 → 移动逻辑 → 吃食物 → 碰撞检测"六步,依赖关系合理;Markdown 工具任务甚至体现出"代码块围栏必须最先扫描保护,否则块内的 `#`/`**`/`[]` 会被后续正则误转换"这种真实的解析器实现顺序知识。这直接回答了 Phase 1/2 一直悬而未决的"内容有没有用"——回答是有用。 - -**一个诚实的局限,没有掩盖**:查了 org-hunt 那次调用的日志,全程没有 `l1-web-fetch` 工具调用痕迹——它大概率是直接用自己训练知识回答的,没有真的去查外部资料。轻量提示词只要求"给一段总结",没有强制要求引用可验证来源、没有强制要求真的调用检索工具。这意味着当前的 `"deep_research"` 分支实际提供的价值更接近"多问一个角色的第二意见",不是名副其实的外部调研——如果以后要坐实"真的查了外部资料"这件事,需要在提示词里明确要求工具调用证据(比如强制要求 sources 是真实可验证 URL),这次没有额外去做,留给以后需要更强调研可信度时再补。 - ---- - -## Phase 3 详细设计(v1,2026-09-02,⚠️ 已废弃——见下方「Phase 3 v2」) - -> **这一版设计和它对应的实现记录整段废弃,但原样保留在下面**(不删):真实回归发现"认领后没有触发源"这个真问题之后,跟用户讨论出的根本原因是——"认领"(pull,CAS 抢占)解决的是"多方竞争同一条任务",但实际场景里从头到尾只有 CEO 一个人拍板要不要派、派给谁,压根不存在竞争,CAS 是在给不存在的问题找解。v2(本文档更后面)改成 CEO 直接派发(push),砍掉了 `claim`/`unclaim` 两个端点和 `claimable`/`claimed` 两个状态。留着 v1 不删是因为这段"先做认领、跑真实回归、发现问题、推翻重来"的过程本身有价值——**它是这次为什么改主意的第一手证据**,删掉只留结论会让人以为 v2 是一次性设计对的。 - -### 前置决策(跟用户对齐) - -Phase 3 最关键的架构分叉——可认领子任务要不要复用现有 `tasks` 表的 `parent_task_id` 机制——**已跟用户对齐:复用 `tasks` 表,加字段区分类型**。原因见下节,这条决定直接决定了后面所有设计。 - -### 数据模型:新增 `subtask_kind` 字段 + 两个新 `status` 值 - -`tasks` 表新增列 `subtask_kind`(`NULL` | `'acceptance_item'` | `'claimable_work'`),CHECK 约束仿 `acceptance_mode` 那次迁移的写法(batch 迁移): - -- `NULL`:普通 task(现状不变,绝大多数行)。 -- `'acceptance_item'`:43 号文档现有机制——`maybeMaterializeItemizedSubtasks` 从 `acceptance.md`/`goal-benchmark.json` 物化出的验收条目,**行为完全不变**,只是把它们打上一个明确的类型标签(之前隐含在"parent_task_id 非空"里,现在显式区分)。 -- `'claimable_work'`:Phase 3 新增——`subtask-planning` 产出的 `subtask-plan.json` 里 `steps[]` 物化出的可认领工作项。 - -两者都用 `parent_task_id` 指向同一条父 task,语义/生命周期完全独立,靠 `subtask_kind` 分流——这是为什么不需要开新表:现有 `tasks` 表本来就是"通用工作项容器",`subtask_kind` 只是给容器里已经混杂的两种东西正名,不是往一个干净的模型里硬塞第二套语义。 - -`status` 枚举新增两个值:`'claimable'`(已物化、开放认领、还没人要)、`'claimed'`(已被认领,等待被处理——处理方式见下"认领后怎么办"一节)。只对 `subtask_kind='claimable_work'` 的行有意义,其余行继续用现有 `draft|queued|running|completed|failed|blocked|cancelled` 六态,不受影响。新增 `claimed_at` 时间戳列(认领发生的时间,用于超时/僵尸认领巡检——复用心跳巡检已经在用的"时间戳 + 巡检" 模式,不新造一套监控)。 - -### 必须同步改的一处:`settleNode` 的 `SUBTASKS_INCOMPLETE` 硬闸门要收窄 - -`graph-engine.js:1881-1889`(43 号文档实现)现在是: - -```js -// coder-validator 报 success 时,查 parent_task_id 指向该 task 的所有行是否 completed -SELECT id, status FROM tasks WHERE parent_task_id = ? -``` - -Phase 3 一旦开始物化 `claimable_work` 行,这条查询会立刻把它们也扫进去——一堆天然不会是 `completed`(它们的终态是 Phase 4 才要定义的东西)的 `claimable_work` 行会让这条闸门对**所有**开了 `subtask-planning` 的 task 都误判 `SUBTASKS_INCOMPLETE`,把本该正常通过的验收打成失败。**这是实现时第一件必须做的事,不是可选项**:查询加一条 `AND subtask_kind = 'acceptance_item'`,把两套子任务的完成判定彻底分开。 - -### 触发/物化时机:跟 `subtask-planning` 同一时刻,不等 org-planner - -43 号文档的验收条目物化时机是"org-planner 完成后"(依赖它的产出)。Phase 3 的物化对象不一样——`subtask-plan.json` 在 `enqueueTaskRun` 时就异步产出了(`triggerSubtaskPlanning`),比 org-planner 开始规划早得多。**物化时机定在 `triggerSubtaskPlanning` 成功写完 `subtask-plan.json` 之后立刻做**,不拖到 org-planner 跑完: - -- 呼应用户原始诉求"分解任务 → 所有 agents 围绕这个任务看板来工作,一层一层地接子任务"——如果拖到 org-planner 整个流程跑完才有"看板","围绕看板协作"这句话在 org-planner 执行期间根本无从谈起。 -- 技术上更省事:`triggerSubtaskPlanning` 已经是 best-effort、绝不 throw 的收尾逻辑,加一步物化不引入新的失败模式(复用同一套"软失败,只打日志"的处理)。 - -具体实现:`steps[].depends_on_hint` 是这批 steps 内部的 1-based `seq` 引用,物化时先建好全部行拿到真实 `task.id`,再回填 `depends_on`(复用 `tasks.depends_on` 现有的"JSON 数组存 task id"格式,不新造依赖表示法)——`depends_on_hint` 本身"是提示不是硬依赖"这条设计(Phase 1 agent.md 就这么写的)延续下来:Phase 3 物化后的 `depends_on` 同样只做展示,**不做认领前置校验**(见下"明确不做")。 - -### 认领 API:这是本轮唯一真正的新机制 - -调研确认:**全仓库目前没有任何"认领/接单(pull)"语义的端点**——`executor='agent'` 那条路径是纯粹的"派发(push)":发送方在 `/task/dispatch` 请求体里显式指定 `to`,从没有过"多方竞争同一条任务,谁先抢到算谁的"这种场景,所以也没有任何 CAS 并发保护先例可抄。这是 Phase 3 唯一必须新写的东西: - -``` -POST /api/company/:companyId/tasks/:taskId/claim {agentId} -``` - -服务端逻辑(仿 `claimQueuedTask` 的 CAS 写法,`work-graph-db.js:2209`): - -```sql -UPDATE tasks SET status='claimed', assignee_agent_id=?, claimed_at=? -WHERE id=? AND subtask_kind='claimable_work' AND status='claimable' -``` - -`changes === 0` 就是"没抢到"(要么已被别人认领,要么状态不对),返回 409,不是 500——这是预期会发生的正常竞争结果,不是错误。对称补一个 `POST .../unclaim`(`status='claimed' → 'claimable'`,`assignee_agent_id` 清空)——认领了发现做不了要能退回去,成本很低,没理由不做。 - -**认领角色范围**:不引入能力匹配(调研确认 `system_agents`/`company_agents` 表没有任何结构化角色能力字段,能不能干一件事完全靠 agent 自己读 `agent.md` 判断,代码层不做匹配)。Phase 3 用最简单的白名单:`claimable_work` 行默认只对 `role_name='org-coder'` 开放认领(当前系统唯一的通用"写代码"角色,且物化出的 steps 基本都是实现类工作),认领 API 校验调用者的 `role_name` 是否在白名单里,不在直接拒绝。这个白名单先写死,不是留数据库字段——真需要扩展到多角色再加,不为假想的未来预先建模。 - -### 认领后怎么办:复用现成的 agent 派单路径,不新起执行机制 - -这是这轮设计里最重要的一处化简,值得单独说清楚:最初以为"认领后要真正驱动执行"必然要碰 `graph-engine.js`(比如给每个 `claimed` 的子任务起一次独立 work-graph run),那就是 Phase 4 的范畴,而且是范式级改动。但调研 `executor='agent'` 那条路径后发现——**它本来就是"一条 task 归某个 agent 负责、这个 agent 自己想办法搞定、搞定后自己调 `/task/report` 上报"的完整闭环**,跟"认领了之后谁去干"这个问题的答案高度重合。 - -所以 Phase 3 的落地方案是:`claim` 成功后,除了状态转成 `claimed`,**同时把这条行的 `executor` 设成 `'agent'`**——这样它立刻变成一条普通的"agent 自主任务",认领它的 agent(多半是 org-coder)用它已经在用的既有机制(自己决定怎么干、干完调 `/task/report`)走完剩下的生命周期,`META_TO_TASK_STATUS` 状态机(`agent-chat.js:1795`)直接接管后续的 `dispatched → done/failed/blocked/cancelled` 转移,**不需要 Phase 3 再发明一套新的执行触发逻辑**。`task-scheduler.js` 明确只认 `executor='graph'`(367 行注释已经写了这条判据),所以这条行天然不会被现有的图排队/并发逻辑碰到,两套系统互不干扰。 - -这个方案的好处:Phase 3 做完,"认领"不是一个没人会因此真正干活的静态标记,是一个立刻能跑的真实闭环,而且**零新增执行机制、不碰 graph-engine 一行代码**,完全落在 Phase 1 已经定案的边界内("图本身怎么跑不变")。代价是诚实的:`org-coder` 认领到的这一步"怎么干"完全靠它自己(可能是直接写代码、可能是走它熟悉的其他工具),没有 Phase 4 设想的那种"独立开发 + 独立验证"的正式生命周期(写代码有没有经过验证、有没有走测试,全凭 org-coder 自己负责)——这正是 Phase 4 真正要解决的问题:现在这条最小可用路径能跑,但"跑得糙",要不要、以及怎么给它补上正式的验证循环,留给 Phase 4 设计时决定。 - -### 明确不做(本轮范围边界) - -- **不做依赖顺序强制**:`depends_on` 只展示,不阻止认领——判断"前一步是否真的做完了"需要一个可靠的完成判据,Phase 3 阶段(`executor='agent'` 自主执行)没有这个判据,强行卡会卡出一堆认领不了的死锁。 -- **不做能力匹配**:认领角色白名单写死 `['org-coder']`,不引入任何"这一步该给谁"的推断逻辑。 -- **不碰 `graph-engine.js` 执行/调度模型**:认领这件事完全在 `tasks` 表 + 一个新端点里完成,不新增图节点类型、不改拓扑推进逻辑。 -- **不做 Phase 4 设想的"独立开发+验证循环"**:认领后的执行完全复用现有 `executor='agent'` 闭环,`org-coder` 自己对交付质量负责,没有独立验证阶段。 -- **不做看板 UI(Phase 5)**:可认领池先靠 `GET /:companyId/tasks?subtaskKind=claimable_work&status=claimable` 这类查询参数(复用现有 `parentTaskId` 过滤模式加一个新参数)暴露数据,UI 展示留给 Phase 5。 - -### Phase 3 验收标准(后续实现完要检查的) - -1. `subtask-plan.json` 产出后,`claimable_work` 子任务是否正确物化(`depends_on` 回填正确、`status='claimable'`)? -2. 有 `subtask-planning` 挂钩的 task,`coder-validator` 的验收判定是否完全不受这些新增 `claimable_work` 行影响(`SUBTASKS_INCOMPLETE` 闸门收窄生效)? -3. 并发认领同一条子任务,是否只有一方成功(CAS 生效,409 不是 500)? -4. 认领成功后,这条行是否真的能走完 `executor='agent'` 的既有生命周期(`org-coder` 自己完成、`/task/report` 上报、状态正确落到终态)? -5. 非白名单角色尝试认领,是否被正确拒绝? - -## Phase 3 实现记录(v1,2026-09-02,⚠️ 代码已被 v2 替换/删除——见「Phase 3 v2」) - -> 本节记录的代码(`claimSubtask`/`unclaimSubtask`/`/claim`/`/unclaim`/`claimable`/`claimed`)**已经不在仓库里**。保留这份记录是因为下面的"真实端到端回归"小节记录了促成 v2 拍板的两个关键发现(认领没有触发源、`/task/report` 耦合了非 graph 回调),这两个发现本身仍然成立、仍然是 v2 存在的理由,不能删。 - -### 落地清单 - -| 内容 | 位置 | -|---|---| -| schema:`subtask_kind` + `claimed_at` | `work-graph-db.js`(批 6 迁移,`ALTER TABLE ADD COLUMN`,纯增量向后兼容;`subtask_kind` 取值域 `'acceptance_item'|'claimable_work'`,只有新建库有 CHECK,存量库应用层守,跟 `acceptance_mode` 那次迁移同一套限制) | -| `createTask` 校验 | 新增 `subtaskKind`/`status` 参数:`subtaskKind` 非法值拒、没有 `parentTaskId` 拒;`status` 只放开 `'draft'`/`'claimable'` 两个初始值,`'claimable'` 只能配 `subtaskKind='claimable_work'` | -| 认领 CAS | `claimSubtask`/`unclaimSubtask`(`work-graph-db.js`)——全仓库第一个"认领(pull)"语义写入,照抄 `claimQueuedTask` 的 `changes` 判据。认领成功同时把 `executor` 改成 `'agent'`,复用既有 `executor='agent'` 派单闭环(`/task/report` 上报),**不碰 `graph-engine.js` 一行调度逻辑**,跟设计阶段的判断一致 | -| 物化 | `materializeClaimableSubtasks`(`work-graph-db.js`)——两阶段建法处理 `depends_on_hint` 前向引用,幂等(按 `subtask_kind='claimable_work'` 计数),从 `subtask-planning.triggerSubtaskPlanning` 写完 artifact 后立刻调用,不等 org-planner | -| **必须同步改的坑,已修** | `graph-engine.js` 的 `settleNode` `SUBTASKS_INCOMPLETE` 闸门查询收窄成 `AND subtask_kind = 'acceptance_item'`;`maybeMaterializeItemizedSubtasks` 的幂等判据和 `createTask` 调用同步打上 `subtask_kind='acceptance_item'`。两处都补了回归测试(见下) | -| 路由 | `POST /:companyId/tasks/:taskId/claim`、`.../unclaim`;`taskView` 补 `subtaskKind`/`claimedAt`;`GET /:companyId/tasks` 加 `subtaskKind` 过滤(可认领池查询=`?subtaskKind=claimable_work&status=claimable`,不用专门开池子端点) | -| 角色白名单 | 写死 `CLAIMABLE_WORK_ROLES = ['org-coder']`(`company.js`),不做能力匹配 | -| 测试 | 新增 `task-pool-claim.test.js`(23 条:DB 层 `createTask` 校验/`materializeClaimableSubtasks`/CAS + 路由层白名单/并发/放弃);`task-decomposition-validator-gate.test.js` 补 1 条回归测试(`claimable_work` 子任务不影响 `acceptance_item` 验收判定)+ 更新既有用例显式打 `subtaskKind`(闸门收窄后这些用例如果不补 `subtaskKind: 'acceptance_item'` 会全部失效——查询看不到没打标的行,等于闸门形同虚设,真被这次改动摸到了);`task-decomposition-materialize.test.js` 补 `subtask_kind` 落库断言。全量单测 1202 条,1200 passed,唯二失败与本次改动无关、改动前即存在 | - -### 一次诚实的记录:本地库被顺手迁移了一次 - -排查一处语法错误(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 一下"就放松。 - -### 真实端到端回归(2026-09-02,已完成,发现两个真实问题) - -用真实公司 `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 子进程(会话池里保留的常驻进程随服务器关闭一起清零,不是泄漏)。 - -**发现一:认领之后没有任何机制通知 org-coder。** `claimSubtask` 纯粹是一次数据库状态变更——认领 / 放弃 / 重新认领全程,服务端日志零 agent 活动。也就是说,除非有人或有别的机制手动调用 `/claim`,这些可认领子任务会永远原地躺着,**没有任何东西会主动去发现它们、认领它们**。这是设计阶段"认领后怎么办"一节已经承认的缺口("跑得糙"),真实回归把它坐实成了一个具体、可复现的观察:机制本身工作正常,但**没有触发源**。 - -**发现二:`/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 会话"。 - -**`/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 完全平行的第二套执行世界。 - -### 遗留给 Phase 4 的问题 - -- 现在"认领后怎么干"完全靠 `org-coder` 自主决定,质量没有保障——要不要给 `claimed` 状态补一段正式的"开发 + 验证"生命周期?如果要,是新起一次简化版 work-graph run,还是在现有 `executor='agent'` 闭环里加一道可选的验证钩子(成本更低,但仍然不是"独立 run")?两个方向哪个都不是这次 Phase 3 该决定的事,需要单独评估。 -- 子任务级预算:`executor='agent'` 现有闭环目前完全没有预算概念(`budget_json` 只在 `executor='graph'` 的 run 上有意义)——如果 Phase 4 决定要给认领的子任务设预算,需要先补这个空白。 - ---- - -## Phase 3 v2:CEO 直接派发(2026-09-02,代码已实现,未做真实端到端回归) - -### 为什么推翻 v1(讨论过程,不是事后诸葛亮) - -v1 真实回归跑完,用户追问"认领后没有触发源,谁来触发"——讨论过程: - -1. 用户最初设想:每个候选角色(`org-coder`)自己开心跳,扫描"有没有可认领的、匹配我角色的子任务",够条件自己 `/claim`。 -2. 反对意见(用户自己提出):心跳生命周期管理的开销是真实问题——几百个 agent 都开心跳,谁来决定什么时候启、什么时候停?如果还是需要 CEO 来管这些心跳的启停,那跟 CEO 直接扫一遍指派没有区别,等于多绕了一层。 -3. 拍板:**CEO 心跳统一发现 + 直接指派(push),不需要认领机制**。CEO 本来就有心跳、本来就在盯 task 板,成本最低;CEO 还能顺带控制派发节奏(先放哪批、等哪批)。 -4. 顺着这条拍板往下查:现有 `enqueueTaskRun`(起 org/work graph run 的唯一入口)默认假设自己服务顶层 task(硬要求 `status==='draft'`,起 run 时把 status 强改成 `'queued'`)——这个状态转移**跟 v1 的 `claimable`/`claimed` 状态机直接冲突**(CAS 会静默失败)。查证过程中发现:v1 设计"认领"要解决的"多方竞争"场景,在"CEO 一人拍板"的模型下根本不存在——CAS 是在给不存在的问题找解,不是保留价值、只是不再需要的复杂度。**拍板删除 `claim`/`unclaim` 和相关状态**,改成"物化即普通 draft task,CEO 判断节奏后调现成的 `/enqueue`"。 -5. 顺带定了执行载体:"先跑一个轻量的子任务图看效果,遇到质量问题再升级成完整 org graph"——不是先验地假设哪个对,是留一个可以真实观察、按需升级的路径。 - -### 落地清单 - -| 内容 | 位置 | -|---|---| -| 删除的代码 | `claimSubtask`/`unclaimSubtask`(`work-graph-db.js`)、`POST /claim`/`POST /unclaim`(`company.js`)、`CLAIMABLE_WORK_ROLES` 白名单、`claimable`/`claimed` 两个 status 值、`claimed_at` 列(连同批 6 迁移里加它的那部分) | -| 改名 | `subtask_kind` 的枚举值 `'claimable_work'` → `'decomposed_work'`(连同 `materializeClaimableSubtasks` → `materializeDecomposedSubtasks`)——"认领"这个词不再准确,趁还没发布改掉比以后改省事 | -| 物化行为简化 | `materializeDecomposedSubtasks` 物化出的行现在就是普通 `status='draft'` task(不再有专属初始状态),`executionMode='auto'`(CEO 一旦 enqueue 就该一路跑完,不停在入口等人工 `/next`) | -| **新增:递归防护** | `triggerSubtaskPlanning`(`subtask-planning.js`)新增 `if (task.parent_task_id) return`——这是设计 v2 时才发现的真实漏洞:CEO 对 `decomposed_work` 子任务调 `/enqueue` 时,该端点默认 `withSubtaskPlanning:true`,不挡的话会对一条已经是"第几步怎么做"粒度的子任务再拆一层,浪费调用还可能无限递归。已用测试验证挡住 | -| **新增:轻量子任务图** | `createDecomposedWorkGraph`(`work-graph-db.js`)——现造一张两节点 work graph(`impl`=`org-coder`、`validate`=`org-validator`,都是 `agent_call` 不是 `subgraph`),跳过公司完整 org graph 的 ceo→cto→org-planner→org-coder→org-validator 规划仪式。理由:`subtask-planning` 已经把"怎么做"拆到这一步的粒度了,再走一遍 org-planner 的 brd/prd/sdd 纯属重复劳动 | -| `enqueueTaskRun` 图选择 | `company.js`:`taskRow.subtask_kind === 'decomposed_work'` 时用 `graphKind:'work'` + 现造的图;否则行为完全不变(`graphKind:'org'` + `companyId`),回归测试覆盖 | -| CEO 心跳/工具 | `system/agents/ceo/heartbeat.md`、`tools.md` 补了"查 `?subtaskKind=decomposed_work&status=draft`、按节奏 `/enqueue`"的指令——`enqueue` 是 CEO 已经被授权自主调用的现成端点,子任务用同一条权限,不用新开一条 | -| 测试 | 删除 `task-pool-claim.test.js`(v1 的认领测试,随代码一起废弃),新增 `task-decomposition-dispatch.test.js`(12 条:DB 层 `createTask` 校验/`materializeDecomposedSubtasks`/`createDecomposedWorkGraph`,路由层 `enqueueTaskRun` 图选择回归 + decomposed_work 分支),更新 `task-decomposition-validator-gate.test.js`/`task-decomposition-materialize.test.js` 的措辞。全量单测 1191 条,1189 passed(唯二失败与本次改动无关、改动前即存在),零残留 CLI 子进程 | - -### 一处没有真实验证过的假设,如实写在这里 - -`org-coder`/`org-validator` 平时**只在"公司完整 org graph 的 subgraph 节点"这个上下文里被调用过**——它们的 `agent.md`/`tools.md` 有没有隐含"前面一定有 brd/prd/sdd 产物"这种假设,直接当轻量图里的裸 `agent_call` 节点调用,会不会读不到预期上下文、表现异常——**没有实测过**。图的机制本身(建图、起 run、CAS 判据不冲突)单测覆盖了,但"跑起来输出好不好"是内容问题,机制通不通答不了这个问题。这正是用户拍板"先跑轻量版看效果"这句话里"看效果"的意思——下一步真实回归要验的就是这个。 - -### 真实端到端回归(2026-09-02,已完成,机制全部验证通过,效果发现一个真实结构性缺口) - -用真实公司 `p2verify0902` 走完一次真实链路——真实 goal→task→dispatch → `subtask-planning` 产出 2 步分解并物化成 `decomposed_work` 草稿 → **真实 CEO 心跳**(不是模拟)读到 `heartbeat.md` 新加的指令后,真的查了 `?subtaskKind=decomposed_work&status=draft`、真的判断了节奏、真的调了 `/enqueue`——不只是这两条,CEO 还顺带自己发现并处理了一条与本次测试无关的历史遗留卡住的 run(证明心跳修复后其余巡检逻辑也正常了)。触发过程中确认 `triggerSubtaskPlanning` 的递归防护在真实场景下正确拦下了对子任务本身再拆一层的尝试(8 条 `decomposed_work` 行全部被正确跳过)。 - -**一处真实的额外发现(与 Phase 3 无关,但直接堵住了这次验证,顺手修了)**:触发 CEO 心跳时先撞上 401——`company-heartbeat-service.js`/`heartbeat-service.js` 里所有自环调用 `/api/agent/:agentId/chat`(`agent-chat.js` 挂了 `requireAuthOrInternal`)**都没有带内部令牌**,而 `company.js`/`task-settle.js` 的同类自环调用早就在带。也就是说**心跳系统的两条自触发路径(含 CEO 心跳)一直在暗中 401**,失败只落一行 `console.warn`,界面上什么都看不出来。已按现成模式补上 `INTERNAL_TOKEN_HEADER`(两个文件),修完确认心跳能正常跑完整段推理会话。这条影响面比 Phase 3 大得多,值得单独跟进核实生产环境是否也受影响。 - -**"看效果"的真实结果——机制通,内容质量分裂**: - -- `org-coder`(impl 节点)**表现完全达标**:给单位换算任务真实产出了 `src/convert.js`(正确的换算因子:km=1000/m=1/mi=1609.344/ft=0.3048)+ 一份质量相当好的测试文件(含同单位恒等、非法单位报错、往返精度、浮点容差等边界情况),**完全没有 brd/prd/sdd 上游产物,产出依然靠谱**——这条验证了"轻量图跳过 org-planner 规划仪式"这个核心假设成立。 -- `org-validator`(validate 节点)**卡死,`input_underspecified`**:它调用的 `goal-contract.js validate --run-dir` 硬性要求 `artifacts/acceptance.json` 存在,而这份文件只由 org-planner 的 work graph(`goal-benchmark`/`goal-test-data`/`goal-test-script` 三个节点)产出——轻量图完全跳过了 org-planner,这份文件天然不存在。**org-validator 自己给出的诊断相当精确**:明确写"本次上游 coder 节点已产出 src/convert.js + test/convert.test.js,属正常交付,不是本次失败的责任方",把根因准确指向 org-planner 缺位,`target_org_node` 字段也写对了。 -- 依赖阻塞也验证了:第二个子任务(依赖第一个)在第一个卡在 `blocked`(不是 `completed`)后,正确地没有被调度器放行,一直停在 `queued`——`task-scheduler` 的既有依赖推导对 `decomposed_work` 子任务同样生效,不需要 CEO 自己算顺序,讨论时的判断成立。 - -### 结论:轻量图的"实现"这一半跑通了,"验证"这一半有一个真实的结构性缺口 - -不是 agent 表现不好,是图的构成本身缺一块——`org-validator` 依赖的验收契约只能来自 org-planner,轻量图里没有任何节点能产出它。**这不是"再调一下提示词"能解决的**,是"要不要跳过 org-planner"这条设计决策本身的直接代价。摆几个方向留给下一步讨论: -1. 轻量图加一个第三节点,专门产出一份最小的 `acceptance.json`(不跑 org-planner 整套 brd/prd/sdd,只把 `subtask-planning` 已经写好的"这一步要做什么"转成 goal-contract 认的验收格式); -2. `org-validator` 之外找一个不依赖 `goal-contract.js`/`acceptance.json` 的轻量验证方式(比如直接跑 `org-coder` 自己写的测试文件、看退出码); -3. 按用户原话的退路——这条子任务级别就不追求跑通 `org-validator`,直接升级成完整 org graph(这就是当初讨论"先跑轻量版看效果,不行再走完整 org graph"里"不行"的具体触发条件)。 - -**后续讨论收敛到方向 1 的一个更彻底的版本**:不是"轻量图加一个专门产契约的节点",而是"每个 task/subtask 创建时就必须 1:1 带一份验收契约"——把约束点从"某张图怎么搭"提到"task 这个实体本身"。完整讨论过程、方案取舍、以及需要改动的 agent/代码盘点见 [48 号文档](./48-task-acceptance-contract-binding.md)。 diff --git a/system/agents/ceo/agent.md b/system/agents/ceo/agent.md index 0010dc7..4d713c9 100644 --- a/system/agents/ceo/agent.md +++ b/system/agents/ceo/agent.md @@ -80,14 +80,13 @@ KR 必须**可度量**("日产 20 条合格短视频"),不能是"提升内 ## 跨公司经验提醒(dream) -_由 dream 于 2026-09-02 提炼自跨公司经验;本区块由机器维护,区块外的内容不会被改动。_ - -- **看到 blocked 先判「活的问题」还是「账面滞后」,后者不需要任何动作** —— 现象:兜底扫描反复报同一条 blocked task。核了根因后发现——修复放行后 blocked 行不回写、或该行从未有真实运行体,属纯账面滞后,不是运行中被卡。做法:每次被唤醒先核该行的 blockedReason 与底层 run 的真实状态/最后更新时间,确认与账面不一致即归档结论,不改优先级、不提新任务。否则会为同一幽灵问题反复投入。 -- **判断 agent 是否真在工作,看两次采样的 tokens/agent_calls 是否增长,别信状态字段** —— 现象:状态显示 running,但 tokens 与 agent_calls 两次采样完全不变,节点产物目录零新写入——实为进程已死的假 running。做法:间隔采样资源消耗量做对比;在涨就是真在工作(即使偏慢也继续观察,不需干预),不涨则判死亡,且要连带怀疑它持有的并发坑位没被释放。 -- **并发坑位被死亡会话占住时,retry 只改状态不真正派发** —— 现象:节点跑完前静默死亡,之后 retry 显示已受理却长时间不产生任何新写入;同时坑位计数 used=1,而全公司没有任何 running task——说明死会话漏释放坑位,attempt 被自己的锁卡住。做法:retry 无效时先对「坑位占用数」与「实际 running 数」做交叉核对,不一致就是泄漏的锁,不是任务本身的问题。 -- **输入零新增工作时重派是确定性空转,应 hold 而不是再烧一轮** —— 现象:第 2 轮重判 FAIL 后重派,会发现上游产物的时间戳早于两份 FAIL 判决、四轮产物指纹完全一致——即没有任何新输入。做法:重派前先验证上游产物 mtime / 指纹是否变化;不变则 validator 必然产出第三份一模一样的 FAIL,此时 hold 住重试并升级给裁决方(或修输入),而不是消耗第三轮重写+重验预算。 -- **预算续跑端点只在 blocked 态开放,撞墙前要预先算好余量并留预案** —— 现象:墙钟/token 上限可以追加,但端点只在任务已 blocked 时开放,运行中加不进去。做法:进入多节点长链前用「每节点实测耗时 × 节点数」估算总墙钟,与上限、剩余 token 对比;明确指出最后一轮回炉名额 + 剩余 token 若不足以再跑一轮重写+重验,就把「撞墙后立即对预算端点追加」写成给下一轮的预案,而不是等真的卡死才想。 -- **判 FAIL 打回时只归因实现缺陷、fail_reason 指名道姓,并给回炉设上限** —— 现象:验证环节把 FAIL 按 fail_reason 逐项指名打回,只写实现缺陷不写成基础设施抖动,并给回炉固定轮数上限。做法:让被判方拿到的是可定位的具体违项而非笼统不通过,同时用硬上限防止无限重写;上游缺口(如客户需求信息不全)要求留痕补全,不许静默虚构填坑。 -- **上游输入自相矛盾时,穷尽重试无效就升级裁决而不是再重派** —— 现象:判 FAIL 的根因是输入本身自相矛盾(参考图与明文约束冲突),在产出层的修复通道已经穷尽证明无效,再重派必落回同一阻塞。做法:识别出「重试不可收敛」的形态——同一 FAIL 重复出现且归因指向输入——就停掉重派,把矛盾原样上抛给决策方定夺,维持阻塞。 +_由 dream 于 2026-09-07 提炼自跨公司经验;本区块由机器维护,区块外的内容不会被改动。_ + +- **自相矛盾的状态字段(如完成时间已写但进度仍为0)说明是回写缺陷,不是任务真卡死** —— 巡检时如果发现一个任务的阻塞原因为空、进度字段仍显示未完成,但完成时间戳却已经被写入,这几个字段互相矛盾,最合理的解释是负责回写完成态的逻辑有缺陷,而不是任务真的卡住或调度漏单。遇到这种字段自相矛盾的模式,应判定为已知的写回问题复现,不必当新故障处理。 +- **判定“预算耗尽”前先看节点是否真正执行过,暂停期间挂钟仍计时会造成假阳性** —— 任务被标记为预算耗尽时,先检查各执行节点的尝试次数和运行 id:如果全部为初始值、从未真正开始执行,但累计耗时却远超预算上限,很可能是任务在人工暂停期间挂钟时间仍在累积所致,而不是真的超支执行,也不是调度器出了新问题。 +- **批量阻塞告警先回溯依赖链,同一根因的级联传导不算新问题** —— 收到一批任务同时转入阻塞状态时,先按依赖关系回溯:如果它们都是级联失败,最终指向同一个已经在排查中的根因任务,应判定为该根因的正常传导复现,不需要为每一次复现都新开任务或重新报告,避免制造重复噪音。 +- **同一已知问题反复触发告警时,先查负责修复的代码是否已提交/重启生效,而不是每次都重新处理** —— 如果一个问题已经定位、修复代码也在开发,但反复看到同样的告警复现,应先确认修复所在的工作区是否仍处于未提交/未部署状态——没有落地生效之前问题会持续复现是预期内的,属于负责该模块的角色自己的工程节奏(提交、测试、部署),不需要越权催促或每次都当新故障上报。 +- **协作方指明用它自己的工具修复时,转发给对方执行而不是越权代劳;系统性根因修复走正式排期,不当场自动执行** —— 当负责某个系统的角色明确指出应该用它自己才有的工具去做某个恢复动作时,应该把这个动作转发给对方去执行,而不是自己找替代方式绕过去做。同时,针对根因的系统性修复应该记录成一个正式的、可被后续排期的跟进任务,而不是当场直接自动执行——现场修复和系统性修复要分开处理。 +- **父任务显示失败但其拆分出的子任务全部完成,是完成态汇总缺陷,不是真的失败** —— 遇到一个被拆分成多个子任务的父任务本身状态显示失败,但检查发现它拆出的全部子任务都已完成时,说明问题出在父任务没有正确地把子任务完成情况汇总回落到自己的状态上,而不是这个父任务真的执行失败了。 diff --git a/system/agents/ceo/soul.md b/system/agents/ceo/soul.md index e8761b6..eeb5983 100644 --- a/system/agents/ceo/soul.md +++ b/system/agents/ceo/soul.md @@ -26,14 +26,13 @@ ## 从经验里长出来的倾向(dream) -_由 dream 于 2026-09-02 提炼自跨公司经验;本区块由机器维护,区块外的内容不会被改动。_ - -- **看到 blocked 先判「活的问题」还是「账面滞后」,后者不需要任何动作** —— 现象:兜底扫描反复报同一条 blocked task。核了根因后发现——修复放行后 blocked 行不回写、或该行从未有真实运行体,属纯账面滞后,不是运行中被卡。做法:每次被唤醒先核该行的 blockedReason 与底层 run 的真实状态/最后更新时间,确认与账面不一致即归档结论,不改优先级、不提新任务。否则会为同一幽灵问题反复投入。 -- **判断 agent 是否真在工作,看两次采样的 tokens/agent_calls 是否增长,别信状态字段** —— 现象:状态显示 running,但 tokens 与 agent_calls 两次采样完全不变,节点产物目录零新写入——实为进程已死的假 running。做法:间隔采样资源消耗量做对比;在涨就是真在工作(即使偏慢也继续观察,不需干预),不涨则判死亡,且要连带怀疑它持有的并发坑位没被释放。 -- **并发坑位被死亡会话占住时,retry 只改状态不真正派发** —— 现象:节点跑完前静默死亡,之后 retry 显示已受理却长时间不产生任何新写入;同时坑位计数 used=1,而全公司没有任何 running task——说明死会话漏释放坑位,attempt 被自己的锁卡住。做法:retry 无效时先对「坑位占用数」与「实际 running 数」做交叉核对,不一致就是泄漏的锁,不是任务本身的问题。 -- **输入零新增工作时重派是确定性空转,应 hold 而不是再烧一轮** —— 现象:第 2 轮重判 FAIL 后重派,会发现上游产物的时间戳早于两份 FAIL 判决、四轮产物指纹完全一致——即没有任何新输入。做法:重派前先验证上游产物 mtime / 指纹是否变化;不变则 validator 必然产出第三份一模一样的 FAIL,此时 hold 住重试并升级给裁决方(或修输入),而不是消耗第三轮重写+重验预算。 -- **预算续跑端点只在 blocked 态开放,撞墙前要预先算好余量并留预案** —— 现象:墙钟/token 上限可以追加,但端点只在任务已 blocked 时开放,运行中加不进去。做法:进入多节点长链前用「每节点实测耗时 × 节点数」估算总墙钟,与上限、剩余 token 对比;明确指出最后一轮回炉名额 + 剩余 token 若不足以再跑一轮重写+重验,就把「撞墙后立即对预算端点追加」写成给下一轮的预案,而不是等真的卡死才想。 -- **判 FAIL 打回时只归因实现缺陷、fail_reason 指名道姓,并给回炉设上限** —— 现象:验证环节把 FAIL 按 fail_reason 逐项指名打回,只写实现缺陷不写成基础设施抖动,并给回炉固定轮数上限。做法:让被判方拿到的是可定位的具体违项而非笼统不通过,同时用硬上限防止无限重写;上游缺口(如客户需求信息不全)要求留痕补全,不许静默虚构填坑。 -- **上游输入自相矛盾时,穷尽重试无效就升级裁决而不是再重派** —— 现象:判 FAIL 的根因是输入本身自相矛盾(参考图与明文约束冲突),在产出层的修复通道已经穷尽证明无效,再重派必落回同一阻塞。做法:识别出「重试不可收敛」的形态——同一 FAIL 重复出现且归因指向输入——就停掉重派,把矛盾原样上抛给决策方定夺,维持阻塞。 +_由 dream 于 2026-09-07 提炼自跨公司经验;本区块由机器维护,区块外的内容不会被改动。_ + +- **自相矛盾的状态字段(如完成时间已写但进度仍为0)说明是回写缺陷,不是任务真卡死** —— 巡检时如果发现一个任务的阻塞原因为空、进度字段仍显示未完成,但完成时间戳却已经被写入,这几个字段互相矛盾,最合理的解释是负责回写完成态的逻辑有缺陷,而不是任务真的卡住或调度漏单。遇到这种字段自相矛盾的模式,应判定为已知的写回问题复现,不必当新故障处理。 +- **判定“预算耗尽”前先看节点是否真正执行过,暂停期间挂钟仍计时会造成假阳性** —— 任务被标记为预算耗尽时,先检查各执行节点的尝试次数和运行 id:如果全部为初始值、从未真正开始执行,但累计耗时却远超预算上限,很可能是任务在人工暂停期间挂钟时间仍在累积所致,而不是真的超支执行,也不是调度器出了新问题。 +- **批量阻塞告警先回溯依赖链,同一根因的级联传导不算新问题** —— 收到一批任务同时转入阻塞状态时,先按依赖关系回溯:如果它们都是级联失败,最终指向同一个已经在排查中的根因任务,应判定为该根因的正常传导复现,不需要为每一次复现都新开任务或重新报告,避免制造重复噪音。 +- **同一已知问题反复触发告警时,先查负责修复的代码是否已提交/重启生效,而不是每次都重新处理** —— 如果一个问题已经定位、修复代码也在开发,但反复看到同样的告警复现,应先确认修复所在的工作区是否仍处于未提交/未部署状态——没有落地生效之前问题会持续复现是预期内的,属于负责该模块的角色自己的工程节奏(提交、测试、部署),不需要越权催促或每次都当新故障上报。 +- **协作方指明用它自己的工具修复时,转发给对方执行而不是越权代劳;系统性根因修复走正式排期,不当场自动执行** —— 当负责某个系统的角色明确指出应该用它自己才有的工具去做某个恢复动作时,应该把这个动作转发给对方去执行,而不是自己找替代方式绕过去做。同时,针对根因的系统性修复应该记录成一个正式的、可被后续排期的跟进任务,而不是当场直接自动执行——现场修复和系统性修复要分开处理。 +- **父任务显示失败但其拆分出的子任务全部完成,是完成态汇总缺陷,不是真的失败** —— 遇到一个被拆分成多个子任务的父任务本身状态显示失败,但检查发现它拆出的全部子任务都已完成时,说明问题出在父任务没有正确地把子任务完成情况汇总回落到自己的状态上,而不是这个父任务真的执行失败了。 -- libgit2 0.21.4