From fa42d4e49068c5e5b3966c10d4f7ea664152bfbd Mon Sep 17 00:00:00 2001 From: Linghong Date: Thu, 13 Aug 2026 13:50:24 +0000 Subject: [PATCH 1/2] =?UTF-8?q?=E6=96=87=E6=A1=A3=EF=BC=9A=E8=AE=A2?= =?UTF-8?q?=E6=AD=A3=20M1A-1=20=E7=9A=84=20retry=20=E5=A4=8D=E6=A0=B8?= =?UTF-8?q?=E7=BB=93=E8=AE=BA=EF=BC=8Cplan=20=E6=A0=B9=20run=20=E4=BF=9D?= =?UTF-8?q?=E6=BA=90=E5=8D=95=E5=88=97=E4=B8=BA=20M1A-3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit M1A-1 把 resolve_game_creator_agent_runtime_retry_configuration_at 判成 「已被 profile 挡住、本包不改函数」。该结论只对了一半:它确实不会让 plan 误得 autonomous 语义,但复核模板只问了「plan 进 matcher 后会不会误得不该 有的语义」,没问「plan 落到通用兜底后会不会丢掉该有的语义」。这个调用点 既是判据也是 run 构造器,两个方向都要问。 缺陷:plan 根 run 是 standard + 顶层无 parent,两个特例分支都不命中,落 agent-background-task 兜底。run_profile 由 agent_runtime_run_profile_identity_at 原样返回,实际只丢 source——第 22 节证据表原写「保留 source/profile」会 误导实现者去修一个没坏的东西,一并订正。 降级无声:retry 走 start_game_creator_agent_background_task_with_link_in_ session_lane_at,该路径对 source 无门禁;validate_agent_runtime_run_profile_ binding_record 也只在 autonomous-game-build 档要求可信 source。于是造出一个 正规启动路径造不出来的状态。 后果分两类。放行类:reject_supervisor_plan_root_steer 只认精确 source, 重试后不再命中,steer 重新放开,违反第 4.1 / 23.1 节裁决。死路类: root_control_authority 转 false 后广告层删掉 agent.goal_contract 与 agent.acceptance_update,validate_goal_contract_record 也拒绝建约,重试后 的根 run 建不出 Goal Contract,第 13.0 节审批前置门的取证永远收敛不了。 agent.delegate 不受该权限影响、委派照发,所以故障要到审批那一步才暴露。 处置:不回改已合入的 M1A-1,第 23.8 节新列 M1A-3,一并交付第 4.1 节要求的 强判据函数与 generic fallback 前的 exact plan root 分支;M1C-2a 依赖随之改为 M1C-1、M1A-3。修法边界写死:gui/cli 根 run 的现役降级行为不在范围,只能在 兜底之前插分支、不得改兜底默认值。 decision-log 原条目对应半句加删除线并挂指针,避免后续 PR 沿用旧结论。 Co-Authored-By: Claude Opus 5 --- docs/project-memory/shared-memory/decision-log.md | 13 ++++++++++++- ...技术方案】立项策划Agent(Fast GDD)-2026-08-10.md | 9 ++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 575d7978f..2dc41dec9 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -1,5 +1,16 @@ # 决策记录 +## 2026-08-13 订正 `M1A-1` 的 retry 复核结论;plan 根 run retry 保源单列为 `M1A-3` + +- **被订正的是同日 `M1A-1` 条第三项第三类中的 `lifecycle_control.rs` `resolve_game_creator_agent_runtime_retry_configuration_at`。** 原结论「不适用且已被 profile 挡住,本包不改函数」**只对了一半**:该处确实不会让 plan 误得 autonomous 语义(`autonomous-game-build` 分支先判 profile),但 `M1A-1` 的复核模板只问了「plan 进 matcher 后会不会**误得**不该有的语义」,没有问「plan 落到通用兜底后会不会**丢掉**该有的语义」。retry 这个调用点既是判据也是 run 构造器,两个方向都要问。 +- **实际缺陷**:plan 根 run 是 `standard` + 顶层无 parent,两个特例分支都不命中,落入 `agent-background-task` 字面量兜底。`run_profile` 由 `agent_runtime_run_profile_identity_at` 原样返回、不受影响,**丢的只有 source**。 +- **为什么无声**:retry 走 `start_game_creator_agent_background_task_with_link_in_session_lane_at`,该启动路径对 source 无门禁;而 supervisor 正规启动路径 `start_game_creator_supervisor_background_task_for_session_at` 有 trusted 检查。`validate_agent_runtime_run_profile_binding_record` 也只在 `profile == autonomous-game-build` 时要求 trusted source,`standard` 档照写。于是 retry 造出一个**正规启动路径造不出来的状态**:`agentId=project-supervisor` + root binding + `standard` + `source=agent-background-task`,全程零告警。 +- **后果分两类**。放行类:`reject_supervisor_plan_root_steer` 只认精确 source 字符串,重试后不再命中,**steer 重新放开**,直接违反第 4.1 / 23.1 节裁决。死路类:`root_control_authority` 判 binding.source 是否 trusted,变 `false` 后广告层删掉 `agent.goal_contract` 与 `agent.acceptance_update`、`root_goal_contract_required` 变 `false`、`validate_goal_contract_record` 也拒绝建约——重试后的根 run **建不出 Goal Contract**,第 13.0 节审批前置门要的验收取证永远收敛不了,且**用户会走到审批那一步才撞墙**。`agent.delegate` 不受 `root_control_authority` 影响,委派仍可发出,所以故障不会在 retry 当场暴露。 +- **处置**:不回改已合入的 `M1A-1`,新列 `M1A-3`(见技术方案第 23.8 节)一并交付第 4.1 节要求的两件事——① 所有 plan 根 run 例外共用的**强判据函数**(须核 durable binding 并逐项相等,不得只比较内存中的 `runtime.source`;`M1A-1` 交付的 `agent_runtime_supervisor_source_is_plan` 是纯字符串比较,用于**拒绝** steer 是安全的,但不足以**授予** retry 的 source 保留);② generic standard fallback **之前**的 exact plan root 分支。`M1A-3` 必须早于 `M1C-2a` 合入。 +- **修法边界**:`project-supervisor-gui` / `-cli` 根 run 重试同样降级为 `agent-background-task`,这是现役行为,不在本次范围。只能在兜底分支**之前**插精确分支,不得改兜底默认值(第 4.1 节「不扩大现役 retry 的破坏面」)。 +- **前瞻风险**:M1 将实现「每个项目同一时刻最多一条非终态 plan lineage」。若该判据按 plan source 判定,重试产物会**隐身**——不占 lineage 名额却实际在跑,用户此时能并发开出第二条策划链路。`M1A-3` 修好 source 保留即消解。 +- 关联文档:技术方案第 4.1 节(第 5、7 段)、第 22 节证据表 `plan retry` 行、第 23.8 节 `M1A-3`。 + ## 2026-08-13 M1A-1:`project-supervisor-plan` 进可信 matcher,steer 独立否决 - 落地:新增 `AGENT_RUNTIME_SUPERVISOR_PLAN_SOURCE = "project-supervisor-plan"` 并加入 `agent_runtime_supervisor_source_is_trusted`。启动路径 `start_game_creator_supervisor_background_task_for_session_at` / Tauri command 接受该 source,`standard` 放行,`plan + autonomous-game-build` 返回 `kind=plan-autonomous-profile-unsupported`。旧字面 `project-supervisor-plan-chat` 仍不在 matcher 内。 @@ -7,7 +18,7 @@ - 消费点复核(`rg agent_runtime_supervisor_source_is_trusted`,测试除外): - **适用(plan 进 matcher 后语义正确)**:`commands.rs` 启动门、`task_start.rs` 启动门、`goal_contract.rs` 的 `validate_goal_contract_record` / `create_game_creator_agent_runtime_goal_contract_at`、`acceptance_graph.rs` 的 `update_game_creator_agent_runtime_acceptance_graph_at` / `goal_contract_acceptance_completion_blocker_at_locked`、`provider_request_builders.rs` 的 `root_control_authority`、`run_configuration.rs` 根 binding 写入(autonomous 组合另由启动门拒绝)。 - **不适用 → 独立否决**:`commands.rs` `steer_game_creator_agent_runtime_task`、`steering.rs` `goal_contract_root_steer_task_at`(及 `steer_..._for_profile_at` 入口)。不得用「不进 matcher」实现。 - - **不适用且已被 profile 挡住,本包不改函数**:`task_start.rs` `current_autonomous_game_build_root_task_at`(先要求 `run_profile == autonomous-game-build`);`lifecycle_control.rs` `resolve_game_creator_agent_runtime_retry_configuration_at`(autonomous 分支才读 trusted source,standard 走 `agent-background-task`);`project_gates.rs` `ensure_current_autonomous_ready_child_mutation_at_locked`(`profile != autonomous` 即 `Ok(())`);`autonomous_completion.rs` 的 `autonomous_game_build_root_run_active_at` / `validate_autonomous_completion_contract` / `ensure_autonomous_completion_contract_for_task_at` 均先看 autonomous profile;`failed_terminal_autonomous_root_contract_before_task_at` 由后者以及 `autonomous_effective_root_task_at` 调用,后者先要求已存在完成合同(完成合同只由 autonomous 根写入)。 + - **不适用且已被 profile 挡住,本包不改函数**:`task_start.rs` `current_autonomous_game_build_root_task_at`(先要求 `run_profile == autonomous-game-build`);~~`lifecycle_control.rs` `resolve_game_creator_agent_runtime_retry_configuration_at`(autonomous 分支才读 trusted source,standard 走 `agent-background-task`)~~ **← 本条已于同日订正,见本文件上方「订正 `M1A-1` 的 retry 复核结论」条:结论只对了「不会误得 autonomous 语义」这一半,漏了「会丢掉 plan 语义」这一半;该函数改由 `M1A-3` 处理,后续 PR 不得沿用此处的「本包不改」结论**;`project_gates.rs` `ensure_current_autonomous_ready_child_mutation_at_locked`(`profile != autonomous` 即 `Ok(())`);`autonomous_completion.rs` 的 `autonomous_game_build_root_run_active_at` / `validate_autonomous_completion_contract` / `ensure_autonomous_completion_contract_for_task_at` 均先看 autonomous profile;`failed_terminal_autonomous_root_contract_before_task_at` 由后者以及 `autonomous_effective_root_task_at` 调用,后者先要求已存在完成合同(完成合同只由 autonomous 根写入)。 - 本包不做:工具面、brief、`plan.submit_gdd`、planning sidecar、审批、前端入口。 - 回归:`autonomous_supervisor_source_allowlist_includes_game_chat_and_plan`、`plan_source_rejects_autonomous_profile_and_accepts_standard`、`plan_root_steer_is_rejected_inside_and_outside_trusted_matcher`、`plan_supervisor_start_rejects_autonomous_and_allows_standard`。 - 关联文档:`docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md` 第 23.8 节 `M1A-1`。 diff --git a/docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md b/docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md index d04027ff6..bb40ea403 100644 --- a/docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md +++ b/docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md @@ -1713,7 +1713,7 @@ M0 文档 PR 本身最低验证:Markdown 结构与三张 Mermaid 图可解析 | JSON sidecar | `apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/json_sidecar.rs:44-104,122-250` | 现有 writer 可覆盖;不可变文件必须新增 no-replace helper | | 项目锁 | `apps/ai-game-creator-shell/src-tauri/src/project/filesystem.rs:85-138`、`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/project_gates.rs:1467-1505` | 所有 planning mutation 在同一项目锁内重读事实 | | completion blocker | `apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/project_gates.rs:808-860,934-948`、`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/main_loop.rs:1792-1832` | **2026-08-13 收窄**:现役 collaboration blocker 对**策划子 Agent**返回不适用;Supervisor 根 run 侧不再整体豁免(见第 4.3 节)。仍需新增 plan GDD 专用完成门,检查提问/审批等待、receipt、observation、session 与 recovery 状态 | -| plan retry | `apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/lifecycle_control.rs:740-772,775-907` | generic standard fallback 前保留 exact plan source/profile/session lineage | +| plan retry | `apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/lifecycle_control.rs:740-772,775-907` | generic standard fallback 前保留 exact plan source/profile/session lineage。**2026-08-13 复核归属**:本项属工作包 `M1A-3`(第 23.8 节),不属已合入的 `M1A-1`——后者的消费点复核只覆盖了「plan 是否误得 autonomous 语义」这一半。现役实现里 `run_profile` 由 `agent_runtime_run_profile_identity_at` 原样返回、并未丢失,**实际只丢 source**;降级无声是因为 retry 走的 `start_game_creator_agent_background_task_with_link_in_session_lane_at` 对 source 无门禁,而 `validate_agent_runtime_run_profile_binding_record` 只在 `autonomous-game-build` 档要求可信 source | | planning hydrate command | `apps/ai-game-creator-shell/src-tauri/src/commands.rs:856-875`、`apps/ai-game-creator-shell/src-tauri/src/main.rs:2178-2180`、`apps/ai-game-creator-shell/src/App.tsx:1550,1650,2957,3364,3702` | 新增单一 hydrate command/注册与前端生命周期调用;不把 runtime polling 当 GDD authority | | agent.db | `apps/ai-game-creator-shell/src-tauri/src/project/agent_db.rs:955-1034,1551-1605,1995-2088` | 普通 append 不满足 decision 幂等;新增专用保留入口 | | 16 任务 DAG | `server-rs/crates/shared-contracts/src/game_creation_app.rs:263-425` | 2026-08-12 复核 seed 仍是 16 个,M0/M1 不改拓扑;但固定 DAG 已不是完成语义的唯一来源,见下一行 | @@ -1956,11 +1956,12 @@ M0 完成不表示任何策划功能已上线。M1 的功能实现仍未开始 | --- | --- | --- | --- | | `M1A-1` | source 常量 `project-supervisor-plan` + 进可信 matcher + steer 独立否决 | — | **已落地**。消费点复核见 decision-log 2026-08-13 `M1A-1` 条;steer `kind=plan-root-steer-unsupported`,独立于 matcher | | `M1A-2` | 两层工具面 + `project-planning` 角色 brief | `M1A-1` | 两层工具面快照;`user.input_request` 在广告层不出现且执行层仍拒(第 19 节第 2 条两半) | +| `M1A-3` | plan 根 run 强判据函数 + retry 保源(本节下方「`M1A-3` 的由来」,规格见第 4.1 节第 5、7 段) | `M1A-1` | 强判据须核 durable binding 并逐项相等,**不得只比较内存中的 `runtime.source`**;retry 命中 plan 根 run 时保留 `project-supervisor-plan` 并在项目锁内写 session revision+1 successor,身份校验失败**直接拒绝 retry 而非降级**;`project-supervisor-gui/cli` 的现役 retry 行为逐字节不变。**必须早于 `M1C-2a`** | | `M1B-1` | `.agent/planning/` 存储层、strict schema、typed 指纹、版本链;含 `.agent/planning/**` 只挡写判据 | `M1A-2` | **第 9.1 节 golden vector 逐字节相等且指纹相等**(先于其它测试);create-only 与等前缀不可变 | | `M1B-2` | `plan.submit_gdd` 原生工具与提交点 | `M1B-1` | 全部拒绝分支;提交点前后强杀恢复;同 submissionId replay 不产生 vN+1 | | `M1C-0` | 新增 `StaticDelegateContractStatus::UserRevisionRequested` 与分类分支 | `M1A-1` | **本 PR 无写入方、是惰性路径,行为零变化**;做游戏链路返工仍在 `depth=1` 被拒;无该变体的历史记录分类不变 | | `M1C-1` | `gdd-approval` pending、审批命令、receipt;receipt 写入上述 status | `M1B-2`、`M1C-0` | 三动作全通;版本+指纹竞态防护;两窗口并发;**连续多次修订均可通过且 `repair_depth` 不变** | -| `M1C-2a` | Goal Contract 接线:turn 1 冻结、固定验收图、审批前置门取证 | `M1C-1` | turn 1 只能是一个 `agent.goal_contract`;`acceptanceNodes` 不接受自定义;**第 13.0 节审批前置门:取证未通过时不出现审批卡、而是产生返工委派**(取证顺序是协议时序问题,归本包而非 `M1C-1`) | +| `M1C-2a` | Goal Contract 接线:turn 1 冻结、固定验收图、审批前置门取证 | `M1C-1`、`M1A-3` | turn 1 只能是一个 `agent.goal_contract`;`acceptanceNodes` 不接受自定义;**第 13.0 节审批前置门:取证未通过时不出现审批卡、而是产生返工委派**(取证顺序是协议时序问题,归本包而非 `M1C-1`) | | `M1C-2b` | 澄清中转接线、轮次派生、预算注入 | `M1C-2a` | 3 轮上限;continuation 重放幂等不增加轮次;答案绑定冲突被拒 | | `M1D-1` | 前端 hydrate 与 GDD 审批卡 | `M1C-2b` | 前端只经 `hydrate_game_creator_plan_gdd_state` 读权威状态,不在页面侧合成批准事实 | | `M1D-2` | 入口分流与阶段进度 | `M1D-1` | 「直接开建」跳过路径与现状零差异 | @@ -1970,7 +1971,9 @@ M0 完成不表示任何策划功能已上线。M1 的功能实现仍未开始 - `M1C-0` 与 `M1C-1` 拆开的理由是**风险类别不同**:前者改的是 master 已发布的静态委派机制,后者是 M1 新增功能。合并成一个 PR 会让「做游戏链路返工额度未被误放宽」这条最关键的回归淹没在审批闭环的 diff 里。拆开后 `M1C-0` 全程不产生半状态——它没有写入方,`UserRevisionRequested` 要到 `M1C-1` 才被写出。 - `M1C-2` 拆成 a/b 的理由是两者失败模式无关:Goal Contract 是协议时序问题,澄清中转是身份派生与幂等问题。 -- 顺序是依赖顺序不是优先级。`M1C-0` 只依赖 `M1A-1`,可与 `M1B-*` 并行。 +- 顺序是依赖顺序不是优先级。`M1C-0` 只依赖 `M1A-1`,可与 `M1B-*` 并行;`M1A-3` 同样只依赖 `M1A-1`,可与 `M1A-2`、`M1B-*` 并行。 + +**`M1A-3` 的由来(2026-08-13 补列)**:本 PR 的内容原属 `M1A-1` 的消费点复核范围,被判成「已被 profile 挡住、本包不改」而漏出。漏出的机制原因是**复核方向单一**——`M1A-1` 的模板只问「plan 进可信 matcher 后会不会**误得**不该有的语义」,而 `resolve_game_creator_agent_runtime_retry_configuration_at` 既是判据也是 run 构造器,还必须反过来问「plan 落到通用兜底后会不会**丢掉**该有的语义」。后者的答案是会:plan 根 run 落 `agent-background-task` 兜底后 steer 重新放开,且 `root_control_authority` 转为 `false` 使其建不出 Goal Contract,第 13.0 节审批前置门要的取证永远收敛不了——而因为 `agent.delegate` 不受该权限影响,故障要到审批那一步才暴露。不回改已合入的 `M1A-1`,单列本 PR;详细定位见 decision-log 2026-08-13「订正 `M1A-1` 的 retry 复核结论」条。**凡「既是判据又是构造器」的调用点,后续 PR 的复核必须双向提问。** ## 24. 最终不变量摘要 From 17f7152f70d4d584b959000459c23709538362e9 Mon Sep 17 00:00:00 2001 From: Linghong Date: Thu, 13 Aug 2026 14:11:34 +0000 Subject: [PATCH 2/2] =?UTF-8?q?=E7=AB=8B=E9=A1=B9=E7=AD=96=E5=88=92?= =?UTF-8?q?=EF=BC=9A=E8=90=BD=E5=9C=B0=20M1A-3=EF=BC=8Cplan=20=E6=A0=B9=20?= =?UTF-8?q?run=20retry=20=E4=BF=9D=E6=BA=90=E4=B8=94=E8=BA=AB=E4=BB=BD?= =?UTF-8?q?=E5=A4=B1=E8=B4=A5=E4=B8=8D=E9=99=8D=E7=BA=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 plan 根 run 强判据,核 durable binding 而非只看内存 source retry 在 generic 兜底前保留 project-supervisor-plan 身份校验失败返回 plan-root-retry-identity-unsupported gui 与 delegate 对照回归保持现役兜底 同步 Fast GDD 方案、decision-log 与 pitfalls --- .../src-tauri/src/agent/runtime_driver.rs | 96 +++++ .../agent/runtime_driver/lifecycle_control.rs | 3 + .../autonomous_completion_contract_tests.rs | 337 ++++++++++++++++++ .../shared-memory/decision-log.md | 11 + docs/project-memory/shared-memory/pitfalls.md | 1 + ...方案】立项策划Agent(Fast GDD)-2026-08-10.md | 6 +- 6 files changed, 451 insertions(+), 3 deletions(-) diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver.rs index bcb641444..8cda53724 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver.rs @@ -112,6 +112,8 @@ pub(crate) const AGENT_RUNTIME_PLAN_AUTONOMOUS_PROFILE_UNSUPPORTED_KIND: &str = "plan-autonomous-profile-unsupported"; pub(crate) const AGENT_RUNTIME_PLAN_ROOT_STEER_UNSUPPORTED_KIND: &str = "plan-root-steer-unsupported"; +pub(crate) const AGENT_RUNTIME_PLAN_ROOT_RETRY_IDENTITY_UNSUPPORTED_KIND: &str = + "plan-root-retry-identity-unsupported"; pub(super) const GAME_CHAT_FIXED_TASK_GRAPH_STALLED_ERROR: &str = "game-chat 首版固定任务图无法继续推进,拒绝回退到普通 Provider 协作波"; @@ -151,6 +153,100 @@ pub(crate) fn reject_supervisor_plan_root_steer(source: &str) -> Result<(), Stri } Ok(()) } + +fn plan_root_identity_source_profile_match( + source: &str, + profile: &str, + binding_fingerprint: &str, + expected_fingerprint: &str, +) -> bool { + agent_runtime_supervisor_source_is_plan(source) + && profile.trim() == AGENT_RUNTIME_RUN_PROFILE_STANDARD + && binding_fingerprint.trim() == expected_fingerprint.trim() + && !expected_fingerprint.trim().is_empty() +} + +pub(crate) fn supervisor_plan_root_identity_holds_at( + root: &Path, + task: &AgentRuntimeTaskRecord, +) -> Result { + if task.agent_id != GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID + || !agent_runtime_supervisor_source_is_plan(&task.source) + || task.run_profile.trim() != AGENT_RUNTIME_RUN_PROFILE_STANDARD + || task.parent_agent_id.is_some() + || task.parent_run_id.is_some() + || task.delegation_id.is_some() + || task.run_id.trim().is_empty() + { + return Ok(false); + } + let Some(binding) = + read_game_creator_agent_runtime_run_profile_binding(root, &task.agent_id, &task.run_id)? + else { + return Ok(false); + }; + if validate_agent_runtime_run_profile_binding_record(root, &binding).is_err() + || binding.agent_id != GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID + || binding.run_id != task.run_id + || binding.root_agent_id != binding.agent_id + || binding.root_run_id != binding.run_id + || binding.parent_agent_id.is_some() + || binding.parent_run_id.is_some() + || !plan_root_identity_source_profile_match( + &binding.source, + &binding.profile, + &binding.binding_fingerprint, + &task.run_profile_binding_fingerprint, + ) + { + return Ok(false); + } + let runtime = read_game_creator_agent_runtime_at(root, &task.agent_id)?; + if runtime.state.run_id == task.run_id + && (!plan_root_identity_source_profile_match( + &runtime.state.source, + &runtime.state.run_profile, + &runtime.state.run_profile_binding_fingerprint, + &binding.binding_fingerprint, + ) || runtime.state.parent_agent_id.is_some() + || runtime.state.parent_run_id.is_some() + || runtime.state.delegation_id.is_some()) + { + return Ok(false); + } + if game_creator_agent_runtime_provider_action_batch_exists(root, &task.agent_id, &task.run_id) + { + let batch = read_game_creator_agent_runtime_provider_action_batch( + root, + &task.agent_id, + &task.run_id, + )?; + if !plan_root_identity_source_profile_match( + &batch.source, + &batch.run_profile, + &batch.run_profile_binding_fingerprint, + &binding.binding_fingerprint, + ) { + return Ok(false); + } + } + Ok(true) +} + +pub(crate) fn reject_supervisor_plan_root_retry_without_identity( + root: &Path, + task: &AgentRuntimeTaskRecord, +) -> Result<(), String> { + if !agent_runtime_supervisor_source_is_plan(&task.source) { + return Ok(()); + } + if supervisor_plan_root_identity_holds_at(root, task)? { + return Ok(()); + } + Err(format!( + "立项策划根 Run 重试身份校验失败,拒绝降级为通用 background source(kind={AGENT_RUNTIME_PLAN_ROOT_RETRY_IDENTITY_UNSUPPORTED_KIND})" + )) +} pub(super) const AGENT_RUNTIME_RUN_PROFILE_BINDING_SCHEMA_VERSION: &str = "game-creator-run-profile-binding.v1"; pub(super) const AGENT_RUNTIME_AUTONOMOUS_COMPLETION_CONTRACT_SCHEMA_VERSION: &str = diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/lifecycle_control.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/lifecycle_control.rs index 4a8869bbc..af745a164 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/lifecycle_control.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/lifecycle_control.rs @@ -766,6 +766,9 @@ pub(crate) fn resolve_game_creator_agent_runtime_retry_configuration_at( return Err("自主构建 Agent Runtime 重试绑定不是可信 Supervisor 根 Run".to_string()); } binding.source + } else if agent_runtime_supervisor_source_is_plan(&task.source) { + reject_supervisor_plan_root_retry_without_identity(root, task)?; + AGENT_RUNTIME_SUPERVISOR_PLAN_SOURCE.to_string() } else { "agent-background-task".to_string() }; diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/autonomous_completion_contract_tests.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/autonomous_completion_contract_tests.rs index e9209ce15..7592f9e8a 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/autonomous_completion_contract_tests.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/autonomous_completion_contract_tests.rs @@ -188,6 +188,343 @@ fn plan_supervisor_start_rejects_autonomous_and_allows_standard() { ); } +fn failed_supervisor_task( + run_id: &str, + source: &str, + run_profile: &str, + binding_fingerprint: &str, + session_id: &str, +) -> AgentRuntimeTaskRecord { + AgentRuntimeTaskRecord { + goal_id: None, + goal_revision: 0, + goal_status: None, + schema_version: AGENT_RUNTIME_SCHEMA_VERSION.to_string(), + agent_id: GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID.to_string(), + task_id: GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID.to_string(), + session_id: session_id.to_string(), + run_id: run_id.to_string(), + source: source.to_string(), + run_profile: run_profile.to_string(), + run_profile_binding_fingerprint: binding_fingerprint.to_string(), + parent_agent_id: None, + parent_run_id: None, + delegation_id: None, + task: "做一份 Fast GDD".to_string(), + status: "failed".to_string(), + phase: "failed".to_string(), + current_action: "测试失败".to_string(), + terminal_detail: Some("测试失败".to_string()), + error: Some("测试失败".to_string()), + updated_at: unix_timestamp(), + } +} + +fn plan_goal_contract_draft() -> AgentRuntimeGoalContractDraft { + AgentRuntimeGoalContractDraft { + outcome: "完成一份可审批的 Fast GDD".to_string(), + non_negotiables: vec!["保留用户明确要求".to_string()], + preferences: vec!["优先复用现有资源".to_string()], + forbidden_assumptions: vec!["不能把工具成功当作目标完成".to_string()], + open_questions: vec!["最终视觉效果仍需观察".to_string()], + acceptance_nodes: vec![ + AgentRuntimeGoalContractAcceptanceNodeDraft { + criterion_id: "behavior".to_string(), + criterion: "核心交互可运行".to_string(), + required: true, + required_evidence: vec!["tool:project.verify".to_string()], + dependencies: Vec::new(), + }, + AgentRuntimeGoalContractAcceptanceNodeDraft { + criterion_id: "final-observation".to_string(), + criterion: "最终结果符合用户目标".to_string(), + required: true, + required_evidence: vec!["tool:preview.validate".to_string()], + dependencies: vec!["behavior".to_string()], + }, + ], + } +} + +#[test] +fn plan_root_identity_requires_durable_binding_not_runtime_source() { + let temporary = crate::tests::canonical_test_tempdir("plan-root-identity-"); + let root = temporary.path().join("project"); + init_local_game_project_at(&root, "plan-root-identity", "强判据").expect("init"); + let session_id = resolve_agent_conversation_session_id_at( + &root, + GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, + None, + true, + ) + .expect("session"); + + let runtime_only = failed_supervisor_task( + "plan-runtime-only", + AGENT_RUNTIME_SUPERVISOR_PLAN_SOURCE, + AGENT_RUNTIME_RUN_PROFILE_STANDARD, + "not-a-binding-fingerprint", + &session_id, + ); + assert!( + !supervisor_plan_root_identity_holds_at(&root, &runtime_only).expect("identity"), + "只有 source 字符串不得授予 plan 根身份" + ); + + let binding = bind_game_creator_agent_runtime_run_profile_at( + &root, + GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, + "plan-identity-ok", + AGENT_RUNTIME_SUPERVISOR_PLAN_SOURCE, + Some(AGENT_RUNTIME_RUN_PROFILE_STANDARD), + None, + ) + .expect("bind plan root"); + let held = failed_supervisor_task( + "plan-identity-ok", + AGENT_RUNTIME_SUPERVISOR_PLAN_SOURCE, + AGENT_RUNTIME_RUN_PROFILE_STANDARD, + &binding.binding_fingerprint, + &session_id, + ); + assert!(supervisor_plan_root_identity_holds_at(&root, &held).expect("held identity")); +} + +#[test] +fn plan_root_retry_rejects_identity_mismatch_instead_of_degrading() { + let temporary = crate::tests::canonical_test_tempdir("plan-root-retry-reject-"); + let root = temporary.path().join("project"); + init_local_game_project_at(&root, "plan-root-retry-reject", "重试拒降级").expect("init"); + let session_id = resolve_agent_conversation_session_id_at( + &root, + GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, + None, + true, + ) + .expect("session"); + + let missing_binding = failed_supervisor_task( + "plan-retry-missing-binding", + AGENT_RUNTIME_SUPERVISOR_PLAN_SOURCE, + AGENT_RUNTIME_RUN_PROFILE_STANDARD, + "", + &session_id, + ); + let missing_error = resolve_game_creator_agent_runtime_retry_configuration_at( + &root, + &missing_binding, + false, + ) + .expect_err("缺少 binding 必须拒绝而不是降级"); + assert!( + missing_error.contains(AGENT_RUNTIME_PLAN_ROOT_RETRY_IDENTITY_UNSUPPORTED_KIND), + "{missing_error}" + ); + + let binding = bind_game_creator_agent_runtime_run_profile_at( + &root, + GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, + "plan-retry-drift", + AGENT_RUNTIME_SUPERVISOR_PLAN_SOURCE, + Some(AGENT_RUNTIME_RUN_PROFILE_STANDARD), + None, + ) + .expect("bind"); + let drifted = failed_supervisor_task( + "plan-retry-drift", + AGENT_RUNTIME_SUPERVISOR_PLAN_SOURCE, + AGENT_RUNTIME_RUN_PROFILE_STANDARD, + "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", + &session_id, + ); + let drift_error = + resolve_game_creator_agent_runtime_retry_configuration_at(&root, &drifted, false) + .expect_err("指纹漂移必须拒绝而不是降级"); + assert!( + drift_error.contains(AGENT_RUNTIME_PLAN_ROOT_RETRY_IDENTITY_UNSUPPORTED_KIND) + || drift_error.contains("与持久绑定不一致"), + "{drift_error}" + ); + + let mut with_parent = failed_supervisor_task( + "plan-retry-drift", + AGENT_RUNTIME_SUPERVISOR_PLAN_SOURCE, + AGENT_RUNTIME_RUN_PROFILE_STANDARD, + &binding.binding_fingerprint, + &session_id, + ); + with_parent.parent_agent_id = Some(GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID.to_string()); + with_parent.parent_run_id = Some("not-a-root".to_string()); + let parent_error = + resolve_game_creator_agent_runtime_retry_configuration_at(&root, &with_parent, false) + .expect_err("带 parent 的 plan 根候选必须拒绝"); + assert!( + parent_error.contains(AGENT_RUNTIME_PLAN_ROOT_RETRY_IDENTITY_UNSUPPORTED_KIND), + "{parent_error}" + ); + + bind_game_creator_agent_runtime_run_profile_at( + &root, + GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, + "plan-retry-source-mismatch", + AGENT_RUNTIME_SUPERVISOR_GUI_SOURCE, + Some(AGENT_RUNTIME_RUN_PROFILE_STANDARD), + None, + ) + .expect("bind gui"); + let mismatched = failed_supervisor_task( + "plan-retry-source-mismatch", + AGENT_RUNTIME_SUPERVISOR_PLAN_SOURCE, + AGENT_RUNTIME_RUN_PROFILE_STANDARD, + "", + &session_id, + ); + let mismatch_error = + resolve_game_creator_agent_runtime_retry_configuration_at(&root, &mismatched, false) + .expect_err("task.source 与 binding.source 不一致必须拒绝"); + assert!( + mismatch_error.contains(AGENT_RUNTIME_PLAN_ROOT_RETRY_IDENTITY_UNSUPPORTED_KIND), + "{mismatch_error}" + ); +} + +#[test] +fn plan_root_retry_keeps_plan_source_and_goal_contract_authority() { + let temporary = crate::tests::canonical_test_tempdir("plan-root-retry-keep-"); + let root = temporary.path().join("project"); + init_local_game_project_at(&root, "plan-root-retry-keep", "重试保源").expect("init"); + let session_id = resolve_agent_conversation_session_id_at( + &root, + GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, + None, + true, + ) + .expect("session"); + let original_run_id = "plan-failed-original"; + let binding = bind_game_creator_agent_runtime_run_profile_at( + &root, + GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, + original_run_id, + AGENT_RUNTIME_SUPERVISOR_PLAN_SOURCE, + Some(AGENT_RUNTIME_RUN_PROFILE_STANDARD), + None, + ) + .expect("bind plan root"); + let failed = failed_supervisor_task( + original_run_id, + AGENT_RUNTIME_SUPERVISOR_PLAN_SOURCE, + AGENT_RUNTIME_RUN_PROFILE_STANDARD, + &binding.binding_fingerprint, + &session_id, + ); + let (profile, source) = + resolve_game_creator_agent_runtime_retry_configuration_at(&root, &failed, false) + .expect("plan 根 retry 应保源"); + assert_eq!(profile, AGENT_RUNTIME_RUN_PROFILE_STANDARD); + assert_eq!(source, AGENT_RUNTIME_SUPERVISOR_PLAN_SOURCE); + + append_game_creator_agent_runtime_task_record(&root, &failed).expect("append failed plan task"); + let _runtime_lock = try_acquire_game_creator_agent_runtime_task_lock( + &root, + GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, + ) + .expect("lock") + .expect("lock available"); + let retried = retry_game_creator_agent_runtime_task_at( + &root, + GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, + original_run_id, + "plan-failed-original-retry", + ) + .expect("retry plan root"); + let retry_run_id = retried + .accepted_run_id + .as_deref() + .expect("retry accepted run"); + let queued = read_latest_game_creator_agent_runtime_task_by_run_id( + &root, + GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, + retry_run_id, + ) + .expect("read retry task") + .expect("retry task exists"); + assert_eq!(queued.source, AGENT_RUNTIME_SUPERVISOR_PLAN_SOURCE); + assert_eq!(queued.run_profile, AGENT_RUNTIME_RUN_PROFILE_STANDARD); + let retry_binding = read_game_creator_agent_runtime_run_profile_binding( + &root, + GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, + retry_run_id, + ) + .expect("read retry binding") + .expect("retry binding exists"); + assert_eq!(retry_binding.source, AGENT_RUNTIME_SUPERVISOR_PLAN_SOURCE); + assert_eq!(retry_binding.profile, AGENT_RUNTIME_RUN_PROFILE_STANDARD); + assert!(retry_binding.parent_agent_id.is_none()); + assert!(agent_runtime_supervisor_source_is_trusted(&retry_binding.source)); + + let steer_error = steer_game_creator_agent_runtime_task_at( + &root, + GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, + &queued.session_id, + retry_run_id, + "steer-after-retry", + "改成另一套玩法", + "test", + ) + .expect_err("重试后 steer 仍须拒绝"); + assert!( + steer_error.contains(AGENT_RUNTIME_PLAN_ROOT_STEER_UNSUPPORTED_KIND), + "{steer_error}" + ); + + create_game_creator_agent_runtime_goal_contract_at( + &root, + GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, + retry_run_id, + &queued.task, + &plan_goal_contract_draft(), + ) + .expect("重试后仍能创建 Goal Contract"); +} + +#[test] +fn gui_and_delegate_retry_sources_stay_on_existing_fallback() { + let temporary = crate::tests::canonical_test_tempdir("gui-retry-fallback-"); + let root = temporary.path().join("project"); + init_local_game_project_at(&root, "gui-retry-fallback", "对照兜底").expect("init"); + let session_id = resolve_agent_conversation_session_id_at( + &root, + GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, + None, + true, + ) + .expect("session"); + let gui_binding = bind_game_creator_agent_runtime_run_profile_at( + &root, + GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, + "gui-standard-retry", + AGENT_RUNTIME_SUPERVISOR_GUI_SOURCE, + Some(AGENT_RUNTIME_RUN_PROFILE_STANDARD), + None, + ) + .expect("bind gui"); + let gui_task = failed_supervisor_task( + "gui-standard-retry", + AGENT_RUNTIME_SUPERVISOR_GUI_SOURCE, + AGENT_RUNTIME_RUN_PROFILE_STANDARD, + &gui_binding.binding_fingerprint, + &session_id, + ); + let (_, gui_source) = + resolve_game_creator_agent_runtime_retry_configuration_at(&root, &gui_task, false) + .expect("gui retry"); + assert_eq!(gui_source, "agent-background-task"); + let (_, delegated_source) = + resolve_game_creator_agent_runtime_retry_configuration_at(&root, &gui_task, true) + .expect("delegated retry"); + assert_eq!(delegated_source, "agent-delegate-retry"); +} + #[test] fn game_chat_manifest_seed_projection_starts_only_the_single_main_agent() { let game_chat_tasks = diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 2dc41dec9..186fb5e85 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -1,5 +1,16 @@ # 决策记录 +## 2026-08-13 M1A-3:plan 根 run 强判据与 retry 保源 + +- 落地:新增 `supervisor_plan_root_identity_holds_at`。必须核 durable run-profile binding(含 project/fingerprint 校验),并与 task 的 `agentId/source/profile/parent/delegation/root*` 以及「存在且 runId 相同」的 runtime、尚存 provider action batch 逐项相等。**不得只比较内存 `runtime.source`。** `agent_runtime_supervisor_source_is_plan` 仍只用于拒绝(steer),本函数只用于授予 retry 保源。 +- retry:`resolve_game_creator_agent_runtime_retry_configuration_at` 在 generic `agent-background-task` 兜底**之前**插入弱候选分支——`task.source == project-supervisor-plan` 时先走强判据;通过则写出 `project-supervisor-plan`,失败返回 `kind=plan-root-retry-identity-unsupported`,**不得降级**。`delegated` / `autonomous-game-build` 两支未改。 +- 强弱分工:steer 继续用弱判据。不得把 steer 改成强判据——binding 缺失时会判不成 plan,反而 fail-open。 +- 本包不做:`.agent/planning/`、`gddId`、plan session revision+1、按 `gdd-approval` kind 禁 retry(现役已拒 `waiting-for-user-input`)。conversation `sessionId` 复用走现役 retry 入队。 +- 同族 source 重建复核(`rg` 生产路径,测试除外):字面量 `agent-background-task` 的**唯一**构造点仍是本函数兜底分支。`start_game_creator_agent_background_task_with_link_in_session_lane_at` 接受调用方 source、自身不改写;resume / pending_recovery / recovery_scan 续跑既有 `task.source`,不另造 source。 +- 对照:`project-supervisor-gui` + `standard` 仍降级为 `agent-background-task`;`delegated=true` 仍为 `agent-delegate-retry`;autonomous 仍从 binding 取回可信 source。 +- 回归:`plan_root_identity_requires_durable_binding_not_runtime_source`、`plan_root_retry_rejects_identity_mismatch_instead_of_degrading`、`plan_root_retry_keeps_plan_source_and_goal_contract_authority`、`gui_and_delegate_retry_sources_stay_on_existing_fallback`;既有 `autonomous_supervisor_retry_restores_trusted_source_from_run_profile_binding` 继续绿。 +- 关联文档:技术方案第 4.1 节第 5、7 段(本包只落地 source 保源与强判据,不提前实现第 7 段里依赖 M1B/M1C 的 session/gdd 合同)、第 22 节 `plan retry` 行、第 23.8 节 `M1A-3`。 + ## 2026-08-13 订正 `M1A-1` 的 retry 复核结论;plan 根 run retry 保源单列为 `M1A-3` - **被订正的是同日 `M1A-1` 条第三项第三类中的 `lifecycle_control.rs` `resolve_game_creator_agent_runtime_retry_configuration_at`。** 原结论「不适用且已被 profile 挡住,本包不改函数」**只对了一半**:该处确实不会让 plan 误得 autonomous 语义(`autonomous-game-build` 分支先判 profile),但 `M1A-1` 的复核模板只问了「plan 进 matcher 后会不会**误得**不该有的语义」,没有问「plan 落到通用兜底后会不会**丢掉**该有的语义」。retry 这个调用点既是判据也是 run 构造器,两个方向都要问。 diff --git a/docs/project-memory/shared-memory/pitfalls.md b/docs/project-memory/shared-memory/pitfalls.md index ef3eb6305..aee2d8e35 100644 --- a/docs/project-memory/shared-memory/pitfalls.md +++ b/docs/project-memory/shared-memory/pitfalls.md @@ -17,6 +17,7 @@ - 更坏的一半:把新 source 排除出白名单**并不能**脱身。同一协议还有一道入口门 `validate_root_goal_contract_control_plan_at`(`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/autonomous_policy.rs:171`),由 `provider_tool_plan.rs:434` 在通用 tool-plan 解析路径上无条件调用,判据只有「`agent_id` 是 `project-supervisor` + binding 的 root 是自己 + 存在 run profile binding」——**连 source 都不看**。合同不存在时它强制本轮恰好一个 `agent.goal_contract` 动作且 `plan_update`/legacy plan/`response` 全为空,于是「第一轮先问用户一个问题」或「第一轮先回复」的 Agent 会被直接判协议错误。三处判据里只有 `agent_id == GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID` 是共同项,改 `agent_id` 是唯一能一次性解耦的做法。 - 为什么现役 Agent 没事:工具面是 **deny-list** 模型。`agent_runtime_tool_policy_snapshot_at`(`tool_policy_snapshot.rs:130-178`)的 `allowed_tools` 直接等于全量 `agent_runtime_executable_tools()`,`agent.goal_contract` 天然在内;`standard` profile 在 `agent_runtime_tool_policy_snapshot_for_run_at:198` 提前返回、不做裁剪;只有 `!root_control_authority && goal_contract_participant` 的后代才在 `provider_request_builders.rs` 被剔除。所以 gui/cli/game-chat 根 Supervisor 默认就握着这个工具,两道门对它们是「照着做」而不是「过不去」。**按 exact allowlist 设计工具面的新 source 才会撞上**,而 allow-list 正是更安全的那个方向——这条陷阱专门惩罚更严格的设计。 - 处理:新增 trusted source 前,先逐个确认这些调用对新 source 的语义是否成立,尤其是 Goal Contract 创建、验收图完成门与 steer 三处,再单独确认不看 source 的入口门;需要区分时,应当拆出「可信入口」与「Goal Contract 参与者」两条判据,而不是继续复用同一个函数。立项策划已按第 23.1 节裁决进 matcher 并参与 Goal Contract;steer 用独立于 matcher 的显式否决(`reject_supervisor_plan_root_steer`),不得用「不进 matcher」实现。复核结论见 decision-log 2026-08-13 `M1A-1` 条。 +- 双向提问:调用点**既是判据又是构造器**时,只问「会不会误得不该有的语义」不够,还要问「落到通用兜底会不会丢掉该有的语义」。`resolve_game_creator_agent_runtime_retry_configuration_at` 因此在 `M1A-1` 漏出,由 `M1A-3` 补强判据与保源;拒绝继续用弱判据,授予必须用强判据。 - 相关:`requiredEvidence` 只接受 `tool:` 且必须命中 `agent_runtime_acceptance_evidence_tools()`(`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/tool_policy_snapshot.rs:74-96`,当前 18 项)。确定性收束的任务和 Runtime 内部产物验证都不产生 Provider 回执,因此无法为验收节点提供证据——不要指望「让 Runtime 自己验一下」能满足验收图。 ## 2026-08-12 给 manifest 加"新鲜度门控"或身份字段的两个陷阱 diff --git a/docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md b/docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md index bb40ea403..eca75b858 100644 --- a/docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md +++ b/docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md @@ -1713,7 +1713,7 @@ M0 文档 PR 本身最低验证:Markdown 结构与三张 Mermaid 图可解析 | JSON sidecar | `apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/json_sidecar.rs:44-104,122-250` | 现有 writer 可覆盖;不可变文件必须新增 no-replace helper | | 项目锁 | `apps/ai-game-creator-shell/src-tauri/src/project/filesystem.rs:85-138`、`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/project_gates.rs:1467-1505` | 所有 planning mutation 在同一项目锁内重读事实 | | completion blocker | `apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/project_gates.rs:808-860,934-948`、`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/main_loop.rs:1792-1832` | **2026-08-13 收窄**:现役 collaboration blocker 对**策划子 Agent**返回不适用;Supervisor 根 run 侧不再整体豁免(见第 4.3 节)。仍需新增 plan GDD 专用完成门,检查提问/审批等待、receipt、observation、session 与 recovery 状态 | -| plan retry | `apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/lifecycle_control.rs:740-772,775-907` | generic standard fallback 前保留 exact plan source/profile/session lineage。**2026-08-13 复核归属**:本项属工作包 `M1A-3`(第 23.8 节),不属已合入的 `M1A-1`——后者的消费点复核只覆盖了「plan 是否误得 autonomous 语义」这一半。现役实现里 `run_profile` 由 `agent_runtime_run_profile_identity_at` 原样返回、并未丢失,**实际只丢 source**;降级无声是因为 retry 走的 `start_game_creator_agent_background_task_with_link_in_session_lane_at` 对 source 无门禁,而 `validate_agent_runtime_run_profile_binding_record` 只在 `autonomous-game-build` 档要求可信 source | +| plan retry | `apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/lifecycle_control.rs`(`resolve_game_creator_agent_runtime_retry_configuration_at`)、`runtime_driver.rs`(`supervisor_plan_root_identity_holds_at`) | **2026-08-13 `M1A-3` 已落地 source 保源**:`task.source == project-supervisor-plan` 时先走强判据,通过则保留该 source,失败 `kind=plan-root-retry-identity-unsupported`、不降级。gui/cli 仍走 `agent-background-task`。plan-session revision / `gddId` / 按 `gdd-approval` kind 禁 retry 仍属后续包(现役已拒 `waiting-*`) | | planning hydrate command | `apps/ai-game-creator-shell/src-tauri/src/commands.rs:856-875`、`apps/ai-game-creator-shell/src-tauri/src/main.rs:2178-2180`、`apps/ai-game-creator-shell/src/App.tsx:1550,1650,2957,3364,3702` | 新增单一 hydrate command/注册与前端生命周期调用;不把 runtime polling 当 GDD authority | | agent.db | `apps/ai-game-creator-shell/src-tauri/src/project/agent_db.rs:955-1034,1551-1605,1995-2088` | 普通 append 不满足 decision 幂等;新增专用保留入口 | | 16 任务 DAG | `server-rs/crates/shared-contracts/src/game_creation_app.rs:263-425` | 2026-08-12 复核 seed 仍是 16 个,M0/M1 不改拓扑;但固定 DAG 已不是完成语义的唯一来源,见下一行 | @@ -1878,7 +1878,7 @@ M0 完成不表示任何策划功能已上线。M1 的功能实现仍未开始 | 三 | `project-planning` 的 agentCatalog 登记 | **已完成**(机制冻结见第 3.1 节;**代码亦已落地**,2026-08-13:manifest、prompt bundle、runtime adapter、`prompt.rs` 角色合成分支及四处 needs_change 全部合入) | | 四 | `M0A-3` 批二:拓扑与工具面部分 | **已完成**(2026-08-13),拆解见下 | | 四之余 | schema 与 golden vector 收口 | **已完成**(2026-08-13),拆解见下 | -| 五 | M1 本体:策划闭环功能实现 | **`M1A-1` 已落地**(source + matcher + steer 独立否决);其余 PR 未开始。合入门见第 23.8 节 | +| 五 | M1 本体:策划闭环功能实现 | **`M1A-1`、`M1A-3` 已落地**;`M1A-2` 与其余 PR 未开始。合入门见第 23.8 节 | 批二在 2026-08-13 拆成两半,因为其中一半在 M1 代码存在之前**做不完**: @@ -1956,7 +1956,7 @@ M0 完成不表示任何策划功能已上线。M1 的功能实现仍未开始 | --- | --- | --- | --- | | `M1A-1` | source 常量 `project-supervisor-plan` + 进可信 matcher + steer 独立否决 | — | **已落地**。消费点复核见 decision-log 2026-08-13 `M1A-1` 条;steer `kind=plan-root-steer-unsupported`,独立于 matcher | | `M1A-2` | 两层工具面 + `project-planning` 角色 brief | `M1A-1` | 两层工具面快照;`user.input_request` 在广告层不出现且执行层仍拒(第 19 节第 2 条两半) | -| `M1A-3` | plan 根 run 强判据函数 + retry 保源(本节下方「`M1A-3` 的由来」,规格见第 4.1 节第 5、7 段) | `M1A-1` | 强判据须核 durable binding 并逐项相等,**不得只比较内存中的 `runtime.source`**;retry 命中 plan 根 run 时保留 `project-supervisor-plan` 并在项目锁内写 session revision+1 successor,身份校验失败**直接拒绝 retry 而非降级**;`project-supervisor-gui/cli` 的现役 retry 行为逐字节不变。**必须早于 `M1C-2a`** | +| `M1A-3` | plan 根 run 强判据函数 + retry 保源(本节下方「`M1A-3` 的由来」,规格见第 4.1 节第 5、7 段) | `M1A-1` | **已落地**(source 保源与强判据)。`kind=plan-root-retry-identity-unsupported`;gui/cli 兜底不变。第 4.1 节第 7 段里依赖 plan-session / `gddId` / `gdd-approval` kind 的部分仍后置。必须早于 `M1C-2a` | | `M1B-1` | `.agent/planning/` 存储层、strict schema、typed 指纹、版本链;含 `.agent/planning/**` 只挡写判据 | `M1A-2` | **第 9.1 节 golden vector 逐字节相等且指纹相等**(先于其它测试);create-only 与等前缀不可变 | | `M1B-2` | `plan.submit_gdd` 原生工具与提交点 | `M1B-1` | 全部拒绝分支;提交点前后强杀恢复;同 submissionId replay 不产生 vN+1 | | `M1C-0` | 新增 `StaticDelegateContractStatus::UserRevisionRequested` 与分类分支 | `M1A-1` | **本 PR 无写入方、是惰性路径,行为零变化**;做游戏链路返工仍在 `depth=1` 被拒;无该变体的历史记录分类不变 |