Commit cfedd2b557f75e02bd91f2686e75f03fe2f9d958

Authored by bboonnee
1 parent 033865be

docs: close out the 4 implemented todo docs, fix cross-references

org-graph-unification-and-universal-self-planning-requirements.md,
org-graph-unification-phase2-universal-self-planning-design.md,
scheduler-new-goal-dispatch-requirements.md, and
work-graph-node-execution-deblackbox-requirements.md all moved
docs/todo/ -> docs/done/ (already done in earlier commits via git mv) --
this fixes the remaining docs/todo/ path references to these four files
across the docs that cross-link them.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
docs/done/org-graph-unification-and-universal-self-planning-requirements.md
@@ -8,7 +8,7 @@ @@ -8,7 +8,7 @@
8 8
9 **触发场景**:任务 `2084590a`(修复 `work_node_run.py` 的 `execution_log_file` bug)里,Planner 自规划出的 `benchmark` 节点连续 4 次产出的都是"叙述我做了什么"的散文(跟 `validator-test-script-narrative-output-issue.md` 同一类问题,这次发生在 benchmark 上),LLM judge 的 `on_fail=retry_self` 循环耗尽 `max_attempts=3` 后任务直接 `blocked_pending_human`——暴露出 Validator 从来没有机会在 Coder 开始实现之前复核 Planner 自己的产出。用户随后 2026-08-08 提出四点架构改动设想(见 §1),与当天早些时候整理的 `docs/todo/` 待办优先级建议(P0~P3)高度交叉,要求重新盘点、融合、给出 phase 规划。 9 **触发场景**:任务 `2084590a`(修复 `work_node_run.py` 的 `execution_log_file` bug)里,Planner 自规划出的 `benchmark` 节点连续 4 次产出的都是"叙述我做了什么"的散文(跟 `validator-test-script-narrative-output-issue.md` 同一类问题,这次发生在 benchmark 上),LLM judge 的 `on_fail=retry_self` 循环耗尽 `max_attempts=3` 后任务直接 `blocked_pending_human`——暴露出 Validator 从来没有机会在 Coder 开始实现之前复核 Planner 自己的产出。用户随后 2026-08-08 提出四点架构改动设想(见 §1),与当天早些时候整理的 `docs/todo/` 待办优先级建议(P0~P3)高度交叉,要求重新盘点、融合、给出 phase 规划。
10 10
11 -**状态**: 草稿,讨论中——§4 关键架构决策已拍板(方案 A,合图,2026-08-08),其余 §7 列出的问题仍待确认。沿用 `org-graph-as-work-graph-unified-loop-requirements.md` 的"逐条开放问题讨论 → 用户拍板 → 展开设计文档"两段式流程,本文档本身不是最终设计。 11 +**状态**: ✅ Phase 1/2/3 全部已实现(Phase 1: commit `ea19c7d`,2026-08-08;Phase 2a/2b/2c: 2026-08-12~13;Phase 3: 2026-08-13)。§7 列出的问题全部已拍板/已回答,见该节各条目状态。本文档提出的四点架构改动(§1)已全部落地。
12 12
13 --- 13 ---
14 14
@@ -76,7 +76,7 @@ @@ -76,7 +76,7 @@
76 3. **Fix-skill 节点**:不是修这一次的输出,是**修 Skill 本身**——真实调用已经存在的 Skill 迭代机制(`src/skill_update.py::update_skill()` + `src/bug_record.py::create_bug_record(root_cause_category='skill_defect')`),产出一次真实的、Git 版本化、可回滚的 Skill 内容更新(`skill_iteration` 表,`before_git_commit_sha`/`after_git_commit_sha` 都是真实 commit,不是模拟)。**这套机制在代码库里早就存在**(U-015/U-016 时期就建好了),但至今只在 `org_level_loop.py` 的 Org 级粗粒度路径上被触发过——FR-4 要把它真正接进 Work Graph 内部的判断/回路循环,让它成为节点级失败恢复的常规路径之一,不是只有跨 Org Agent 的大失败才配触发。 76 3. **Fix-skill 节点**:不是修这一次的输出,是**修 Skill 本身**——真实调用已经存在的 Skill 迭代机制(`src/skill_update.py::update_skill()` + `src/bug_record.py::create_bug_record(root_cause_category='skill_defect')`),产出一次真实的、Git 版本化、可回滚的 Skill 内容更新(`skill_iteration` 表,`before_git_commit_sha`/`after_git_commit_sha` 都是真实 commit,不是模拟)。**这套机制在代码库里早就存在**(U-015/U-016 时期就建好了),但至今只在 `org_level_loop.py` 的 Org 级粗粒度路径上被触发过——FR-4 要把它真正接进 Work Graph 内部的判断/回路循环,让它成为节点级失败恢复的常规路径之一,不是只有跨 Org Agent 的大失败才配触发。
77 4. **跳回执行**:Skill 更新落地后,跳回"用这个 Skill 的节点"重新执行——这次用的是刚修好的新 Skill,不是拿旧 Skill 再赌一次运气。跟 `_loop_history_prompt_suffix`(现有机制,把历史反馈拼进 prompt)的本质区别:prompt 里的反馈是**这一次任务专属、跑完就忘**的临时纠偏;Skill 更新是**持久化、跨任务复用**的真正修复——两者不互斥(可以同时生效:这次重试既带着历史反馈,也带着已经更新的 Skill),但 FR-4 的核心价值在后者。 77 4. **跳回执行**:Skill 更新落地后,跳回"用这个 Skill 的节点"重新执行——这次用的是刚修好的新 Skill,不是拿旧 Skill 再赌一次运气。跟 `_loop_history_prompt_suffix`(现有机制,把历史反馈拼进 prompt)的本质区别:prompt 里的反馈是**这一次任务专属、跑完就忘**的临时纠偏;Skill 更新是**持久化、跨任务复用**的真正修复——两者不互斥(可以同时生效:这次重试既带着历史反馈,也带着已经更新的 Skill),但 FR-4 的核心价值在后者。
78 78
79 -**跟 Phase 2 SDD 的关系**:`docs/todo/org-graph-unification-phase2-universal-self-planning-design.md` §5 已经是四步的完整设计(2026-08-12 补齐)——`on_fail.action="dynamic_loop_back"` 触发时,Reflection-review(§5.4)同时判断"跳回哪个节点"和"这是一次性错误还是系统性 Skill 缺陷";系统性缺陷时先经过 Fix-Skill(§5.5,真实调用既有但从未真正投入生产使用的 `skill_update.py::update_skill()`,产出 Git 版本化的 Skill 迭代)再跳回执行。由哪个身份执笔改 Skill,已确认(2026-08-12)统一用 Validator(该文档 §5.5)。 79 +**跟 Phase 2 SDD 的关系**:`docs/done/org-graph-unification-phase2-universal-self-planning-design.md` §5 已经是四步的完整设计(2026-08-12 补齐)——`on_fail.action="dynamic_loop_back"` 触发时,Reflection-review(§5.4)同时判断"跳回哪个节点"和"这是一次性错误还是系统性 Skill 缺陷";系统性缺陷时先经过 Fix-Skill(§5.5,真实调用既有但从未真正投入生产使用的 `skill_update.py::update_skill()`,产出 Git 版本化的 Skill 迭代)再跳回执行。由哪个身份执笔改 Skill,已确认(2026-08-12)统一用 Validator(该文档 §5.5)。
80 80
81 --- 81 ---
82 82
@@ -118,8 +118,8 @@ @@ -118,8 +118,8 @@
118 - **前置条件**:无——SDD 已给出具体改动点,可以直接实施。复核阶段本身可以先用任务内部的粗粒度重试兜底(复核失败 → 完整重新规划),不必等 Phase 2 的精确 loop_back 才能上线,先把"Coder 开始前多一道独立判断"这件事跑起来。 118 - **前置条件**:无——SDD 已给出具体改动点,可以直接实施。复核阶段本身可以先用任务内部的粗粒度重试兜底(复核失败 → 完整重新规划),不必等 Phase 2 的精确 loop_back 才能上线,先把"Coder 开始前多一道独立判断"这件事跑起来。
119 - **验收**:能在(已真实复现过的)"benchmark 输出叙述文字冒充产出"场景下,在 Coder 动手之前拦住任务;崩溃后 resume 不再假设 Planner artifact 一定存在;fresh-run 也不再假设 prd/benchmark 一定存在。 119 - **验收**:能在(已真实复现过的)"benchmark 输出叙述文字冒充产出"场景下,在 Coder 动手之前拦住任务;崩溃后 resume 不再假设 Planner artifact 一定存在;fresh-run 也不再假设 prd/benchmark 一定存在。
120 120
121 -### Phase 2 — 全员自规划 + 合图(方案 A)+ 节点级验证 skill  
122 -- **详细设计**:`docs/todo/org-graph-unification-phase2-universal-self-planning-design.md`。Phase 2a(节点级 subagent/skill 绑定 + `dynamic_loop_back`/Reflection-review/Fix-Skill,commit `86fc0bb`)+ Phase 2b(Coder 接入自规划,commit `7c46a2f`)已实现(2026-08-12);Phase 2c(Validator 接入自规划 + `org_node` 列删除 + 退休粗粒度路由)待实施。设计阶段发现范围比原设想更大——`_GENERIC_PROMPT_TEMPLATE` 从未征集 judgment/on_fail、`run_graph()` 的 subagent_type/skill_ref 是 run 级常量不是节点级、现有 `loop_back` 是规划阶段静态声明的单一目标不支持终审失败时动态选择根因节点(新增 `on_fail.action="dynamic_loop_back"` 解决,复用 `root_cause_trace.py` 陪审团结构)——详见该文档 §1/§5。 121 +### Phase 2 — 全员自规划 + 合图(方案 A)+ 节点级验证 skill ✅ 已实现(2026-08-13)
  122 +- **详细设计**:`docs/done/org-graph-unification-phase2-universal-self-planning-design.md`(已随实现移入 `docs/done/`)。Phase 2a(节点级 subagent/skill 绑定 + `dynamic_loop_back`/Reflection-review/Fix-Skill,commit `86fc0bb`)+ Phase 2b(Coder 接入自规划,commit `7c46a2f`)+ Phase 2c(Validator 接入自规划、`org_node` 列删除、`work-graph-node-review` skill)全部落地,详见该文档最终状态行。设计阶段发现范围比原设想更大——`_GENERIC_PROMPT_TEMPLATE` 从未征集 judgment/on_fail、`run_graph()` 的 subagent_type/skill_ref 是 run 级常量不是节点级、现有 `loop_back` 是规划阶段静态声明的单一目标不支持终审失败时动态选择根因节点(新增 `on_fail.action="dynamic_loop_back"` 解决,复用 `root_cause_trace.py` 陪审团结构)——详见该文档 §1/§5。
