澄清转述那一轮的 context bundle 不再被判身份不匹配
走过一轮澄清的立项策划必挂:策划子 Agent 完成、父 run 醒来认领委派回执时,整轮报 「恢复 Agent Runtime context bundle 失败:身份与当前任务不匹配」,用户答案已落盘却 无法继续。 bundle.task 存的是写出这份 bundle 的那一轮的任务,跟 run 的持久 task 是两个独立参数 (build_game_creator_agent_runtime_context_bundle 的 task 形参)。用户答完澄清后整条 run 改跑在转述任务上(run_recovered_game_creator_context_on_fresh_task 传的就是 pending.task),那一轮写出的 bundle 因此带着转述文本,而 task record 与 current_task 始终是用户原始请求。实测项目里两侧身份字段全等,只有 task 一项分叉。 validate_agent_runtime_pending_context 早就为 pending 入口豁免过同一条文本相等断言, 并留了注释说明转述是唯一一处 task 不等于本 run 任务的场景;bundle 这个入口漏了。这里 补上同一个豁免,只放开文本相等一条——project/agent/task_id/session/run/source/profile/ 绑定指纹与 verification_gate 三项照旧全等,转述文本也只能由 Runtime 在本 run 内生成, 不放开任何跨 run 或跨身份的重放面。 新增用例同时断言两侧:转述 bundle 必须能读回,其它文本分叉照旧 fail closed。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -486,6 +486,21 @@ pub(in crate::agent) fn read_game_creator_agent_runtime_context_bundle_with_supe
|
||||
bundle.run_profile_binding_fingerprint = runtime.run_profile_binding_fingerprint.clone();
|
||||
bundle = sanitize_game_creator_agent_runtime_context_bundle(root, &bundle);
|
||||
}
|
||||
// `bundle.task` 存的是**写出这份 bundle 的那一轮**的任务,和 run 的持久 task 是两个
|
||||
// 独立参数(build_game_creator_agent_runtime_context_bundle 的 `task` 形参)。澄清转述
|
||||
// 是唯一一处二者必然不同的场景:用户答完后整条 run 改跑在转述任务上
|
||||
// (run_recovered_game_creator_context_on_fresh_task 传的就是 pending.task),那一轮
|
||||
// 写出的 bundle 因此带着转述文本,而 task record 与 current_task 始终是用户原始请求。
|
||||
//
|
||||
// validate_agent_runtime_pending_context 已经为 pending 入口豁免过同一条文本相等断言,
|
||||
// 但 bundle 这个入口漏了:D11 实测走过一轮澄清后,策划子 Agent 完成、父 run 醒来认领
|
||||
// 委派回执时必定在这里判失败,用户答案已落盘却整轮报「身份与当前任务不匹配」。
|
||||
//
|
||||
// 豁免同样只放开文本相等这一条。project/agent/task_id/session/run/source/profile/绑定
|
||||
// 指纹和 verification_gate 三项在上下都照旧全等,转述文本本身也只能由 Runtime 在本 run
|
||||
// 内生成,所以不放开任何跨 run 或跨身份的重放面。
|
||||
let bundle_task_is_clarification_relay =
|
||||
agent_runtime_task_is_delegate_clarification_relay(&bundle.task);
|
||||
if bundle.project_id != game_creator_agent_runtime_context_project_id(root)?
|
||||
|| bundle.agent_id != redact_agent_runtime_project_paths(root, &runtime.agent_id, 160)
|
||||
|| bundle.task_id != redact_agent_runtime_project_paths(root, &runtime.task_id, 160)
|
||||
@@ -494,7 +509,8 @@ pub(in crate::agent) fn read_game_creator_agent_runtime_context_bundle_with_supe
|
||||
|| bundle.source != redact_agent_runtime_project_paths(root, &runtime.source, 120)
|
||||
|| bundle.run_profile != runtime.run_profile
|
||||
|| bundle.run_profile_binding_fingerprint != runtime.run_profile_binding_fingerprint
|
||||
|| validate_agent_runtime_context_task_parameter(root, runtime, &bundle.task).is_err()
|
||||
|| (!bundle_task_is_clarification_relay
|
||||
&& validate_agent_runtime_context_task_parameter(root, runtime, &bundle.task).is_err())
|
||||
|| bundle.verification_gate.project_id != bundle.project_id
|
||||
|| bundle.verification_gate.agent_id != bundle.agent_id
|
||||
|| bundle.verification_gate.run_id != bundle.run_id
|
||||
|
||||
@@ -1971,6 +1971,59 @@ fn legacy_context_and_pending_records_fail_closed() {
|
||||
fs::remove_dir_all(root).ok();
|
||||
}
|
||||
|
||||
/// 澄清转述那一轮写出的 bundle 必须能被父 run 唤醒时读回来。
|
||||
///
|
||||
/// 用户答完澄清后整条 run 改跑在转述任务上,那一轮的 bundle 因此带着转述文本,而 run 的
|
||||
/// 持久 task 始终是用户原始请求。validate_agent_runtime_pending_context 早就为 pending 入口
|
||||
/// 豁免过同一条文本相等断言,bundle 入口漏了——D11 走过一轮澄清后,策划子 Agent 完成、父
|
||||
/// run 认领委派回执时必定在这里失败。豁免只放开转述这一种 task,别的文本照旧 fail closed。
|
||||
#[test]
|
||||
fn agent_runtime_context_bundle_accepts_only_the_clarification_relay_task_divergence() {
|
||||
let root = unique_project_path();
|
||||
init_local_game_project_at(&root, "project-1", "澄清转述上下文项目").expect("project init");
|
||||
let task = "贪吃蛇";
|
||||
let state = start_game_creator_agent_runtime_task_at(
|
||||
&root,
|
||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||
task,
|
||||
"plan-clarification-relay-run",
|
||||
AGENT_RUNTIME_SUPERVISOR_PLAN_SOURCE,
|
||||
"等待澄清回答",
|
||||
vec!["回答澄清后续跑".to_string()],
|
||||
)
|
||||
.expect("start clarification runtime state");
|
||||
let context_tracker = AgentRuntimeContextWindowTracker::default();
|
||||
let relay_task = format!(
|
||||
"{AGENT_RUNTIME_DELEGATE_CLARIFICATION_TASK_PREFIX}delegation-96db734b07f794a576ac5c99;questionCount=1 · questionsSha256=ef5b1201。请回答以下问题。"
|
||||
);
|
||||
let relay_bundle = build_game_creator_agent_runtime_context_bundle(
|
||||
&root,
|
||||
&state,
|
||||
&relay_task,
|
||||
&AgentRuntimeToolPlan::default(),
|
||||
&[],
|
||||
0,
|
||||
&context_tracker,
|
||||
)
|
||||
.expect("build clarification relay context bundle");
|
||||
write_game_creator_agent_runtime_context_bundle(&root, &relay_bundle)
|
||||
.expect("write clarification relay bundle");
|
||||
let restored = read_game_creator_agent_runtime_context_bundle(&root, &state)
|
||||
.expect("clarification relay bundle must restore")
|
||||
.expect("clarification relay bundle present");
|
||||
assert_eq!(restored.task, relay_task);
|
||||
|
||||
let mut unrelated_task = relay_bundle;
|
||||
unrelated_task.task = "另一个不相干的任务".to_string();
|
||||
write_game_creator_agent_runtime_context_bundle(&root, &unrelated_task)
|
||||
.expect("write unrelated-task bundle");
|
||||
let error = read_game_creator_agent_runtime_context_bundle(&root, &state)
|
||||
.expect_err("非转述任务的文本分叉必须照旧 fail closed");
|
||||
assert!(error.contains("身份与当前任务不匹配"), "{error}");
|
||||
|
||||
fs::remove_dir_all(root).ok();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn agent_runtime_context_bundle_rejects_task_source_and_loop_identity_mismatches() {
|
||||
let root = unique_project_path();
|
||||
|
||||
Reference in New Issue
Block a user