From 2bdf6a732bff4dda18d35244dabddf682f4dadc9 Mon Sep 17 00:00:00 2001 From: Linghong Date: Fri, 21 Aug 2026 08:33:10 +0000 Subject: [PATCH] =?UTF-8?q?plan=20=E6=A0=B9=E4=B8=8D=E5=86=8D=E7=BB=B4?= =?UTF-8?q?=E6=8A=A4=E7=BB=93=E6=9E=84=E5=8C=96=E8=AE=A1=E5=88=92=EF=BC=8C?= =?UTF-8?q?=E7=A0=8D=E6=8E=89=20update=5Fagent=5Fplan?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit run 19 跑通全链路用了 14 轮,其中只有 5 轮在干活:goal_contract、delegate、 file.read、action_history、acceptance_update。浪费的 9 轮里有 5 轮是纯 plan_update.explanation_only——Runtime 明确标了这个事件——每轮 blocked、每轮烧一次 Provider 调用。 plan 根的流程形状是固定的四步(冻结 → 委派 → 取证 → 交审批),Runtime 自己就知道, 模型维护一份结构化计划不产生任何信息,却提供了一个「看起来像动作、实际什么都不 推进」的合法输出。本地原型没有这个概念,它的工具全是推进动作,也就没有这种输出。 移除不会卡住收束:structured_plan_completion_blocker 第一行就是 agent_runtime_has_structured_plan(plan_revision > 0),从不调用就恒为假,那道门 不参与;plan 根的收束本来就由 runtime.plan_gdd 的审批门管。前端 planSteps 为空时 不渲染步骤条,plan 根的进度改由 current_action / waiting_on / next_step 呈现。 既有的空转自愈本来就会在连续空转后把这个函数摘掉(provider_request_builders 的 idle repair),run 19 第 12 轮那次 acceptance_update 正是被它救回来的。这次只是把 「出事后补救」提前成「从头就不给」。 一并清掉两段已经失效的合同文字:plan/common.md 里的 user.input_request 协议段 (该工具上一笔已从 plan 根移除),以及 update_agent_plan 的用法段。prompt 头部 改成说明「本 run 不维护结构化计划,流程形状固定,每轮只做当前阶段该做的那件事」。 Co-Authored-By: Claude Opus 5 --- .../src-tauri/prompts/runtime/plan/common.md | 4 +-- .../src-tauri/src/agent/prompt.rs | 5 +-- .../src-tauri/src/agent_native_tools.rs | 31 +++++++++++++------ 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/apps/ai-game-creator-shell/src-tauri/prompts/runtime/plan/common.md b/apps/ai-game-creator-shell/src-tauri/prompts/runtime/plan/common.md index 4a9760ee7..d1029e423 100644 --- a/apps/ai-game-creator-shell/src-tauri/prompts/runtime/plan/common.md +++ b/apps/ai-game-creator-shell/src-tauri/prompts/runtime/plan/common.md @@ -1,10 +1,8 @@ 用户只描述玩法类型、机制或相似体验时,不代表授权复刻现有游戏。所有专业 Agent 必须创建原创标题、阵营、资源、单位名称、角色造型、界面术语和视觉语言;禁止沿用、翻译或近似改写现有游戏的专有角色、单位名、Logo、贴图、标志性布局与受保护视觉语言。除非用户明确提供有权使用的项目内素材,否则不得把 Sunflower、Peashooter、向日葵、豌豆射手、僵尸等知名塔防元素写入策划、记忆、代码、图片提示或正式产物。 -用户输入请求协议:user.input_request 使用 {"questions":[{"id":"唯一 snake_case","header":"最多 12 字符","question":"单句问题","options":[{"label":"短选项","description":"一条影响说明"},{"label":"另一选项","description":"一条影响说明"}]}]},一次 1-3 题、每题 2-3 个选项且始终允许自由输入。它必须是本轮唯一函数调用,不得同批调用 update_agent_plan、其他动作函数或 respond_to_user。只有 Project Supervisor 或没有父委派身份的静态 Agent 开发试聊可直接调用;委派专业 Agent 和动态隔离 child 必须把澄清需要回传父 Agent。 - 静态委派协议:新 agent.delegate 必须提交 1-8 条 acceptanceCriteria、0-16 个精确项目内非私有 expectedArtifacts,以及 nullable repairOfDelegationId/runId/continuationOfDelegationId/questionsSha256/answersSha256,普通委派后三项传 null。专业 Agent 收到的 task 会携带完整合同。Supervisor 认领回执后必须区分 evidence-ready、needs-user-input 与 needs-repair;前者仍需语义验收,needs-repair 不能作为成功。专业 Agent 若缺少会实质改变结果的用户事实,不能调用 user.input_request,必须以最终回复首行 `AGC_NEEDS_USER_INPUT_V1`,下一行短 JSON `{"questions":[...]}` 返回 1-3 个结构化问题;Runtime 会把它作为内部回执交给 Supervisor。Supervisor 对每个原 delivery 逐一用现有 user.input_request 提问,收齐对应答案后最多创建一次 continuation 委派,并同时提交 continuationOfDelegationId、questionsSha256、answersSha256;Runtime 会自动派生稳定 continuation identity,不得把多个 delivery 的问题或答案混入同一 continuation。 每 6 轮只是一次进度 checkpoint 与停滞检测,不是上下文压缩或 run 的终止上限;只要 observation 出现新的独立进展,就在同一 run 继续下一窗口,只有窗口没有新进展时才按停滞处理。真正的上下文压缩仅由 token 阈值或显式 compact 触发。 -必须直接调用 update_agent_plan、与当前请求广告的工具一一对应的动作函数或 respond_to_user;只有步骤或状态真实变化时,update_agent_plan 才可单独作为持久进度 checkpoint;当前 in_progress 步骤已具备执行条件时,必须在同一响应附带具体动作,不能反复只改 explanation。update_agent_plan 也可在同一响应中按顺序附带最多三个动作或最终回复,动作与最终回复不得共存。工具结果会由 Runtime 作为 observation 返回;不要假装工具已执行,不要把计划、动作或回复放进普通文本,不要 markdown,不要泄露密钥。 +必须直接调用与当前请求广告的工具一一对应的动作函数,或在本阶段确实无事可做时调用 respond_to_user。本 run 不维护结构化计划,也没有 update_agent_plan 可调;工具结果会由 Runtime 作为 observation 返回,不要假装工具已执行,不要把动作或回复放进普通文本,不要 markdown,不要泄露密钥。 \ No newline at end of file diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/prompt.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/prompt.rs index a63fb2a75..b11bd5fc5 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/prompt.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/prompt.rs @@ -620,7 +620,7 @@ fn game_creator_project_supervisor_plan_tool_plan_system_prompt() -> String { // 「合同说有、请求里没有」的自相矛盾。 let tool_catalog = agent_runtime_plan_root_supervisor_tools().join("、"); let prompt_header = format!( - "你正在使用 Genarrative AI 游戏创作多智能体 Runtime。你必须直接调用当前请求广告的原生函数:复杂任务首次拆解、实际进度变化、steer 调整顺序或最终收束时调用 update_agent_plan,并提交 explanation 与完整 steps;无需更新时不要调用 update_agent_plan。steps 只允许 pending、in_progress、completed 且同时最多一个 in_progress;已完成步骤必须保留且不得回退,所有必要步骤 completed 前不得调用 respond_to_user,Runtime 不会按工具动作下标代替你更新进度。本 run 的原生可执行工具目录只有:{tool_catalog},并且**按阶段开放**——每一轮只广告当前阶段能真正推进链路的那几个,没有出现在本轮函数目录里的,这一阶段调用不了,也不需要调用。澄清卡不由你发:子 Agent 的问询信封由 Runtime 直接转成决策卡,你只会在用户答完之后被恢复。写入、补丁、删除、命令、预览、素材生成、任务图、记忆、黑板、isolated child 与 MCP 工具在本 run 都不存在,调用它们只会失败。" + "你正在使用 Genarrative AI 游戏创作多智能体 Runtime。你必须直接调用当前请求广告的原生函数,不要把动作或回复写进普通文本。本 run 不维护结构化计划——流程形状是固定的(冻结目标合同 → 委派策划子 Agent → 取证验收 → 交审批),进度由 Runtime 自己记录,你只需要每一轮做当前阶段唯一该做的那件事。本 run 的原生可执行工具目录只有:{tool_catalog},并且**按阶段开放**——每一轮只广告当前阶段能真正推进链路的那几个,没有出现在本轮函数目录里的,这一阶段调用不了,也不需要调用。澄清卡不由你发:子 Agent 的问询信封由 Runtime 直接转成决策卡,你只会在用户答完之后被恢复。写入、补丁、删除、命令、预览、素材生成、任务图、记忆、黑板、isolated child 与 MCP 工具在本 run 都不存在,调用它们只会失败。" ); // `$platform` 整段是 command.start/exec/poll/stdin/terminate 的用法合同, // plan 根一个 command 工具都没有;GDD 里的平台事实由 Runtime 另行注入,与 @@ -1494,9 +1494,10 @@ mod tests { fn plan_common_paragraphs_are_verbatim_slices_of_the_shared_common_section() { let common = required_runtime_prompt_section("common"); let plan_common = required_runtime_prompt_section("planCommon"); + // `用户输入请求协议` 不再继承:plan 根没有 user.input_request,澄清卡由 + // Runtime 在 parent-wake 屏障处按信封原文直接构造。 let inherited = [ "用户只描述玩法类型、机制或相似体验时,不代表授权复刻现有游戏。", - "用户输入请求协议:", "静态委派协议:", ]; let mut matched = 0; 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 ed1223869..2acbe61e7 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 @@ -378,10 +378,19 @@ pub(crate) fn retain_plan_root_supervisor_native_tools( functions: &mut Vec, stage: PlanRootSupervisorStage, ) -> Result<(), String> { - let mut allowed = BTreeSet::from([ - AGENT_RUNTIME_UPDATE_PLAN_FUNCTION_NAME.to_string(), - AGENT_RUNTIME_RESPOND_FUNCTION_NAME.to_string(), - ]); + // plan 根不带 `update_agent_plan`。它的四步流程(冻结→委派→取证→审批)是 + // Runtime 早就知道的固定形状,模型维护一份结构化计划不产生任何信息,却提供了 + // 一个「看起来像动作、实际什么都不推进」的合法输出:实测一次跑通的 run 里 14 + // 轮有 5 轮是纯 `plan_update.explanation_only`,全部被 blocked,每轮烧一次 + // Provider 调用。既有的空转自愈本来就会在连续空转后把这个函数摘掉 + // (`provider_request_builders` 里的 idle repair),这里只是把"出事后补救" + // 提前成"从头就不给"。 + // + // 移除它不会卡住收束:`structured_plan_completion_blocker` 第一行就是 + // `agent_runtime_has_structured_plan`(`plan_revision > 0`),从不调用就恒为 + // 假,那道门不参与;plan 根的收束由 `runtime.plan_gdd` 的审批门管。前端 + // 步骤条为空时不渲染,plan 根的进度改由 current_action/waiting_on/next_step 呈现。 + let mut allowed = BTreeSet::from([AGENT_RUNTIME_RESPOND_FUNCTION_NAME.to_string()]); for tool in agent_runtime_plan_root_supervisor_tools_for_stage(stage) { allowed.insert( native_runtime_function_name(tool) @@ -2160,10 +2169,9 @@ mod tests { let mut staged = functions.clone(); retain_plan_root_supervisor_native_tools(&mut staged, stage) .expect("retain plan root tools"); - let mut expected = vec![ - AGENT_RUNTIME_UPDATE_PLAN_FUNCTION_NAME.to_string(), - AGENT_RUNTIME_RESPOND_FUNCTION_NAME.to_string(), - ]; + // plan 根只保留 respond_to_user 这一个协议函数;update_agent_plan 从 + // 头就不广告。 + let mut expected = vec![AGENT_RUNTIME_RESPOND_FUNCTION_NAME.to_string()]; for tool in agent_runtime_plan_root_supervisor_tools_for_stage(stage) { expected.push(native_runtime_function_name(tool).expect("plan root function name")); } @@ -2183,8 +2191,8 @@ mod tests { .iter() .any(|function| function.name.starts_with(AGENT_RUNTIME_NATIVE_MCP_PREFIX))); } - // 广告层不得再出现 user.input_request:澄清卡由 Runtime 在 parent-wake 屏障处 - // 直接按信封原文构造,Supervisor 没有调用它的时机。 + // 广告层不得再出现 user.input_request(澄清卡由 Runtime 在 parent-wake 屏障处 + // 直接按信封原文构造)与 update_agent_plan(plan 根不维护结构化计划)。 let user_input_function = native_runtime_function_name("user.input_request").expect("user input function name"); for stage in [ @@ -2198,6 +2206,9 @@ mod tests { assert!(!staged .iter() .any(|function| function.name == user_input_function)); + assert!(!staged + .iter() + .any(|function| function.name == AGENT_RUNTIME_UPDATE_PLAN_FUNCTION_NAME)); } }