# 追加规划的 node_id 跨 org_node 撞名——需求文档 **日期**: 2026-08-13 **性质**: 需求文档(BRD/PRD 层面),不含详细技术设计。喂给 graphlemon 自己实现的 Goal,本次会话 不直接改代码。 **状态**: 草案,待确认后作为 Goal 提交。 ## 1. 背景 `work_graph_plan.py::plan_work_graph(..., append_to_run_id=...)`(Phase 2c 的合并 run 架构, Coder/Validator 各自往同一个共享 run 里追加自己规划的节点)在检测到新规划的 `node_id` 跟已有节点 撞名时会直接拒绝: ```python collision = existing_ids_set & set(new_node_ids) if collision: raise PlanningFailedError( "append_to_run_id={} node_id collision with existing nodes: {}".format(append_to_run_id, collision) ) ``` 调用方(`plan_work_graph`)会把已有节点 id 列表写进 prompt,明确要求"不要重复用这些 id",但今天 (2026-08-13)真实任务 `911861fe` 撞上了这个拒绝:Validator 自己规划节点时选了 `n_test_script` 这个 id,而这个 id 在同一个 run 里已经被 Coder 用过了。 根因不是"LLM 没读提示词"这么简单——`src/work_graph_plan.py` 里 Coder 和 Validator 的建议节点 目录(`_CODER_NODE_CATALOG`/`_VALIDATOR_NODE_CATALOG`)**都**给"写一个可机械验证的测试脚本"这类 节点建议了同一个 `node_type: "test_script"`。两个不同 org_node 在描述"概念上相似的东西"时,天然 会往同一个规范化命名(`n_test_script`)收敛——即使 prompt 里明确列出了"这些 id 已经被用了", 自然语言指令本身也无法保证绝对不再犯。这不是这一次运气不好,是这个架构下的一个系统性碰撞概率 问题:**只要两个 org_node 的建议目录用相同/相似的 `node_type` 描述概念上相似的节点,撞名概率就 不是小概率事件**。 `PlanningFailedError` 不在 `scheduler._ROUTABLE_EXCEPTIONS` 白名单里,所以这次撞名直接变成了 `blocked_pending_human`/`execution_error`,需要人工介入——跟 `docs/todo/boss-agent-autonomous-blocked-task-arbiter-requirements.md` FR-7 是同一类问题(系统 自己能分类的原因,不该都堆进笼统的兜底桶),但这条更进一步:**这类碰撞,理想情况下根本不该走到 需要人工介入这一步**,应该有更根本的预防或自动恢复机制。 ## 2. 目标 让追加规划(`append_to_run_id`)在遇到 `node_id` 跨 org_node 撞名时,不需要人工介入就能正确处理 (预防撞名,或者撞了之后自动恢复),而不是直接硬失败变成 `blocked_pending_human`。 ## 3. 非目标 1. **不裁定具体用哪种方案**(见 §7)——命名规范前缀(比如按 org_node 加前缀)、失败后带反馈重 规划(跟 planner_reviewer 的重试模式类似)、自动重命名并改写内部引用,三种代价和可靠性都不同, 需要设计阶段权衡。 2. **不改变 `_CODER_NODE_CATALOG`/`_VALIDATOR_NODE_CATALOG` 这两份目录已有的 `node_type` 分类 本身**——"Coder 和 Validator 都需要一个 test_script 类型的节点"这个业务事实是对的、不用改,需要 改的是"两者都可能选出同一个具体 `node_id` 字符串"这件事。 ## 4. 功能需求 | # | 需求描述 | 优先级 | |---|---|---| | FR-1 | `append_to_run_id` 模式下发生 `node_id` 撞名时,不能直接终结为需要人工介入的 `blocked_pending_human`——需要预防(撞名概率显著降低)或自动恢复(撞名后不需要人工也能继续)之一,具体方案见 §7。 | 必须 | | FR-2 | 如果最终方案里撞名之后仍然可能真正无法自动恢复(比如重试次数用尽),这种情况下打的原因也要专属、可区分,不能落入笼统的 `execution_error`(跟 Boss Agent 文档 FR-7 同一方向)。 | 必须 | ## 5. 验收标准 构造一个真实场景:两个不同 org_node(比如 Coder 和 Validator)在同一个共享 run 里,各自的 建议目录都指向同一个 `node_type`(如 `test_script`),触发一次真实的 `claude -p` 自主规划——验证 最终不会因为 `node_id` 撞名而进入需要人工介入的状态。 ## 6. 风险 如果选择"自动重命名"类方案,需要非常小心地同步改写这个新节点自己内部的 `depends_on`/ `on_failure` 引用(如果它引用了自己),改写不完整会引入比"直接拒绝"更隐蔽的错误(图结构损坏, 但没有任何报错,节点执行时才会诡异地找不到依赖)——这条风险比"要不要做"更需要在设计阶段认真 对待。 ## 7. 开放问题 - [ ] **具体用哪种方案**: - (a) 命名规范前缀——要求所有自规划节点的 `node_id` 强制带 org_node 前缀(如 `validator_test_script`),从命名规则上让跨 org_node 撞名在正常情况下不可能发生。最简单、 风险最低,但需要确认这不会破坏已有的 `_normalize_graph_manifest`/`_extract_graph_manifest` 对 `node_id` 格式的既有假设。 - (b) 撞名后带反馈重规划——检测到撞名时不直接失败,而是把撞名信息作为反馈重新调用一次规划 (类似 `_review_planner_output_with_retries` 的重试模式),要求换一个不同的 id。需要设计 重试上限。 - (c) 自动重命名——检测到撞名时自动加后缀改名并同步改写这次新规划里所有对该 id 的内部引用。 见 §6 的风险,代价最高。 - [ ] 跟 `docs/todo/boss-agent-autonomous-blocked-task-arbiter-requirements.md`/ `docs/todo/agent-call-liveness-not-fixed-duration-timeout-requirements.md` 的实现顺序: 三份文档都在处理"什么情况不该变成需要人工介入的 blocked_pending_human",建议一起规划实现 顺序,不要三个 Goal 各自独立设计出三套不一致的"是否需要人工介入"判断逻辑。