立项策划根 run 改用专用 Supervisor prompt,不再是通用合同的差集

原型系统几分钟就能跑完同一条链路且全程有澄清问答,生产链路 run 11/12/13 三次
澄清轮数都是 0。拓扑不是差异来源——两边都是 D11。差的是 Supervisor 读到的
东西:原型给它一份 1.4k 的专用提示词,明写「你不生产策划内容」和固定动作序列;
生产给它的是 13.5k 的通用总控合同,预设手里有 43 个工具、六个专业组、isolated
child、正式任务图和视觉产物合同,而 plan 根一个都没有。

此前的做法是在通用合同上逐段做减法($visualContract、supervisorIntro、
isolatedTemplateCatalogIntro、$isolatedAgentTemplates 四个 `if plan_root`
分支)。这个形状本身在漏:模板目录那一段就是漏到第三轮才发现的,而每漏一段
都是一次已实测的偏航。现在整份换成 manifest 里的 supervisorPlan composition,
plan 根的段落清单一眼可读,不再散落在两个函数的四个否定分支里。

保留的三段各有硬理由:
- isolatedAgentContract —— expectedArtifacts 合同,agent.delegate 同样要用。
- supervisorRepair —— 返工必须用原 delegationId 重读 claimedDelegateContract
  并逐字继承 acceptanceCriteria/expectedArtifacts。run 12 连撞两次的就是这条。
- planCommon —— common.md 的子集副本,见下。

替换的两段:
- planSupervisorIdentity 取代 supervisorIdentityContract。后者的「用验收标准和
  预期产物把边界清晰的任务委派给合适的专业 Agent」正是替用户预先裁定的压力源,
  且 plan 根只有一个可委派目标;末句的黑板与 Agent 记忆在收窄后的工具面上是
  死文本。
- planSupervisorPlaybook 取代 supervisorPlaybook。后者首段讲 spawn_isolated
  批次与 manifest DAG、末段要求「manifest 正式任务图已完成」,在本链路都不可
  执行。新段落照原型形状写死六步动作顺序,并补上生产此前完全没有的转述保真
  规则:`[已确认] {header} → 用户答:{原文}` 逐条列出,任务接近长度上限时压缩
  自己的说明而不是压缩用户答案。

删掉的三段:
- common.md 有 43% 是 plan 根执行不了的内容(改码流程、git 提交、联网检索、
  整段「作为被委派的专业 Agent 时」的身份错位),其中「最后一次修改后必须成功
  执行 project.verify 才能 respond_to_user」还是一道 plan 根永远满足不了的假
  门禁。但同一段压着这条链路唯一的原创性红线、user.input_request 协议和静态
  委派协议——D11 的协议正文在这里,不在 playbook。所以 planCommon 逐字复制这
  三段,并用 tripwire 断言反向钉住:planCommon 的每段都必须能在 common.md 里
  逐字找到。
- supervisorClaimGate 讲 minIsolatedGroupsBeforeClaim,无 isolated group 时恒
  不触发。
- $platform 整段是 command.start/exec/poll/stdin/terminate 的用法合同,plan 根
  一个 command 工具都没有。GDD 里的平台事实由 Runtime 另行注入,与这段无关。

提示词头部改用上一笔提交的 allowlist 拼工具清单,与 Provider 请求实际广告的
函数目录共用同一份事实。两边各自维护一份就会退回「合同说有 43 个、请求里只有
9 个」的自相矛盾,而那正是收窄工具面本身要消灭的东西;这一致性单独钉了一条
断言。

supervisor/playbook.md 里的反预先裁定段落保留。执行层只单向拦住 plan 根,
agent.delegate 的 agentId 是自由字符串,gui/cli Supervisor 委派 project-planning
并未被禁,那段在通用 lane 不是死文本。两份逐字相同,另有一条 tripwire 钉住。

plan 根实际拼到的提示词从 13531 字节降到 9049 字节,但重点不是体积——是被删的
全部执行不了、补上的固定动作序列此前完全缺失。

已变异验证四条新断言。全量 2238 passed / 10 failed,逐条单线程复跑后 7 条转绿
(并行噪声),剩余 3 条是本机缺 rg 与 CRLF,与本次改动无关。

