append-run-node-id-collision-across-org-nodes-requirements.md 5.85 KB

追加规划的 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. 非目标

  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 各自独立设计出三套不一致的"是否需要人工介入"判断逻辑。