追加规划的 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 跟已有节点
撞名时会直接拒绝:
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. 非目标
- 不裁定具体用哪种方案(见 §7)——命名规范前缀(比如按 org_node 加前缀)、失败后带反馈重 规划(跟 planner_reviewer 的重试模式类似)、自动重命名并改写内部引用,三种代价和可靠性都不同, 需要设计阶段权衡。
- 不改变
_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 的风险,代价最高。
- (a) 命名规范前缀——要求所有自规划节点的
- [ ] 跟
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 各自独立设计出三套不一致的"是否需要人工介入"判断逻辑。