完善Agent持久动作与隔离任务恢复

收紧Agent DB容量、句柄锁、终态回执和动作历史身份校验
补齐pending动作、task、event、observation与receipt崩溃恢复幂等
扩展跨平台路径清洗、仓库上下文门禁和瞬时LLM重试
让隔离父run持久等待并通过parent-wake恢复同一run
修复确认动作阶段投影与最近工具状态推进
扩充Rust安全回归和真实Provider E2E验收
同步Runtime技术方案、实施计划与项目决策记录
This commit is contained in:
AIGameCreator App
2026-07-13 20:09:26 +08:00
parent e714c8c2b6
commit 0d643ddbbb
10 changed files with 7811 additions and 246 deletions
@@ -4216,3 +4216,23 @@
- 决策:context window 压缩新增 `runtime.milestones` 安全摘要,跨窗口保留已成功完成的 `agent.spawn_isolated / agent.delegate / canvas.asset_generate / preview.validate / project.patchset / project.restore / task.create`,并明确禁止无任务依据的重复高成本或副作用动作。账本只用于规划连续性,不替代 pending-action、task/event、Agent DB、revision、verification 或 finalization 事实源。
- 决策:旧 `runtime.context / runtime.milestones` 不再占普通最近 observation 槽;账本在再次压缩时合并旧摘要与新里程碑,所有 detail 先做路径、凭据和长度清洗。`project.diff` checkpoint 内容 hunk 与 `git.inspect` 工作树 hunk 分别保留最新一项,不能互相顶掉;总 bundle 仍不得超过 128 KiB。
- 验证:新增连续窗口单测证明 spawn、patchset 和 checkpointId 经两次压缩仍存在,两类大 diff 同时保留。真实 `gpt-5.5` Git E2E 曾准确捕获一次上下文遗忘导致的重复 spawn;修复后 94 条 task、156 条 event、140 条 Agent DB、12 次成功工具执行中 `git.inspect=2 / patchset=1 / spawn=1 / join=1`,revision=3,Runner 强杀恢复身份稳定,验证与桌面/移动浏览器证据通过,副作用重放、重复 action/message/receipt、半完成文件、密钥和诱饵泄露均为 0。
## 2026-07-13 AI 游戏创作 Agent Runtime V1.6 持久动作回执与模型回查
- 决策:继续复用 `.agent/agent.db` 作为唯一长期审计源,不新增数据库或平行事实源。每个带 `actionId` 的已落盘终态 observation 必须追加或补齐 terminal receipt,身份固定包含 `agentId / taskId / sessionId / runId / actionId / actionFingerprint / tool / executionMode / status / inputSummary / summary / safeDetail / updatedAt`。
- 决策:`safeDetail` 只允许按工具类型和字段名双重白名单抽取,禁止整段复制工具 detail,禁止保存 `file.read` 源码、命令完整输出、diff 正文、消息 / 记忆 / 委派正文、密钥和绝对路径;首版只保留 `project.patchset` 的 checkpoint / revision / count 等结构化字段,无法安全还原 detail 的历史旧记录允许显式标记 `detailUnavailable`。
- 决策:动作历史读取必须把 receipt 当持久输入而不是可信展示 DTO,重新验证终态 status、actionId、fingerprint、executionMode、tool 和 task / session ledger 绑定,并按当前工具白名单重新解析 `safeDetail`;无法通过二次校验的 detail 只能标记 `detailUnavailable`。
- 决策:新增只读模型工具 `agent.action_history`,只能查询当前 Agent;输入支持 `runId / actionId / tool / status / limit`,`limit` 默认 5、上限 10,省略 `runId` 时只查当前 run。结果优先使用终态 receipt,并兼容折叠历史 terminal observation;旧记录无法还原安全 detail 时标记 `detailUnavailable`。未指定 `tool` 时默认排除 `agent.action_history` 自身,只有显式 `tool=agent.action_history` 才允许回查它,避免递归污染。
- 决策:`agent.action_history` 复用 `agent.audit` 权限,默认 `auto`,项目或 per-Agent policy 可改为 `confirm / deny`;查询不推进 revision、不改变 verification gate、不认领 join。receipt 写入失败时 durable action 必须进入 `needs-reconciliation`,恢复只按原 `actionId` 补齐 receipt,不得重放动作。
- 决策:receipt 判重命中后还必须全等复核 Session、fingerprint、tool、executionMode、status 和安全结果字段,冲突失败关闭。普通 append 禁止写 `agent.runtime.action_receipt`,幂等动作入口只接受字段完整的终态 receipt。Agent DB 只自动修复强杀造成的最后一条不完整 JSONL,中间损坏不跳过;单条记录上限 1 MiB,receipt 幂等全量扫描在文件超过 256 MiB 或记录超过 100 万条时失败关闭,禁止复用锁外快照。普通审计约在 192 MiB 或 999,936 条停止,并给字节 / 记录门槛预留 64 条最大 1 MiB terminal 记录;仅 terminal receipt、带 actionId 的终态 observation / observed 和 reconciliation 可用预留区,`command-failed / verification-failed` 也是终态。普通和终态追加都在同一 DB 句柄锁内真实计数,容量判断和判重失败关闭,不做轮转。普通读取使用最近 32 MiB 有界尾窗、最多保留 16,384 个完整 JSON object,并显式标记 `truncated`。
- 决策:Agent DB 不再依赖普通路径锁文件保障安全。Unix 必须从可信项目目录句柄使用 `openat + O_NOFOLLOW` 打开,校验普通文件与 `nlink=1` 后直接对 DB 文件句柄 `flock`;Windows 必须用相对 `NtCreateFile` 打开并拒绝 reparse point / hardlink,同时以独占 share 持有句柄。每次 append、尾部补换行或截断修复执行 `flush + sync_data`;Unix 新建 `.agent` 和 `agent.db` 后分别同步项目根目录与 `.agent` 目录,写入前后复核身份。32 MiB 尾窗恰好落在记录边界、UTF-8 半字符或精确 1 MiB 尾记录时不得丢弃合法记录;同 UID 恶意进程的 rename / hardlink ABA 不承诺绝对隔离。
- 2026-07-13 真实验收修正:pending action 的精确 project revision / verification gate 不再拦截纯读取工具;不同 Agent 并行推进 revision 后,`file.read / project.search / git.inspect / agent.action_history` 等读取动作必须读取最新事实并返回 observation。写入、命令、验证、预览证据和 `agent.run_status` join 认领仍复核原 revision / gate,repository fingerprint gate 也继续独立生效。该修正来自真实 Provider 首轮中隔离子 Agent 因父 Agent revision 推进而把 `file.read` 误判为 `needs-reconciliation`、导致 all-join 无法形成的失败证据。
- 决策:共享项目事实读取使用项目一致性锁;等待锁后必须重读 durable pending sidecar,并与调用方完整 pending 对象逐字段一致,再核对 policy 和 repository fingerprint。`agent.run_status` 的 all-join claim 必须在同一项目锁内重验 revision / gate 并完成认领,关闭检查与认领之间的 TOCTOU。policy denied 和未知工具也先形成 durable observed pending,再写 terminal receipt;一旦 terminal observation 已持久化,必须先成功落 receipt 才响应取消,receipt 失败统一保留 `needs-reconciliation` 补写入口且不得重放工具。
- 决策:父 run 存在 `joinMode=all` 隔离组时,所有子结果终态且 ready join 被当前父 run 的 `agent.run_status` action 认领前,最终回复和 `agent.action_history` 都必须失败关闭并要求继续查询状态;只有持久 join 认领完成后才解除门禁。
- 决策:最终回复在项目锁内创建 finalization journal 前必须再次复核 all-join 已由当前父 run 持久认领;未认领按 `Stale` 回到同 run planning,不写 journal、assistant 或 completed,不能只依赖 planning 阶段旧快照。
- 决策:父 run 在 `waitingGroups > 0` 时必须持久进入 `waiting-for-isolated-join`、保存原 context cursor 并释放 Agent lane;重复 resume 只返回等待状态,不请求 LLM、不推进 loop,也不取消仍在工作的 child。最后一个 child 就绪后写 `deliveryTarget=parent-wake` 并唤醒同一 parent run / session,由模型通过持久 `agent.run_status` actionId 认领;不得创建 join continuation。活跃 planning / running 父 run 直接保留 ready join 等待认领,重复 dispatch 不创建任务。旧 delivery 缺少 target 时按 continuation 单向兼容。
- 决策:action task / event 投影的幂等阶段键为 `runId + actionId + phase`,允许同一 action 从 waiting-for-confirmation 合法推进到终态 observation,同阶段冲突仍失败关闭。Agent DB 终态 observation 扫描跳过同 action 的非终态前置记录,只对既有终态做全字段一致性检查;`recentToolCalls` 按 actionId 原位更新,避免 waiting 投影遮住最终结果。
- 决策:动作历史结构化 detail 上限 7,200 字符;超预算时只能先删除可选字段,再按最旧优先删除完整记录,不得字符截断 JSON,也不得清空 `runId / actionFingerprint` 破坏身份。运行时文本清洗必须覆盖 Unix、Windows 盘符、正反斜杠 UNC、`file:/...`、`file:///...` 及百分号编码绝对路径,统一替换为 `<absolute-path>`,并保留普通相对文本与 HTTP(S) URL。
- 决策:后台 planning 和最终回复的瞬时 LLM 错误重试上限提高为额外 5 次,覆盖 `Timeout / Connectivity / Transport` 与上游 `408 / 429 / 5xx`,按 500ms 线性递增退避;不可重试错误继续直接失败,任何重试都不得跨越工具执行或回复落盘提交点。
- 决策:本轮只交付模型工具,不新增前端动作历史弹窗;UI 继续显示最近动作投影,后续历史查看必须使用独立弹窗。
- 验证:Rust 全量 507 项中 504 通过、3 项真实浏览器 opt-in 用例按设计忽略;覆盖 receipt 折叠、组合过滤、默认值与上限、敏感清洗、旧记录、尾部修复、中间损坏失败关闭、身份冲突、句柄安全、目录同步、确认阶段到终态投影、`parent-wake` 和恢复补齐。最终真实 `gpt-5.5` V1.6 `llm-runtime` 套件中,模型实际调用 1 次 `agent.action_history` 并返回 1 条与 `.agent/agent.db` 全身份对齐的当前 run 记录;94 条 task、158 条 event、164 条 Agent DB、11 条合法工具协议、13 次成功工具执行和 24 条 terminal receipt 中,主 run receipt 为 18,递归历史、重复 receipt / action / message、receipt identity 冲突、密钥和诱饵泄漏均为 0。Runner 强杀后恢复原 run / session 且身份稳定,3 个隔离实例形成唯一 all-join 认领,本次真实竞态未创建 continuation task;动作历史只在父 run 认领 join 后执行,项目、桌面和移动验证通过。
@@ -198,7 +198,7 @@ delegationId, instanceId, templateAgentId, runId, status,
summary, artifacts[path, sha256], evidence[], verifiedRevision, error
```
同一 group 全部终态后,Runtime 使用 `.agent/runtime/isolated-agents/join-deliveries/<groupId>.json` 持久记录交付状态,并且只允许固定 `joinRunId` 入队一次。父 run 仍持有自身 lane 时,只能通过当前已持久化 `agent.run_status` 工具动作的 `actionId` 认领 ready all-join;交付记录保存 `claimedByActionId`,同一 action 崩溃重试可幂等重读,同一 run 的其他 action 不再看到该 join。认领后取消尚未执行的固定 continuation;continuation 已开始时拒绝认领。父 run 未认领时,唯一 continuation 才在 lane 释放后执行,`dispatched -> claimed-by-parent / suppressed` 单向不可逆。恢复、并发 child 终态和重复状态查询都不能重新开放交付、生成 `-dup-*` join run 或重复调用父 LLM。
同一 group 全部终态后,Runtime 使用 `.agent/runtime/isolated-agents/join-deliveries/<groupId>.json` 持久记录交付状态。父 run 仍在 planning / action 时不创建 continuation,只能通过当前已持久化 `agent.run_status` 工具动作的 `actionId` 认领 ready all-join;交付记录保存 `claimedByActionId`,同一 action 崩溃重试可幂等重读,同一 run 的其他 action 不再看到该 join。若父 run 在 child 尚未终态时返回空 actions,则持久进入 `waiting-for-isolated-join`、保存原 context cursor 并释放自身 lane,不继续请求 LLM,也不消耗后续上下文窗口;重复 resume 只读取等待状态。最后一个 child 就绪后写 `deliveryTarget=parent-wake` 并唤醒同一父 run / session,由模型继续调用 `agent.run_status` 认领,不能创建 `joinRunId` continuation。旧 delivery 缺少 `deliveryTarget` 时按 `continuation` 兼容;只有非活跃父任务或旧记录的兜底路径才保留固定 continuation。`dispatched -> claimed-by-parent / suppressed` 单向不可逆,恢复、并发 child 终态和重复状态查询都不能重新开放交付、生成 `-dup-*` join run 或重复调用父 LLM。
## 5. 真实 Provider 验收
@@ -356,6 +356,38 @@ npm run ai-game-creator-shell:agent-runtime:real-e2e -- --config-dir <AppData> -
修复后的真实 `gpt-5.5` 回归在 10 轮内完成并收束,唯一 spawn / patchset / join 均保持 1 次,两次 Git 审阅和两类 diff 同时留在最终 context bundle,重复副作用为 0。
## V1.6 持久动作回执与模型回查
- 复用 `.agent/agent.db` 作为唯一长期审计源,不新增数据库或平行事实源。每个带 `actionId` 的已落盘终态 observation 都必须在该审计源中追加或补齐 terminal receipt,身份固定包含 `agentId / taskId / sessionId / runId / actionId / actionFingerprint / tool / executionMode / status / inputSummary / summary / safeDetail / updatedAt`。
- `safeDetail` 必须按工具类型和字段名双重白名单抽取,不能把某类工具的整段 detail 直接复制到 receipt;不得写入 `file.read` 读取的源码、命令完整输出、diff 正文、消息 / 记忆 / 委派正文、密钥或绝对路径。首版只保留 `project.patchset` 的 `checkpointId / revision / changeCount / revisionAdvanced` 等结构化字段;历史旧记录无法安全还原 detail 时允许显式标记 `detailUnavailable`,不得为补齐字段重新读取或扩散敏感正文。
- `agent.action_history` 不能把 Agent DB 中已有 receipt 当成已经可信的展示 DTO。读取时必须再次验证终态 status、actionId、64 位 fingerprint、executionMode、tool,并把 receipt 的 task / session 与同 run 的 task ledger 绑定;`safeDetail` 还要按当前工具白名单重新解析,无法通过时只返回 `detailUnavailable`,不得回显伪造或旧版本遗留的原始 detail。
- 新增只读模型工具 `agent.action_history`,只能查询当前 Agent 的持久终态动作。输入支持 `runId / actionId / tool / status / limit`;`limit` 默认 5、上限 10,省略 `runId` 时只查询当前 run。结果优先使用 terminal receipt,并兼容折叠历史 terminal observation;旧记录缺少安全结构化 detail 时显式返回 `detailUnavailable`。
- `agent.action_history` 自身默认不出现在未指定 `tool` 过滤条件的结果中,避免模型查询动作递归污染历史;只有显式传入 `tool=agent.action_history` 时才允许查询它自身的 receipt。
- 权限复用 `agent.audit`,默认 `auto`,仍可由项目或 per-Agent policy 改为 `confirm` 或 `deny`。该工具只读,不推进 project revision、不改变 verification gate,也不认领 join。
- terminal receipt 写入失败时,对应 durable action 必须进入 `needs-reconciliation`,不得出现动作已经完成但长期历史静默缺失。恢复只能按原 `actionId` 幂等补齐 receipt,不能重放动作。
- receipt 幂等复用 `recordType + agentId + runId + actionId` 定位,但命中旧记录后必须继续全等复核 `taskId / sessionId / actionFingerprint / tool / executionMode / status` 和结果摘要,任何冲突都失败关闭。普通 Agent DB append 明确拒绝 `agent.runtime.action_receipt`;幂等动作入口只接受字段完整、身份合法且 status 终态的 receipt,不能被任意 recordType 或非终态记录借用。Agent DB 尾部因进程强杀形成不完整 JSONL 时,只允许在追加锁内修复最后一条不完整记录;中间损坏仍失败关闭。单条 JSONL 最大 1 MiB;receipt 幂等全量扫描在文件超过 256 MiB 或完整非空记录超过 100 万条时失败关闭,不能把该阈值误解成通用 append 自动轮转上限。普通审计在约 192 MiB 或 999,936 条的任一软上限停止,同时为字节容量和记录门槛预留 64 条最大 1 MiB terminal 记录;只有 terminal receipt、带 actionId 的终态 observation / observed 和 reconciliation 能使用预留区,`command-failed / verification-failed` 与通用 `failed` 同属终态,非终态记录不得消耗预留。普通和终态追加都在同一 DB 句柄锁内真实计数,连同字节容量一起失败关闭,不做静默轮转。
- Agent DB 的跨进程安全以已验证文件句柄为边界,不再依赖可被路径替换的普通 lock 文件。Unix 从可信目录句柄以 `openat + O_NOFOLLOW` 打开并校验普通文件、`nlink=1`,随后直接 `flock` DB 句柄;Windows 使用相对 `NtCreateFile`,拒绝 reparse point / hardlink,并以独占 share 持有句柄。每次 append、尾部补换行或截断修复都执行 `flush + sync_data`;Unix 新建 `.agent` 后同步项目根目录,新建 `agent.db` 后同步 `.agent` 目录,写入完成后再复核路径身份。同 UID 恶意进程制造的 rename / hardlink ABA 不在 v1 绝对隔离承诺内。
- 普通历史读取使用最近 32 MiB 的有界尾窗,最多保留 16,384 个完整 JSON object,超出时返回 `truncated=true`;尾窗恰好落在记录边界、UTF-8 半字符或精确 1 MiB 最后一条记录时都不能丢弃合法完整记录。
- pending action 的精确 project revision / verification gate 复核继续约束写入、命令、验证、预览证据和会认领 join 的协调动作;`memory.read / conversation.read / asset.list / project.index / project.search / project.diff / git.inspect / file.list / file.read / task.list / agent.action_history` 等纯读取动作在其他 Agent 推进 revision 后允许读取最新事实,并把结果作为新 observation 返回。适用仓库规范的 fingerprint gate 仍独立生效,不能借只读分类绕过规范漂移。
- 共享项目事实读取必须使用项目一致性锁;等待锁后重新读取 durable pending sidecar,并与调用方携带的完整 pending 对象逐字段一致,再重验 policy 和 repository fingerprint。pending 被替换、迁移或损坏时失败关闭,不能继续使用锁外旧对象。`agent.run_status` 的 all-join claim 在同一项目锁内完成 revision / verification gate 复核与认领,避免检查通过后项目状态又发生变化。
- 父 run 创建 `joinMode=all` 的隔离组后,在所有子结果终态且 ready join 已由当前父 run 的 `agent.run_status` action 认领前,Runtime 必须阻止最终回复和 `agent.action_history`,并要求继续查询状态;认领成功后才解除门禁,避免模型绕过唯一 join 提前收束或先查询不完整动作历史。
- all-join 门禁不能只在 planning 阶段检查。最终回复取得项目锁后、创建 finalization journal 前必须再次读取持久 join 交付并确认已由当前父 run 认领;未认领时按 `Stale` 回到同 run planning,不写 journal、assistant 或 completed,关闭 planning 到最终提交之间的竞态窗口。
- all-join 等待不能占用父 Agent lane 或伪装成新的 continuation。`waitingGroups > 0` 时父 run 保存 task / state / context / audit 后直接释放 lane;最后一个 child 在父 lane 释放前后完成的竞态由持久 `parent-wake` delivery 和释放后有界复核共同关闭。父 run 已恢复 planning / running 时,重复 dispatch 只确认现状,不创建 join task。
- policy denied 和未知工具也要先落 durable observed pending,再追加 terminal receipt。terminal observation 一旦持久化,就不能在 receipt 前响应取消;receipt 成功后再结束取消流程。receipt 写入失败统一进入 `needs-reconciliation`,恢复仅补回执,不重放工具。
- action 关联的 task / event 投影按 `runId + actionId + phase` 幂等,同一动作允许从 `waiting-for-confirmation` 合法推进到终态 observation,但同阶段身份冲突继续失败关闭。Agent DB 终态 observation 扫描跳过同 action 的非终态前置记录,只对既有终态做全字段复核;`recentToolCalls` 按 actionId 原位更新,不能让 waiting 投影遮住后续 `ok / failed`。
- `agent.action_history` 结构化 detail 上限 7,200 字符;超预算时先移除可选字段,再删除完整最旧项,禁止用字符截断破坏 JSON,禁止清空 `runId / actionFingerprint`。上下文清洗覆盖 Unix、Windows 盘符、正反斜杠 UNC、`file:/...`、`file:///...` 和百分号编码的 `file:` URI,绝对路径统一替换为 `<absolute-path>`,同时保留普通相对文本与 HTTP(S) URL。
- 后台 planning 与最终回复遇到 `Timeout / Connectivity / Transport` 或上游 `408 / 429 / 5xx` 时最多额外重试 5 次,按 `500 / 1000 / 1500 / 2000 / 2500ms` 退避;配置、请求、流协议、反序列化错误及其他 `4xx` 不重试。重试只发生在工具计划执行前或最终回复落盘前,不能重放已完成副作用。
- 本轮只提供模型工具,不新增前端动作历史弹窗。UI 继续显示最近动作投影;后续若增加历史查看能力,必须使用点击后打开的独立弹窗,不得在当前面板下方追加内容。
### 2026-07-13 真实验收结果
- Rust 测试已覆盖 receipt 折叠、组合过滤、默认值与上限边界、跨 Agent 隔离、敏感信息清洗、历史旧记录 `detailUnavailable`、JSONL 尾部修复与中间损坏失败关闭,以及 `needs-reconciliation` 恢复按原 `actionId` 补齐。
- 真实 Provider 已实际调用 `agent.action_history`,并证明返回的 `agentId / taskId / sessionId / runId / actionId` 与 `.agent/agent.db` identity 对齐;Runner 强制终止后恢复保持动作零重放、敏感内容零泄漏。
发布 AppData 中配置的真实 `gpt-5.5` 已通过最终 V1.6 `llm-runtime` 套件。模型实际调用 1 次 `agent.action_history` 并返回 1 条当前 Agent、当前 run 的历史,递归结果为 0;返回 identity 与 `.agent/agent.db` 全字段对齐。最终形成 94 条 task、158 条 event、164 条 Agent DB 记录、11 条合法工具协议、13 次结构化成功工具执行和 24 条 terminal receipt;主 run 含 18 条 receipt,要求覆盖的 3 类工具均有回执,重复 receipt、重复 action、重复 message、receipt identity 冲突、密钥泄漏和项目诱饵泄漏均为 0。
Runner 强制终止后恢复原 run / session 且身份稳定,project revision 为 3;项目验证、桌面 / 移动浏览器验证、3 个隔离实例和唯一 all-join 认领均通过,本次真实竞态走“活跃父 run 直接认领”路径,未创建 continuation task,真实执行顺序证明 `agent.action_history` 只在隔离结果被父 run 认领后发生。确定性 Rust 集成用例另覆盖 `parent-wake` 等待路径、多次 resume 零 LLM 请求、同父 run 唤醒和无 continuation。pending `file.read` 在其他 Agent 推进 revision 后仍读取最新事实;写入、命令、验证、预览和 join 认领保持严格 gate。
## 验收命令
- `npm run ai-game-creator-shell:typecheck`
File diff suppressed because one or more lines are too long