123 - **范围**:FR-2 + FR-3 + FR-4,按 §4/§4.1 已拍板的合图路径实现。 123 - **范围**:FR-2 + FR-3 + FR-4,按 §4/§4.1 已拍板的合图路径实现。
124 - **产出**(按拍板后的最终形状): 124 - **产出**(按拍板后的最终形状):
125 1. `work_graph_run.org_node` 列彻底删除(不是下沉/保留),`work_graph_run_query.py`/`work_graph_fork.py` 改造为从 `work_node_execution.subagent_type` 聚合/逐节点判断;`ux_work_graph_run_active` 收窄为 `UNIQUE(task_id)`。 125 1. `work_graph_run.org_node` 列彻底删除(不是下沉/保留),`work_graph_run_query.py`/`work_graph_fork.py` 改造为从 `work_node_execution.subagent_type` 聚合/逐节点判断;`ux_work_graph_run_active` 收窄为 `UNIQUE(task_id)`。
@@ -127,15 +127,15 @@ @@ -127,15 +127,15 @@
127 3. `run_graph()`/`_run_one_node()` 扩展为读取节点自己声明的 `subagent_type`/`skill_ref`(缺省退回图级默认),新增节点级验证 skill `work-graph-node-review`,`org_level_loop.py` 的粗粒度 `fast_lane`/`full_replan` 路由被 `dynamic_loop_back` 取代(不删除该模块,其他调用方仍在用)。 127 3. `run_graph()`/`_run_one_node()` 扩展为读取节点自己声明的 `subagent_type`/`skill_ref`(缺省退回图级默认),新增节点级验证 skill `work-graph-node-review`,`org_level_loop.py` 的粗粒度 `fast_lane`/`full_replan` 路由被 `dynamic_loop_back` 取代(不删除该模块,其他调用方仍在用)。
128 - **前置条件**:Phase 1 落地的"复核"阶段先用 `org_level_loop.py` 粗粒度路由兜底上线;Phase 2 把这段兜底逻辑替换成图内精确 loop_back。 128 - **前置条件**:Phase 1 落地的"复核"阶段先用 `org_level_loop.py` 粗粒度路由兜底上线;Phase 2 把这段兜底逻辑替换成图内精确 loop_back。
129 - **风险**:Coder 开始自规划、产出内容型节点后,`validator-test-script-narrative-output-issue.md` 记录的叙述文字复发风险需要在本 phase 内验证是否触发,必要时补加机械拦截网(参考 `check_script_compiles()` 的先例),不是事后打补丁。 129 - **风险**:Coder 开始自规划、产出内容型节点后,`validator-test-script-narrative-output-issue.md` 记录的叙述文字复发风险需要在本 phase 内验证是否触发,必要时补加机械拦截网(参考 `check_script_compiles()` 的先例),不是事后打补丁。
130 -- **建议前置(非强制)**:`docs/todo/work-graph-node-execution-deblackbox-requirements.md`(2026-08-08,单节点执行层去黑盒化,正交但相关)——Phase 2 会大量新增 `judgment.kind="llm"` 节点级判断调用,如果去黑盒化工作先完成,Phase 2 可以直接基于新的 `trigger_org_node_structured()` 设计,不需要在原生 Workflow 和新机制之间选一套来复用。 130 +- **建议前置(非强制)**:`docs/done/work-graph-node-execution-deblackbox-requirements.md`(2026-08-08,单节点执行层去黑盒化,正交但相关)——Phase 2 会大量新增 `judgment.kind="llm"` 节点级判断调用,如果去黑盒化工作先完成,Phase 2 可以直接基于新的 `trigger_org_node_structured()` 设计,不需要在原生 Workflow 和新机制之间选一套来复用。
131 131
132 -### Phase 3 — 前端动态 + 历史图谱(可与 Phase 2 并行) 132 +### Phase 3 — 前端动态 + 历史图谱 ✅ 已实现(2026-08-13)
133 - **范围**:FR-5a + FR-5b。 133 - **范围**:FR-5a + FR-5b。
134 -- **产出**:Org 级元图 + `work_graph_run` 内 `attempt_number` 回放时间轴。  
135 -- **依赖说明**:FR-5b 只依赖 `loop_back_events` 数据形状,今天已存在,不需要等 Phase 2 完成;FR-5a 的"复核"这一段需要 Phase 1 新增的 `task.status` 值才有东西可渲染,其余三段可以先做。 134 +- **产出**:`frontend/app.js` 新增独立的四段 Org 级元图(`renderOrgMetaGraph` 等,不是扩展早期 `renderOrgGraphTopology`——后者仍是三段,被既有 smoke test 钉住,未改动);`planner_reviewer` 这一段没有自规划 Work Graph 可展开,改为展示它的 `planner_review_verdict` execution_event 历史列表(`task_query.py` 新增 `_planner_review_events_for_task()`);FR-5b 的 `attempt_number` 回放时间轴(`renderLoopBackTimelineHtml`)复用了此前"已拉取但未渲染"的 `loop_back_events[].feedback`。新增 `frontend/test_org_meta_graph_smoke.js`(35 断言)。
  135 +- **依赖说明**:FR-5b 只依赖 `loop_back_events` 数据形状,不需要等 Phase 2 完成;FR-5a 的"复核"这一段需要 Phase 1 新增的 `task.status` 值才有东西可渲染,均已满足。
