完善Agent Provider持久重试与恢复
新增按Agent和run隔离的Provider重试sidecar与原子恢复扫描 接入首次工具规划和自动上下文压缩的持久等待与到期续跑 补齐Goal暂停恢复、取消、steer、FIFO、finalization和Runner idle门禁 移除请求指纹中的刷新时间并覆盖跨秒恢复与物理请求一致性 补充sidecar、Runner、并行调度回归及V1.39技术文档
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -66,6 +66,7 @@ mod preview;
|
||||
mod process_session;
|
||||
mod process_session_bridge;
|
||||
mod project;
|
||||
mod provider_retry;
|
||||
mod repository_context;
|
||||
mod runner;
|
||||
mod swarm_cli;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2096,7 +2096,11 @@ impl ExternalAgentRunnerTargetRunProbe {
|
||||
|
||||
fn still_requires_wake(&self) -> bool {
|
||||
self.status == "pending"
|
||||
|| (self.status == "running" && self.phase == "waiting-for-delegate-receipts")
|
||||
|| (self.status == "running"
|
||||
&& matches!(
|
||||
self.phase.as_str(),
|
||||
"waiting-for-delegate-receipts" | "waiting-for-provider-retry"
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2741,6 +2745,7 @@ fn external_agent_runner_root_is_idle(root: &Path) -> Result<bool, String> {
|
||||
for durable_dir in [
|
||||
root.join(".agent/runtime/pending-actions"),
|
||||
root.join(".agent/runtime/finalizations"),
|
||||
root.join(".agent/runtime/provider-retries"),
|
||||
] {
|
||||
if external_agent_runner_directory_has_durable_files(&durable_dir)? {
|
||||
return Ok(false);
|
||||
@@ -3929,6 +3934,8 @@ mod tests {
|
||||
let agent_id = "project-supervisor";
|
||||
let run_id = "supervisor-parent-run-1";
|
||||
let waiting = probe(agent_id, run_id, "running", "waiting-for-delegate-receipts");
|
||||
let provider_retry_waiting =
|
||||
probe(agent_id, run_id, "running", "waiting-for-provider-retry");
|
||||
|
||||
assert_eq!(
|
||||
classify_external_agent_runner_target_wake(agent_id, run_id, &[], Some(&waiting)),
|
||||
@@ -3960,6 +3967,16 @@ mod tests {
|
||||
Err(ExternalAgentRunnerTargetWakeRetry::StillPending),
|
||||
"observing the target without advancing it remains retryable"
|
||||
);
|
||||
assert_eq!(
|
||||
classify_external_agent_runner_target_wake(
|
||||
agent_id,
|
||||
run_id,
|
||||
std::slice::from_ref(&provider_retry_waiting),
|
||||
Some(&provider_retry_waiting),
|
||||
),
|
||||
Err(ExternalAgentRunnerTargetWakeRetry::StillPending),
|
||||
"a durable Provider retry wait remains retryable until the target advances"
|
||||
);
|
||||
|
||||
let advanced = probe(agent_id, run_id, "running", "planning");
|
||||
assert_eq!(
|
||||
@@ -4450,6 +4467,109 @@ mod tests {
|
||||
assert!(!state.draining.load(Ordering::Acquire));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn durable_provider_retry_prevents_shutdown_and_reopens_writes() {
|
||||
let directory = unique_test_directory();
|
||||
let root = directory.0.join("project");
|
||||
let identity = crate::provider_retry::AgentRuntimeProviderRetryIdentity {
|
||||
project_id: "project-provider-retry-idle".to_string(),
|
||||
agent_id: "code-prototype".to_string(),
|
||||
task_id: "task-provider-retry-idle".to_string(),
|
||||
session_id: "session-provider-retry-idle".to_string(),
|
||||
run_id: "run-provider-retry-idle".to_string(),
|
||||
source: "agent-chat".to_string(),
|
||||
goal_id: None,
|
||||
goal_revision: 0,
|
||||
goal_snapshot_fingerprint: String::new(),
|
||||
applied_steer_cursor: 0,
|
||||
request_kind: "tool-plan".to_string(),
|
||||
base_request_slot: "loop-0-repair-0".to_string(),
|
||||
request_fingerprint: "a".repeat(64),
|
||||
provider_config_fingerprint: "b".repeat(64),
|
||||
web_search_enabled: false,
|
||||
allow_idle_context_compaction: false,
|
||||
};
|
||||
let retry = crate::provider_retry::write_next_at(
|
||||
&root,
|
||||
&identity,
|
||||
"loop-0-repair-0-transient-1",
|
||||
1,
|
||||
3,
|
||||
250,
|
||||
"timeout",
|
||||
&"c".repeat(64),
|
||||
)
|
||||
.expect("write durable Provider retry");
|
||||
|
||||
assert!(!external_agent_runner_root_is_idle(&root).expect("scan primary retry"));
|
||||
let retry_path = root
|
||||
.join(".agent/runtime/provider-retries/code-prototype/run-provider-retry-idle.json");
|
||||
let previous_path = crate::agent::agent_runtime_json_sidecar_backup_path(&retry_path);
|
||||
fs::rename(&retry_path, &previous_path).expect("move Provider retry to previous");
|
||||
assert_eq!(
|
||||
crate::provider_retry::list_at(&root).expect("scan previous Provider retry"),
|
||||
vec![retry]
|
||||
);
|
||||
assert!(!external_agent_runner_root_is_idle(&root).expect("scan previous retry"));
|
||||
|
||||
let token = "provider-retry-shutdown-token-provider-retry-shutdown-token";
|
||||
let state = ExternalAgentRunnerServerState::new(
|
||||
directory.0.join(EXTERNAL_AGENT_RUNNER_ENDPOINT_FILE_NAME),
|
||||
test_endpoint(token, "provider-retry-shutdown-boot", 32324),
|
||||
);
|
||||
state.remember_root(&root);
|
||||
let busy_response = handle_external_agent_runner_request(
|
||||
ExternalAgentRunnerRequest {
|
||||
protocol_version: EXTERNAL_AGENT_RUNNER_PROTOCOL_VERSION,
|
||||
request_id: "shutdown-provider-retry-busy-1".to_string(),
|
||||
token: token.to_string(),
|
||||
method: "runner.shutdown_if_idle".to_string(),
|
||||
params: ExternalAgentRunnerRequestParams::default(),
|
||||
},
|
||||
&state,
|
||||
);
|
||||
|
||||
assert!(busy_response.ok);
|
||||
assert_eq!(
|
||||
busy_response
|
||||
.result
|
||||
.as_ref()
|
||||
.and_then(|value| value["idle"].as_bool()),
|
||||
Some(false)
|
||||
);
|
||||
assert!(!state.shutdown_requested.load(Ordering::Acquire));
|
||||
assert!(!state.draining.load(Ordering::Acquire));
|
||||
|
||||
crate::provider_retry::remove_at(&root, &identity.agent_id, &identity.run_id)
|
||||
.expect("remove durable Provider retry");
|
||||
assert!(crate::provider_retry::list_at(&root)
|
||||
.expect("scan removed Provider retries")
|
||||
.is_empty());
|
||||
assert!(external_agent_runner_root_is_idle(&root).expect("scan idle root"));
|
||||
|
||||
let idle_response = handle_external_agent_runner_request(
|
||||
ExternalAgentRunnerRequest {
|
||||
protocol_version: EXTERNAL_AGENT_RUNNER_PROTOCOL_VERSION,
|
||||
request_id: "shutdown-provider-retry-idle-1".to_string(),
|
||||
token: token.to_string(),
|
||||
method: "runner.shutdown_if_idle".to_string(),
|
||||
params: ExternalAgentRunnerRequestParams::default(),
|
||||
},
|
||||
&state,
|
||||
);
|
||||
|
||||
assert!(idle_response.ok);
|
||||
assert_eq!(
|
||||
idle_response
|
||||
.result
|
||||
.as_ref()
|
||||
.and_then(|value| value["idle"].as_bool()),
|
||||
Some(true)
|
||||
);
|
||||
assert!(state.shutdown_requested.load(Ordering::Acquire));
|
||||
assert!(state.draining.load(Ordering::Acquire));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stale_protocol_endpoint_does_not_override_instance_lock_arbitration() {
|
||||
let directory = unique_test_directory();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4893,3 +4893,11 @@
|
||||
- 漂移: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 身份推进且不得取得新 delivery;global 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**。
|
||||
|
||||
## 2026-07-19 AI 游戏创作 Agent Runtime V1.39 首次规划 Provider 持久重试
|
||||
|
||||
- 决策:tool-plan 首次请求及其自动 context-compaction 的可重试瞬态失败不再依赖 Runner 进程内 sleep。每个 Agent/run 使用唯一严格 v1 retry sidecar,持久绑定项目、Agent、task、Session、run、Goal、steer、请求和 Provider 配置指纹,以及下一个 `-transient-N` attempt 和绝对到期时间;一次只允许一个待重试 attempt。
|
||||
- 提交与恢复:每次物理请求仍先闭合自己的 Provider lifecycle;随后按 retry audit -> 原子 sidecar/.previous -> `running / waiting-for-provider-retry` 投影提交,再释放 lane。Runner 启动扫描 primary/.previous,未到期只重建唤醒,到期后重验 durable control 和完整指纹并恢复同一 Session/run/loop/attempt。sidecar/torn projection 冲突失败关闭,不从 Agent DB 或 UI 状态猜回请求。
|
||||
- 调度:等待态释放执行 lane,允许不同 Agent 并行;同 Agent 当前 running 等待任务继续阻挡后续 pending task,保持 FIFO。活跃 sidecar 阻断 finalization 与 `shutdown_if_idle`,cancel、steer、终态和耗尽负责清理。
|
||||
- 稳定身份:Provider 请求指纹不得包含工具策略 `updatedAt` 等非语义刷新时间。Goal pause 保留 sidecar,resume 恢复原等待态;跨秒恢复必须仍命中相同 request fingerprint 和 `-transient-N` slot。final-reply、手动压缩与 tool-plan `repair-N` 继续使用进程内重试,待具备可无歧义重建的持久请求上下文后再单独升级。
|
||||
- 证据边界:`provider_retry_` 19/19、`provider_transient_retry_` 6/6,Tauri/Rust 串行全量 968 passed/4 ignored,`cargo check`、rustfmt、编码与 diff 检查通过。本轮确定性验证与 V1.38 真实 Provider E2E 分开记账;任何旧失败轮、旧瞬态重试 PASS 或最小探针都不能拼接成 V1.39 真实 PASS。
|
||||
|
||||
@@ -3357,3 +3357,11 @@
|
||||
- 原因:`.admin-info-list div` 会命中列表内所有后代 `div`,把字段值内部的 `.admin-inline-identity` 和昵称容器也覆盖成双列 grid;陶泥号又允许任意位置换行,最终只剩单字符宽度。素材详情布局若始终固定为 `220px + 信息列`,移动端也没有足够空间。
|
||||
- 处理:信息列表的行布局只使用直接子选择器 `.admin-info-list > div`;作者昵称与陶泥号在身份组件内分行,陶泥号保持单行并在真正不足时省略。`560px` 以下的素材详情改为单列,缩略图居中;素材查询与精选审核共用该规则。
|
||||
- 验证:在桌面、560px、390px 和 320px 浏览器宽度打开素材详情,确认 `.admin-inline-identity` 的 computed `display` 为 `flex`、陶泥号横向显示、详情字段不溢出页面。
|
||||
|
||||
## Provider 瞬态重试不能只依赖进程内 sleep,也不能让刷新时间进入请求指纹
|
||||
|
||||
- 现象:Provider 首次规划请求发生 timeout/connectivity/transport 后,Runner 在 backoff 期间退出会丢失 retry,或重启后清零 attempt、提前补发;另一种偶发现象是 Goal pause/resume 看似保留 sidecar,但只要等待跨过一秒,恢复请求就被判为 context drift,旧 `-transient-N` attempt 被删除并改成新 loop 请求。
|
||||
- 原因:退避 attempt 和到期时间只存在于进程内;或虽然已有 sidecar,请求 prompt 却直接序列化 Runtime 工具策略快照,把每次刷新都会变化的 `updatedAt` 带进 request fingerprint。同一权限内容因此仅因时间变化产生不同请求身份。
|
||||
- 处理:先闭合物理请求 lifecycle,再原子持久 retry sidecar/.previous,最后投影 `waiting-for-provider-retry` 并释放 lane;Runner 启动扫描并按绝对到期时间恢复。请求指纹只绑定实际 Provider 请求的稳定语义,UI/审计时间戳、剩余等待毫秒和等待态文案不得进入 prompt。Goal pause 保留 sidecar,resume 必须校验同一 Goal/steer/request/config 身份后恢复原 attempt。
|
||||
- 验证:测试必须故意让 pause/resume 跨秒,捕获失败请求与恢复请求的 HTTP body 并比较 SHA-256,同时断言 lifecycle 使用原 `-transient-N` slot而不是新 loop;另覆盖 `.previous` 扫描、Runner idle blocker、同 Agent FIFO、跨 Agent 并行、cancel/steer/耗尽清理和重启未到期零请求。
|
||||
- 关联:`apps/ai-game-creator-shell/src-tauri/src/provider_retry.rs`、`agent.rs`、`runner.rs`、`tests.rs`、`docs/technical/【技术方案】AI游戏创作Agent Runtime V1.1-2026-07-12.md`。
|
||||
|
||||
@@ -1346,6 +1346,33 @@ V1.38 把 collaboration policy 的执行语义从“每次动作或恢复都读
|
||||
- 真实 `supervisor-swarm-static-isolated-autonomous-chat` 曾在单次独立运行中完整形成 2 个 isolated group / 3 个 child、1 个 observed join claim 覆盖两组、唯一 repair、宿主验证、Runner pidfd 强杀恢复和唯一 Supervisor assistant;snapshot/binding 均为唯一、字节及字段稳定,global policy drift 被观察,重复、临时 sidecar、正文、API Key、项目路径和配置路径泄漏均为 0。但该轮运行期间正式客户端在测试外部重启了正式 Runner,source 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 稳定后以最终代码重新独立运行。
|
||||
|
||||
## V1.39 首次规划 Provider 瞬态重试持久等待态
|
||||
|
||||
V1.39 把 V1.28 的显式瞬态重试从单纯进程内退避扩展为可跨 Runner 重启恢复的持久等待态。本切片只覆盖后台 tool-plan 的首次请求(`repair-0`)以及该请求前自动触发的 context-compaction;final-reply、手动压缩和 tool-plan `repair-N` 仍使用进程内重试,因为现有持久上下文不能仅凭请求指纹无歧义重建这些请求。
|
||||
|
||||
### Sidecar 与提交顺序
|
||||
|
||||
- 每个 Agent/run 最多存在一条 `.agent/runtime/provider-retries/<agentKey>/<runKey>.json`。安全 ID 原样使用;危险 ID 使用有界安全前缀加完整原始 ID 的 SHA-256,避免 lossy 替换碰撞。记录使用严格 v1 schema,拒绝未知字段,并绑定 `projectId / agentId / taskId / sessionId / runId / source / goalId / goalRevision / goalSnapshotFingerprint / appliedSteerCursor / requestKind / baseRequestSlot / requestFingerprint / providerConfigFingerprint / webSearchEnabled / allowIdleContextCompaction`,以及下一 `requestSlot / attempt / maxRetries / backoff / retryAt / errorKind / errorFingerprint`。
|
||||
- 每个物理请求继续使用 `max_retries=0` 的 Provider client。瞬态失败先闭合当前 `started -> failed` lifecycle,再写 retry audit,随后原子提交 sidecar 和 `.previous`,最后把 task/state 投影为 `running / waiting-for-provider-retry` 并释放 Agent lane。sidecar 未可信落盘前不得宣称进入等待态;task/state 已投影但 sidecar 缺失,或两者身份冲突时进入 `needs-reconciliation`,不得猜测补发。
|
||||
- 首次请求保留原 slot;第 `N` 次持久重试固定使用 `<baseRequestSlot>-transient-N`。同 attempt 的重复写必须字节语义幂等,attempt 只能逐一递增,不能跳号、回退或在同一 sidecar 中排队多个请求。
|
||||
- sidecar 只保存错误类别/哈希、请求与配置指纹和恢复身份,不保存 prompt、messages、tool arguments、Provider 正文、API Key、配置文件绝对路径或项目绝对路径。
|
||||
|
||||
### 恢复、并行与控制
|
||||
|
||||
- Runner 启动扫描 `provider-retries`,primary 缺失时允许从原子 `.previous` 恢复,并对 primary/previous 去重。未到期记录只重建定时唤醒;到期后先重新校验 cancel、steer、Goal、task/run、Provider 配置和重建请求指纹,再沿原 Session/run/loop/attempt 发出一个物理请求。重启不得清零 attempt,也不得因进程启动提前发送。
|
||||
- `waiting-for-provider-retry` 释放当前执行 lane,因此其它 Agent 可继续并行;同 Agent 后续 pending task 仍被该 running task 阻挡,保持 FIFO。每个 project/Agent/run 的定时唤醒必须 singleflight,重复 resume 或到期通知不能制造第二个物理请求。
|
||||
- cancel、终态、重试耗尽和有效 steer 会删除旧 sidecar;steer 使用同一 run 的新 cursor 重新规划,不复用旧 attempt。Goal pause 保留 sidecar且零请求,resume 校验完整身份后恢复原等待态;即使暂停跨越秒级时间边界,也必须保留相同 request fingerprint 和 `-transient-N` slot。
|
||||
- 请求指纹只包含实际 Provider 请求的稳定语义。工具策略的 `updatedAt` 等刷新时间只用于 Runtime/UI 投影,不进入 planning prompt;否则一次普通 pause/resume 或 Runner 等待跨秒就会把同一请求误判为上下文漂移并作废 sidecar。
|
||||
- 活跃 sidecar 同时阻断 finalization 与 `runner.shutdown_if_idle`。Runner 只有在 retry sidecar、pending action、confirmation、finalization、进程会话和其它 durable 工作全部清空后才能进入 idle shutdown。
|
||||
|
||||
### 验收边界
|
||||
|
||||
确定性回归必须覆盖:未到期零请求、到期唯一请求、耗尽清理、cancel、steer、Goal pause/resume 跨秒保持正文与 attempt、自动 context-compaction 先恢复再进入 tool-plan、sidecar 先于 task/state 的 torn projection、Runner 重启扫描、同 Agent FIFO、跨 Agent 并行、finalization/idle blocker、`.previous` 恢复/去重/排序和危险路径身份。原有 final-reply/repair 进程内重试回归必须继续通过。
|
||||
|
||||
V1.39 当前只完成确定性实现与回归,不把 V1.38 的失败轮、旧瞬态重试真实样本或最小 HTTP 探针拼接成新的真实 Provider PASS。需要真实验收时必须在最终代码上以独立 disposable 项目和隔离 AppData 完整运行,并证明 Runner 在退避期强杀后仍只发送同一 `-transient-N` attempt、其它 Agent 真并行、同 Agent FIFO、唯一最终回复、零重复/残留/正文/密钥/路径泄漏。
|
||||
|
||||
2026-07-19 最终代码的确定性门禁已通过:`provider_retry_` 19/19、`provider_transient_retry_` 6/6;Tauri/Rust 串行全量为 968 passed、4 个环境依赖用例按设计 ignored,`cargo check`、rustfmt、编码与 diff 检查通过。以上结果不替代尚未执行的 V1.39 独立真实 Provider E2E。
|
||||
|
||||
## 验收命令
|
||||
|
||||
- `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml structured_plan_ -- --nocapture`
|
||||
@@ -1354,6 +1381,8 @@ V1.38 把 collaboration policy 的执行语义从“每次动作或恢复都读
|
||||
- `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 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`
|
||||
- `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml provider_transient_retry_ -- --nocapture --test-threads=1`
|
||||
- `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml parallel_read_batch_ -- --nocapture`
|
||||
- `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml runtime_v134_ -- --nocapture`
|
||||
- `cargo test --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml isolated -- --nocapture --test-threads=1`
|
||||
|
||||
@@ -605,3 +605,4 @@ game-project/
|
||||
- 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` 的旧父 run;terminal、`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 节为准。
|
||||
- 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` 暂不扩展为持久重试;工具策略刷新时间不得进入请求指纹。当前只记录确定性回归,不改变 V1.38 真实 E2E 尚未 PASS 的结论。
|
||||
|
||||
Reference in New Issue
Block a user