返工委派的两个数组也改由 Runtime 继承
acceptanceCriteria 与 expectedArtifacts 必须逐字继承原委派, validate_static_delegate_repair_request_at 会逐项比对。但这两个值 Runtime 从 repairOfDelegationId 指向的 delivery 直接读得到——它就是拿这份权威值去 比对的——要求 Supervisor 手抄一遍不带来任何信息增益。 上一次无头验证实测:一个两轮澄清的 plan run 里, 「静态委派返工必须完整继承原 acceptanceCriteria 和 expectedArtifacts」 出现 4 次,每建一条 continuation 都要先白跑两轮工具调用才抄对。出问题的 那次生产 run 也有同一条。 现在带 repairOfDelegationId 时这两个数组可以整体传 null,由 Runtime 从原 delivery 补齐;只省一半仍然按原样交给既有比对报错,初次委派仍须自己写。 和上一批指纹改动同源,形状一致。 plan playbook 同步:第 4 步四个字段一起传 null;第 6 步不再要求先用 agent.run_status 取回合同再手抄,省掉一次往返。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,9 +3,9 @@
|
||||
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. 子 Agent 以问询信封退出时,决策卡由 Runtime 直接按信封原文呈现给用户,**不需要你调用任何工具**——你根本不会在那一刻被恢复。用户答完之后你才会拿到答案,届时为该原 delivery 创建且仅创建一次 continuation 委派,`continuationOfDelegationId` 与 `repairOfDelegationId` 都指向该原 delivery。`questionsSha256` 与 `answersSha256` 传 null——Runtime 会从该原 delivery 补齐权威指纹,你不要自己抄。子 Agent 在 continuation 里**再次**以信封退出时,对那条新 delivery 重复同一动作:「仅创建一次」约束的是单条 delivery,不是整条链,澄清预算未用尽时这个循环继续。Runtime 会在委派 task 末尾写明已用轮次与上限,不需要你自己数,也不要替它宣布预算已尽。
|
||||
4. 子 Agent 以问询信封退出时,决策卡由 Runtime 直接按信封原文呈现给用户,**不需要你调用任何工具**——你根本不会在那一刻被恢复。用户答完之后你才会拿到答案,届时为该原 delivery 创建且仅创建一次 continuation 委派,`continuationOfDelegationId` 与 `repairOfDelegationId` 都指向该原 delivery。`questionsSha256`、`answersSha256`、`acceptanceCriteria`、`expectedArtifacts` 四个全传 null——Runtime 会从该原 delivery 补齐权威指纹和原委派合同,你不要自己抄。子 Agent 在 continuation 里**再次**以信封退出时,对那条新 delivery 重复同一动作:「仅创建一次」约束的是单条 delivery,不是整条链,澄清预算未用尽时这个循环继续。Runtime 会在委派 task 末尾写明已用轮次与上限,不需要你自己数,也不要替它宣布预算已尽。
|
||||
5. 回执 contractStatus=evidence-ready 且 GDD 已提交时,用 `file.read` 从第 1 行读到 `game/fast_gdd.md` 末尾取证,每次都传 `maxLines: 240`(上限),尽量一页读完;确实需要第二页时从上一页的下一行开始,不要重复读同一段。每次 `file.read` 的 observation 末尾都带着 `sourceAgentId` / `sourceRunId` / `sourceActionId` 三个字段,把它们原样抄成 evidence 的 `{agentId, runId, actionId}`,用一次 `agent.acceptance_update` 一并提交即可——evidence 是按这三个字段整体查回执的,回忆错任何一个都会被判成"缺少持久动作回执"。不要为了取这些字段再去查动作历史。取证完成前审批卡不会出现。
|
||||
6. 用户在审批卡上选择修改或退回时,先用 `agent.run_status` 按原 delegationId 取回已认领的权威委派合同,把其中的 acceptanceCriteria 与 expectedArtifacts 逐字照抄进返工委派(`runId` 传 null),再把用户原话完整附在 task 里;同一原委派只能返工一次。用户通过后只做一句简短收尾。
|
||||
6. 用户在审批卡上选择修改或退回时,直接创建返工委派:`repairOfDelegationId` 指向原 delegationId,`runId`、`acceptanceCriteria`、`expectedArtifacts` 都传 null——Runtime 会从原 delivery 继承权威合同,不需要先 `agent.run_status` 去取再手抄。把用户原话完整附在 task 里;同一原委派只能返工一次。用户通过后只做一句简短收尾。
|
||||
|
||||
【转达的规则】
|
||||
|
||||
|
||||
@@ -658,14 +658,36 @@ pub(crate) fn observe_agent_runtime_agent_delegate_at_locked(
|
||||
detail: None,
|
||||
};
|
||||
}
|
||||
let acceptance_criteria = agent_runtime_tool_input_string_list(
|
||||
let mut acceptance_criteria = agent_runtime_tool_input_string_list(
|
||||
input,
|
||||
&["acceptanceCriteria", "acceptance_criteria", "criteria"],
|
||||
);
|
||||
let expected_artifacts = agent_runtime_tool_input_string_list(
|
||||
let mut expected_artifacts = agent_runtime_tool_input_string_list(
|
||||
input,
|
||||
&["expectedArtifacts", "expected_artifacts", "artifacts"],
|
||||
);
|
||||
// 返工与澄清续跑必须逐字继承原委派的两个数组(下面
|
||||
// validate_static_delegate_repair_request_at 会逐项比对)。两个都空时由 Runtime
|
||||
// 从原 delivery 补齐,省掉一次 agent.run_status 往返和一整轮手抄。只补齐"两个
|
||||
// 都空"这一种形态:只写了一半是有歧义的输入,仍旧交给既有比对报错。
|
||||
// 读不到原 delivery 时保持原样,让下游的既有错误如实说明问题。
|
||||
{
|
||||
let original_delegation_id = agent_runtime_tool_input_text(
|
||||
input,
|
||||
&["repairOfDelegationId", "repair_of_delegation_id"],
|
||||
);
|
||||
if !original_delegation_id.trim().is_empty()
|
||||
&& acceptance_criteria.is_empty()
|
||||
&& expected_artifacts.is_empty()
|
||||
{
|
||||
if let Ok(Some(original)) =
|
||||
read_static_delegate_delivery_at(root, original_delegation_id.trim())
|
||||
{
|
||||
acceptance_criteria = original.acceptance_criteria.clone();
|
||||
expected_artifacts = original.expected_artifacts.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
let explicit_contract = input.as_object().is_some_and(|object| {
|
||||
object.contains_key("acceptanceCriteria")
|
||||
|| object.contains_key("acceptance_criteria")
|
||||
|
||||
@@ -735,20 +735,31 @@ fn validate_native_agent_delegate_input(
|
||||
}
|
||||
validate_native_delegate_string(object.get("agentId"), "agentId", 96, false)?;
|
||||
validate_native_delegate_string(object.get("task"), "task", 2_400, false)?;
|
||||
validate_native_delegate_string_list(
|
||||
object.get("acceptanceCriteria"),
|
||||
"acceptanceCriteria",
|
||||
1,
|
||||
8,
|
||||
240,
|
||||
)?;
|
||||
validate_native_delegate_string_list(
|
||||
object.get("expectedArtifacts"),
|
||||
"expectedArtifacts",
|
||||
0,
|
||||
16,
|
||||
240,
|
||||
)?;
|
||||
// 返工/澄清续跑必须逐字继承原委派的两个数组,Runtime 从 repairOfDelegationId
|
||||
// 指向的 delivery 直接读得到权威值。两个都传 null 时由 Runtime 补齐;手抄一遍
|
||||
// 不带来任何信息增益,只制造反复失败的返工委派。初次委派仍然必须自己写。
|
||||
let repair_hop = object
|
||||
.get("repairOfDelegationId")
|
||||
.is_some_and(Value::is_string);
|
||||
let inherits_contract = repair_hop
|
||||
&& object.get("acceptanceCriteria").is_some_and(Value::is_null)
|
||||
&& object.get("expectedArtifacts").is_some_and(Value::is_null);
|
||||
if !inherits_contract {
|
||||
validate_native_delegate_string_list(
|
||||
object.get("acceptanceCriteria"),
|
||||
"acceptanceCriteria",
|
||||
1,
|
||||
8,
|
||||
240,
|
||||
)?;
|
||||
validate_native_delegate_string_list(
|
||||
object.get("expectedArtifacts"),
|
||||
"expectedArtifacts",
|
||||
0,
|
||||
16,
|
||||
240,
|
||||
)?;
|
||||
}
|
||||
validate_native_delegate_string(
|
||||
object.get("repairOfDelegationId"),
|
||||
"repairOfDelegationId",
|
||||
@@ -2068,6 +2079,39 @@ mod tests {
|
||||
.expect("legacy delegate input without clarification fields");
|
||||
}
|
||||
|
||||
/// 返工/续跑的两个数组必须逐字继承原委派,Runtime 读得到权威值。要求 Supervisor
|
||||
/// 手抄它们只会反复失败:实测一次两轮澄清的 plan run 里,
|
||||
/// 「静态委派返工必须完整继承原 acceptanceCriteria 和 expectedArtifacts」
|
||||
/// 出现 4 次,每建一条 continuation 先白跑两轮工具调用。
|
||||
#[test]
|
||||
fn native_agent_delegate_repair_may_inherit_the_original_contract() {
|
||||
let mut inherit = valid_delegate_input(json!("delegation-id"), Value::Null);
|
||||
let object = inherit.as_object_mut().expect("delegate input object");
|
||||
object.insert("acceptanceCriteria".to_string(), Value::Null);
|
||||
object.insert("expectedArtifacts".to_string(), Value::Null);
|
||||
|
||||
validate_native_agent_delegate_input(&inherit).expect("repair may inherit the contract");
|
||||
|
||||
// 初次委派没有可继承的原 delivery,两个数组仍然必须自己写。
|
||||
let mut initial = valid_delegate_input(Value::Null, json!("run-1"));
|
||||
let object = initial.as_object_mut().expect("delegate input object");
|
||||
object.insert("acceptanceCriteria".to_string(), Value::Null);
|
||||
object.insert("expectedArtifacts".to_string(), Value::Null);
|
||||
assert!(validate_native_agent_delegate_input(&initial)
|
||||
.expect_err("initial delegate must still carry its own contract")
|
||||
.to_string()
|
||||
.contains("acceptanceCriteria"));
|
||||
|
||||
// 只省一半是有歧义的输入,不放行。
|
||||
let mut half = valid_delegate_input(json!("delegation-id"), Value::Null);
|
||||
let object = half.as_object_mut().expect("delegate input object");
|
||||
object.insert("acceptanceCriteria".to_string(), Value::Null);
|
||||
assert!(validate_native_agent_delegate_input(&half)
|
||||
.expect_err("half-omitted contract must fail")
|
||||
.to_string()
|
||||
.contains("acceptanceCriteria"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn native_agent_delegate_accepts_clarification_continuation_without_fingerprints() {
|
||||
// 指纹由 Runtime 从原 delivery 补齐,Supervisor 只需要指出续跑的是哪个委派。
|
||||
|
||||
@@ -4394,6 +4394,66 @@ fn clarification_continuation_chain_supports_multiple_rounds() {
|
||||
fs::remove_dir_all(root).ok();
|
||||
}
|
||||
|
||||
/// 返工/澄清续跑的 acceptanceCriteria 与 expectedArtifacts 由 Runtime 从原 delivery
|
||||
/// 继承:四个字段全传 null 时续跑照样建得起来,落盘值与原委派逐字相同。
|
||||
///
|
||||
/// 生产实测里 Supervisor 手抄这两个数组会反复抄错——一次两轮澄清的 plan run 中
|
||||
/// 「静态委派返工必须完整继承原 acceptanceCriteria 和 expectedArtifacts」出现 4 次,
|
||||
/// 每建一条 continuation 先白跑两轮工具调用才成功。
|
||||
#[test]
|
||||
fn planning_clarification_continuation_inherits_the_original_contract() {
|
||||
let mut fixture = planning_clarification_fixture("inherit-contract");
|
||||
let answered = answer_planning_clarification_round(
|
||||
&mut fixture,
|
||||
1,
|
||||
PLAN_TEST_OPTION_A,
|
||||
"inherit-contract",
|
||||
);
|
||||
let original = answered.original_delivery.clone();
|
||||
assert!(
|
||||
!original.acceptance_criteria.is_empty(),
|
||||
"原委派合同为空会让下面的继承断言失去意义"
|
||||
);
|
||||
|
||||
let continuation_id = clarification_continuation_delegation_id(
|
||||
&fixture.root,
|
||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||
&fixture.supervisor.run_id,
|
||||
GAME_CREATOR_PROJECT_PLANNING_AGENT_ID,
|
||||
&original.delegation_id,
|
||||
&answered.questions_sha256,
|
||||
&answered.answers_sha256,
|
||||
);
|
||||
let delegate_input = serde_json::json!({
|
||||
"agentId": GAME_CREATOR_PROJECT_PLANNING_AGENT_ID,
|
||||
"task": "根据用户回答继续收敛 Fast GDD",
|
||||
"acceptanceCriteria": serde_json::Value::Null,
|
||||
"expectedArtifacts": serde_json::Value::Null,
|
||||
"repairOfDelegationId": original.delegation_id,
|
||||
"runId": serde_json::Value::Null,
|
||||
"continuationOfDelegationId": original.delegation_id,
|
||||
"questionsSha256": serde_json::Value::Null,
|
||||
"answersSha256": serde_json::Value::Null,
|
||||
});
|
||||
let observation = observe_agent_runtime_agent_delegate(
|
||||
&fixture.root,
|
||||
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
|
||||
&fixture.supervisor.run_id,
|
||||
Some("planning-inherit-contract-continuation"),
|
||||
&delegate_input,
|
||||
);
|
||||
assert_eq!(observation.status, "ok", "{observation:?}");
|
||||
|
||||
let continuation = read_static_delegate_delivery_at(&fixture.root, &continuation_id)
|
||||
.expect("read inherited continuation delivery")
|
||||
.expect("inherited continuation delivery exists");
|
||||
assert_eq!(
|
||||
continuation.acceptance_criteria,
|
||||
original.acceptance_criteria
|
||||
);
|
||||
assert_eq!(continuation.expected_artifacts, original.expected_artifacts);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn planning_clarification_three_rounds_project_session_and_structured_injection() {
|
||||
let mut fixture = planning_clarification_fixture("three-rounds");
|
||||
|
||||
Reference in New Issue
Block a user