澄清任务的身份豁免按任务判而不是按工具判
上一笔把豁免收窄成「工具必须是 user.input_request」,只覆盖了转述动作本身。 实际上用户答完后 pending_execution 调的是 run_recovered_game_creator_context_on_fresh_task(root, agent_id, pending.task.clone(), ...):整条 run 从此改跑在转述任务上,同一 run 后续的 agent.run_status、agent.delegate 全都带着它,而 runtime.current_task 仍是用户 原始请求。 于是 run 15 过了转述那一关,却在续跑第一步就挂:Supervisor 按 supervisorRepair 的要求调 agent.run_status 重读权威合同,动作在等到项目锁之后被 validate_agent_runtime_pending_context 判成「pending action 身份已变化」,仍是 needs-reconciliation。 pending.tool = agent.run_status pending.task = 子 Agent 需要用户澄清后才能继续。delegationId=delegation-4216f572… runtime.currentTask = 做个横版像素解谜小游戏,主角是个能操控自己影子的小机器人。 判据改成只看 task 是不是 Runtime 生成的转述指令。同一个 || 链里的 validate_agent_runtime_pending_action_after_lock 不比较 task,所以豁免面仍然只 有这一条文本相等;agent/task_id/session/run/source 与轮次检查照旧全走。 回归测试补上 run 15 实测的那个形状:同一 run、同一转述任务、工具是 agent.run_status 的续跑动作必须通过,而 task 被改写的同形状动作必须照旧被拒。 三向变异验证:恢复严格比较、整条删掉、把判据换成恒真替身,都会红。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -173,18 +173,22 @@ pub(in crate::agent) fn validate_agent_runtime_pending_context(
|
||||
{
|
||||
return Err("Agent Runtime 待确认动作身份与当前状态不匹配".to_string());
|
||||
}
|
||||
// 转述 pending 是唯一一处 `task` 字段存的不是本 run 任务的动作:Runtime 代
|
||||
// 澄清转述是唯一一处 `task` 字段存的不是本 run 任务的场景:Runtime 代
|
||||
// Supervisor 汇总子 Agent 澄清问题时,把「回答后创建唯一 continuation 委派」
|
||||
// 那段指令连同 delegationId 写在这里(provider_recovery.rs),而 run 的
|
||||
// current_task 始终是用户原始请求。其余五个 pending 创建点传的都是 run 的真实
|
||||
// task,所以这条相等断言对它们成立,对转述则永不可能成立——D11 澄清路径实测
|
||||
// 每一次都会在 resume 时被判成 needs-reconciliation,用户答案已落盘却无法继续。
|
||||
//
|
||||
// 豁免按 task 判、不按工具判:用户答完后整条 run 都改跑在转述任务上
|
||||
// (run_recovered_game_creator_context_on_fresh_task 传的就是 pending.task),
|
||||
// 所以续跑里的 agent.run_status、agent.delegate 同样带着它。
|
||||
//
|
||||
// 这里只豁免文本相等这一条。pending 与 runtime 的 agent/task_id/session/run/
|
||||
// source 五项身份检查在上面已经全部通过,轮次检查在下面继续执行,转述 pending
|
||||
// 本身也只能由 Runtime 在本 run 内生成,所以豁免不放开任何跨 run 或跨身份的
|
||||
// 重放面。
|
||||
if !agent_runtime_pending_is_delegate_clarification_relay(pending) {
|
||||
if !agent_runtime_task_is_delegate_clarification_relay(&pending.task) {
|
||||
validate_agent_runtime_context_task_parameter(root, runtime, &pending.task)?;
|
||||
}
|
||||
if pending.loop_iteration != runtime.loop_iteration {
|
||||
@@ -894,20 +898,36 @@ mod tests {
|
||||
};
|
||||
|
||||
let relay = build(&relay_task, &action);
|
||||
assert!(agent_runtime_pending_is_delegate_clarification_relay(
|
||||
&relay
|
||||
assert!(agent_runtime_task_is_delegate_clarification_relay(
|
||||
&relay.task
|
||||
));
|
||||
assert_ne!(relay.task, runtime.current_task);
|
||||
validate_agent_runtime_pending_context(&root, &runtime, &relay)
|
||||
.expect("转述 pending 必须能通过身份校验,否则用户答案落盘后无法续跑");
|
||||
|
||||
// 豁免只覆盖转述本身。同一工具、同一 run,但 task 不是 Runtime 生成的
|
||||
// 转述指令时,原有的相等断言必须照旧拒绝——否则这就是个洞而不是修复。
|
||||
// 用户答完后整条 run 都改跑在转述任务上,后续动作同样带着它。按工具收窄
|
||||
// 豁免会让续跑第一步 agent.run_status 就被判身份变化——run 15 实测如此,
|
||||
// 所以这里显式覆盖非 user.input_request 的后续动作。
|
||||
let follow_up = AgentRuntimeToolAction {
|
||||
tool: "agent.run_status".to_string(),
|
||||
reason: Some("读取原委派的权威合同,准备创建唯一一次续跑委派".to_string()),
|
||||
input: serde_json::json!({}),
|
||||
};
|
||||
let resumed = build(&relay_task, &follow_up);
|
||||
validate_agent_runtime_pending_context(&root, &runtime, &resumed)
|
||||
.expect("续跑动作同样跑在转述任务上,必须通过身份校验");
|
||||
|
||||
// 豁免只覆盖 Runtime 生成的转述任务。task 不是转述指令时,原有的相等断言
|
||||
// 必须照旧拒绝——否则这就是个洞而不是修复。
|
||||
let forged = build("被改写的任务", &action);
|
||||
assert!(!agent_runtime_pending_is_delegate_clarification_relay(
|
||||
&forged
|
||||
assert!(!agent_runtime_task_is_delegate_clarification_relay(
|
||||
&forged.task
|
||||
));
|
||||
assert!(validate_agent_runtime_pending_context(&root, &runtime, &forged).is_err());
|
||||
let forged_follow_up = build("被改写的任务", &follow_up);
|
||||
assert!(
|
||||
validate_agent_runtime_pending_context(&root, &runtime, &forged_follow_up).is_err()
|
||||
);
|
||||
|
||||
// 身份五项仍然照查:换一个 run_id 的转述 pending 不得被放行。
|
||||
let mut cross_run = relay.clone();
|
||||
|
||||
@@ -17,12 +17,15 @@ pub(crate) fn agent_runtime_delegate_clarification_delegation_id(task: &str) ->
|
||||
.filter(|value| !value.is_empty())
|
||||
}
|
||||
|
||||
/// 该 pending 是否由 Runtime 代 Supervisor 生成的子 Agent 澄清转述。
|
||||
pub(crate) fn agent_runtime_pending_is_delegate_clarification_relay(
|
||||
pending: &AgentRuntimePendingToolAction,
|
||||
) -> bool {
|
||||
pending.action.tool == GAME_CREATOR_USER_INPUT_REQUEST_TOOL
|
||||
&& agent_runtime_delegate_clarification_delegation_id(&pending.task).is_some()
|
||||
/// 该任务是否是 Runtime 代 Supervisor 生成的子 Agent 澄清转述任务。
|
||||
///
|
||||
/// 判据只看 task,不看工具:用户答完后
|
||||
/// `run_recovered_game_creator_context_on_fresh_task` 会把 `pending.task` 当作同一
|
||||
/// run 后续每一轮的任务,所以转述任务会一路传播到 `agent.run_status`、
|
||||
/// `agent.delegate` 等动作上,而不是只停在那一次 `user.input_request`。按工具收窄
|
||||
/// 会让续跑第一步就被判身份变化——run 15 实测如此。
|
||||
pub(crate) fn agent_runtime_task_is_delegate_clarification_relay(task: &str) -> bool {
|
||||
agent_runtime_delegate_clarification_delegation_id(task).is_some()
|
||||
}
|
||||
|
||||
pub(crate) const AGENT_RUNTIME_USER_INPUT_SCHEMA_VERSION: &str =
|
||||
|
||||
Reference in New Issue
Block a user