完善父 run 协作策略快照与恢复

固化父 run 协作策略快照与独立绑定记录
统一危险 Agent/run ID 的路径键与锁身份
补齐 legacy claim、aborted batch 和非协作 v1 恢复边界
增强真实 E2E 的确认链与 source endpoint 隔离门禁
扩展协作回归至 52 项并同步 V1.38 文档验收状态
This commit is contained in:
AIGameCreator App
2026-07-19 18:52:33 +08:00
parent 29228afb9e
commit 0ee8e664fa
9 changed files with 5036 additions and 210 deletions
File diff suppressed because it is too large Load Diff
+240 -17
View File
@@ -3646,6 +3646,44 @@ fn game_creator_agent_runtime_task_is_terminal_for_cancel(
|| (task.status == "failed" && task.phase != "needs-reconciliation" && !has_pending_action)
}
pub(crate) fn game_creator_agent_runtime_run_is_non_terminal_for_collaboration_migration_at(
root: &Path,
agent_id: &str,
run_id: &str,
) -> Result<bool, String> {
if let Some(task) =
read_latest_game_creator_agent_runtime_task_by_run_id(root, agent_id, run_id)?
{
return Ok(matches!(
task.status.as_str(),
"pending" | "running" | "waiting-for-confirmation" | "waiting-for-user-input"
) && !matches!(
task.phase.as_str(),
"completed"
| "failed"
| "cancelled"
| "cancelling"
| "finalizing"
| "needs-reconciliation"
));
}
let runtime = read_game_creator_agent_runtime_at(root, agent_id)?;
Ok(runtime.state.run_id == run_id
&& matches!(
runtime.state.status.as_str(),
"pending" | "running" | "waiting-for-confirmation" | "waiting-for-user-input"
)
&& !matches!(
runtime.state.phase.as_str(),
"completed"
| "failed"
| "cancelled"
| "cancelling"
| "finalizing"
| "needs-reconciliation"
))
}
fn resolve_game_creator_agent_runtime_cancel_target(
root: &Path,
agent_id: &str,
@@ -13872,7 +13910,12 @@ pub(crate) async fn prepare_game_creator_agent_runtime_provider_action_batch(
let (collaboration_policy, collaboration_state, collaboration_preflight) =
if runtime.agent_id == GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID {
let collaboration_policy = read_supervisor_collaboration_policy_at(root)?;
let collaboration_policy = resolve_supervisor_collaboration_policy_for_run_at(
root,
&runtime.agent_id,
&runtime.run_id,
)?
.policy;
let collaboration_state =
read_supervisor_collaboration_state_at(root, &runtime.agent_id, &runtime.run_id)?;
let collaboration_preflight = preflight_supervisor_collaboration_plan(
@@ -15923,8 +15966,8 @@ fn supervisor_orchestrator_mutation_block_at(
{
return None;
}
let policy = match read_supervisor_collaboration_policy_at(root) {
Ok(policy) => policy,
let policy = match resolve_supervisor_collaboration_policy_for_run_at(root, agent_id, run_id) {
Ok(resolution) => resolution.policy,
Err(error) => {
return Some(AgentRuntimeToolObservation {
tool: tool.to_string(),
@@ -15980,8 +16023,8 @@ async fn supervisor_orchestrator_mcp_mutation_block_at(
{
return None;
}
let policy = match read_supervisor_collaboration_policy_at(root) {
Ok(policy) => policy,
let policy = match resolve_supervisor_collaboration_policy_for_run_at(root, agent_id, run_id) {
Ok(resolution) => resolution.policy,
Err(error) => {
return Some(AgentRuntimeToolObservation {
tool: GAME_CREATOR_MCP_CALL_TOOL.to_string(),
@@ -16433,8 +16476,8 @@ fn supervisor_collaboration_policy_completion_blocker_at_locked(
if agent_id != GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID {
return None;
}
let policy = match read_supervisor_collaboration_policy_at(root) {
Ok(policy) => policy,
let policy = match resolve_supervisor_collaboration_policy_for_run_at(root, agent_id, run_id) {
Ok(resolution) => resolution.policy,
Err(error) => {
return Some(AgentRuntimeToolObservation {
tool: "runtime.collaboration_policy".to_string(),
@@ -16466,6 +16509,15 @@ fn supervisor_collaboration_policy_completion_blocker_at_locked(
})
}
#[cfg(test)]
pub(crate) fn supervisor_collaboration_policy_completion_blocker_for_test_at(
root: &Path,
agent_id: &str,
run_id: &str,
) -> Option<AgentRuntimeToolObservation> {
supervisor_collaboration_policy_completion_blocker_at_locked(root, agent_id, run_id)
}
fn isolated_join_barrier_has_waiting_groups(detail: &str) -> bool {
detail
.split_whitespace()
@@ -20035,7 +20087,7 @@ fn build_game_creator_agent_background_tool_plan_request(
let tool_policy_json = serde_json::to_string_pretty(&tool_policy)
.map_err(|error| format!("序列化 Agent 工具策略失败:{error}"))?;
let collaboration_policy_json = if agent_id == GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID {
render_supervisor_collaboration_policy_for_prompt_at(root)?
render_supervisor_collaboration_policy_for_prompt_at(root, agent_id, run_id)?
} else {
"null".to_string()
};
@@ -21299,7 +21351,7 @@ fn game_creator_agent_runtime_tool_command_id(tool: &str) -> Option<&'static str
}
}
fn agent_runtime_confirmation_path_component(value: &str, fallback: &str) -> String {
pub(crate) fn agent_runtime_confirmation_path_component(value: &str, fallback: &str) -> String {
let normalized = value
.trim()
.chars()
@@ -21725,6 +21777,16 @@ fn agent_runtime_provider_action_batch_id(
fn validate_game_creator_agent_runtime_provider_action_batch(
root: &Path,
batch: &AgentRuntimeProviderActionBatch,
) -> Result<(), String> {
validate_game_creator_agent_runtime_provider_action_batch_with_recovery_policy(
root, batch, None,
)
}
fn validate_game_creator_agent_runtime_provider_action_batch_with_recovery_policy(
root: &Path,
batch: &AgentRuntimeProviderActionBatch,
recovery_policy: Option<&SupervisorCollaborationPolicy>,
) -> Result<(), String> {
if !matches!(
batch.schema_version.as_str(),
@@ -21904,7 +21966,17 @@ fn validate_game_creator_agent_runtime_provider_action_batch(
_ => {}
}
if batch.agent_id == GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID {
let policy = read_supervisor_collaboration_policy_at(root)?;
let policy = match recovery_policy {
Some(policy) => policy.clone(),
None => {
resolve_supervisor_collaboration_policy_for_run_at(
root,
&batch.agent_id,
&batch.run_id,
)?
.policy
}
};
let state = read_supervisor_collaboration_state_at(root, &batch.agent_id, &batch.run_id)?;
if let Some(contract) = batch.collaboration_contract.as_ref() {
if batch.schema_version != AGENT_RUNTIME_PROVIDER_ACTION_BATCH_SCHEMA_VERSION {
@@ -21986,6 +22058,11 @@ pub(crate) fn write_game_creator_agent_runtime_provider_action_batch(
batch: &AgentRuntimeProviderActionBatch,
) -> Result<(), String> {
validate_game_creator_agent_runtime_provider_action_batch(root, batch)?;
let requires_initial_snapshot = batch.agent_id == GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID
&& batch.status != AGENT_RUNTIME_PROVIDER_ACTION_BATCH_STATUS_ABORTED
&& batch.collaboration_contract.is_some()
&& read_supervisor_collaboration_policy_snapshot_at(root, &batch.agent_id, &batch.run_id)?
.is_none();
write_agent_runtime_json_sidecar_with_max_bytes(
root,
&game_creator_agent_runtime_provider_action_batch_relative_path(
@@ -21995,7 +22072,42 @@ pub(crate) fn write_game_creator_agent_runtime_provider_action_batch(
"Agent Runtime Provider action 批次",
batch,
AGENT_RUNTIME_PROVIDER_ACTION_BATCH_SIDECAR_MAX_BYTES,
)
)?;
if requires_initial_snapshot {
fail_supervisor_collaboration_policy_snapshot_after_batch_for_test_at(root)?;
let contract = batch
.collaboration_contract
.as_ref()
.ok_or_else(|| "Project Supervisor 首个协作批次缺少合同".to_string())?;
bind_supervisor_collaboration_policy_snapshot_for_new_batch_at(
root,
&batch.agent_id,
&batch.run_id,
&contract.policy,
)?;
validate_game_creator_agent_runtime_provider_action_batch(root, batch)?;
}
Ok(())
}
#[cfg(test)]
fn fail_supervisor_collaboration_policy_snapshot_after_batch_for_test_at(
root: &Path,
) -> Result<(), String> {
if root
.join(".agent/runtime/test-collaboration-policy-snapshot-after-batch-failpoint")
.exists()
{
return Err("测试断点:Provider action batch 已持久化,协作策略快照尚未绑定".to_string());
}
Ok(())
}
#[cfg(not(test))]
fn fail_supervisor_collaboration_policy_snapshot_after_batch_for_test_at(
_root: &Path,
) -> Result<(), String> {
Ok(())
}
pub(crate) fn read_game_creator_agent_runtime_provider_action_batch(
@@ -22015,10 +22127,101 @@ pub(crate) fn read_game_creator_agent_runtime_provider_action_batch(
if batch.agent_id != agent_id || batch.run_id != run_id {
return Err("Agent Runtime Provider action 批次 Agent 或 run 身份不匹配".to_string());
}
if batch.agent_id == GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID {
if let Some(contract) = batch.collaboration_contract.as_ref() {
if read_supervisor_collaboration_policy_snapshot_at(
root,
&batch.agent_id,
&batch.run_id,
)?
.is_none()
{
validate_game_creator_agent_runtime_provider_action_batch_with_recovery_policy(
root,
&batch,
Some(&contract.policy),
)?;
let can_recover_aborted_snapshot = batch.status
!= AGENT_RUNTIME_PROVIDER_ACTION_BATCH_STATUS_ABORTED
|| read_supervisor_collaboration_policy_snapshot_binding_at(
root,
&batch.agent_id,
&batch.run_id,
)?
.is_some();
if can_recover_aborted_snapshot {
recover_supervisor_collaboration_policy_snapshot_from_batch_at(
root,
&batch.agent_id,
&batch.run_id,
&contract.policy,
)?;
} else {
return Ok(batch);
}
}
}
}
validate_game_creator_agent_runtime_provider_action_batch(root, &batch)?;
Ok(batch)
}
pub(crate) fn recover_supervisor_collaboration_policy_snapshot_from_pending_batch_if_any_at(
root: &Path,
agent_id: &str,
run_id: &str,
) -> Result<Option<SupervisorCollaborationPolicySnapshot>, String> {
if agent_id != GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID
|| !game_creator_agent_runtime_provider_action_batch_exists(root, agent_id, run_id)
{
return Ok(None);
}
if let Some(snapshot) =
read_supervisor_collaboration_policy_snapshot_at(root, agent_id, run_id)?
{
return Ok(Some(snapshot));
}
let relative_path =
game_creator_agent_runtime_provider_action_batch_relative_path(agent_id, run_id);
let batch = read_agent_runtime_json_sidecar_with_max_bytes::<AgentRuntimeProviderActionBatch>(
root,
&relative_path,
"Agent Runtime Provider action 批次",
AGENT_RUNTIME_PROVIDER_ACTION_BATCH_SIDECAR_MAX_BYTES,
)?
.ok_or_else(|| "Agent Runtime Provider action 批次不存在".to_string())?;
if batch.agent_id != agent_id || batch.run_id != run_id {
return Err("Agent Runtime Provider action 批次 Agent 或 run 身份不匹配".to_string());
}
let Some(contract) = batch.collaboration_contract.as_ref() else {
if supervisor_collaboration_actions_require_contract(&batch.plan.actions)? {
return Err(
"旧版或缺失 collaborationContract 的 Project Supervisor 协作批次不能绑定当前项目策略;需要进入人工核对"
.to_string(),
);
}
return Ok(None);
};
validate_game_creator_agent_runtime_provider_action_batch_with_recovery_policy(
root,
&batch,
Some(&contract.policy),
)?;
if batch.status == AGENT_RUNTIME_PROVIDER_ACTION_BATCH_STATUS_ABORTED
&& read_supervisor_collaboration_policy_snapshot_binding_at(root, agent_id, run_id)?
.is_none()
{
return Ok(None);
}
recover_supervisor_collaboration_policy_snapshot_from_batch_at(
root,
agent_id,
run_id,
&contract.policy,
)
.map(Some)
}
fn remove_game_creator_agent_runtime_provider_action_batch(
root: &Path,
agent_id: &str,
@@ -30317,6 +30520,14 @@ pub(crate) fn observe_agent_runtime_run_status(
})
}
.and_then(|mut detail| {
let collaboration_policy_status = (agent_id == GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID)
.then(|| {
supervisor_collaboration_policy_status_for_run_at(root, agent_id, run_id)
.unwrap_or_else(|_| "unavailable".to_string())
});
if let Some(policy_status) = collaboration_policy_status.as_deref() {
detail = format!("collaborationPolicy: {policy_status}\n\n{detail}");
}
let claim_action_id = action_id.map(str::trim).filter(|value| !value.is_empty());
let static_delegate_output_may_be_present =
if agent_id == GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID {
@@ -30477,6 +30688,7 @@ pub(crate) fn observe_agent_runtime_run_status(
claimed_join_count,
ready_delegate_count,
claimed_delegate_count,
collaboration_policy_status,
))
});
match result {
@@ -30486,6 +30698,7 @@ pub(crate) fn observe_agent_runtime_run_status(
claimed_join_count,
ready_delegate_count,
claimed_delegate_count,
collaboration_policy_status,
)) => {
let count = if is_all_scope {
detail.matches("agentId: ").count()
@@ -30516,6 +30729,17 @@ pub(crate) fn observe_agent_runtime_run_status(
",已有 {claimed_delegate_count} 个专业 Agent 回执被当前父 run 认领;语义复核或返工前按 delegationId 重读权威合同"
));
}
if collaboration_policy_status
.as_deref()
.is_some_and(|status| status.contains("projectPolicyStatus=drifted"))
{
summary.push_str(",项目协作策略已漂移,当前父 run 继续使用已绑定快照");
} else if collaboration_policy_status
.as_deref()
.is_some_and(|status| status.contains("projectPolicyStatus=unreadable"))
{
summary.push_str(",项目协作策略当前不可读,当前父 run 继续使用已绑定快照");
}
AgentRuntimeToolObservation {
tool: "agent.run_status".to_string(),
status: "ok".to_string(),
@@ -30591,17 +30815,16 @@ fn ensure_supervisor_isolated_join_claim_policy_ready_at(
if parent_agent_id != GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID {
return Ok(());
}
let policy = read_supervisor_collaboration_policy_at(root)?;
let required_group_count = policy.min_isolated_groups_before_claim;
if required_group_count == 0 {
return Ok(());
}
if let Some(action_id) = action_id {
if read_isolated_join_claim_at(root, parent_agent_id, parent_run_id, action_id)?.is_some() {
return Ok(());
}
}
if claimed_isolated_join_count_for_parent_at(root, parent_agent_id, parent_run_id)? > 0 {
let policy =
resolve_supervisor_collaboration_policy_for_run_at(root, parent_agent_id, parent_run_id)?
.policy;
let required_group_count = policy.min_isolated_groups_before_claim;
if required_group_count == 0 {
return Ok(());
}
let state = read_supervisor_collaboration_state_at(root, parent_agent_id, parent_run_id)?;
File diff suppressed because it is too large Load Diff
@@ -543,6 +543,13 @@ pub(crate) fn claim_ready_static_delegate_receipts_with_budget_at(
&required_delegation_ids,
max_payload_chars,
)?;
if parent_agent_id == GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID
&& receipts
.iter()
.any(|receipt| !required_delegation_ids.contains(&receipt.delegation_id))
{
resolve_supervisor_collaboration_policy_for_run_at(root, parent_agent_id, parent_run_id)?;
}
let delivery_locks = acquire_static_delegate_delivery_locks_at(
root,
receipts
@@ -635,6 +642,8 @@ fn select_static_delegate_receipt_batch(
required_payload_chars, max_payload_chars
));
}
// 缺失 claim journal 时只恢复已经归属当前 action 的 receipt,下一轮再认领新 Ready。
return Ok(selected);
}
for receipt in receipts
.iter()
@@ -1742,6 +1751,14 @@ mod tests {
.expect("project init");
let parent_agent_id = GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID;
let parent_run_id = "target-summary-parent-run";
bind_supervisor_collaboration_policy_snapshot_at(
&root,
parent_agent_id,
parent_run_id,
&SupervisorCollaborationPolicy::default(),
"legacy-current-project-policy",
)
.expect("bind collaboration snapshot");
let claimed = new_static_delegate_delivery(
parent_agent_id,
@@ -1850,6 +1867,14 @@ mod tests {
.expect("project init");
let parent_run_id = "claim-monotonic-parent-run";
let claim_action_id = "claim-monotonic-action";
bind_supervisor_collaboration_policy_snapshot_at(
&root,
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
parent_run_id,
&SupervisorCollaborationPolicy::default(),
"legacy-current-project-policy",
)
.expect("bind collaboration snapshot");
let delivery = new_static_delegate_delivery(
GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID,
"claim-monotonic-parent-session",
@@ -1932,6 +1957,14 @@ mod tests {
let parent_agent_id = GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID;
let parent_run_id = "required-recovery-parent-run";
let action_id = "required-recovery-claim-action";
bind_supervisor_collaboration_policy_snapshot_at(
&root,
parent_agent_id,
parent_run_id,
&SupervisorCollaborationPolicy::default(),
"legacy-current-project-policy",
)
.expect("bind collaboration snapshot");
let required = new_static_delegate_delivery(
parent_agent_id,
"required-recovery-parent-session-with-long-identity",
File diff suppressed because it is too large Load Diff
@@ -4827,6 +4827,7 @@
- 决策:新增独立项目控制面 `.agent/collaboration-policy.json`,声明首波 `auto / static / isolated / mixed`、最少 static delegate、required static Agent、最少 isolated child 和委派后总控只编排开关。缺失 sidecar 时不强制特定协作拓扑,但默认在当前父 run 形成任何 delivery/group 后禁止 Supervisor 直接修改项目。
- 原子性:Supervisor 首波协作复用 Provider action batch 的整批预检;策略不满足或协作批次混入总控项目 mutation 时,任何 pending、确认、delivery、group、child、revision 和项目写入发生前整批返回 blocked observation。通过时 batch v2 固化策略与动作合同指纹,恢复时重新校验策略漂移。
- 恢复顺序:batch 成员必须在委派或 spawn 副作用前持久化为 `executing`;恢复、确认和 replay 必须先校验当前策略、完整协作合同、batchId 与 action 身份。策略漂移或旧协作 batch 缺少合同只能进入 `needs-reconciliation`,不得重放 child 副作用。isolated 最低 child 数量按单一 durable group 计算,不能拼接多个不足最低数量的小 group。
- 覆盖说明:上条关于“恢复时重验当前策略、live policy 漂移即 reconciliation”的部分自 V1.38 起不再是现行口径。V1.32 的其它 batch/contract/action 身份与副作用前门禁继续有效;现行策略选择、漂移和恢复顺序以本文件 V1.38 决策为准。
- 控制面边界:`.agent/collaboration-policy.json` 对 Agent 通用文件工具隐藏并拒绝写入;委派后的 Supervisor 只允许严格只读 MCP,注解不完整或 destructive MCP 失败关闭。`project.git_commit``canvas.asset_generate` 在取得项目锁后再次读取 durable 协作事实,堵住 dispatch 首检后的并发落盘窗口。
- 完成边界:finalization 只认可同一父 run 的 durable static delivery 和 isolated group。repair delegate、合法 isolated 检查、读取、状态查询和项目验证继续允许;源码写入、patch/restore、Git commit、命令启动及平台素材生成由专业 Agent 承担。
- 验证方式:运行 `supervisor_collaboration_``provider_action_batch_``project_supervisor_mixed_` 定向 Rust 回归,随后执行编码检查和 `git diff --check`;真实 Provider V1.32 必须在最终代码 diff 上独立完成,不能复用 V1.31 报告。
@@ -4881,3 +4882,14 @@
- Scope 边界:只读 isolated task 也必须声明 expected artifact 的最小目录 scope,不得扩大到 sibling scope 或共同父目录;该约束写入通用边界提示,不为单个验收任务硬编码。
- 定向验收:`supervisor_collaboration_policy_` 23/23、`project_supervisor_` 47/47 通过,E2E self-test **PASS**Tauri/Rust 全量为 930 passed、4 个环境依赖用例按设计 ignored。
- 真实 Provider:第一次独立运行因模型初始 child scope 不符合 expected artifact 最小边界而 **FAIL**child / claim / project mutation 均为 `0` 且现场自动清理,不与后续证据拼接。补强通用边界提示后的第二次独立运行 **PASS**policy=`2`2 个 group / 3 个 child1 个 `observed` join claim 覆盖 2 个 groupRunner 强杀恢复身份稳定,Provider lifecycle `64/64` completed、failed=`0`,重复、残留、泄漏均为 `0`,最终回复唯一。
## 2026-07-18 AI 游戏创作 Agent Runtime V1.38 父 run 协作策略持久快照与绑定记录
- 决策:首个非 `aborted`、携带 v2 `collaborationContract` 的 durable collaboration batch 是父 run 策略线性化点。Runtime 必须按 `v2 batch -> snapshot -> binding sidecar -> action side effects` 持久化:snapshot 位于 `.agent/runtime/collaboration-policy-snapshots/<agentKey>/<runKey>.json`,独立 binding 位于 `.agent/runtime/collaboration-policy-snapshot-bindings/<agentKey>/<runKey>.json`,用于持久证明“该 run 曾绑定”。没有既存 binding 的首次 `aborted` batch 不创建两类 sidecar;若 binding 已证明该 run 先前完成绑定,snapshot 丢失时可用完整验真的 matching v2 contract 恢复原快照,即使当前保留的 batch 为 `aborted`,这不构成新绑定。batch -> snapshot 与 snapshot -> binding 都是零副作用可恢复窗口。
- 数据契约:snapshot v1 固定且完整包含 `schemaVersion / projectId / parentAgentId / parentRunId / boundFrom / policy / policyFingerprint / snapshotFingerprint / boundAt`policy 先规范化,snapshot fingerprint 绑定除 `snapshotFingerprint / boundAt` 外的全部稳定字段。binding v1 固定包含 `schemaVersion / projectId / parentAgentId / parentRunId / boundFrom / policyFingerprint / snapshotFingerprint / boundAt`,必须与 snapshot 的绑定身份逐字段一致。两者按 project/parent run 共用锁并在锁内 CAS,冲突失败关闭,禁止通用 replace 覆盖。
- 路径身份:安全 Agent/run ID 可原样作为 `agentKey/runKey`;任何不安全或规范化后变化的 ID 必须使用有界安全前缀加原始完整 ID 的稳定 SHA-256,不能让 lossy 字符替换制造路径碰撞。锁 key 固定对完整 `parentAgentId + NUL + parentRunId` 计算稳定 SHA-256,不复用路径规范化结果。
- 恢复:优先级固定为 existing valid snapshot > 完整验真的 v2 batch contract > 符合严格状态门禁的 legacy 当前有效 policy。正常绑定使用 `boundFrom=initial-collaboration-batch`v2 contract 必须先独立校验 batch/project/action/contract 全身份与两层指纹,才能以 `boundFrom=legacy-provider-batch-contract` 补绑。snapshot 存在但 binding 缺失时从 snapshot 补写;binding 存在但 snapshot 丢失时只按 matching binding 与可信 v2 contract 恢复,没有可信 v2 contract 时禁止按 live policy 重绑。
- Legacy 与旧 batchcontractless/v1 collaboration batch 必须在任何 live policy 回退前失败关闭并进入 reconciliation,不能忽略旧 batch 后把已有 run 当成 fresh run。`boundFrom=legacy-current-project-policy` 仅允许无 snapshot/binding、无可信 v2 contract,且不存在上述旧 batch,并由 durable run 身份与状态明确证明属于 `pending / running / waiting-for-confirmation / waiting-for-user-input` 的旧父 runterminal、`needs-reconciliation` 或身份/状态未知 run 的状态读取不得新建 snapshot。没有任何 durable run/collaboration 事实的真正新父 run只能读取 live policy 构造首个 v2 contract,不创建 legacy snapshot。
- 漂移:snapshot 绑定后,后续 spawn、repair、新 claim、Supervisor mutation、MCP、prompt/status、completion/finalization 与恢复全部使用 snapshot。global policy 的 `matched / drifted / unreadable` 只作有界状态报告,不改变执行、revision、verification 或 reconciliation;已有 run 不重绑,新 policy 只由后续新父 run 采用。
- Claim 兼容:旧 durable claim、未观察 claim 和 legacy claimed delivery 的恢复先于 effective snapshot 解析及新 claim 门禁,继续按原 action/group 身份推进且不得取得新 deliveryglobal policy、snapshot 或 binding 故障不能把已提交 claim 卡死。新的 claim 必须先成功解析 effective snapshot 并核对 binding,失败发生在 journal、delivery 锁和 mutation 之前;随后仍执行 V1.35-V1.37 的全锁、预算、完整 observation 和 group 数量门禁。
- 覆盖与验收:本决策明确覆盖 V1.32 的 live drift reconciliation 旧口径,但不把 V1.32/V1.35/V1.37 历史 PASS 外推为 V1.38 证据。2026-07-19 self-test、snapshot/binding 双故障窗口与 CAS/丢失/篡改/旧 batch/危险 ID/claim 分流确定性覆盖、`supervisor_collaboration_` 52/52、`provider_action_batch_` 12/12、`project_supervisor_mixed_` 5/5 和 Tauri/Rust 全量 949 passed/4 ignored 已通过;终态 Runtime 清理后 snapshot/binding 保持原字节并继续解析为 `run-snapshot`。真实 mixed-swarm 独立功能样本已形成 2 group/3 child、policy drift、Runner 恢复、唯一最终回复和零重复/泄漏,但同轮正式 endpoint 被外部客户端重启;改用私有配置源后多轮又耗尽 transient Provider retry,最后在 `300000ms / maxRetries=3` 下于首批业务动作前形成 4 failed/3 retry 并干净终止。两类失败证据不得拼接,当前**仍不得声称 V1.38 真实 E2E 已 PASS**。
@@ -3243,6 +3243,17 @@
- 验证:Rust 定向回归使用 `project_supervisor_` 前缀,覆盖 delivery/claim 状态机、同 action 幂等、第 4 个新委派拒绝与已预留委派复用/拒绝 suppression、后续 delivery 锁忙时零部分认领、Agent DB 故障后回执仍可重放、未 Observed 阻断 final、Provider planning 前 durable 等待、parent-wake coalescing/结构性错误、重启损坏 barrier、错配和迟到 child、executing `run_status` 续接与 delegate policy 重验;`agent_background_enqueue_notifies_only_after_session_lane_release` 覆盖入队锁序,Runner 内部测试覆盖定向 wake 与不缓存重试。真实 Provider 必须同时证明专业 Agent 时间区间重叠、父 run 仅一次 waiting、同一 Observed claim 认领全部回执、唯一 assistant、第二轮历史引用不新增委派和项目范围密钥扫描为 0。
- 关联:`docs/technical/【技术方案】AI游戏创作Agent Runtime V1.1-2026-07-12.md``apps/ai-game-creator-shell/src-tauri/src/delegation.rs``agent.rs``runner.rs``tests.rs`
## 父 run 协作策略不能在绑定后继续按全局 live policy 重验
- 现象:同一 Supervisor 父 run 已经持久化合法 collaboration batch,管理员随后修改或损坏 `.agent/collaboration-policy.json`,后续 spawn、claim、mutation、MCP 或 finalization 却突然改用新策略、进入 reconciliation;或者 snapshot 被删除后,Runtime 又按 live policy 把已有 run 当成未绑定 run。另一类症状是 contractless/v1 batch 被跳过、两个不安全 run ID 经字符替换落到同一 snapshot/锁 key,或旧 `Prepared / Committed` claim 因 snapshot/binding 不可读而不能重放 observation。
- 原因:把项目级 policy 当成每个动作的 live 执行事实,没有为父 run 设置明确线性化点、不可变策略快照和独立“曾绑定”记录;或者在 v2 batch 完整验真前就用 `contract.policy` 播种 snapshot。只对 run ID 做 lossy 规范化、让锁复用该路径片段,或用通用原子 replace 代替同一身份锁内 CAS,也会制造路径碰撞、并发覆盖和伪合同漂移。
- 处理:V1.38 固定顺序为 `v2 batch -> snapshot -> binding sidecar -> action side effects`。snapshot 位于 `.agent/runtime/collaboration-policy-snapshots/<agentKey>/<runKey>.json`,其完整字段必须统一为 `schemaVersion / projectId / parentAgentId / parentRunId / boundFrom / policy / policyFingerprint / snapshotFingerprint / boundAt`snapshot fingerprint 覆盖除 `snapshotFingerprint / boundAt` 外的全部稳定字段。独立 binding 位于 `.agent/runtime/collaboration-policy-snapshot-bindings/<agentKey>/<runKey>.json`,固定包含 `schemaVersion / projectId / parentAgentId / parentRunId / boundFrom / policyFingerprint / snapshotFingerprint / boundAt`,与 snapshot 逐字段交叉验证并持久证明“该 run 曾绑定”。
- 路径与恢复:不安全或规范化后变化的 Agent/run ID 使用有界安全前缀加原始 ID 稳定 SHA-256,锁 key 对完整 `parentAgentId + NUL + parentRunId` 计算稳定 SHA-256,不能只做字符替换。恢复顺序为 existing valid snapshot > 完整验真的 v2 batch contract > 符合严格状态门禁的 legacy 当前有效 policysnapshot 缺 binding 可从 snapshot 补写,binding 存在但 snapshot 丢失只能按可信 v2 contract 和首次绑定身份恢复,无可信 v2 时禁止 live policy 重绑。contractless/v1 collaboration batch 必须先失败关闭。`legacy-current-project-policy` 只允许无 snapshot/binding、无可信 v2 contract,且不存在上述旧 batch,并由可信身份和状态明确证明属于 `pending / running / waiting-for-confirmation / waiting-for-user-input` 的旧父 runterminal、`needs-reconciliation` 或身份/状态未知 run 的状态读取不得新建 snapshot。
- 漂移与 Claim:绑定后 global policy 的 `matched / drifted / unreadable` 只报告状态,不能改变后续动作或完成门禁;新 policy 只用于后续新父 run。旧 durable claim、未观察 claim 和 legacy claimed delivery 先按原 action/group 身份恢复且不得取得新 delivery;新的 claim 必须先成功解析 effective snapshot 并核对 binding,再执行 V1.35-V1.37 的全锁、预算、完整 observation 和 group 数量门禁。
- 真实 E2E 现场:正在运行的正式客户端可能在验收期间启动或重启正式 Runner,导致 source endpoint 身份真实变化。不得关闭 `sourceRunnerEndpointUnchanged` 门禁,也不得杀掉不属于验收器的进程;应把同一配置内容复制到仓库外的大容量磁盘私有目录,目录/文件权限分别为 `0700/0600`,不复制 endpoint、锁、会话或数据库,验收后删除。功能完整但 source endpoint 被外部改变的报告与后续干净清理报告不得拼接。
- 验证:必须覆盖 snapshot 9 个完整字段、首次 `aborted` batch 无 snapshot/binding、matching binding 已存在时可用可信 `aborted` v2 contract 恢复缺失 snapshot、双故障窗口零副作用恢复、同内容并发 CAS、snapshot/binding 冲突或丢失、篡改 contract 不得播种、binding 已存在且无可信 v2 时禁止 live 重绑、contractless/v1 协作 batch 先失败关闭且非协作 v1 batch 不误伤、四种 legacy 非终态可迁移而 terminal/`needs-reconciliation`/身份状态未知读取不建 snapshot、危险 ID 路径/锁不碰撞、四类 global policy 状态,以及旧 claim 可恢复而新 claim 先过 effective snapshot。2026-07-19 上述确定性门禁、E2E self-test、52/52 collaboration 定向回归、终态 snapshot/binding 字节保留回归和 949 passed/4 ignored Rust 全量已完成;真实功能闭合轮受正式 endpoint 外部重启污染,私有配置源轮又连续耗尽 transient Provider retry,不能拼接为 PASS,故当前仍**不得声称 V1.38 真实 E2E 已 PASS**。
- 关联:`docs/technical/【技术方案】AI游戏创作Agent Runtime V1.1-2026-07-12.md``docs/project-memory/shared-memory/decision-log.md``apps/ai-game-creator-shell/src-tauri/src/collaboration.rs``agent.rs``tests.rs``scripts/agent-runtime-real-e2e.mjs`
## 单 Agent 持久计划不能靠工具下标或恢复猜进度
- 现象:工具 action 1 成功后第二个计划步骤被自动标成完成,模型仍有 pending / in_progress 步骤却写出最终回复;或 Runner 重启、刷新 UI、same-run steer 后 `planRevision` 回退、已完成步骤消失,legacy `plan` 又覆盖新计划。另一类错误是仅更新计划就触发项目 revision 漂移、verification 失效或权限确认。
@@ -1175,6 +1175,8 @@ V1.31 证明真实 Provider 可以自主形成 static + isolated 混合协作,
- finalization 在两类 delivery/join blocker 之外追加协作策略完成门禁。配置要求的 initial static/isolated 事实缺失、required static Agent 不完整或策略 sidecar 损坏时,原 Supervisor run 必须回到 planning,不能提交最终回复。完成事实只来自当前父 run 的 durable delivery/group,不接受 prompt 声明、计划文字或 Agent DB 诊断投影代替。
- 确定性验收至少覆盖:不完整 mixed 首波零 child/零 revision;完整 mixed 首波形成唯一 v2 合同;`executing` 先于首个 durable delivery 且 cursor 正确推进;恢复先验合同并在策略漂移时零 replay;委派后 mutation 在 confirmation 前被拒绝;Git commit 与画板生成在项目锁内再次阻断;破坏性 MCP、策略文件写/patch 和 revision 推进均为 0repair delegate、isolated spawn、`project.verify` 和读取仍允许;batch round-trip/Runner 恢复保持 batchId、policy/contract fingerprint 和 actionId,重复 child/delivery/group 为 0。真实 Provider 另建 V1.32 suite,不能把 V1.31 的一次成功样本外推为 Runtime 合同已验收。
**V1.38 覆盖说明:** 本节关于“恢复、确认和每次 batch 读取都重新校验当前项目策略,live policy 漂移即进入 `needs-reconciliation`”的口径自 V1.38 起废止。V1.32 的既有真实 PASS 只保留为当时实现的历史证据;现行编码与恢复语义统一以 V1.38 的父 run 持久策略快照为准,不能用 V1.32 报告宣称 V1.38 已通过。
2026-07-17 在最终代码 diff 上使用 `gpt-5.5 / openai_chat / high` 完成 V1.32 独立真实 PASS。`supervisor-swarm-collaboration-policy-mixed-recovery` 只在隔离 AppData 配置副本中把瞬态重试设为 `maxRetries=2 / retryBackoffMs=500`,正式 AppData、源配置和 Runner endpoint 均未修改;86 个 Provider lifecycle 全部完成,本轮未触发重试。首批 v2 batch 固化 3 个 action,包含 2 个指定 static delegate 与 1 个三 child isolated spawnwaiting-confirmation 零副作用边界、pidfd 强杀恢复、batch/contract/action identity、两类 Provider 重叠、1 次 repair、3 个 delivery、1 个 isolated group/3 个 instance/3 个 result/1 个 claimed join、宿主验证和唯一 Supervisor assistant 全部通过。
成功报告共记录 178 个 task snapshot、326 个 event、556 个 Agent DB record、30 个 action execution 和 38 个 receipt;重复 delivery/group/instance/result/join/claim/message/action/receipt/Provider lifecycle 与 pending/batch/finalization/confirmation sidecar 均为 0,私密正文、Provider payload、API Key、项目路径、正式配置路径和最终报告泄漏均为 0。`turn.report=settled` 且 reconciliation Agent 为 0。49 次 native tool plan 中发生 30 次格式修复,未破坏动作幂等与最终结果,但说明真实链路仍有明显延迟和 Provider 调用成本,后续应单独收敛工具合同表达和 repair 频率。
@@ -1268,6 +1270,82 @@ V1.35 已证明模型可以先建立一个 isolated group,再在首次 join cl
真实 Provider 第一次独立运行因模型给出的初始 child scope 不满足 expected artifact 最小边界而 **FAIL**;验收确认 child、claim 和 project mutation 均为 `0`,现场自动清理,该失败证据不得与后续运行拼接。补强通用 scope 边界提示后的第二次独立运行 **PASS**:报告记录 policy=`2`、2 个 group / 3 个 child、1 个 `observed` join claim 覆盖 2 个 groupRunner 强杀恢复前后身份稳定,Provider lifecycle `64/64` completed、failed=`0`,重复、残留与泄漏均为 `0`,且只产生唯一最终回复。
## V1.38 父 run 协作策略持久快照、绑定记录与漂移隔离
V1.38 把 collaboration policy 的执行语义从“每次动作或恢复都读取项目级 live policy”改为“首个有效 durable collaboration batch 为父 run 固化策略”,并用独立 binding sidecar 持久证明“该 run 曾绑定”。本节覆盖 V1.32 的 live drift reconciliation 旧口径,不改变 V1.34 的 isolated child 能力边界、V1.35/V1.36 的 claim/observation 完整性或 V1.37 的首次认领数量门禁。
### 线性化点与写入顺序
- 同一 `project-supervisor` 父 run 的**首个非 `aborted` durable collaboration batch** 是策略线性化点。这里的 collaboration batch 必须是携带完整 v2 `collaborationContract` 的 Provider action batch;尚无 binding 时,只有 `aborted` 事实的批次不绑定快照,也不阻止后续合法批次选择届时有效的项目策略。
- 固定提交顺序为:完整预检并构造 v2 batch/contract -> 先持久化 `status != aborted / nextActionIndex=0` 的 batch -> 在父 run 快照锁内 CAS 写入并验真 collaboration policy snapshot -> CAS 写入并验真独立 binding sidecar -> 两者一致后才允许任何成员进入会产生副作用的 dispatch。delivery、isolated group/child、新 join/receipt claim、项目 mutation、MCP 调用和 finalization 都不得出现在完整绑定之前;没有既存 binding 的首次 `aborted` batch 不创建 snapshot 或 binding。
- batch 已落盘而 snapshot 尚未落盘、以及 snapshot 已落盘而 binding 尚未落盘,都是受支持且保持零 action 副作用的崩溃窗口。前者只能从已完整验真的 v2 contract 补绑,后者只能从有效 snapshot 补写完全一致的 binding;恢复完成后才从原 batch cursor 继续。
- snapshot 一旦存在就是该父 run 的不可变策略事实源;binding 是独立的“曾绑定”持久记录和防降级屏障,不承载 policy 本体。后续 static/isolated spawn、repair、新 claim、Supervisor mutation/MCP 门禁和 finalization 只能使用有效 snapshot,不能在同一 run 重新选择或升级 policy,也不能通过删除 snapshot 把已绑定 run 伪装成未绑定 run。
### Snapshot schema 与指纹
快照固定写入 `.agent/runtime/collaboration-policy-snapshots/<agentKey>/<runKey>.json`。snapshot v1 固定且完整的字段集合为 `schemaVersion / projectId / parentAgentId / parentRunId / boundFrom / policy / policyFingerprint / snapshotFingerprint / boundAt`,不得缺少字段或接受未知字段:
```json
{
"schemaVersion": "game-creator-supervisor-collaboration-policy-snapshot.v1",
"projectId": "project identity",
"parentAgentId": "project-supervisor",
"parentRunId": "parent run identity",
"boundFrom": "initial-collaboration-batch",
"policy": {},
"policyFingerprint": "sha256",
"snapshotFingerprint": "sha256",
"boundAt": 0
}
```
- `policy` 必须先经过 V1.32/V1.37 的完整规范化和上限校验;`policyFingerprint` 只对规范化 policy 的稳定序列化计算 SHA-256。
- `snapshotFingerprint` 对稳定身份字段计算 SHA-256,必须绑定 `schemaVersion / projectId / parentAgentId / parentRunId / boundFrom / policy / policyFingerprint`,不包含审计时间 `boundAt`。读取时逐字段复核当前项目身份和调用方 parent 身份,任何未知 schema、未规范 policy、非法指纹、跨项目或跨 run 内容都失败关闭。
- `boundFrom=initial-collaboration-batch` 用于正常线性化,`boundFrom=legacy-provider-batch-contract` 用于从可信 v2 contract 恢复。`boundFrom=legacy-current-project-policy` 仅允许没有 snapshot/binding、没有可信 v2 contract,且不存在 contractless/v1 collaboration batch,并由 durable run 身份与状态明确证明仍处于 `pending / running / waiting-for-confirmation / waiting-for-user-input` 的旧父 runterminal、`needs-reconciliation` 或身份/状态未知 run 的状态读取不得新建 snapshot。既有 snapshot 重读保持首次 `boundFrom / boundAt / snapshotFingerprint`,不得按本次调用重写。
### Binding sidecar 与安全路径键
独立 binding 固定写入 `.agent/runtime/collaboration-policy-snapshot-bindings/<agentKey>/<runKey>.json`,只保存不可变绑定身份,不复制 policy:
```json
{
"schemaVersion": "game-creator-supervisor-collaboration-policy-snapshot-binding.v1",
"projectId": "project identity",
"parentAgentId": "project-supervisor",
"parentRunId": "parent run identity",
"boundFrom": "initial-collaboration-batch",
"policyFingerprint": "sha256",
"snapshotFingerprint": "sha256",
"boundAt": 0
}
```
- binding 与 snapshot 必须在同一父 run 锁内逐字段交叉验证 `projectId / parentAgentId / parentRunId / boundFrom / policyFingerprint / snapshotFingerprint / boundAt`。已有有效 snapshot 但没有 binding 时可从 snapshot 幂等补写;binding 一旦存在不得删除、替换或按 live policy 重建。
- `agentKey / runKey` 不能只做可能碰撞的 lossy 字符替换。原始 ID 本身满足安全路径组件规则时可原样使用;否则使用有界可读安全前缀加原始完整 ID 的稳定 SHA-256,例如 `<safe-prefix>--<sha256(raw-id)>`,保证 `a/b``a\\b``a_b` 等不安全 run ID 不会落到同一路径。snapshot 与 binding 必须使用同一映射。
- 锁 key 不复用规范化后的路径片段,固定对完整 `parentAgentId + NUL + parentRunId` 计算稳定 SHA-256。路径 key 或锁 key 的计算不能读取 live policy,也不能因进程重启、平台路径分隔符或 locale 改变。
### 锁内 CAS 与恢复优先级
- 创建或读取 snapshot/binding 必须在按完整 `projectId + parentAgentId + parentRunId` 身份隔离的同一锁内完成 CAS。锁内先重读两份 durable 记录:两者一致时返回首次记录并视为幂等;policy、项目、父 run 或任一绑定字段冲突都失败关闭,通用原子 replace 不能覆盖已经绑定的 snapshot/binding。
- 恢复优先级固定为:**existing valid snapshot > 完整验真的 v2 batch contract > 符合严格状态门禁的 legacy 当前有效 project policy**。最后一层不是一般回退,只允许无 snapshot/binding 且身份可信、状态明确属于 `pending / running / waiting-for-confirmation / waiting-for-user-input` 的旧父 runexisting snapshot 与 v2 contract 不一致时是同一父 run 的 durable 身份冲突,进入 reconciliation,不得以 live policy 裁决哪份更新。
- 从 v2 contract 迁移前必须先完成不依赖 snapshot 的两阶段校验:batch v2 schema、project/Agent/run 身份、action 成员与顺序、batchId、contract schema、规范 policy、policy fingerprint 和 contract fingerprint 全部成立。不得先把未验 contract.policy 写成 snapshot,再让后续 batch 校验失败。
- snapshot 存在而 binding 缺失时,从有效 snapshot 补写 bindingbinding 存在而 snapshot 缺失时,只能用完整验真的 v2 contract 按 binding 中的首次身份恢复 snapshot。matching binding 已存在时,完整验真的 `aborted` v2 contract 也可用于重建原 snapshot,因为这是恢复既有绑定而不是建立新绑定。binding 与恢复结果不一致、binding 损坏,或者 binding 已证明曾绑定而 snapshot 丢失且没有可信 v2 contract 时,都保持零新副作用并失败关闭,禁止按 live policy 重绑。
- durable collaboration batch 的 contractless/v1 形态必须先失败关闭并进入 `needs-reconciliation`,不能跳过旧 batch、读取 live policy 后把 run 当成 fresh run;不含协作动作的 contractless/v1 batch 不受该门禁误伤。没有 snapshot/binding、没有上述旧协作 batch且没有可信 v2 contract 时,只有 durable task/run ledger 能同时证明父 run 身份和 `pending / running / waiting-for-confirmation / waiting-for-user-input` 状态,才可用 `legacy-current-project-policy` 迁移;terminal、`needs-reconciliation` 或身份/状态未知只允许无副作用读取状态。尚无任何 durable run/collaboration 事实的真正新父 run可在首批 planning/preflight 读取当前 project policy,并把它固化进首个 v2 contract,但这不创建 legacy snapshot。
### 绑定后的统一读取与漂移语义
- 绑定后,planning prompt、batch preflight/validation、后续 spawn、`agent.run_status` 新 claim、Supervisor mutation、破坏性 MCP 判断、completion blocker、finalization 创建与 finalization 恢复都必须通过同一个 effective-snapshot resolver 取得有效 snapshot,并核对 matching binding。项目级 `.agent/collaboration-policy.json` 不再参与这些执行裁决。
- global project policy 相对 snapshot 的状态只允许报告 `matched / drifted / unreadable`:当前有效 policy 与 snapshot 相同为 `matched`,有效但不同为 `drifted`,读取或解析失败为 `unreadable`。三者只进入有界状态与诊断元数据;planning prompt 继续渲染 snapshot 中的规范 policy 本体,不改变现有 JSON 外形。漂移状态不能改变后续 spawn、claim、mutation、MCP、finalization 或 batch replay,也不能制造 project revision、verification 或 reconciliation。
- policy 更新只供后续新父 run 在其首个非 aborted collaboration batch 选择;已有 snapshot 的 run 不原地重绑。需要应用新策略时必须创建新父 run,不能通过删除 snapshot、重写 batch 或 status 查询迁移活跃 run。
- durable claim 恢复优先于 effective snapshot 解析和新 claim 门禁。已有 `Prepared / Committed / Observed` static 或 isolated claim、尚未观察 claim 及 legacy claimed delivery,允许先按原 action/group 身份幂等重放或补齐;global policy 漂移、snapshot 缺失或 binding 故障都不能把已经提交的 claim 卡成第二次认领。恢复路径不得取得新的 delivery。只有创建新 claim 时,才必须先成功解析 effective snapshot 并核对 binding;解析失败必须发生在新 claim journal、delivery 锁与 delivery mutation 之前,成功后再执行 V1.35-V1.37 的全锁、预算、完整观察和 group 数量门禁。
### 本轮验证状态
- 2026-07-19 确定性门禁已完成:E2E self-test **PASS**,同时覆盖 modern `provider_action_batch.confirmation_required` 与 legacy `tool_confirmation_required`、requirement/approval/receipt 唯一性、`confirmation` execution mode、目标 Session/run 和严格持久化顺序;snapshot/binding 的终态预期改为由已验真的非 `aborted` v2 collaboration batch 决定,不再用 mixed suite 拓扑代替 durable 事实。`supervisor_collaboration_` 52/52、`provider_action_batch_` 12/12、`project_supervisor_mixed_` 5/5 通过;Tauri/Rust 全量为 949 passed、4 个环境依赖用例按设计 ignored,`check:rustfmt` 通过。
- 确定性覆盖包含 snapshot 的 9 个完整字段、首次 `aborted` 零绑定与 matching binding 的 `aborted` v2 恢复、batch -> snapshot -> binding 双故障窗口、CAS 冲突、篡改 contract、binding/snapshot 丢失组合、contractless/v1 协作批次失败关闭与非协作批次兼容、legacy 非终态迁移与终态拒绝、危险 Agent/run ID 路径及锁隔离、global policy 漂移、已有 claim 恢复与新 claim 失败关闭。新增 `supervisor_collaboration_policy_snapshot_survives_terminal_runtime_cleanup` 证明终态只删除 pending/provider batch/confirmation 等临时 sidecarsnapshot/binding 字节保持不变且 resolver 继续返回 `run-snapshot`
- 真实 `supervisor-swarm-static-isolated-autonomous-chat` 曾在单次独立运行中完整形成 2 个 isolated group / 3 个 child、1 个 observed join claim 覆盖两组、唯一 repair、宿主验证、Runner pidfd 强杀恢复和唯一 Supervisor assistantsnapshot/binding 均为唯一、字节及字段稳定,global policy drift 被观察,重复、临时 sidecar、正文、API Key、项目路径和配置路径泄漏均为 0。但该轮运行期间正式客户端在测试外部重启了正式 Runnersource endpoint 所有权门禁按设计失败,因此该功能样本不能记为 PASS。
- 随后使用权限为 `0700/0600`、不含 endpoint/锁/会话的私有配置源副本隔离正式客户端干扰,source endpoint、源目录和清理门禁均稳定;五次最小 OpenAI-chat 探针全部 HTTP 200。然而多次独立完整运行仍在长链路耗尽 transient Provider retry。最后一轮隔离 overlay 已提高到 `requestTimeoutMs=300000 / maxRetries=3 / retryBackoffMs=500`,仍在首个业务批次前形成 4 个 failed lifecycle / 3 个 retry 后终止,child、delivery、claim 和项目 mutation 均为 0,现场清理与泄漏门禁通过。失败轮不得与前述功能完整轮拼接;截至当前,**V1.38 独立真实 Provider E2E 仍未 PASS**,需在外部 Provider 稳定后以最终代码重新独立运行。
## 验收命令
- `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml structured_plan_ -- --nocapture`
@@ -601,3 +601,7 @@ game-project/
- 2026-07-18 V1.35 后续真实 Provider E2E **PASS**:同一父 Session/run 先创建含 2 个 child 的初始 isolated all-join group;首次 `parent-wake` 后、任何 join claim 前,再创建含 1 个 child 的 follow-up group。两组的精确 `writeScopes` 集合互不重叠,最终由同一个状态为 `observed` 的 join claim journal 同时覆盖两个 groupRunner 强杀/恢复前后身份稳定。Provider lifecycle `53/53` 全部 completed、failed 为 `0`;重复、泄漏与残留均为 `0`。V1.35 真实 Provider 门禁据此关闭;V1.36 的 static + isolated 混合 observation 完整性仍按独立门禁验收。
- 2026-07-18 起,同一 Runtime 文档的“V1.36 混合协作 observation 完整性”补齐 `readyDelegateReceipts``readyIsolatedJoins` 同轮返回边界。静态回执完整 JSON 单批最多 6000 字符;isolated-only all-join 保持 10000 字符,和静态回执混合时降为 6000 字符;普通 Runtime 状态与 claimed 摘要最多占 3500 字符。完整 `agent.run_status` detail 仍以 16000 字符为硬上限,超过上限必须失败关闭,禁止先认领后静默截断证据。
- V1.36 的静态回执先完整保留已绑定当前 action 的 recovery receipts,再按 `delegationId` 为新 ready delivery 选择稳定前缀;只预取本批 delivery 锁,并在锁内重读核对快照,超预算或未选中的后续 delivery 保持 `Ready`,其锁竞争也不能阻断必选恢复。必选集合本身无法完整放入时在写 claim sidecar 和改 delivery 前失败。pending observation 从 `readyDelegateReceipts` 解析唯一 delegationId 集合,只有与 durable claim receipts 精确相等且前置区块唯一、`ready=true` 时才允许 `Committed -> Observed`;缺失、额外、重复或无效 ID 均继续阻断 finalization。mixed 路径仍保留 isolated 先认领、static 后续失败可由下一 action 完整重放 isolated claim 的 V1.35 恢复顺序。定向回归为 `project_supervisor` 46/46、mixed 5/5、`isolated` 37/37、`supervisor_collaboration` 27/27、`provider_action_batch` 12/12Tauri/Rust 全量为 923 passed、4 个环境依赖用例按设计 ignored。本切片未重跑真实 Provider,不把既有 PASS 扩大解释为 V1.36 已重新外部验收。
- 2026-07-18 起,同一 Runtime 文档的“V1.38 父 run 协作策略持久快照、绑定记录与漂移隔离”覆盖 V1.32 的 live drift reconciliation 旧口径。首个非 `aborted` durable collaboration batch 是线性化点:v2 batch 必须先落盘,随后在任何 action 副作用前,以同一父 run 锁内 CAS 依次绑定 `.agent/runtime/collaboration-policy-snapshots/<agentKey>/<runKey>.json` 和独立 `.agent/runtime/collaboration-policy-snapshot-bindings/<agentKey>/<runKey>.json`;没有既存 binding 的首次 `aborted` batch 两者都不创建,matching binding 已存在时则可用完整验真的 `aborted` v2 contract 恢复缺失 snapshot。binding 是“该 run 曾绑定”的持久记录,不能通过删除 snapshot 把 run 降级为未绑定。
- snapshot v1 固定且完整包含 `schemaVersion / projectId / parentAgentId / parentRunId / boundFrom / policy / policyFingerprint / snapshotFingerprint / boundAt`snapshot fingerprint 绑定除 `snapshotFingerprint / boundAt` 外的全部稳定字段。binding v1 固定包含 `schemaVersion / projectId / parentAgentId / parentRunId / boundFrom / policyFingerprint / snapshotFingerprint / boundAt`,必须与 snapshot 逐字段一致。安全 ID 可原样作 key;不安全 Agent/run ID 必须使用有界安全前缀加原始 ID 稳定 SHA-256,锁 key 对完整父 Agent/run 身份计算稳定指纹,禁止 lossy 规范化碰撞。
- 恢复优先级为 existing valid snapshot > 完整验真的 v2 batch contract > 符合严格状态门禁的 legacy 当前有效 policy。snapshot 存在但 binding 缺失时可从 snapshot 补写;binding 存在但 snapshot 丢失时只允许可信 v2 contract 按首次身份恢复,没有可信 v2 contract 时禁止按 live policy 重绑。contractless/v1 collaboration batch 必须先失败关闭,不能伪装 fresh run。`legacy-current-project-policy` 仅允许无 snapshot/binding、无可信 v2 contract,且不存在上述旧 batch,并由 durable 身份和状态明确证明属于 `pending / running / waiting-for-confirmation / waiting-for-user-input` 的旧父 runterminal、`needs-reconciliation` 或身份/状态未知 run 的状态读取不得新建 snapshot。
- 已有 durable/未观察 claim 与 legacy claimed delivery 继续按原 action/group 身份恢复,不要求先创建新绑定;新 claim 必须先成功解析 effective snapshot 并核对 binding,再进入 V1.35-V1.37 的全锁、预算、完整 observation 和 group 数量门禁。snapshot 绑定后 global policy 的 `matched / drifted / unreadable` 只进入有界 status/诊断,不能改变后续执行;新 policy 只由后续新父 run 采用。2026-07-19 self-test、52/52 collaboration 定向回归和 949 passed/4 ignored Rust 全量已完成,终态快照保留也有独立回归;真实 mixed-swarm 功能样本已闭合但受正式 endpoint 外部重启污染,私有配置源的后续独立运行又连续耗尽 transient Provider retry,不能拼接证据,当前仍**不得声称 V1.38 真实 E2E 已 PASS**。详细报告以 Runtime 技术方案 V1.38 节为准。