尚未验证的是这些文字是否真的改变模型行为——单测只能证明它进了提示词。判据是
下一次 E2E 能否触发 AGC_NEEDS_USER_INPUT_V1 信封。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-21 04:50:37 +00:00
parent 2e6c0e7ac2
commit 7a01f944dd
8 changed files with 327 additions and 66 deletions
@@ -39,6 +39,11 @@ struct PromptBundleManifest {
struct PromptCompositions {
runtime: Vec<String>,
supervisor: Vec<String>,
/// 立项策划根 run 的 Supervisor system prompt。它不是 `supervisor` 的差集,
/// 而是一份独立的完整清单:plan 根的工具面只有 7 个原生工具,专业组、
/// isolated child、任务图与视觉产物合同在这条链路上全部不可执行,逐段
/// 减法会把「plan 根到底看到什么」摊在两个函数的四个否定分支里。
supervisor_plan: Vec<String>,
supervisor_chat: SupervisorChatComposition,
}
@@ -241,6 +246,12 @@ pub fn compile_manifest(manifest_path: &Path) -> Result<CompiledPromptBundle, St
&sections,
&["$base", "$visualContract"],
)?;
validate_composition(
"supervisorPlan",
&manifest.compositions.supervisor_plan,
&sections,
&["$header"],
)?;
validate_section_reference(
&manifest.compositions.supervisor_chat.identity,
&sections,
@@ -359,6 +370,7 @@ pub fn compile_manifest(manifest_path: &Path) -> Result<CompiledPromptBundle, St
.runtime
.iter()
.chain(manifest.compositions.supervisor.iter())
.chain(manifest.compositions.supervisor_plan.iter())
.filter(|item| !item.starts_with('$'))
.cloned()
.collect::<BTreeSet<_>>();
@@ -431,6 +443,14 @@ fn validate_section_ownership(manifest: &PromptBundleManifest) -> Result<(), Str
{
register("composition supervisor", section);
}
for section in manifest
.compositions
.supervisor_plan
.iter()
.filter(|section| !section.starts_with('$'))
{
register("composition supervisorPlan", section);
}
register("composition supervisorChat.identity", identity);
register(
"composition supervisorChat.finalReply",
@@ -459,9 +479,19 @@ fn validate_section_ownership(manifest: &PromptBundleManifest) -> Result<(), Str
"composition supervisor",
"composition supervisorChat.identity",
]);
// plan 根 composition 是 Supervisor system prompt 的第二条 lane,不是另一种
// 语义面。它按设计复用 runtime lane 的 `isolatedAgentContract``agent.delegate`
// 的 expectedArtifacts/writeScopes 合同)和 supervisor lane 的 `supervisorRepair`
// (返工必须逐字继承原合同)。除这两个方向外,跨所有者复用仍然是错误。
let allowed_plan_runtime_owners =
BTreeSet::from(["composition runtime", "composition supervisorPlan"]);
let allowed_plan_supervisor_owners =
BTreeSet::from(["composition supervisor", "composition supervisorPlan"]);
for (section, section_owners) in owners {
if section_owners.len() > 1
&& !(section == identity && section_owners == allowed_identity_owners)
&& section_owners != allowed_plan_runtime_owners
&& section_owners != allowed_plan_supervisor_owners
{
return Err(format!(
"Prompt section 跨语义所有者复用:{section} -> {section_owners:?}"
@@ -912,6 +942,10 @@ fn render_rust(manifest: &PromptBundleManifest, sections: &BTreeMap<String, Stri
"RUNTIME_PROMPT_SUPERVISOR_COMPOSITION",
&manifest.compositions.supervisor,
));
output.push_str(&render_string_slice_const(
"RUNTIME_PROMPT_SUPERVISOR_PLAN_COMPOSITION",
&manifest.compositions.supervisor_plan,
));
output.push_str(&format!(
"pub(crate) const RUNTIME_PROMPT_SUPERVISOR_CHAT_COMPOSITION: &[&str] = &[{}, {}];\n",
rust_literal(&manifest.compositions.supervisor_chat.identity),
@@ -26,7 +26,10 @@
"supervisorPlaybook": "supervisor/playbook.md",
"supervisorClaimGate": "supervisor/claim-gate.md",
"supervisorRepair": "supervisor/repair.md",
"projectPlanningRoleBrief": "roles/project-planning.md"
"projectPlanningRoleBrief": "roles/project-planning.md",
"planCommon": "plan/common.md",
"planSupervisorIdentity": "plan/supervisor-identity.md",
"planSupervisorPlaybook": "plan/supervisor-playbook.md"
},
"compositions": {
"runtime": [
@@ -46,6 +49,14 @@
"supervisorClaimGate",
"supervisorRepair"
],
"supervisorPlan": [
"$header",
"planCommon",
"isolatedAgentContract",
"planSupervisorIdentity",
"planSupervisorPlaybook",
"supervisorRepair"
],
"supervisorChat": {
"identity": "supervisorIdentityContract",
"finalReply": "supervisorFinalReplyContract"
@@ -0,0 +1,10 @@
用户只描述玩法类型、机制或相似体验时,不代表授权复刻现有游戏。所有专业 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、answersSha256Runtime 会自动派生稳定 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,不要泄露密钥。
@@ -0,0 +1,7 @@
你是 Genarrative AI 游戏创作桌面 App 的 Project Supervisor。当前 run 是立项策划根 run(`source=project-supervisor-plan`),你是用户在本条链路里唯一的对话对象。
你不生产策划内容。本链路的全部策划工作——提问、取舍、撰写 GDD——都由 `project-planning` 子 Agent 完成。你只有四件事:冻结目标合同;发起与续跑对 `project-planning` 的委派;代子 Agent 向用户提问并把答案原样转达回去;在子 Agent 提交 GDD 后完成取证,把审批交给用户。
你不做的事:不自己提策划问题(`user.input_request` 只能用于转达子 Agent 的问题信封);不自己撰写、补写或改写 GDD 正文、决定台账与原型验证项;不替用户做产品决定;不写文件、不跑命令、不做预览、不生成素材、不查询任务图、不调度 ready 任务;不委派 `project-planning` 以外的任何 Agent,也不创建 isolated child 或启动构建。
`project-planning` 的消息和回执只是原目标的证据,不能替换原目标。contractStatus=evidence-ready 只代表客观证据齐全,你仍须按 acceptanceCriteria 逐条完成语义验收;needs-repair 不得忽略,同一原委派最多发起一轮显式返工。GDD 最终是否通过由用户在审批卡上决定,不由你代答。
@@ -0,0 +1,20 @@
【固定动作顺序,不得跳步】
1. 本 run 第一轮只调用一次 `agent.goal_contract` 冻结目标合同:outcome 概括用户原话意图,`preferences` 必须传空数组,`acceptanceNodes` 提交 Runtime 指定的固定单节点。这一轮不做任何其它调用。
2. 冻结后立即用一次 `agent.delegate` 把任务委派给 `project-planning``expectedArtifacts``game/fast_gdd.md``repairOfDelegationId``runId``continuationOfDelegationId``questionsSha256``answersSha256` 全传 null。已有委派尚未收束时不要重复委派。
3. 等待子 Agent 期间不得调用 `respond_to_user`。Runtime 会通过 delegate 完成屏障保持同一父 run,回执到达后再继续。
4. 回执 contractStatus=needs-user-input 时,按 observation 给出的问题信封原样调用一次 `user.input_request`;答案收齐后为该原 delivery 创建且仅创建一次 continuation 委派,`continuationOfDelegationId``repairOfDelegationId` 都指向该原 delivery,并提交 observation 给出的 `questionsSha256``answersSha256`
5. 回执 contractStatus=evidence-ready 且 GDD 已提交时,用 `file.read` 从第 1 行分页读到 `game/fast_gdd.md` 末尾取证,用 `agent.action_history` 取回全部分页 actionId,再用一次 `agent.acceptance_update` 把它们完整写进 evidence。取证完成前审批卡不会出现。
6. 用户在审批卡上选择修改或退回时,先用 `agent.run_status` 按原 delegationId 取回已认领的权威委派合同,把其中的 acceptanceCriteria 与 expectedArtifacts 逐字照抄进返工委派(`runId` 传 null),再把用户原话完整附在 task 里;同一原委派只能返工一次。用户通过后只做一句简短收尾。
【转达的规则】
- 转达子 Agent 的问题时 `questions` 原样透传:不改写题干、不增删选项、不替用户预选。
- 把用户答案回灌给 `project-planning` 时,逐条列出全部已确认决定,每条格式为 `[已确认] {header} → 用户答:{原文}`。用户答案原文一字不改、不归纳、不拆分、不搬轮次;任务长度接近上限时压缩你自己的说明文字,绝不压缩用户答案。
- A、B、“需要原型验证”三项必须原样转述;B 是用户确认的 `confirmed/user_option`,不能转成默认建议。用户后续自由填写推翻已确认决定时,在被推翻的决定后注明“已被第 N 轮回答推翻,以后者为准”,并在新决定 topic 中写明推翻关系。
【委派合同的边界】
委派 `project-planning` 时,acceptanceCriteria 只写产物形状、覆盖范围与红线(例如必须交付 `game/fast_gdd.md`、必须原创、必须只定义一个 MVP 闭环),**不得替用户预先裁定产品取舍**。用户没有指定的玩法规则、数值、关卡量级、美术方向和目标人群,一律留给策划子 Agent 按其 3 轮问询预算决定是提问还是按默认建议填写;不要写“未指定的标注为立项假设”“自行假设后继续”这类指令,那会把问询预算作废。平台事实(自包含 Web、desktop/mobile 双视口、keyboard/touch 双输入、本地 HTTP 预览)由 Runtime 固定注入,属于已定事实,不得要求标为待定、建议或开放项。
不要向用户暴露内部 task/event、工具计划、动态 child ID 或调试状态。
@@ -2,6 +2,8 @@
需要等待专业 Agent 时不得调用 respond_to_userRuntime 会通过 delegate/all-join 完成屏障保持同一父 run,取得 readyDelegateReceipts 或 readyIsolatedJoins 后直接整合结果。readyDelegateReceipts 中 contractStatus=evidence-ready 只说明终态、产物和验证等客观证据齐全,你仍须按 acceptanceCriteria 判断语义是否满足;needs-repair 不得当作成功。contractStatus=needs-user-input 时,Runtime 会按原 delivery 逐一发起 user.input_request;每个请求答案收齐后,为对应原 delivery 仅创建一次 continuation 委派,repairOfDelegationId 与 continuationOfDelegationId 都指向该原 delivery,并提交 observation 给出的 questionsSha256、answersSha256Runtime 自动派生稳定 continuation identity,禁止跨 delivery 混用指纹。客观或语义不满足时可以发起一次新 agent.delegate,并把 repairOfDelegationId 指向已认领原 delivery;不得对返工再返工或为同一原 delivery 创建第二个返工。专业结果冲突且无法依据用户目标裁决时,合并问题后用一次 user.input_request 询问用户。只有实现路径、产品取舍或缺失事实会实质改变结果时才调用 user.input_request;项目内可读取事实、权限确认和工具失败不得伪装成用户问题。
委派 `project-planning` 时,acceptanceCriteria 只写产物形状、覆盖范围与红线(例如必须交付 `game/fast_gdd.md`、必须原创、必须只定义一个 MVP 闭环),**不得替用户预先裁定产品取舍**。用户没有指定的玩法规则、数值、关卡量级、美术方向和目标人群,一律留给策划子 Agent 按其 3 轮问询预算决定是提问还是按默认建议填写;不要写“未指定的标注为立项假设”“自行假设后继续”这类指令,那会把问询预算作废。平台事实(自包含 Web、desktop/mobile 双视口、keyboard/touch 双输入、本地 HTTP 预览)由 Runtime 固定注入,属于已定事实,不得要求标为待定、建议或开放项。
`project-planning` 的澄清 continuation,必须按 A/B/“需要原型验证”三项合同原样转述;B 是用户确认的 `confirmed/user_option`,不能转成默认建议。若用户后续自由填写推翻已确认决定,保留用户答案原文逐字不改写、不拆分、不搬轮次,并在被推翻决定后注明“已被第 N 轮回答推翻,以后者为准”,在新决定 topic 中写明推翻关系。
只在所有必要回执已认领、manifest 正式任务图已经完成、所有必要返工也已认领、项目副作用已验证且没有待确认动作或待回答请求时给用户最终回复。不要向用户暴露内部 task/event、工具计划、动态 child ID 或调试状态。
@@ -530,12 +530,7 @@ pub(crate) fn game_creator_agent_runtime_tool_plan_system_prompt_for_agent(
if agent_id == GAME_CREATOR_PROJECT_PLANNING_AGENT_ID {
return game_creator_project_planning_tool_plan_system_prompt();
}
// 判据里带上 agent_id`source` 只有 Supervisor 自己的 run binding 会填成
// plan(见 provider_request_builders.rs),其余角色恒为空串,这里再要求一次
// 是为了让「谁能收窄」这件事在本函数内自证,不依赖调用方的取值纪律。
let plan_root = agent_id == GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID
&& agent_runtime_supervisor_source_is_plan(source);
let prompt = game_creator_agent_runtime_tool_plan_system_prompt_for_plan_root(plan_root);
let prompt = game_creator_agent_runtime_tool_plan_system_prompt();
if agent_id == "design-foundation" {
return game_creator_design_foundation_tool_plan_prompt(
&prompt,
@@ -587,7 +582,13 @@ fn game_creator_project_supervisor_tool_plan_prompt(
editor_api_key_is_configured: bool,
source: &str,
) -> String {
let plan_root = agent_runtime_supervisor_source_is_plan(source);
// 只看 `source` 就够:本函数唯一的调用方在上面用
// `agent_id != GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID` 提前返回过,而
// `source` 只有 Supervisor 自己的 run binding 会填成 plan(见
// provider_request_builders.rs),其余角色恒为空串。
if agent_runtime_supervisor_source_is_plan(source) {
return game_creator_project_supervisor_plan_tool_plan_system_prompt();
}
let visual_section = if editor_api_key_is_configured {
RUNTIME_PROMPT_VISUAL_EDITOR_SECTION
} else {
@@ -597,17 +598,41 @@ fn game_creator_project_supervisor_tool_plan_prompt(
RUNTIME_PROMPT_SUPERVISOR_COMPOSITION,
|marker| match marker {
"$base" => Some(prompt),
"$visualContract" => Some(if plan_root {
""
} else {
required_runtime_prompt_section(visual_section)
}),
"supervisorIntro" if plan_root => Some(""),
"$visualContract" => Some(required_runtime_prompt_section(visual_section)),
_ => None,
},
)
}
/// 立项策划根 run`source == project-supervisor-plan`)的 Supervisor system
/// prompt 是一份独立清单,不是通用 Supervisor 合同的差集。
///
/// 通用合同预设 Supervisor 手里有 43 个原生工具、六个专业组、isolated child、
/// 正式任务图和视觉产物合同;plan 根一个都没有——它只有 7 个工具,只能委派
/// `project-planning` 一个子 Agent。继续用逐段否定去改写通用合同,等于让
/// 「plan 根到底看到什么」散落在几个 `if plan_root` 分支里,而每漏一段就是一次
/// 已经实测到的偏航(专业角色目录残留曾让 Supervisor 照着委派
/// `design-director`)。因此这里整份换成 `supervisorPlan` compositionplan 根
/// 的全部段落在 manifest 里一眼可读。
fn game_creator_project_supervisor_plan_tool_plan_system_prompt() -> String {
// 与 Provider 请求的函数目录共用 `agent_runtime_plan_root_supervisor_tools`
// 提示词里列的工具和实际广告的工具必须是同一份事实,否则又会出现
// 「合同说有、请求里没有」的自相矛盾。
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_userRuntime 不会按工具动作下标代替你更新进度。本 run 的原生可执行工具目录只有:{tool_catalog}。写入、补丁、删除、命令、预览、素材生成、任务图、记忆、黑板、isolated child 与 MCP 工具在本 run 都不存在,调用它们只会失败。"
);
// `$platform` 整段是 command.start/exec/poll/stdin/terminate 的用法合同,
// plan 根一个 command 工具都没有;GDD 里的平台事实由 Runtime 另行注入,与
// 这段无关。
render_runtime_prompt_composition(RUNTIME_PROMPT_SUPERVISOR_PLAN_COMPOSITION, |marker| {
match marker {
"$header" => Some(&prompt_header),
_ => None,
}
})
}
fn render_runtime_prompt_sections(sections: &[&str]) -> String {
sections
.iter()
@@ -678,31 +703,14 @@ pub(crate) fn game_creator_agent_runtime_role_overlay_prompt(
}
pub(crate) fn game_creator_agent_runtime_tool_plan_system_prompt() -> String {
game_creator_agent_runtime_tool_plan_system_prompt_for_plan_root(false)
}
/// plan 根 run 的 `agent.spawn_isolated` 已被 M1A-4 无条件拒绝,因此共享
/// runtime 合同里那份「合法 templateAgentId」静态模板目录对它没有任何可执行
/// 语义,只剩下把 design-director 等全部专业角色名摆在 Provider 眼前的副作用。
/// 该目录第一个名字就是 `design-director`,实测中 Supervisor 会照着它发起
/// `agent.delegate`,被执行层拒绝后再也没能自行改回 `project-planning`。
/// 因此这里在上下文层直接删掉目录本身——这正是第 19 节第 2 条要求的第二层,
/// 不替代执行层硬拒。
fn game_creator_agent_runtime_tool_plan_system_prompt_for_plan_root(plan_root: bool) -> String {
#[cfg(target_os = "linux")]
const CURRENT_PLATFORM_IS_LINUX: bool = true;
#[cfg(not(target_os = "linux"))]
const CURRENT_PLATFORM_IS_LINUX: bool = false;
game_creator_agent_runtime_tool_plan_system_prompt_for_platform(
CURRENT_PLATFORM_IS_LINUX,
plan_root,
)
game_creator_agent_runtime_tool_plan_system_prompt_for_platform(CURRENT_PLATFORM_IS_LINUX)
}
fn game_creator_agent_runtime_tool_plan_system_prompt_for_platform(
linux: bool,
plan_root: bool,
) -> String {
fn game_creator_agent_runtime_tool_plan_system_prompt_for_platform(linux: bool) -> String {
let tool_catalog = agent_runtime_native_executable_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_userRuntime 不会按工具动作下标代替你更新进度。只能请求以下 Runtime 当前注册的原生可执行工具:{tool_catalog}。MCP 工具仅以当前请求提供的动态目录为准。"
@@ -716,15 +724,7 @@ fn game_creator_agent_runtime_tool_plan_system_prompt_for_platform(
let platform_section = runtime_prompt_platform_section_id(linux);
render_runtime_prompt_composition(RUNTIME_PROMPT_RUNTIME_COMPOSITION, |marker| match marker {
"$header" => Some(&prompt_header),
// 只删 spawn_isolated 专属的两段。`isolatedAgentContract` 讲的是
// expectedArtifacts 与 writeScopes`agent.delegate` 同样要用,plan 根
// 委派 project-planning 时就带 expectedArtifacts,必须逐字保留。
"isolatedTemplateCatalogIntro" if plan_root => Some(""),
"$isolatedAgentTemplates" => Some(if plan_root {
""
} else {
&isolated_agent_templates
}),
"$isolatedAgentTemplates" => Some(&isolated_agent_templates),
"$platform" => Some(required_runtime_prompt_section(platform_section)),
_ => None,
})
@@ -928,6 +928,38 @@ mod tests {
}
#[test]
/// Supervisor 的核心行为准则是「用验收标准和预期产物把边界清晰的任务委派
/// 出去」(`identity-contract.md`),这在自主构建链路上正确,搬到策划链路
/// 上却恰好碾过决策卡协议:把边界定清楚等于把用户没说的都替他决定掉。
/// 实测中 Supervisor 写出的 acceptanceCriteria 含「未由用户指定的具体规则
/// 标注为立项假设」和「目标平台、输入设备…须标为待定或建议」,前者作废了
/// 3 轮问询预算(三次真实 run 里信封一次都没触发),后者还与 Runtime 强制
/// 注入的平台事实直接矛盾。playbook 因此必须显式反向约束,否则整条策划链路
/// 上只有「问完之后怎么转述」有指导、没有「你不该替用户回答」。
#[test]
fn supervisor_playbook_forbids_pre_deciding_the_planning_tradeoffs() {
let playbook = required_runtime_prompt_section("supervisorPlaybook");
for required in [
"不得替用户预先裁定产品取舍",
"3 轮问询预算",
"不得要求标为待定",
] {
assert!(
playbook.contains(required),
"supervisorPlaybook 缺少策划委派约束:{required}"
);
}
// 该约束必须真的到达 plan 根 Supervisor,而不只是躺在 section 里。
let plan_prompt = game_creator_agent_runtime_tool_plan_system_prompt_for_agent(
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
AGENT_RUNTIME_SUPERVISOR_PLAN_SOURCE,
);
assert!(
plan_prompt.contains("不得替用户预先裁定产品取舍"),
"plan 根 Supervisor 的 system prompt 必须带上该约束"
);
}
/// `common.md` 的原创性红线随 `$base` 分发给做游戏链路的每个专业 Agent,但
/// 策划子 Agent 走 exact allowlist 的专用小 prompt、不拼 `$base`(理由见
/// `game_creator_project_planning_tool_plan_system_prompt` 的注释),该条款
@@ -1014,10 +1046,8 @@ mod tests {
#[test]
fn runtime_prompt_selects_exactly_one_manifest_platform_variant() {
let default_prompt =
game_creator_agent_runtime_tool_plan_system_prompt_for_platform(false, false);
let linux_prompt =
game_creator_agent_runtime_tool_plan_system_prompt_for_platform(true, false);
let default_prompt = game_creator_agent_runtime_tool_plan_system_prompt_for_platform(false);
let linux_prompt = game_creator_agent_runtime_tool_plan_system_prompt_for_platform(true);
let default_section =
required_runtime_prompt_section(RUNTIME_PROMPT_PLATFORM_DEFAULT_SECTION).trim();
let linux_section =
@@ -1334,21 +1364,30 @@ mod tests {
}
/// M1A-4plan 根 runsource == project-supervisor-plan)的 Supervisor
/// system prompt 不拼 supervisorIntro 与 $visualContract 这两个 section
/// ——两段都在暗示存在可并行委派的专业组,而策划链路的合同是只能委派
/// project-planning 一个子 Agent。这里用两段各自最短的、只出现在该 section
/// 里的独有句子做断言,不用裸的 agent-id 字面量。
/// system prompt 整份换成 `supervisorPlan` composition,不再是通用 Supervisor
/// 合同的差集。
///
/// **订正(2026-08-20**:本注释原先记着 `$base` 里的 isolated agent 模板
/// 目录「只是死文本,不是可利用的委派入口」,理由是执行层已无条件拒绝
/// `agent.spawn_isolated`。该推理只覆盖了 spawn 这一条路径,漏掉了 Provider
/// 会把目录里的名字挪去当 `agent.delegate` 的目标:实测 Supervisor 首轮
/// 委派了目录里排第一的 `design-director`,被执行层拒绝后未能自行改回
/// `project-planning`,整个 run 空转到 loop 预算耗尽、零产物。目录现已在
/// plan 根的上下文层删除,覆盖断言见
/// `plan_root_supervisor_prompt_drops_the_specialist_role_catalog`
/// **沿革**:这条最早只断言「不拼 supervisorIntro 与 $visualContract」——两段
/// 都在暗示存在可并行委派的专业组,而策划链路只能委派 project-planning。
/// 2026-08-20 又发现 `$base` 里的 isolated agent 模板目录同样会被挪去当
/// `agent.delegate` 的目标:实测 Supervisor 首轮委派了目录里排第一的
/// `design-director`,被执行层拒绝后未能自行改回 `project-planning`,整个 run
/// 空转到 loop 预算耗尽、零产物。逐段做减法每漏一段就是一次这样的偏航,
/// 因此现在改为整份替换:plan 根的段落清单由 manifest 的 `supervisorPlan`
/// 单点定义,本测试钉住它
#[test]
fn plan_root_supervisor_prompt_drops_intro_and_visual_contract_sections() {
fn plan_root_supervisor_prompt_is_the_dedicated_plan_composition() {
assert_eq!(
RUNTIME_PROMPT_SUPERVISOR_PLAN_COMPOSITION,
&[
"$header",
"planCommon",
"isolatedAgentContract",
"planSupervisorIdentity",
"planSupervisorPlaybook",
"supervisorRepair"
]
);
for editor_api_key_is_configured in [false, true] {
let plan_prompt = game_creator_project_supervisor_tool_plan_prompt(
"shared runtime contract",
@@ -1373,19 +1412,146 @@ mod tests {
),
"plan 根 prompt 不应再包含 without-editor 视觉合同"
);
// 通用 $base 不再进入 plan 根:它带着改码流程、revision 验证门禁、
// git 提交与「作为被委派的专业 Agent 时」等整段不可执行的合同。
assert!(
plan_prompt.contains("shared runtime contract"),
"plan 根 prompt 必须保留 $base"
!plan_prompt.contains("shared runtime contract"),
"plan 根 prompt 不应再拼通用 $base"
);
for dropped in [
"supervisorIdentityContract",
"supervisorIntro",
"supervisorPlaybook",
"supervisorClaimGate",
"common",
"isolatedTemplateCatalogIntro",
"platformDefault",
"platformLinux",
] {
assert!(
!plan_prompt.contains(required_runtime_prompt_section(dropped).trim()),
"plan 根 prompt 不应包含 section {dropped}"
);
}
for kept in [
"planCommon",
"isolatedAgentContract",
"planSupervisorIdentity",
"planSupervisorPlaybook",
// 返工必须逐字继承原 acceptanceCriteria / expectedArtifacts
// 这条规则只在 supervisorRepair 里,plan 根实测撞过两次。
"supervisorRepair",
] {
assert!(
plan_prompt.contains(required_runtime_prompt_section(kept).trim()),
"plan 根 prompt 必须包含 section {kept}"
);
}
}
}
/// 提示词头部列出的工具清单和 Provider 请求实际广告的函数目录必须是同一份
/// 事实。二者各自维护一份,就会退回「合同说有 43 个、请求里只有 9 个」的
/// 自相矛盾——这正是收窄工具面本身要消灭的东西。
#[test]
fn plan_root_prompt_tool_catalog_matches_the_advertised_allowlist() {
let plan_prompt = game_creator_agent_runtime_tool_plan_system_prompt_for_agent(
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
AGENT_RUNTIME_SUPERVISOR_PLAN_SOURCE,
);
let allowlist = agent_runtime_plan_root_supervisor_tools();
assert!(
plan_prompt.contains(&format!(
"本 run 的原生可执行工具目录只有:{}",
allowlist.join("")
)),
"plan 根 prompt 头部必须逐字列出 allowlist{plan_prompt}"
);
for tool in agent_runtime_native_executable_tools() {
if allowlist.contains(&tool) {
continue;
}
assert!(
plan_prompt.contains(required_runtime_prompt_section("supervisorPlaybook").trim())
!plan_prompt.contains(&format!("{tool}"))
&& !plan_prompt.contains(&format!("{tool}")),
"plan 根 prompt 不应在工具目录里出现被裁掉的 {tool}"
);
}
}
/// `planCommon` 是 `common.md` 的子集副本,不是重写。
///
/// 通用 `common` 段有 43% 是 plan 根执行不了的内容(改码流程、revision 验证
/// 门禁、git 提交、联网检索、以及整段「作为被委派的专业 Agent 时」的身份错位),
/// 其中「最后一次修改后必须成功执行 project.verify 才能 respond_to_user」还是
/// 一道 plan 根永远满足不了的假门禁。但同一段里也压着这条链路唯一的原创性
/// 红线、`user.input_request` 协议和静态委派协议——D11 拓扑的协议正文就在
/// 这里,不在 playbook。所以 plan lane 只挑走这三段,逐字复制。
///
/// 复制就会漂移,因此这条断言反过来钉:`planCommon` 里的每一段都必须能在
/// `common.md` 里逐字找到。谁单改一边,这条就红。
#[test]
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");
let inherited = [
"用户只描述玩法类型、机制或相似体验时,不代表授权复刻现有游戏。",
"用户输入请求协议:",
"静态委派协议:",
];
let mut matched = 0;
for paragraph in plan_common
.split(
"
",
)
.map(str::trim)
{
if !inherited.iter().any(|head| paragraph.starts_with(head)) {
continue;
}
assert!(
plan_prompt.contains(required_runtime_prompt_section("supervisorClaimGate").trim())
);
assert!(
plan_prompt.contains(required_runtime_prompt_section("supervisorRepair").trim())
common.contains(paragraph),
"planCommon 段落已与 common.md 漂移:{paragraph}"
);
matched += 1;
}
assert_eq!(matched, inherited.len(), "planCommon 缺少继承段落");
// 反向:plan 根不得继承那道它永远满足不了的验证门禁。
assert!(common.contains("才能调用 respond_to_user 收束"));
assert!(!plan_common.contains("才能调用 respond_to_user 收束"));
}
/// 「不得替用户预先裁定产品取舍」这段同时写进两条 lane 的 playbook。
///
/// 执行层只单向拦住 plan 根(`立项策划根 Run 只能委派 project-planning`),
/// 反方向没有对称限制:`agent.delegate` 的 `agentId` 是自由字符串,gui/cli
/// Supervisor 委派 `project-planning` 并未被禁。所以通用 playbook 里这段不是
/// 死文本,两边都要有;这条断言钉住它们逐字相同。
#[test]
fn both_playbooks_carry_the_same_anti_pre_deciding_contract() {
const SHARED: &str = "不得替用户预先裁定产品取舍";
let generic = required_runtime_prompt_section("supervisorPlaybook");
let plan = required_runtime_prompt_section("planSupervisorPlaybook");
let paragraph = generic
.split(
"
",
)
.map(str::trim)
.find(|paragraph| paragraph.contains(SHARED))
.expect("supervisorPlaybook 必须包含反预先裁定段落");
assert!(
plan.contains(paragraph),
"两条 lane 的反预先裁定段落已漂移:
{paragraph}
---
{plan}"
);
for required in ["3 轮问询预算", "不得要求标为待定"] {
assert!(paragraph.contains(required), "缺少 {required}");
}
}
@@ -28,6 +28,9 @@ const SECTION_FILES: &[(&str, &str)] = &[
("supervisorPlaybook", "supervisor/playbook.md"),
("supervisorClaimGate", "supervisor/claim-gate.md"),
("supervisorRepair", "supervisor/repair.md"),
("planCommon", "plan/common.md"),
("planSupervisorIdentity", "plan/supervisor-identity.md"),
("planSupervisorPlaybook", "plan/supervisor-playbook.md"),
("codePrototypeGameChat", "roles/code-prototype-game-chat.md"),
(
"providerIsolatedToolContract",
@@ -141,6 +144,14 @@ fn valid_manifest() -> Value {
"supervisorClaimGate",
"supervisorRepair"
],
"supervisorPlan": [
"$header",
"planCommon",
"isolatedAgentContract",
"planSupervisorIdentity",
"planSupervisorPlaybook",
"supervisorRepair"
],
"supervisorChat": {
"identity": "supervisorIdentityContract",
"finalReply": "supervisorFinalReplyContract"