立项策划根 run 的上下文层不再列出专业角色目录
共享 runtime 合同里的「agent.spawn_isolated 合法 templateAgentId」静态模板目录 会把 design-director 等全部专业角色名拼进 plan 根 Supervisor 的 system prompt。 plan 根的 agent.spawn_isolated 已被 M1A-4 无条件拒绝,这份目录因此没有任何可执行 语义,此前按「死文本」处理。 实测证伪了这个判断:目录第一个名字就是 design-director,Supervisor 首轮据此发起 agent.delegate,被执行层以 plan-root-child-target-unsupported 拒绝后未能自行改回 project-planning,整个 run 空转到 loop 预算耗尽、零产物。漏掉的推理是目录虽为 spawn 而设,模型会把里面的名字挪去当 delegate 目标。 改为在上下文层删掉目录本身,即第 19 节第 2 条要求的第二层,不替代执行层硬拒。 只删 spawn_isolated 专属的两段;isolatedAgentContract 讲的是 expectedArtifacts 与 writeScopes,agent.delegate 同样要用,逐字保留。 覆盖:plan 根 prompt 遍历 GAME_CREATOR_AGENT_GROUP_DEFINITIONS 断言不含任何角色 task_id(新增角色自动进覆盖范围);非 plan source 与其余 agent 逐字等于未收窄的 合成结果且目录完整。前者已变异验证。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -530,7 +530,12 @@ 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();
|
||||
}
|
||||
let prompt = game_creator_agent_runtime_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);
|
||||
if agent_id == "design-foundation" {
|
||||
return game_creator_design_foundation_tool_plan_prompt(
|
||||
&prompt,
|
||||
@@ -673,14 +678,31 @@ 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)
|
||||
game_creator_agent_runtime_tool_plan_system_prompt_for_platform(
|
||||
CURRENT_PLATFORM_IS_LINUX,
|
||||
plan_root,
|
||||
)
|
||||
}
|
||||
|
||||
fn game_creator_agent_runtime_tool_plan_system_prompt_for_platform(linux: bool) -> String {
|
||||
fn game_creator_agent_runtime_tool_plan_system_prompt_for_platform(
|
||||
linux: bool,
|
||||
plan_root: 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_user,Runtime 不会按工具动作下标代替你更新进度。只能请求以下 Runtime 当前注册的原生可执行工具:{tool_catalog}。MCP 工具仅以当前请求提供的动态目录为准。"
|
||||
@@ -694,7 +716,15 @@ fn game_creator_agent_runtime_tool_plan_system_prompt_for_platform(linux: bool)
|
||||
let platform_section = runtime_prompt_platform_section_id(linux);
|
||||
render_runtime_prompt_composition(RUNTIME_PROMPT_RUNTIME_COMPOSITION, |marker| match marker {
|
||||
"$header" => Some(&prompt_header),
|
||||
"$isolatedAgentTemplates" => Some(&isolated_agent_templates),
|
||||
// 只删 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
|
||||
}),
|
||||
"$platform" => Some(required_runtime_prompt_section(platform_section)),
|
||||
_ => None,
|
||||
})
|
||||
@@ -947,8 +977,10 @@ 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);
|
||||
let linux_prompt = game_creator_agent_runtime_tool_plan_system_prompt_for_platform(true);
|
||||
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_section =
|
||||
required_runtime_prompt_section(RUNTIME_PROMPT_PLATFORM_DEFAULT_SECTION).trim();
|
||||
let linux_section =
|
||||
@@ -1268,12 +1300,16 @@ mod tests {
|
||||
/// system prompt 不拼 supervisorIntro 与 $visualContract 这两个 section
|
||||
/// ——两段都在暗示存在可并行委派的专业组,而策划链路的合同是只能委派
|
||||
/// project-planning 一个子 Agent。这里用两段各自最短的、只出现在该 section
|
||||
/// 里的独有句子做断言,不用裸的 agent-id 字面量:`$base`(共享 runtime
|
||||
/// 合同)里的 isolated agent 模板目录本就会列出 art-director /
|
||||
/// design-foundation / art-asset-plan / code-prototype 等全部模板 taskId
|
||||
/// 供 agent.spawn_isolated 使用,这与本次改动无关,也不构成越权——plan 根
|
||||
/// 已经在执行层被 A2 无条件拒绝 agent.spawn_isolated(见 delegation.rs),
|
||||
/// 所以这份共享目录残留只是死文本,不是可利用的委派入口。
|
||||
/// 里的独有句子做断言,不用裸的 agent-id 字面量。
|
||||
///
|
||||
/// **订正(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`。
|
||||
#[test]
|
||||
fn plan_root_supervisor_prompt_drops_intro_and_visual_contract_sections() {
|
||||
for editor_api_key_is_configured in [false, true] {
|
||||
@@ -1316,6 +1352,89 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// plan 根 Supervisor 的完整 system prompt 里不得再出现任何专业组角色名。
|
||||
/// 断言遍历 `GAME_CREATOR_AGENT_GROUP_DEFINITIONS` 而不是列举几个字面量,
|
||||
/// 这样将来新增角色自动进入覆盖范围,不依赖有人记得回来补这条。
|
||||
#[test]
|
||||
fn plan_root_supervisor_prompt_drops_the_specialist_role_catalog() {
|
||||
let plan_prompt = game_creator_agent_runtime_tool_plan_system_prompt_for_agent(
|
||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||
AGENT_RUNTIME_SUPERVISOR_PLAN_SOURCE,
|
||||
);
|
||||
for group in GAME_CREATOR_AGENT_GROUP_DEFINITIONS {
|
||||
for role in group.roles {
|
||||
assert!(
|
||||
!plan_prompt.contains(role.task_id),
|
||||
"plan 根 prompt 不应出现专业角色名 {}:{plan_prompt}",
|
||||
role.task_id
|
||||
);
|
||||
}
|
||||
}
|
||||
assert!(
|
||||
!plan_prompt
|
||||
.contains(required_runtime_prompt_section("isolatedTemplateCatalogIntro").trim()),
|
||||
"plan 根 prompt 不应保留 spawn_isolated 静态模板目录抬头"
|
||||
);
|
||||
// agent.delegate 同样按这段填 expectedArtifacts / writeScopes,plan 根
|
||||
// 委派 project-planning 时要用,删目录不能把它一起删掉。
|
||||
assert!(
|
||||
plan_prompt.contains(required_runtime_prompt_section("isolatedAgentContract").trim()),
|
||||
"plan 根 prompt 必须保留 expectedArtifacts / writeScopes 合同"
|
||||
);
|
||||
}
|
||||
|
||||
/// 收窄只对 plan 根 Supervisor 生效。其余 source 与其余 agent 的共享
|
||||
/// runtime 合同必须逐字等于「基础 prompt 未被收窄」时的合成结果,且模板
|
||||
/// 目录仍然完整——`agent.spawn_isolated` 在那些链路上是真能用的。
|
||||
#[test]
|
||||
fn only_the_plan_root_supervisor_loses_the_specialist_role_catalog() {
|
||||
let intact_base = game_creator_agent_runtime_tool_plan_system_prompt();
|
||||
for (agent_id, source) in [
|
||||
(GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, ""),
|
||||
(
|
||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||
AGENT_RUNTIME_SUPERVISOR_GUI_SOURCE,
|
||||
),
|
||||
(
|
||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||
AGENT_RUNTIME_SUPERVISOR_CLI_SOURCE,
|
||||
),
|
||||
(
|
||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||
AGENT_RUNTIME_SUPERVISOR_GAME_CHAT_SOURCE,
|
||||
),
|
||||
(
|
||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||
"project-supervisor-plan-forged",
|
||||
),
|
||||
// 专业角色自己即便被伪造成 plan source 也不该被收窄。
|
||||
("code-prototype", AGENT_RUNTIME_SUPERVISOR_PLAN_SOURCE),
|
||||
("quality-review", ""),
|
||||
] {
|
||||
let actual =
|
||||
game_creator_agent_runtime_tool_plan_system_prompt_for_agent(agent_id, source);
|
||||
let expected = if agent_id == GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID {
|
||||
game_creator_project_supervisor_tool_plan_prompt(
|
||||
&intact_base,
|
||||
editor_api_key_is_configured(),
|
||||
source,
|
||||
)
|
||||
} else {
|
||||
intact_base.clone()
|
||||
};
|
||||
assert_eq!(
|
||||
actual, expected,
|
||||
"agent={agent_id} source={source} 不应被 plan 根收窄逻辑改变"
|
||||
);
|
||||
assert!(
|
||||
actual.contains(
|
||||
required_runtime_prompt_section("isolatedTemplateCatalogIntro").trim()
|
||||
),
|
||||
"agent={agent_id} source={source} 必须保留静态模板目录"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// gui/cli/game-chat 等非 plan source 的 Supervisor system prompt 必须与
|
||||
/// M1A-4 之前逐字相同:直接用既有 section 常量手工拼出改动前的合成结果,
|
||||
/// 逐字比对,防止上面的 plan 根分支误伤这条现役路径。
|
||||
|
||||
Reference in New Issue
Block a user