136 136
137 -### 明确不排期  
138 -`scheduler-new-goal-dispatch-requirements.md`(FR-1,新 Goal 自动派发)——保持独立评审,不合并进本文档的任何 phase。 137 +### 明确不排期,现已独立立项实施
  138 +`scheduler-new-goal-dispatch-requirements.md`(FR-1,新 Goal 自动派发)——本文档保持不合并进任何 phase 的原判断不变,但该需求本身已在 2026-08-13 由用户单独拍板、独立实施完成,见 `docs/done/scheduler-new-goal-dispatch-requirements.md`。
139 139
140 --- 140 ---
141 141
docs/done/org-graph-unification-phase1-review-gate-design.md
1 # Phase 1 — Planner 产出复核阶段 + resume/fresh-run artifact 假设修复——详细设计(SDD) 1 # Phase 1 — Planner 产出复核阶段 + resume/fresh-run artifact 假设修复——详细设计(SDD)
2 2
3 **日期**: 2026-08-08 3 **日期**: 2026-08-08
4 -**性质**: 详细设计,对应 `docs/todo/org-graph-unification-and-universal-self-planning-requirements.md` 的 Phase 1(FR-1,含 `task-execution-resume-assumes-planner-artifacts-exist.md` 的修复)。给出可直接实施的 schema/接口/算法/测试方案。 4 +**性质**: 详细设计,对应 `docs/done/org-graph-unification-and-universal-self-planning-requirements.md` 的 Phase 1(FR-1,含 `task-execution-resume-assumes-planner-artifacts-exist.md` 的修复)。给出可直接实施的 schema/接口/算法/测试方案。
5 **依赖代码**:`src/org_node_lifecycle.py`(`_PRE_STATE`/`_POST_STATE`/`_REQUIRED_ARTIFACTS`/`trigger_org_node_full`)、`src/e2e_pipeline.py`(`run_task_lifecycle`/`plan_documents`/`_parse_verdict_line`/`_VALIDATOR_VERDICT_PROMPT` 先例)、`src/task_execution.py`(`run_or_resume_task_lifecycle`/`_RESUMABLE_STATUSES`)、`src/scheduler.py`(`_BLOCKED_REASON_FOR_EXCEPTION`)、`src/schema.py`(`migrate_add_loop_back_event_type` 的 create-copy-drop-rename 先例)、`src/agent_profile.py`(`_discover_role_ids`,角色自动发现)、`roles/validator/role.json`(新角色的模板)。 5 **依赖代码**:`src/org_node_lifecycle.py`(`_PRE_STATE`/`_POST_STATE`/`_REQUIRED_ARTIFACTS`/`trigger_org_node_full`)、`src/e2e_pipeline.py`(`run_task_lifecycle`/`plan_documents`/`_parse_verdict_line`/`_VALIDATOR_VERDICT_PROMPT` 先例)、`src/task_execution.py`(`run_or_resume_task_lifecycle`/`_RESUMABLE_STATUSES`)、`src/scheduler.py`(`_BLOCKED_REASON_FOR_EXCEPTION`)、`src/schema.py`(`migrate_add_loop_back_event_type` 的 create-copy-drop-rename 先例)、`src/agent_profile.py`(`_discover_role_ids`,角色自动发现)、`roles/validator/role.json`(新角色的模板)。
6 **状态**: ✅ 已实现(commit `ea19c7d`,2026-08-08)。实现与本设计逐条吻合(新角色 `planner_reviewer`、`reviewing_planner_output` 状态值、`_REQUIRED_ARTIFACTS["coder"]` 收窄到 `(sdd,)`、P0 resume bug 与 fresh-run KeyError 一并修复,均与本文档 §3.1/§4.1/§7 一致);实现过程中额外发现并修复了一个本设计未预见的问题:`agent_profile` 表的 `migrated_from_profile` 列原是 `NOT NULL`,`planner_reviewer` 这种没有对应 Hermes 遗留 profile 的新角色一注册就违反约束,随本次提交一并放开。`python3 -m unittest discover -s src` 全绿(731 tests,2026-08-12 复核)。移入 `docs/done/` 前保留在 `docs/todo/` 是 CLAUDE.md 记录过的已知文档滞后,本次一并订正。 6 **状态**: ✅ 已实现(commit `ea19c7d`,2026-08-08)。实现与本设计逐条吻合(新角色 `planner_reviewer`、`reviewing_planner_output` 状态值、`_REQUIRED_ARTIFACTS["coder"]` 收窄到 `(sdd,)`、P0 resume bug 与 fresh-run KeyError 一并修复,均与本文档 §3.1/§4.1/§7 一致);实现过程中额外发现并修复了一个本设计未预见的问题:`agent_profile` 表的 `migrated_from_profile` 列原是 `NOT NULL`,`planner_reviewer` 这种没有对应 Hermes 遗留 profile 的新角色一注册就违反约束,随本次提交一并放开。`python3 -m unittest discover -s src` 全绿(731 tests,2026-08-12 复核)。移入 `docs/done/` 前保留在 `docs/todo/` 是 CLAUDE.md 记录过的已知文档滞后,本次一并订正。
7 7
docs/done/org-graph-unification-phase2-universal-self-planning-design.md
1 # Phase 2 — 全员自规划 + 合图 + 节点级精确 loop_back——详细设计(SDD) 1 # Phase 2 — 全员自规划 + 合图 + 节点级精确 loop_back——详细设计(SDD)
2 2
3 **日期**: 2026-08-08 3 **日期**: 2026-08-08
4 -**性质**: 详细设计,对应 `docs/todo/org-graph-unification-and-universal-self-planning-requirements.md` 的 Phase 2(FR-2/FR-3/FR-4),按 §4/§4.1 已拍板的合图(方案 A)路径设计。 4 +**性质**: 详细设计,对应 `docs/done/org-graph-unification-and-universal-self-planning-requirements.md` 的 Phase 2(FR-2/FR-3/FR-4),按 §4/§4.1 已拍板的合图(方案 A)路径设计。
5 **依赖**: 硬依赖 Phase 1(`docs/done/org-graph-unification-phase1-review-gate-design.md`)——已实现(commit `ea19c7d`,2026-08-08)。§9 接口核对清单已于 2026-08-12 逐条核对完成:三项全部吻合(`plan_and_review_documents()` 经 `_review_planner_output_with_retries()` 透传 `plan_documents()` 的 `{"run_id", "artifact_ids"}` 返回形状,含 `run_id`;`task.status` 新值确认是 `reviewing_planner_output`;`roles/planner_reviewer/role.json` 目前 `skills: ["planner-output-review", "unattended-agent-operations"]`,未预留 §7.1 新 skill 位置,Phase 2 实施时新增一行即可,不是不兼容)。 5 **依赖**: 硬依赖 Phase 1(`docs/done/org-graph-unification-phase1-review-gate-design.md`)——已实现(commit `ea19c7d`,2026-08-08)。§9 接口核对清单已于 2026-08-12 逐条核对完成:三项全部吻合(`plan_and_review_documents()` 经 `_review_planner_output_with_retries()` 透传 `plan_documents()` 的 `{"run_id", "artifact_ids"}` 返回形状,含 `run_id`;`task.status` 新值确认是 `reviewing_planner_output`;`roles/planner_reviewer/role.json` 目前 `skills: ["planner-output-review", "unattended-agent-operations"]`,未预留 §7.1 新 skill 位置,Phase 2 实施时新增一行即可,不是不兼容)。
6 **状态**: Phase 2a(commit `86fc0bb`)+ Phase 2b(commit `7c46a2f`)已实现,2026-08-12。Phase 2c 的 Validator 自规划改造(§6 核心部分)已实现,2026-08-12:`validate_implementation_with_own_graph()` 改为 `plan_work_graph(conn, "validator", ..., append_to_run_id=run_id)` 追加进 Planner/Coder 共享的同一个 `work_graph_run`,终审 verdict 变成图内一个真实节点(`node_type='final_verdict'`,`judgment.kind='mechanical'`、`check_ref='verdict_says_pass'`,`on_fail.action='dynamic_loop_back'`——Validator 图里唯一允许用这个 action 的节点),硬门槛(脚本真实非零退出码强制 FAIL,不听 LLM 辩解)通过 `_verdict_node_build_prompt`/`_verdict_says_pass_check` 的共享可变状态闭包保留,Python 强制而非只是 prompt 里说说。`_VALIDATOR_MANIFEST`/`_VALIDATOR_DOC_PROMPTS`/`_VALIDATOR_VERDICT_PROMPT`/`_validator_prompt_builder` 保留但不再使用(同 `_CODER_PROMPT` 先例)。§7.2 的收口验证已完成分析(见 §10 Phase 2c 小节末尾:一旦 verdict 节点自己的 `dynamic_loop_back` 内部消化重试,`validate_implementation_with_own_graph()` 正常返回时 verdict 必为 PASS,`org_level_loop.py` 的旧粗粒度路由对这条新路径自然不可达,不需要显式新旧流程判断——决定保留旧分支代码不删,已在 `scheduler.py::finish_task_lifecycle` 加注释说明)。 6 **状态**: Phase 2a(commit `86fc0bb`)+ Phase 2b(commit `7c46a2f`)已实现,2026-08-12。Phase 2c 的 Validator 自规划改造(§6 核心部分)已实现,2026-08-12:`validate_implementation_with_own_graph()` 改为 `plan_work_graph(conn, "validator", ..., append_to_run_id=run_id)` 追加进 Planner/Coder 共享的同一个 `work_graph_run`,终审 verdict 变成图内一个真实节点(`node_type='final_verdict'`,`judgment.kind='mechanical'`、`check_ref='verdict_says_pass'`,`on_fail.action='dynamic_loop_back'`——Validator 图里唯一允许用这个 action 的节点),硬门槛(脚本真实非零退出码强制 FAIL,不听 LLM 辩解)通过 `_verdict_node_build_prompt`/`_verdict_says_pass_check` 的共享可变状态闭包保留,Python 强制而非只是 prompt 里说说。`_VALIDATOR_MANIFEST`/`_VALIDATOR_DOC_PROMPTS`/`_VALIDATOR_VERDICT_PROMPT`/`_validator_prompt_builder` 保留但不再使用(同 `_CODER_PROMPT` 先例)。§7.2 的收口验证已完成分析(见 §10 Phase 2c 小节末尾:一旦 verdict 节点自己的 `dynamic_loop_back` 内部消化重试,`validate_implementation_with_own_graph()` 正常返回时 verdict 必为 PASS,`org_level_loop.py` 的旧粗粒度路由对这条新路径自然不可达,不需要显式新旧流程判断——决定保留旧分支代码不删,已在 `scheduler.py::finish_task_lifecycle` 加注释说明)。
7 7
@@ -220,7 +220,7 @@ def _run_one_node(conn, run_id, org_node, skill_id, target_folder_path, node, ou @@ -220,7 +220,7 @@ def _run_one_node(conn, run_id, org_node, skill_id, target_folder_path, node, ou
220 220
221 `_resolve_loop_limit()`(`work_graph_orchestrate.py:512-528`)、`_llm_judgment_verify_fn()`(`work_graph_orchestrate.py:420-467`)里同样用到的 `org_node` 参数,同步改成接收 `node_org_node`(调用方在 `_run_one_node` 里已经解析好,直接传下去,不需要这两个函数自己再判断一次)。 221 `_resolve_loop_limit()`(`work_graph_orchestrate.py:512-528`)、`_llm_judgment_verify_fn()`(`work_graph_orchestrate.py:420-467`)里同样用到的 `org_node` 参数,同步改成接收 `node_org_node`(调用方在 `_run_one_node` 里已经解析好,直接传下去,不需要这两个函数自己再判断一次)。
222 222
223 -### 4.2 "tools" 绑定:复用 `docs/todo/work-graph-node-execution-deblackbox-requirements.md` 的 `trigger_org_node_structured()` 223 +### 4.2 "tools" 绑定:复用 `docs/done/work-graph-node-execution-deblackbox-requirements.md` 的 `trigger_org_node_structured()`
224 224
225 节点声明 `tools`(如 `{"disallowed_tools": ["Bash"]}`,只做限制、不做放宽——不能让一个自规划节点给自己开出角色本身 `role.json` 没有的权限)时,去黑盒化文档设计的 `trigger_org_node_structured(org_node, prompt, target_folder_path, output_schema, model=None, timeout=300)` 需要再加一个可选的 `disallowed_tools` 参数,透传成底层 `claude` 调用的 `--disallowedTools` CLI 参数(05 §5.4.1.2 已经确认 CLI 顶层参数支持按工具/子命令限定,跟 subagent frontmatter 不支持是两条不同的解析路径)。 225 节点声明 `tools`(如 `{"disallowed_tools": ["Bash"]}`,只做限制、不做放宽——不能让一个自规划节点给自己开出角色本身 `role.json` 没有的权限)时,去黑盒化文档设计的 `trigger_org_node_structured(org_node, prompt, target_folder_path, output_schema, model=None, timeout=300)` 需要再加一个可选的 `disallowed_tools` 参数,透传成底层 `claude` 调用的 `--disallowedTools` CLI 参数(05 §5.4.1.2 已经确认 CLI 顶层参数支持按工具/子命令限定,跟 subagent frontmatter 不支持是两条不同的解析路径)。
226 226
docs/done/scheduler-new-goal-dispatch-requirements.md
@@ -5,8 +5,7 @@ @@ -5,8 +5,7 @@
5 `docs/todo/scheduler-production-polling-loop-requirements.md`(FR-1/FR-2 合并)已按完成状态拆分—— 5 `docs/todo/scheduler-production-polling-loop-requirements.md`(FR-1/FR-2 合并)已按完成状态拆分——
6 FR-2(在途任务复原)已实现,移入 `docs/done/scheduler-task-reconciliation-requirements.md`/ 6 FR-2(在途任务复原)已实现,移入 `docs/done/scheduler-task-reconciliation-requirements.md`/
7 `-design.md`;本文档只保留仍是草案的 FR-1。 7 `-design.md`;本文档只保留仍是草案的 FR-1。
8 -**状态**: 草稿,未立项。`docs/implementation-gap-analysis.md` 第 6 条已把这条标注为"改变系统自己的  
9 -自主权边界",需要单独评审决策,不与其他 gap 混跑。 8 +**状态**: ✅ 已实施(2026-08-13)。"要不要做"这个曾经被单独搁置的自主权边界决策,已由用户明确拍板:完整开放,常驻轮询、自动派发。
10 9
11 ## 1. 背景 10 ## 1. 背景
12 11
@@ -48,11 +47,23 @@ Goal(还没被任何调用触发过 `run_goal_to_completion`)目前只能靠 @@ -48,11 +47,23 @@ Goal(还没被任何调用触发过 `run_goal_to_completion`)目前只能靠
48 **这是改变系统自主权边界的决策**——意味着系统会在没有人显式点击"Start"的情况下自主开始消耗 47 **这是改变系统自主权边界的决策**——意味着系统会在没有人显式点击"Start"的情况下自主开始消耗
49 真实 API 调用、真实修改代码。这条风险不因为实现简单而减轻,需要在决策阶段被认真对待。 48 真实 API 调用、真实修改代码。这条风险不因为实现简单而减轻,需要在决策阶段被认真对待。
50 49
51 -## 7. 开放问题(留给详细设计阶段回答,需要先拍板"要不要做"再讨论这些)  
52 -  
53 -- [ ] 轮询周期定多少。  
54 -- [ ] 载体形式:`backend_server.py` 内置后台线程,还是独立进程。  
55 -- [ ] 要不要暴露成可配置项(管理后台开关/环境变量),还是先硬编码一个保守的默认行为。  
56 -- [ ] 要不要跟已实现的在途任务复原循环(`docs/done/scheduler-task-reconciliation-design.md`)  
57 - 共用同一个后台线程/轮询周期,还是保持两个独立循环——两者已实现的那部分刻意没有合并  
58 - 成一个"万能调度循环",这个原则是否延续到 FR-1 需要在设计阶段确认。 50 +## 7. 开放问题——✅ 全部回答(2026-08-13 实施落地)
  51 +
  52 +- [x] 轮询周期:45 秒(`--new-goal-dispatch-interval-seconds`,默认值),刻意比"在途任务复原"的
  53 + 300 秒短,满足"接近实时派发"的验收要求。
  54 +- [x] 载体形式:`backend_server.py::main()` 内置后台 daemon 线程(`_start_new_goal_dispatch_loop`),
  55 + 跟"在途任务复原"同款,不是独立进程。
  56 +- [x] 暴露成可配置项:`--new-goal-dispatch-interval-seconds`/`--disable-new-goal-dispatch-loop` 两
  57 + 个 CLI flag,命名风格照抄已实现的 `--reconciliation-interval-seconds`/`--disable-reconciliation-loop`。
  58 +- [x] 跟在途任务复原循环保持独立,不合并成一个"万能调度循环"——新写了独立模块
  59 + `src/new_goal_dispatch.py`(`find_queued_tasks()`/`dispatch_once()`),不改 `task_reconciliation.py`。
  60 + 同时给 `supervisor/deploy_stage.py::_launch_candidate()` 补上了跟 `--disable-reconciliation-loop`
  61 + 同款的联动 flag,防止候选进程用生产库拷贝在健康检查完成前就自主派发真实 Goal。
  62 +
  63 +## 8. 实施summary
  64 +
  65 +新模块 `src/new_goal_dispatch.py`(找 `task.status = 'queued'` 的行,逐个 `task_execution.
  66 +start_task_execution_in_background`,单个候选派发失败不影响其余候选)+ `src/test_new_goal_dispatch.py`;
  67 +`backend_server.py`/`test_backend_server.py` 的 CLI 接线;`supervisor/deploy_stage.py`/
  68 +`supervisor/test_deploy_stage.py` 的候选进程联动。`discover -s src`(797 tests)+
  69 +`discover -s supervisor`(127 tests)全绿。
docs/done/scheduler-task-reconciliation-design.md
@@ -6,7 +6,7 @@ @@ -6,7 +6,7 @@
6 + `src/task_execution.py` 的 in-flight 互斥锁)。**2026-08-08**:随需求文档一起从 6 + `src/task_execution.py` 的 in-flight 互斥锁)。**2026-08-08**:随需求文档一起从
7 `docs/todo/scheduler-production-polling-loop-design.md` 拆分移入 `docs/done/`(原文档同时覆盖已完成 7 `docs/todo/scheduler-production-polling-loop-design.md` 拆分移入 `docs/done/`(原文档同时覆盖已完成
8 的 FR-2 和仍是草案的 FR-1,拆分后本文档只保留 FR-2 部分,FR-1 独立见 8 的 FR-2 和仍是草案的 FR-1,拆分后本文档只保留 FR-2 部分,FR-1 独立见
9 -`docs/todo/scheduler-new-goal-dispatch-requirements.md`)。 9 +`docs/done/scheduler-new-goal-dispatch-requirements.md`)。
10 10
11 ## 1. 整体方案 11 ## 1. 整体方案
12 12
@@ -80,7 +80,7 @@ @@ -80,7 +80,7 @@
80 80
81 ## 7. 关系 81 ## 7. 关系
82 82
83 -跟"新 Goal 自动派发"(`docs/todo/scheduler-new-goal-dispatch-requirements.md`)是独立、正交的 83 +跟"新 Goal 自动派发"(`docs/done/scheduler-new-goal-dispatch-requirements.md`)是独立、正交的
84 两个机制——前者只是让已经存在的任务继续跑完,后者是让还没跑过的 `queued` Goal 自动开始跑,两者 84 两个机制——前者只是让已经存在的任务继续跑完,后者是让还没跑过的 `queued` Goal 自动开始跑,两者
85 不共享循环、不共享判断逻辑,也不应该被合并成一个"万能调度循环"。在后者被单独评审确认前, 85 不共享循环、不共享判断逻辑,也不应该被合并成一个"万能调度循环"。在后者被单独评审确认前,
86 `queued` 状态的新 Goal 仍然需要人工触发 `run_goal_to_completion`/`scheduler.run_scheduler_once()`。 86 `queued` 状态的新 Goal 仍然需要人工触发 `run_goal_to_completion`/`scheduler.run_scheduler_once()`。
docs/done/scheduler-task-reconciliation-requirements.md
@@ -2,7 +2,7 @@ @@ -2,7 +2,7 @@
2 2
3 **日期**: 2026-08-06 3 **日期**: 2026-08-06
4 **性质**: 需求文档(BRD/PRD 层面),不含详细技术设计。回应一次真实事故的根因排查——任务 `3605272e-fe33-48cc-b3f3-e0c0ba26f970` 在 `validating` 状态卡死 15+ 小时,`execution_event` 表在此期间零记录,`work_graph_run` 里从未出现 Validator 的运行记录。 4 **性质**: 需求文档(BRD/PRD 层面),不含详细技术设计。回应一次真实事故的根因排查——任务 `3605272e-fe33-48cc-b3f3-e0c0ba26f970` 在 `validating` 状态卡死 15+ 小时,`execution_event` 表在此期间零记录,`work_graph_run` 里从未出现 Validator 的运行记录。
5 -**状态**: ✅ 已实现(2026-08-06)。**2026-08-08 拆分**:原文档 `scheduler-production-polling-loop-requirements.md` 同时覆盖 FR-1(新 Goal 自动派发,仍是草案)和 FR-2(本文档,已实现),两者状态不同、混在一份文档里不利于归档——本文档是拆分后只保留 FR-2(已完成部分)的版本,移入 `docs/done/`;FR-1 独立留在 `docs/todo/scheduler-new-goal-dispatch-requirements.md`。设计文档见 `docs/done/scheduler-task-reconciliation-design.md`。 5 +**状态**: ✅ 已实现(2026-08-06)。**2026-08-08 拆分**:原文档 `scheduler-production-polling-loop-requirements.md` 同时覆盖 FR-1(新 Goal 自动派发,仍是草案)和 FR-2(本文档,已实现),两者状态不同、混在一份文档里不利于归档——本文档是拆分后只保留 FR-2(已完成部分)的版本,移入 `docs/done/`;FR-1 独立留在 `docs/done/scheduler-new-goal-dispatch-requirements.md`。设计文档见 `docs/done/scheduler-task-reconciliation-design.md`。
6 6
7 ## 0. 实现记录(2026-08-06) 7 ## 0. 实现记录(2026-08-06)
8 8
docs/done/work-graph-node-execution-deblackbox-requirements.md
@@ -3,7 +3,7 @@ @@ -3,7 +3,7 @@
3 **日期**: 2026-08-08 3 **日期**: 2026-08-08
4 **性质**: 需求+设计合并文档——用户已经明确拍板方向(自研,不复用 Claude Code 原生 Dynamic Workflow),且调研发现代码库里已经有一个生产验证过的现成替代方案(`plan_work_graph()`),不需要走"逐条开放问题讨论"流程,直接给出可实施设计。对应 `docs/graphlemon_architecture.md` §10.2(2026-08-08 决策修订)。 4 **性质**: 需求+设计合并文档——用户已经明确拍板方向(自研,不复用 Claude Code 原生 Dynamic Workflow),且调研发现代码库里已经有一个生产验证过的现成替代方案(`plan_work_graph()`),不需要走"逐条开放问题讨论"流程,直接给出可实施设计。对应 `docs/graphlemon_architecture.md` §10.2(2026-08-08 决策修订)。
5 **触发场景**:用户追问"之前设计说用 Claude Code 自带 dynamic workflow,现在要去掉、自己实现,这个描述改了吗?为了可控,不再黑盒"——调研发现 `docs/graphlemon_architecture.md` §10.1"方案 A"(已确认:Work Graph 执行层复用原生 Dynamic Workflow)从未被正式撤回,但图编排层早就自研了(`work_graph_orchestrate.py`,REQ-06 断点续跑驱动);**单节点执行层还没有**——三处调用点仍在用 `write_workflow_script`/`execute_work_graph()` 生成/执行一次性 Dynamic Workflow `.js` 脚本。 5 **触发场景**:用户追问"之前设计说用 Claude Code 自带 dynamic workflow,现在要去掉、自己实现,这个描述改了吗?为了可控,不再黑盒"——调研发现 `docs/graphlemon_architecture.md` §10.1"方案 A"(已确认:Work Graph 执行层复用原生 Dynamic Workflow)从未被正式撤回,但图编排层早就自研了(`work_graph_orchestrate.py`,REQ-06 断点续跑驱动);**单节点执行层还没有**——三处调用点仍在用 `write_workflow_script`/`execute_work_graph()` 生成/执行一次性 Dynamic Workflow `.js` 脚本。
6 -**状态**: 设计完成,待实施。 6 +**状态**: ✅ 已实施(2026-08-13)。三处调用点(`_check_prd_consistency`/`_llm_judgment_verify_fn`/`_make_run_fn`)全部迁移到新增的 `org_node_trigger.trigger_org_node_structured()`;实施过程中额外发现并顺带迁移了文档撰写之后新落地的两处调用点(`_run_reflection_perspective`、`_fix_skill_for_node`,Phase 2a Reflection-review/Fix-Skill 引入)。`extract_json_payload` 泛化落在 `work_graph_plan.py`;`UsageLimitReachedError` 的两份重复定义未强行合并——`org_node_trigger.py` 新增一份规范版本(`.quota_limit_type` 属性齐全),`org_node_lifecycle.py`/`work_graph_execute.py` 各自的旧版本原样保留(前者服务不同调用路径,后者随宿主模块一起冻结)。FR-4 的真实验证(`RUN_REAL_AGENT_E2E=1`,`src/test_org_node_trigger_e2e.py`)确认人设加载、`--model` 覆盖、JSON 围栏提取三点全部符合预期。`write_workflow_script`/`execute_work_graph`/`execute_work_graph_with_usage_limit_retry` 确认无生产调用方,文件保留、docstring 标注历史遗留;`CLAUDE.md` 对应描述已同步更新。`discover -s src`(797 tests)+ `discover -s supervisor`(127 tests)全绿。
7 7
8 --- 8 ---
9 9
@@ -195,4 +195,4 @@ def run_fn(cwd, model=None): @@ -195,4 +195,4 @@ def run_fn(cwd, model=None):
195 195
196 ## 7. 跟 Org-Graph-Unification 系列文档的关系 196 ## 7. 跟 Org-Graph-Unification 系列文档的关系
197 197
198 -正交但有先后关系:这次改动只影响**单节点执行机制**,不影响 Org Graph 流程形状或自规划范围,`docs/done/org-graph-unification-phase1-review-gate-design.md`(Phase 1)不依赖这次改动,可以并行/先后独立推进。但 `docs/todo/org-graph-unification-and-universal-self-planning-requirements.md` 的 **Phase 2**(FR-4,节点级验证 skill)会大量新增 `judgment.kind="llm"` 判断调用——如果这次去黑盒化工作能在 Phase 2 动工前完成,Phase 2 就不需要在两套机制(Workflow 和新的 flat 调用)之间选一套来复用,直接基于 `trigger_org_node_structured()` 设计,减少后续返工。建议顺序:这份文档 → Phase 2,不是必须但值得。 198 +正交但有先后关系:这次改动只影响**单节点执行机制**,不影响 Org Graph 流程形状或自规划范围,`docs/done/org-graph-unification-phase1-review-gate-design.md`(Phase 1)不依赖这次改动,可以并行/先后独立推进。但 `docs/done/org-graph-unification-and-universal-self-planning-requirements.md` 的 **Phase 2**(FR-4,节点级验证 skill)会大量新增 `judgment.kind="llm"` 判断调用——如果这次去黑盒化工作能在 Phase 2 动工前完成,Phase 2 就不需要在两套机制(Workflow 和新的 flat 调用)之间选一套来复用,直接基于 `trigger_org_node_structured()` 设计,减少后续返工。建议顺序:这份文档 → Phase 2,不是必须但值得。
docs/graphlemon_architecture.md
@@ -325,7 +325,7 @@ graphlemon/ @@ -325,7 +325,7 @@ graphlemon/
325 325
326 2026-08-08 用户决策:**这条"留着"的理由不成立**——`work_graph_plan.py::plan_work_graph()`(`c_work_graph_plan`,跟 `_render_node_script()` 面对的是同一个"`--agent` 身份 + 结构化输出"组合需求)从一开始就没有走 Workflow 这条路,而是用最朴素的方式解决:直接 `subprocess.run(["claude", "--agent", org_node, "-p", prompt, "--output-format", "json", ...])`,从自由文本 `result` 字段里用 ```` ```json ```` 围栏 + 正则提取解析(`_extract_graph_manifest`,05 §5.4.1.1 自己推荐的应对方式),且这个方案早已在生产环境真实跑通(Planner 四节点文档链)。也就是说,"必须用 Workflow 才能同时拿到人设+结构化输出"这个前提本身就是错的——两种机制其实都能做到,Workflow 只是恰好也能做到,不是唯一路径。 326 2026-08-08 用户决策:**这条"留着"的理由不成立**——`work_graph_plan.py::plan_work_graph()`(`c_work_graph_plan`,跟 `_render_node_script()` 面对的是同一个"`--agent` 身份 + 结构化输出"组合需求)从一开始就没有走 Workflow 这条路,而是用最朴素的方式解决:直接 `subprocess.run(["claude", "--agent", org_node, "-p", prompt, "--output-format", "json", ...])`,从自由文本 `result` 字段里用 ```` ```json ```` 围栏 + 正则提取解析(`_extract_graph_manifest`,05 §5.4.1.1 自己推荐的应对方式),且这个方案早已在生产环境真实跑通(Planner 四节点文档链)。也就是说,"必须用 Workflow 才能同时拿到人设+结构化输出"这个前提本身就是错的——两种机制其实都能做到,Workflow 只是恰好也能做到,不是唯一路径。
327 327
328 -**新决策(方案 A')**:Work Graph 的执行层(图编排 + 单节点执行)全部自研,不复用 Claude Code 原生 Dynamic Workflow 的任何部分——理由是可控性/可观测性:一次不透明的 `claude -p 'Run /<workflow>...'` 调用对 graphlemon 自己的进程而言是黑盒(拿不到中间状态、拿不到细粒度日志、`work_graph_execute.execute_work_graph_with_usage_limit_retry`'s own docstring 早就承认过"动态工作流脚本没有单一顶层 `--agent` 身份,个别 `agent()` 调用要各自处理模型降级"这类可控性缺口),而 `plan_work_graph()` 已经验证过的"flat `claude --agent` 调用 + JSON 围栏提取"路径完全在 graphlemon 自己的 Python 代码可见范围内。具体迁移计划见 `docs/todo/work-graph-node-execution-deblackbox-requirements.md`——这份文档要把 `work_graph_orchestrate.py`(节点执行 + `judgment.kind="llm"` 判断)和 `e2e_pipeline.py::_check_prd_consistency()` 这三处仍在用 `write_workflow_script`/`execute_work_graph` 的调用点,全部换成 `plan_work_graph()` 已经验证过的同一套机制。 328 +**新决策(方案 A')**:Work Graph 的执行层(图编排 + 单节点执行)全部自研,不复用 Claude Code 原生 Dynamic Workflow 的任何部分——理由是可控性/可观测性:一次不透明的 `claude -p 'Run /<workflow>...'` 调用对 graphlemon 自己的进程而言是黑盒(拿不到中间状态、拿不到细粒度日志、`work_graph_execute.execute_work_graph_with_usage_limit_retry`'s own docstring 早就承认过"动态工作流脚本没有单一顶层 `--agent` 身份,个别 `agent()` 调用要各自处理模型降级"这类可控性缺口),而 `plan_work_graph()` 已经验证过的"flat `claude --agent` 调用 + JSON 围栏提取"路径完全在 graphlemon 自己的 Python 代码可见范围内。具体迁移计划见 `docs/done/work-graph-node-execution-deblackbox-requirements.md`——这份文档要把 `work_graph_orchestrate.py`(节点执行 + `judgment.kind="llm"` 判断)和 `e2e_pipeline.py::_check_prd_consistency()` 这三处仍在用 `write_workflow_script`/`execute_work_graph` 的调用点,全部换成 `plan_work_graph()` 已经验证过的同一套机制。
329 329
330 第 10 节表格第 14 条("让 Claude 自己画图:动态工作流/self-routing")作为课程对照笔记本身不需要改——那是纯粹的技法启发记录;改的只是"要不要采纳这个技法作为我们自己的执行机制"这个决策,本节 10.2 是这个决策的最终修订版本。 330 第 10 节表格第 14 条("让 Claude 自己画图:动态工作流/self-routing")作为课程对照笔记本身不需要改——那是纯粹的技法启发记录;改的只是"要不要采纳这个技法作为我们自己的执行机制"这个决策,本节 10.2 是这个决策的最终修订版本。
331 331
docs/implementation-gap-analysis.md
@@ -83,7 +83,7 @@ @@ -83,7 +83,7 @@
83 verify_fn`/`e2e_pipeline._check_prd_consistency` 三处仍在生成一次性 `.claude/workflows/*.js` 脚本、经 83 verify_fn`/`e2e_pipeline._check_prd_consistency` 三处仍在生成一次性 `.claude/workflows/*.js` 脚本、经
84 `claude -p 'Run /<name>...'` 执行——`plan_work_graph()` 已经证明同一个"`--agent` 身份 + 结构化输出"需求可以 84 `claude -p 'Run /<name>...'` 执行——`plan_work_graph()` 已经证明同一个"`--agent` 身份 + 结构化输出"需求可以
85 不经 Workflow、靠 flat `claude --agent` 调用 + JSON 围栏提取解决。**设计已完成,待实施**: 85 不经 Workflow、靠 flat `claude --agent` 调用 + JSON 围栏提取解决。**设计已完成,待实施**:
86 - `docs/todo/work-graph-node-execution-deblackbox-requirements.md`。 86 + `docs/done/work-graph-node-execution-deblackbox-requirements.md`。
87 87
88 ## 4. 文档本身滞后于实现(这本身就是一种"差距") 88 ## 4. 文档本身滞后于实现(这本身就是一种"差距")
89 89