完善 Agent Runtime 成功交接与终态恢复
新增 Provider 成功响应 handoff,绑定真实 requestId 并支持原子恢复 补齐 compaction 与 finalization v4 的 stream committed 提交事务 让 Runner 在 handoff 存在时保持 busy,并覆盖冲突与崩溃窗口 同步 V1.41 技术方案、实施计划和项目共享记忆
This commit is contained in:
@@ -1391,16 +1391,64 @@ V1.40 把后台 `final-reply` 请求及其前置自动 context-compaction 分别
|
||||
### 验收边界
|
||||
|
||||
- 确定性测试必须分别停在“final-reply 或其前置压缩 retry sidecar 已提交、task/state 尚未投影”的窗口,确认恢复前 phase 仍为 response、重启扫描补齐 waiting 状态且网络接收时间不早于 `retryAt`;到期后必须直接恢复原 `-transient-1` final-reply,或先恢复原 `final-reply-context-compaction` 再继续 final-reply,不得多一次 tool-plan。失败与恢复两次 HTTP body 必须逐字节相同,只在失败信息中公开 SHA-256、字节数和首个差异位置,不公开正文。
|
||||
- 终态必须只有 1 条 completed task、1 条 conversation assistant 和 1 个 committed response stream;final-reply lifecycle 固定为初始 `started -> failed` 加恢复 `started -> completed`,retry audit/waiting 各 1,provider retry sidecar 为 0。前置压缩恢复还必须形成 `final-reply-context-compaction` 的两组唯一 lifecycle,并且不增加 tool-plan。当前确定性门禁为 `provider_retry_` 21/21、`provider_transient_retry_` 5/5、`response_stream_` 16/16,Tauri/Rust 串行全量 `969 passed / 4 ignored`。
|
||||
- 终态必须只有 1 条 completed task、1 条 conversation assistant 和 1 个 committed response stream;final-reply lifecycle 固定为初始 `started -> failed` 加恢复 `started -> completed`,retry audit/waiting 各 1,provider retry sidecar 为 0。前置压缩恢复还必须形成 `final-reply-context-compaction` 的两组唯一 lifecycle,并且不增加 tool-plan。最终实现的当前验证计数统一见 V1.41,不再沿用本切片较早的局部计数。
|
||||
- V1.40 尚未以真实外部 Provider 完成 final-reply 退避期 Runner 强杀门禁,因此不得把 V1.39 首次 tool-plan 的真实 PASS 外推为 V1.40 PASS。后续独立 suite 必须在 Supervisor 已完整认领专业回执、完成 repair 和宿主验证后,对首次 final-reply 注入唯一瞬态故障;退避期强杀后证明原父 Session/run、request fingerprint、retryAt 和 slot 稳定,且 delivery/claim/receipt/finalization/assistant 无重复、终局零 sidecar/泄漏。手动压缩与 tool-plan `repair-N` 仍不在本切片内。
|
||||
- Provider 成功返回到消费端 durable commit 之间仍有独立崩溃窗口:`final-reply-context-compaction` 的成功响应在压缩 sidecar 落盘前、final-reply 的成功响应在 finalization journal `prepared` 前都可能因 Runner 退出而丢失并重新请求 Provider。当前 response stream ready 不是 durable 结果提交协议;finalization journal 清理后到 stream 标记 committed 之间退出或写失败,还可能留下已持久化唯一 assistant、但展示 stream 仍为 ready 的终态差异。V1.40 只保证瞬态失败后的请求恢复与最终 assistant 幂等,不能把它扩大为成功请求 exactly-once 或 stream 终态事务;后续须以可重放成功响应 journal 和可恢复 stream commit 或等价提交协议单独解决。
|
||||
|
||||
## V1.41 Provider 成功响应持久交接与回复流终态恢复
|
||||
|
||||
V1.41 为 V1.40 明确留下的成功响应交接窗口增加 `.agent/runtime/provider-handoffs/<agentKey>/<runKey>.json` 私有 sidecar,schema 固定为 `game-creator-provider-handoff.v1`。每个 Agent/run 最多一条记录,绑定完整 Provider retry identity、**实际已注册的物理 `providerRequestId`**、request slot/attempt、Provider/model、脱敏文本响应、finish reason、response ID、usage、响应指纹和创建时间。schema 拒绝未知字段,记录受字段长度、安全路径和 512 KiB 硬上限约束;写入使用 `0600` 临时文件、`sync_data`、原子替换、父目录同步和跨平台 `.previous` 恢复副本,写后必须回读并与内存记录完全一致。
|
||||
|
||||
本轮实际写入 handoff 的是 tool-plan 前置自动 `context-compaction`、final-reply 前置自动 `final-reply-context-compaction` 和 `final-reply` 三种无工具调用文本请求。手动压缩仍走非持久重试路径,不会写 handoff;tool-plan 本身的成功响应和 function arguments 也不在本轮。响应落盘前会删除 thinking block,并依次过滤密钥、项目路径和其它绝对路径;sidecar 不保存 prompt、请求消息、API Key、Provider URL、tool call/arguments 或错误正文,也不复制到公共 Runtime、event、Agent DB、CLI 或报告。
|
||||
|
||||
### Durable handoff 提交与 handoff-first 恢复
|
||||
|
||||
- 支持 handoff 的物理 Provider 请求返回成功后,Runtime 先用该次 lifecycle 实际解析出的 `requestId` 原子写入并回读 handoff,然后才允许同一 request identity 追加 `completed` 终态。handoff 写入、回读或内容校验失败时,原 lifecycle 保持只有 `started`,Runtime 进入 `needs-reconciliation`,不得自动补发该成功请求。
|
||||
- 每次可持久化请求在读 retry sidecar 或发起网络请求之前必须先读 handoff。完整 identity 相同时,Runtime 核对可选 retry sidecar 的 identity/attempt/slot,并把原实际 `providerRequestId` 的 `started` lifecycle 幂等补齐为 `completed`;随后删除匹配 retry sidecar 并回放响应,不注册新 `started`、不生成新 request identity,也不调用 Provider。
|
||||
- handoff 与 retry sidecar 同时存在时,后者必须精确指向 handoff 的 identity/attempt/slot;任一不符都进入 `needs-reconciliation`,不允许以任意一方覆盖另一方。同一 durable run 内若 Goal/steer/revision/request/config 等稳定 identity 已漂移,Runtime 先用旧 handoff 的实际 `requestId` 闭合旧 lifecycle,公共审计只记录漂移字段名,再删除旧 handoff/retry 并让同一 run 重新规划;跨 durable run 身份冲突则直接失败关闭。
|
||||
- cancel、生效 steer、非 pause Goal 控制、失败、budget-exhausted 和其它终态清理 handoff/retry;Goal pause 保留原交接记录,resume 后仍消费同一响应。`provider-handoffs` 中任何 primary、`.previous` 或损坏普通文件都是 Runner busy 事实,必须阻止 `runner.shutdown_if_idle`,直到恢复消费、明确作废或人工核对后清理。
|
||||
|
||||
### Compaction 消费与 finalization 固定流身份
|
||||
|
||||
- 自动 context-compaction 消费 handoff 后,先原子写入规范 `game-creator-runtime-context-compaction.v1` sidecar,再回读确认整条记录与本次结果完全一致,最后才删除匹配 handoff。若上次已完成 compaction 但在清理 handoff 前退出,恢复时以 source fingerprint、request kind 和 base slot 识别“没有新 source”,复用已提交 compaction 并幂等删除 handoff,不重新请求 Provider。compaction 回读或 handoff 清理失败保持 reconciliation。
|
||||
- 流式 final-reply 在 Provider 成功时调用 publisher `handoff`:持久化最新可见快照并停止 Drop 把它改成 discarded,但不提前写 ready。只有 handoff 完成且经规范脱敏响应回放后,才将同一 stream 身份写为 ready;中途 streaming 半句不是可提交 assistant。
|
||||
- finalization journal 正式升级为 `game-creator-runtime-finalization.v4`,在 `prepared` 时固定绑定 `responseRequestSlot`,并把该 slot 与 Agent/Session/run、response fingerprint/revision、steer cursor、计划和 Goal 快照一起纳入 `finalizationId` 指纹;篡改 slot 必须直接造成幂等身份不匹配。v3 journal 缺少 `responseRequestSlot` 仍可读,按 v1-v3 旧指纹规则校验,并在 stream commit 时由已绑定的 Runtime/finalization revision 与 steer 身份派生 legacy slot,不能反向伪装成 v4。`project.rs` 的 Agent DB `agent.runtime.finalization.lifecycle` 白名单必须兼容 `journalSchemaVersion` v1-v4,保证升级前四阶段 lifecycle 仍可扫描和幂等核对;lifecycle audit schema 本身继续为 v1。final-reply handoff 在 `prepared` 后仍保留,直到整个 finalization 和 stream commit 均完成后才与 journal 一起清理。
|
||||
- assistant、Runtime completed 和 Goal completed 幂等提交后,finalization 必须用 journal 固定身份读取回复流。流缺失,或仍为 `streaming / failed / discarded` 等非 ready 状态时,Runtime 以 journal 中的 canonical response 重建同身份 ready stream;已有流身份冲突则失败关闭。ready 正文必须与 journal 完全一致才能推进为 committed;已 committed 且正文一致是幂等成功,正文冲突则停止恢复。
|
||||
- committed 写入后必须立即回读,再次核对固定身份、`status=committed` 和完整正文;只有回读通过后才删除 finalization journal 与所属 handoff。stream 写入/回读失败,或在 `ResponseStreamCommitted` checkpoint 后、清理前退出,都保留 journal 和可恢复 `finalizing` 状态;Runner 重放同一 finalization,只补 stream commit 或幂等清理,不重写 assistant、不调用 Provider。
|
||||
|
||||
### Exactly-once 承诺边界
|
||||
|
||||
- V1.41 **只保证 handoff 已可靠落盘之后的 exactly-once 消费**:同一响应只能由匹配的 compaction 或 finalization 身份消费,恢复只回放该 handoff,不再请求 Provider,终局只有一条 canonical assistant/completed/committed stream。
|
||||
- **外部 Provider 已返回完整响应、但 Runtime 尚未写入 handoff 的硬杀窗口仍然不能承诺 Provider 调用 exactly-once**。该窗口没有可重放成功响应,只能把未闭合 `started` 视为未知并进入 reconciliation;后续人工处理可能需要重新请求 Provider,因此不得把消费幂等扩大为端到端 Provider 物理调用幂等。
|
||||
- tool-plan 成功响应/function arguments 的 durable handoff 另行设计;真实外部 Provider 的 final-reply 退避期 Runner 强杀也仍需独立 E2E,不得借用 V1.39 首次 tool-plan PASS 或本轮确定性 mock 门禁宣称已通过。
|
||||
|
||||
### 确定性测试矩阵
|
||||
|
||||
| 范围 | 故障/恢复窗口 | 必须断言 | 定向用例 |
|
||||
| --- | --- | --- | --- |
|
||||
| sidecar 契约 | 首次写入、同内容重写、`.previous`、损坏/超限/路径或内容冲突 | 严格 schema、`0600` 原子交接、实际 requestId 稳定、tool call 拒绝、thinking/密钥/路径零落盘 | `provider_handoff_*` 单元用例 |
|
||||
| final-reply handoff-first | Provider 成功交接后、lifecycle 终态前停止 | 恢复零新网络请求,原 requestId `started -> completed`,唯一 assistant/completed/committed stream,终局零 retry/handoff/finalization | `provider_handoff_final_reply_restart_replays_success_without_network_request` |
|
||||
| compaction consumer | 前置压缩 handoff 已落盘、compaction sidecar 未提交,以及 sidecar 已提交但 handoff 未清理 | 压缩结果回读后清理,不重放 tool-plan/压缩,只发后续 final-reply | `provider_handoff_final_reply_compaction_restart_only_requests_final_reply` |
|
||||
| identity/retry 冲突 | Goal/steer/revision/request/config 漂移,或 retry identity/attempt/slot 与 handoff 不一致 | 漂移先闭合旧 lifecycle 再作废,审计零响应正文;retry 冲突进入 reconciliation 且零 Provider 请求 | `provider_handoff_identity_drift_closes_lifecycle_without_leaking_response` 及 `provider_handoff_` 冲突门禁 |
|
||||
| finalization schema/身份 | v4 slot 被篡改,或读取缺少 slot 的 v3 journal 与 v1-v4 lifecycle | v4 `responseRequestSlot` 进入 `finalizationId` 指纹;v3 按旧身份可读;Agent DB lifecycle 白名单接受 v1-v4 journal schema | `finalization_v4_binds_response_request_slot_into_identity`、`finalization_v3_without_response_request_slot_remains_readable`、`finalization_lifecycle_accepts_supported_v1_through_v4_journals` |
|
||||
| finalization 四 checkpoint | `Prepared / AssistantAppended / RuntimeCompleted / ResponseStreamCommitted` 任一点中断 | 原 messageId/finalizationId 幂等恢复,无 Provider/工具重放,最终 journal/handoff 清理 | `finalization_*` 与 `response_stream_committed_checkpoint_recovers_by_idempotent_cleanup` |
|
||||
| 回复流重建 | stream 缺失、停在 streaming、commit 写失败、已 committed 后清理中断 | 按 v4 固定 slot 从 journal 重建 ready,committed 写后回读,正文唯一,冲突失败关闭 | `response_stream_finalization_recovers_missing_stream_after_project_revision_drift`、`response_stream_finalization_repairs_streaming_after_commit_write_failure`、`response_stream_committed_checkpoint_recovers_by_idempotent_cleanup` |
|
||||
| Runner busy | primary、`.previous` 或损坏 handoff 存在时请求 idle shutdown | `idle=false`,不进入 draining;清理后才可 shutdown | `durable_provider_handoff_prevents_shutdown_even_when_corrupt` |
|
||||
|
||||
2026-07-20 当前最终实现的最新验证证据为:`provider_retry_` 21/21、`response_stream_` 23/23、`finalization_resume_` 12/12;Tauri/Rust 串行全量共 989 tests,`985 passed / 4 ignored / 0 failed`。这些数字替代 V1.40 较早快照,后续当前结果统一使用本行口径。
|
||||
|
||||
上表是 V1.41 的确定性门禁,不代表真实外部 Provider E2E 结论。除表内窗口外,还必须继续扫描 task/event/Agent DB/CLI/report,确认 Provider 响应、compaction summary、API Key、Provider URL 和项目/配置绝对路径公共泄漏均为 0。
|
||||
|
||||
## 验收命令
|
||||
|
||||
- `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml structured_plan_ -- --nocapture`
|
||||
- `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml agent_goal_ -- --nocapture`
|
||||
- `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml goal_context_bundle_v4_migrates_v3_and_v2_then_rejects_plan_mismatch -- --nocapture`
|
||||
- `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml provider_handoff_ -- --nocapture --test-threads=1`
|
||||
- `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml response_stream_ -- --nocapture`
|
||||
- `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml finalization_v4_binds_response_request_slot_into_identity -- --nocapture`
|
||||
- `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml finalization_v3_without_response_request_slot_remains_readable -- --nocapture`
|
||||
- `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml finalization_lifecycle_accepts_supported_v1_through_v4_journals -- --nocapture`
|
||||
- `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml mcp_ -- --nocapture`
|
||||
- `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml provider_action_batch_ -- --nocapture`
|
||||
- `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml provider_retry_ -- --nocapture --test-threads=1`
|
||||
|
||||
@@ -607,3 +607,7 @@ game-project/
|
||||
- 已有 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 节为准。
|
||||
- 2026-07-19 起,同一 Runtime 文档的“V1.39 首次规划 Provider 瞬态重试持久等待态”作为后台首次规划重试的恢复事实源。tool-plan `repair-0` 及其自动 context-compaction 在瞬态失败后先写 per-Agent/run retry sidecar,再投影 `waiting-for-provider-retry` 并释放 lane;Runner 重启按到期时间恢复同一 Session/run/loop/attempt,同 Agent 后续任务保持 FIFO,其它 Agent 可并行。final-reply、手动压缩和 tool-plan `repair-N` 暂不扩展为持久重试;工具策略刷新时间不得进入请求指纹。最终代码已完成独立真实 `gpt-5.5 / openai_chat / high` PASS:父 Agent 首次规划在 `30s` 退避期执行一次 pidfd Runner 强杀,重启后 sidecar 身份/attempt/retryAt 稳定,到期前零早发且第二个请求网络接收时间不早于 retryAt;随后同一父 run 完成双专业 Agent 真重叠、2+1 delivery、唯一 repair、唯一最终回复和零重复/残留/正文/Key/路径泄漏,`37/37` lifecycle 闭合为 `36 completed + 1 injected failed + 1 retry`,incidental failure/retry 均为 0,隔离现场完整清理。额外真实瞬态失败按 request identity 单独计数且仍须完整恢复,不能混入受控注入链。该证据不改变 V1.38 真实 E2E 尚未 PASS 的独立结论。
|
||||
- 2026-07-19 V1.40 在上述 sidecar 协议上补齐后台 `final-reply` 和其前置自动 context-compaction,分别使用 `requestKind=final-reply / final-reply-context-compaction`。final-reply prompt 改用可由 context bundle 精确恢复的有界收束摘要,不包含会随恢复阶段变化的 Runtime 投影或无法持久重建的临时 tool-plan 字段;response 状态先与 context bundle 同步,再允许发出 Provider 请求。Runner 恢复命中任一 final-reply sidecar 时按原 `nextLoopIndex / baseRequestSlot / requestFingerprint` 跳过新 planning,压缩链先完成原压缩再继续 final reply;流式失败半句不提交,成功后仍只经原 finalization journal 写一条 canonical assistant。稳定身份漂移则删除旧 sidecar并在同一 run 重新规划。确定性门禁已覆盖两种 sidecar-first 投影窗口、恢复扫描、网络接收时间不早于 `retryAt`、失败/恢复 HTTP body 逐字节一致、唯一 lifecycle/assistant/stream 和终局零 sidecar;当前 `provider_retry_` 为 21/21。真实外部 Provider 的 final-reply 退避期 Runner 强杀尚未独立 PASS;成功响应到压缩 sidecar/finalization journal 的交接窗口,以及 journal 清理到 stream committed 的窗口也尚未补齐,不能借用 V1.39 首次 tool-plan 证据或宣称成功请求 exactly-once/stream 终态事务。手动压缩与 tool-plan `repair-N` 仍保持进程内重试。
|
||||
- 2026-07-20 起,同一 Runtime 文档的“V1.41 Provider 成功响应持久交接与回复流终态恢复”覆盖 V1.40 的两个成功窗口。`.agent/runtime/provider-handoffs/<agentKey>/<runKey>.json` 使用严格 `game-creator-provider-handoff.v1`,绑定完整 retry identity、实际物理 `providerRequestId`、slot/attempt 和经 thinking、密钥、项目路径及绝对路径过滤后的规范文本响应;写入采用 `0600` 原子 sidecar、`.previous` 恢复和写后完整回读。当前仅覆盖 tool-plan 前置自动 `context-compaction`、final-reply 前置自动 `final-reply-context-compaction` 与 `final-reply`;手动压缩和 tool-plan 成功响应/function arguments 不写 handoff。
|
||||
- V1.41 对支持范围执行 handoff-first:Provider 成功后先提交并回读 handoff,再闭合同一实际 requestId 的 `completed` lifecycle;恢复先消费 handoff,匹配 retry 时复用原 attempt/slot 且零新网络请求,retry identity/attempt/slot 冲突进入 reconciliation,稳定身份漂移先闭合旧 lifecycle 再作废并同 run 重规划。自动 compaction 必须在规范 sidecar 写入并回读一致后才清理 handoff;final-reply handoff 则保留到 finalization 和 stream commit 全部完成。任一 primary、`.previous` 或损坏 handoff 都让 Runner 保持 busy,阻止 `runner.shutdown_if_idle`。
|
||||
- finalization schema 已正式升为 `game-creator-runtime-finalization.v4`:`responseRequestSlot` 与 response revision/fingerprint、steer cursor、计划及 Goal 快照共同进入 `finalizationId` 指纹,篡改 slot 必须失败关闭;`project.rs` 的 Agent DB finalization lifecycle 白名单兼容 `journalSchemaVersion` v1-v4,缺少 slot 的 v3 journal 继续按旧指纹和 legacy slot 派生规则读取。assistant、Runtime 和 Goal 提交后,缺失或仍为 streaming/failed/discarded 的回复流由 journal 固定身份重建为 ready,再推进 committed;committed 写后必须回读身份、状态和正文,成功后才一起删除 finalization journal/handoff,失败则保留 `finalizing` 供 Runner 幂等恢复。确定性测试矩阵以 Runtime V1.41 章节中的 handoff、compaction、四 checkpoint、stream 重建、schema 兼容和 Runner busy 用例为准。2026-07-20 最新验证为 `provider_handoff_` 11/11、`provider_retry_` 21/21、`response_stream_` 23/23、`finalization_resume_` 12/12;Tauri/Rust 串行全量共 989 tests,`985 passed / 4 ignored / 0 failed`,不再沿用 V1.40 的旧局部计数。
|
||||
- V1.41 的承诺严格限定为 **handoff 已落盘后的 exactly-once 消费**。外部 Provider 已返回完整响应、但 Runtime 尚未写 handoff 的硬杀窗口仍不能承诺 Provider 调用 exactly-once,后续只能进入 reconciliation 并可能需要人工决定是否重发;tool-plan 成功响应仍不在本轮 handoff。真实外部 Provider 的 final-reply 退避期 Runner 强杀继续是独立 E2E,不能用 V1.39 首次 tool-plan PASS 或确定性 mock 结果替代。
|
||||
|
||||
Reference in New Issue
Block a user