返工委派的两个数组也改由 Runtime 继承
Project CI / Repository checks (pull_request) Failing after 14s
Project CI / Backend tests (pull_request) Failing after 14s
Project CI / Frontend tests (pull_request) Successful in 4m27s
Project CI / Native shell tests (pull_request) Successful in 13m33s

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:
2026-08-24 01:35:13 +00:00
parent f38ce456d1
commit 58d9e9a5d4
4 changed files with 144 additions and 18 deletions
@@ -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 取回已认领的权威委派合同,把其中的 acceptanceCriteriaexpectedArtifacts 逐字照抄进返工委派(`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");