From 41228b0cdab7cec623e48bf978e2eb2e7e6483a1 Mon Sep 17 00:00:00 2001 From: Linghong Date: Wed, 26 Aug 2026 13:18:12 +0000 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20GDD=20=E4=BF=AE=E8=AE=A2?= =?UTF-8?q?=E5=90=8E=E7=9A=84=E5=AE=A1=E6=89=B9=E5=8F=96=E8=AF=81=E9=93=BE?= =?UTF-8?q?=E8=B7=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 plan Supervisor 的 AwaitingAcceptanceEvidence 阶段与无副作用 durable 状态判定 在证据不足时收窄工具面,阻止重复 agent.delegate 并保留 file.read、acceptance_update、run_status 补充一条阶段工具面回归及项目排障记录 --- .../runtime_actions/tool_policy_snapshot.rs | 19 ++-- .../runtime_protocol/planning_approval.rs | 87 +++++++++++++++++++ .../agent/runtime_protocol/planning_submit.rs | 23 +++++ .../src-tauri/src/agent_native_tools.rs | 2 + .../shared-memory/decision-log.md | 7 ++ docs/project-memory/shared-memory/pitfalls.md | 7 ++ 6 files changed, 140 insertions(+), 5 deletions(-) diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/tool_policy_snapshot.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/tool_policy_snapshot.rs index 33653ffd5..fd21fa141 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/tool_policy_snapshot.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/tool_policy_snapshot.rs @@ -158,6 +158,9 @@ pub(crate) enum PlanRootSupervisorStage { GoalContract, /// 合同已冻结但本根 run 还没有任何委派:唯一能推进的动作是派出策划子 Agent。 Delegate, + /// 最新 GDD 已提交但尚未完成当前根 Run 的 Acceptance Graph 取证:只能读取 + /// `game/fast_gdd.md`、更新验收图或重放状态,不能抢先创建重复策划 delivery。 + AwaitingAcceptanceEvidence, /// 已有委派:取证、返工与审批相关工具全部开放。 Delegated, } @@ -168,6 +171,9 @@ pub(crate) fn agent_runtime_plan_root_supervisor_tools_for_stage( match stage { PlanRootSupervisorStage::GoalContract => &["agent.goal_contract"], PlanRootSupervisorStage::Delegate => &["agent.delegate"], + PlanRootSupervisorStage::AwaitingAcceptanceEvidence => { + &["file.read", "agent.acceptance_update", "agent.run_status"] + } // 合同已冻结且不可重写,再广告 agent.goal_contract 只会诱导一次必被拒的调用。 PlanRootSupervisorStage::Delegated => &[ "file.read", @@ -194,6 +200,7 @@ mod plan_root_stage_tests { let union = [ PlanRootSupervisorStage::GoalContract, PlanRootSupervisorStage::Delegate, + PlanRootSupervisorStage::AwaitingAcceptanceEvidence, PlanRootSupervisorStage::Delegated, ] .into_iter() @@ -234,11 +241,13 @@ pub(crate) fn plan_root_supervisor_stage_at( let delegated = list_static_delegate_deliveries_at(root)? .into_iter() .any(|delivery| delivery.parent_agent_id == agent_id && delivery.parent_run_id == run_id); - Ok(if delegated { - PlanRootSupervisorStage::Delegated - } else { - PlanRootSupervisorStage::Delegate - }) + if !delegated { + return Ok(PlanRootSupervisorStage::Delegate); + } + if plan_root_supervisor_acceptance_evidence_required_at(root, agent_id, run_id)? { + return Ok(PlanRootSupervisorStage::AwaitingAcceptanceEvidence); + } + Ok(PlanRootSupervisorStage::Delegated) } pub(crate) fn agent_runtime_native_executable_tools() -> Vec<&'static str> { diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_approval.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_approval.rs index 564f7a6c0..1d0782003 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_approval.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_approval.rs @@ -338,6 +338,93 @@ fn latest_plan_gdd_for_root<'a>(gdds: &'a [PlanGddV1], root_run_id: &str) -> Opt }) } +/// Return whether the plan-root Supervisor must collect the current GDD +/// acceptance evidence before it can dispatch another planning child. +/// +/// This is deliberately a read-only projection of the existing acceptance +/// gate. It does not create approval pending or mutate any planning sidecar; +/// the actual pending projection remains owned by +/// `ensure_plan_gdd_approval_pending_after_acceptance_locked` after a successful +/// `agent.acceptance_update`. +pub(crate) fn plan_root_supervisor_acceptance_evidence_required_at( + root: &Path, + agent_id: &str, + run_id: &str, +) -> Result { + if !crate::config::game_creator_planning_capability_enabled()? { + return Ok(false); + } + if agent_id != GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID || run_id.trim().is_empty() { + return Ok(false); + } + let _lock = acquire_game_creator_agent_runtime_project_write_lock_with_wait( + root, + "planning.supervisor-stage", + ) + .map_err(|error| format!("取得 plan Supervisor 阶段判定项目锁失败:{error}"))?; + plan_root_supervisor_acceptance_evidence_required_locked(root, run_id.trim()) +} + +fn plan_root_supervisor_acceptance_evidence_required_locked( + root: &Path, + run_id: &str, +) -> Result { + let gdds = read_plan_gdd_chain_locked(root).map_err(|error| error.to_string())?; + let Some(gdd) = latest_plan_gdd_for_root(&gdds, run_id) else { + return Ok(false); + }; + let Some(global_latest) = gdds.last() else { + return Ok(false); + }; + if global_latest.gdd_id != gdd.gdd_id + || global_latest.version != gdd.version + || global_latest.fingerprint != gdd.fingerprint + { + return Ok(false); + } + validate_plan_gdd(gdd).map_err(|error| error.to_string())?; + + let approvals = read_plan_gdd_approvals_locked(root).map_err(|error| error.to_string())?; + validate_plan_gdd_approvals_against_gdds(&gdds, &approvals) + .map_err(|error| error.to_string())?; + if read_plan_gdd_approval_for_version_locked(root, gdd.version) + .map_err(|error| error.to_string())? + .is_some() + { + return Ok(false); + } + if let Some(pending) = + read_plan_gdd_approval_pending_locked(root).map_err(|error| error.to_string())? + { + if !pending_matches_gdd(&pending, gdd) { + return Err("plan Supervisor 阶段判定发现 approval pending identity 冲突".to_string()); + } + return Ok(false); + } + + let Some(session) = + read_plan_session_with_recovery_locked(root).map_err(|error| error.to_string())? + else { + return Ok(false); + }; + if !plan_gdd_session_matches_submission(&session, gdd) { + return Ok(false); + } + let Some(delivery) = read_static_delegate_delivery_at(root, &gdd.delegation_id)? else { + return Ok(false); + }; + if delivery.status != StaticDelegateDeliveryStatus::ClaimedByParent + || delivery.terminal_status.as_deref() != Some("completed") + { + return Ok(false); + } + + Ok(matches!( + plan_fast_gdd_acceptance_status_at_locked(root, gdd)?, + PlanFastGddAcceptanceStatus::NeedsEvidence + )) +} + pub(crate) fn ensure_plan_gdd_approval_pending_after_acceptance_locked( root: &Path, agent_id: &str, diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_submit.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_submit.rs index f5dee5b6d..d8fd32755 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_submit.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/planning_submit.rs @@ -4721,6 +4721,29 @@ mod tests { cleanup_fixture(root); } + #[test] + fn m1c2a_unapproved_gdd_requires_acceptance_evidence_before_delegate() { + let (root, _gdd, root_runtime) = acceptance_gate_fixture(true); + + assert_eq!( + plan_root_supervisor_stage_at( + &root, + GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, + &root_runtime.run_id, + ) + .expect("classify plan root stage"), + PlanRootSupervisorStage::AwaitingAcceptanceEvidence + ); + assert_eq!( + agent_runtime_plan_root_supervisor_tools_for_stage( + PlanRootSupervisorStage::AwaitingAcceptanceEvidence + ), + &["file.read", "agent.acceptance_update", "agent.run_status"] + ); + + cleanup_fixture(root); + } + #[test] fn m1c2a_failed_acceptance_requires_claim_before_repair_dispatch() { let (root, gdd, root_runtime) = acceptance_gate_fixture(false); diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent_native_tools.rs b/apps/ai-game-creator-shell/src-tauri/src/agent_native_tools.rs index 7ded0e67d..382d9765f 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent_native_tools.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent_native_tools.rs @@ -2256,6 +2256,7 @@ mod tests { for stage in [ PlanRootSupervisorStage::GoalContract, PlanRootSupervisorStage::Delegate, + PlanRootSupervisorStage::AwaitingAcceptanceEvidence, PlanRootSupervisorStage::Delegated, ] { let mut staged = functions.clone(); @@ -2290,6 +2291,7 @@ mod tests { for stage in [ PlanRootSupervisorStage::GoalContract, PlanRootSupervisorStage::Delegate, + PlanRootSupervisorStage::AwaitingAcceptanceEvidence, PlanRootSupervisorStage::Delegated, ] { let mut staged = functions.clone(); diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 134f60bc4..81b76e2c8 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -1,5 +1,12 @@ # 决策记录 +## 2026-08-26 Fast GDD 修订后先取证再允许再次委派 + +- **现象**:GDD v1 经用户选择“修改”后,策划子 Agent 正确提交 v2,但 plan 根 Supervisor 的 `Delegated` 阶段仍同时广告 `agent.delegate` 与审批前置工具;模型可能在 Acceptance Graph 重新取证前重复创建修订 delivery,随后被 `PLAN_PROVIDER_USAGE_DEFERRED` 拦停。 +- **决策**:plan 根阶段增加轻量的 `AwaitingAcceptanceEvidence` 状态。当前根最新 GDD 无 approval receipt/pending、session `latestSubmittedRef` 精确指向该提交、delivery 已由根认领且 Acceptance Graph 返回 `NeedsEvidence` 时,只广告 `file.read`、`agent.acceptance_update`、`agent.run_status`;只有用户真正对最新审批卡选择修改/退回后,才恢复 `agent.delegate`。 +- **边界**:不放宽 Provider usage 门禁,不重构 delegation/repair lineage,不自动生成证据或审批 pending;审批 pending 仍只由既有 acceptance gate 在 `agent.acceptance_update` 成功后创建。 +- **验证**:新增一条阶段工具面回归,并通过 15 条 M1C-2a acceptance gate 定向测试、plan root 原生工具目录测试、`cargo check --all-targets`、格式与 diff 检查。 + ## 2026-08-24 AGC Direct 媒体能力只通过客户端语义工具开放 - 背景:资源页已经补齐视频、角色动画、音效和背景音乐的 create/derive 能力,但 Direct Codex 只能准备标准美术包,无法查询已登记源资源或表达新增媒体意图。直接开放 Tauri invoke 会把项目路径、revision、operation、幂等键、登录态和事务权力交给模型。 diff --git a/docs/project-memory/shared-memory/pitfalls.md b/docs/project-memory/shared-memory/pitfalls.md index eed4970e8..61860d74c 100644 --- a/docs/project-memory/shared-memory/pitfalls.md +++ b/docs/project-memory/shared-memory/pitfalls.md @@ -1,5 +1,12 @@ # 踩坑与排障记录 +## 2026-08-26 GDD 新版本提交后不能沿用“已有委派”工具面 + +- **现象**:`plan_root_supervisor_stage_at` 只按是否存在 delivery 判定 `Delegated`。用户修订产生的新 GDD 仍未完成当前根 Run 的 `file.read → agent.acceptance_update` 取证时,模型会看到 `agent.delegate`,可能重复派发同一条策划链。 +- **原因**:自然语言 playbook 已规定“证据不足先取证、用户修改后才返工”,但阶段工具白名单没有把这条 durable 状态固化。 +- **处理**:阶段判定复用现有 acceptance gate 的 GDD/session/delivery/graph identity 检查,增加无副作用的 `AwaitingAcceptanceEvidence` 阶段;`PLAN_PROVIDER_USAGE_DEFERRED` 保持 fail-closed,不通过放宽 Provider 使用量门禁解决。 +- **排查顺序**:先看最新 `gdd.vN.json`、`session.latestSubmittedRef`、delivery 是否 `ClaimedByParent`,再看 Acceptance Graph 是否 `NeedsEvidence`;若仍可见 `agent.delegate`,优先检查 plan root 阶段快照,而不是修改 acceptance gate 或 Provider 门禁。 + ## 2026-08-15 把校验往链路前面挪,改的不是严格程度而是作用域 - 现象:CI 全量 5 条失败,看上去毫不相干(两条 Goal 续跑停在 `needs-reconciliation`、一条交接用例断言错误文案、一条恢复用例把不可读 state 的错误抛了出来、一条 Linux-only 用例错误码对不上),实际只有 3 个根因,且三者是**同一个形状**:新增或既有的检查被放在了链路更靠前的位置,于是它的语义作用域被悄悄放大或提前,而不是「变严」。