From c703b2ed2f4e5399595406e6ea340d6581ef2e66 Mon Sep 17 00:00:00 2001 From: suzmii Date: Thu, 13 Aug 2026 10:13:31 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E6=96=B0=E5=A2=9EGame=20Agent=20Runtime?= =?UTF-8?q?=E4=BA=A4=E4=BA=92=E8=BE=B9=E7=95=8C=E9=87=8D=E6=9E=84=E8=AE=A1?= =?UTF-8?q?=E5=88=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 梳理Consumer与Supervisor Shell的现状边界 规划统一命令、状态投影、Runner自驱和分阶段迁移 明确旧公开接口的渐进下线与验收门禁 --- ...作Agent Runtime交互边界重构实施计划-2026-08-12.md | 319 ++++++++++++++++++ 1 file changed, 319 insertions(+) create mode 100644 docs/technical/【技术方案】AI游戏创作Agent Runtime交互边界重构实施计划-2026-08-12.md diff --git a/docs/technical/【技术方案】AI游戏创作Agent Runtime交互边界重构实施计划-2026-08-12.md b/docs/technical/【技术方案】AI游戏创作Agent Runtime交互边界重构实施计划-2026-08-12.md new file mode 100644 index 000000000..d3dfe8d0c --- /dev/null +++ b/docs/technical/【技术方案】AI游戏创作Agent Runtime交互边界重构实施计划-2026-08-12.md @@ -0,0 +1,319 @@ +# AI 游戏创作 Agent Runtime 交互边界重构实施计划 + +更新时间:`2026-08-12` +状态:评审中 + +## 0. 目标与范围 + +统一 Consumer(GUI / CLI / 自动化测试)与 Runtime 之间的公开交互边界,达到: + +- **Consumer 只做两件事**:`render(state)` 与 `dispatch(intent)`,中间不保留决策。 +- **交互 Loop 收归后端 Supervisor Shell**:Consumer 不再根据 Runtime 状态自行选择 start / steer / confirm / retry / resume。 +- **GUI、CLI、测试夹具是同一套协议的平等 Consumer**,GUI 没有任何特权通道。 +- **Runner 自驱**:工作发现、恢复、继续执行不依赖 Consumer 在线或主动触发。 + +本重构**不重新设计 Runtime 内部执行模型**(Part D 保持黑盒),只补充 Shell 需要的边界能力。 + +### 不在本轮 + +- Agent 执行状态机(main_loop / task_queue / recovery)内部重构。 +- Runner 进程生命周期策略(开机自启 / 无 GUI 常驻)——自驱只限于"Runner 存活期间",保留 GUI 启动 + GUI-owner watchdog。 +- LLM / Provider / 提示词体系改动。 + +--- + +## 1. 交互 Loop 协议(Interaction Contract) + +这是 Consumer 与 Supervisor Shell 之间唯一的公开协议。协议不绑定 transport(Tauri 命令 / Runner 协议 / 进程内函数均可用同一套 DTO)。 + +### 1.1 出向事件(Shell → Consumer) + +| 事件 | 含义 | 现状落点 | 是否新增 | +|---|---|---|---| +| `progress` | 运行推进 | `status/phase` + `recentEvents` + `waitingOn/nextStep`,经 `read_game_creator_agent_runtimes` 与 `game-creator-agent-progress` 事件 | 收敛 | +| `needs_input` | 等待澄清回答 | `userInputRequest` / phase `waiting-for-user-input`(`AgentRuntimeUserInputRequest`) | 收敛 | +| `approval_required` | 等待开发者批准工具动作 | `pendingToolAction` / phase `waiting-for-confirmation`(`AgentRuntimePendingToolActionSummary`) | 收敛 | +| `tool_request` | 工具已请求/执行中 | 现散在 `recentToolCalls` + phase `action` | **新增派生** | +| `artifact` | 产物 / manifest 变化 | `game-creator-manifest-invalidated` + finalization journal | 收敛 | +| `done` | 本轮终态 | `completed` + `AgentRuntimeFinalizationJournal` + responseStream `committed` | 收敛 | +| `error` | 失败 / 需人工核对 | `failed` / `needs-reconciliation` + `error` | 收敛 | + +协议 DTO(camelCase 序列化,与现有一致): + +```rust +enum AgentRuntimeOutboundEvent { + Progress(AgentRuntimeProgress), + NeedsInput { request: AgentRuntimeUserInputRequestView }, + ApprovalRequired { action: AgentRuntimePendingToolActionSummary }, + ToolRequest(AgentRuntimeToolRequest), // 新增:Shell 从 recentToolCalls+phase 投影 + Artifact(AgentRuntimeArtifactEvent), // 收敛 manifest-invalidated + finalization + Done(AgentRuntimeDoneEvent), + Error(AgentRuntimePublicError), +} + +struct AgentRuntimeProgress { + project_path: String, agent_id: String, run_id: String, + status: String, phase: String, + current_task: String, current_action: String, + plan_steps: Vec, active_plan_step_index: Option, + waiting_on: String, next_step: String, // Shell 负责填充,Consumer 不再映射 phase→文案 + updated_at: u64, +} +``` + +> 与现状的关键差异:`progress` 里的 `waiting_on`/`next_step` 由 **Shell 填充**。当前是前端在 `model.ts:612/644` 硬编码 phase→中文文案,收归 Shell 后前端删除该映射。 + +### 1.2 入向命令(Consumer → Shell) + +| 命令 | 吸收的旧命令 | 说明 | +|---|---|---| +| `submit_intent` | `start_*` / `steer_*` | 一条消息可能 steer 进现有 run,也可能 start 新 run,由 Shell 判定 | +| `answer` | `answer_game_creator_agent_runtime_user_input` | 回答澄清 | +| `approve` | `confirm_*` / `reject_*` | 批准或拒绝,含 policy 确认卡 | +| `cancel` | `cancel_game_creator_agent_runtime_task` | 取消 | +| `resume` | `resume_*` / `confirm_resume_*` / `retry_*` / `confirm_retry_*` / `schedule_game_creator_agent_ready_tasks` | 恢复/重试/调度统一入口 | + +命令签名: + +```rust +#[tauri::command] +async fn submit_game_creator_agent_intent( + project_path: String, session_id: String, intent: String, + run_profile: Option, source: Option, +) -> Result; + +#[tauri::command] +async fn answer_game_creator_agent_interaction( + project_path: String, run_id: String, action_id: String, + request_id: String, response_id: String, answers: BTreeMap, +) -> Result; + +#[tauri::command] +async fn approve_game_creator_agent_interaction( + project_path: String, run_id: String, action_id: String, + approved: bool, note: String, +) -> Result; + +#[tauri::command] +async fn cancel_game_creator_agent_run( + project_path: String, agent_id: String, run_id: String, +) -> Result; + +#[tauri::command] +async fn resume_game_creator_agent_project( + project_path: String, +) -> Result, String>; +``` + +协议外 API(不进交互 Loop,作为管理面保留在 Shell 上):goal CRUD、`compact`、会话管理、配置读写。 + +### 1.3 统一 InteractionRequired 语义 + +Shell 向 Consumer 暴露"必须等待外部回答"的单一概念,替代现在分散的 `pendingToolAction` / `userInputRequest` / policy 确认卡: + +```rust +enum AgentRuntimeInteractionRequired { + UserInput { request: AgentRuntimeUserInputRequestView }, + Approval { action: AgentRuntimePendingToolActionSummary }, + PolicyApproval { policy: String }, // 新增:吸收 confirm_resume/confirm_retry 的 agent.resume 确认卡 +} +``` + +> 现状的 `confirm_resume`(`commands.rs:1148`)与 `confirm_retry`(`commands.rs:1020`)是"要求用户确认策略"的产物,由 GUI 弹确认卡实现。收归 Shell 后,Shell 评估 `enforce_project_auto_permission_policy`(`verification.rs:813`),需要确认时返回 `InteractionRequired::PolicyApproval`,用户确认后经统一的 `approve` 命令继续。 + +--- + +## 2. 现状盘点与复用清单 + +### 2.1 可直接复用的资产(近 1 个月内形成,活跃演进期) + +| 资产 | 位置 | 复用方式 | +|---|---|---| +| CLI 交互决策状态机 | `swarm_cli/turn_dispatch.rs:245` `decide_interaction_action`、`:48` `handle_swarm_user_turn`(steer/start、Reply/Execute/Resume、goal 门禁) | **整体上提**到后端 Shell(纯 Rust、无 UI 纠缠) | +| Runner 自驱续跑定时器 | `runtime_driver/provider_recovery.rs` 的 `schedule_waiting_provider_retry_wake_after_lane_release` 等 | 已存在,P4 直接复用 | +| 确定性 e2e | `scripts/agent-runtime-deterministic-playable-e2e.mjs` + `deterministic-lane-defense-provider.mjs`(`expectedProviderStats`/`expectedChildReport` 断言) | P0 基线扩展(协议级 trace) | +| 版本协商 fallback | 前端 `model.ts:1186-1226` `isMissing*CommandError` | 迁移期新旧并存的标准模式 | +| 逐命令幂等 | `accepted_run_id`(`runtime_state.rs:1548`)、runner requestId 缓存(`runner/protocol.rs:353`)、goal CAS | P1 设计直接沿用 | +| 进程内集成测试 | `src-tauri/tests/`(`command_runtime.rs`、`runtime_actions/`、`collaboration/`、`goal.rs`) | P1-P6 每步回归的护栏 | + +### 2.2 需要收敛/改造的点 + +| 点 | 位置 | 动作 | +|---|---|---| +| 前端 phase→文案映射 | `model.ts:612/644/974/1498/1554` | Shell 填充 `waiting_on/next_step` 后删除 | +| 前端 steer/start 决策 | `model.ts:849` `submitProjectSupervisorRuntimeTask`、`App.tsx:5791` | 迁入 Shell(`submit_intent`) | +| 前端 confirm/retry/resume 门禁 | `model.ts:1131-1154`、`panels.tsx:381-497` | 由 `InteractionRequired` + `approve/resume` 取代 | +| 前端跨轮状态修补 | `model.ts:244` `normalizeAgentRuntimeState`、`:385` `mergeAgentRuntimeStateIntoMap` | 依赖 P2 Snapshot 稳定后删除 | +| CLI 独立状态机 | `swarm_cli/turn_dispatch.rs` | 上提 Shell,CLI 只留终端交互(stdin/stdout/observer) | +| 重复触发恢复 | `App.tsx:2799/10341`、`useDeveloperAgentPanel.ts:684`(启动时 resume)、`App.tsx:10550`(devMode schedule 按钮) | P4 后删除,由 Runner 自驱 | + +--- + +## 3. 分阶段实施计划 + +> 主线:**先建安全网 → 建统一入口 → 统一状态读取 → 收回决策 → Runner 自驱 → 迁移全部 Consumer → 删旧面**。 + +### P0 行为基线(安全网) + +**目标**:在改动前建立可判断"公开行为是否变化"的验证能力。 + +- 复用 `deterministic-lane-defense-provider.mjs`,在现有 `agent-runtime-deterministic-playable-e2e.mjs` 基础上**增加协议级事件 trace**: + - 录制一条确定性完整会话的**出向事件序列**(progress/needs_input/approval_required/done/error 的顺序与关键载荷),归一化 timestamp、`run_id`/`steer_id`/`request_id` 随机 ID、`accepted_run_id` 对账值。 + - 断言当前 master 的 trace 与预期一致(快照 diff)。 +- 建立统一的回归命令,P1-P6 每阶段结束必跑: + - 确定性 e2e(功能正确性) + - `src-tauri/tests/` 进程内集成测试(单元级护栏) + - 协议级 trace(迁移等价性) +- **不动 Runtime 代码**,只建安全网。 + +**验收**:上述三条命令在当前 master 全绿,trace 基线文件入库。 + +### P1+P2 统一入口 + 状态读取(adapter + Snapshot 投影) + +**目标**:Consumer 改走新协议调用旧实现;状态读取收敛为稳定的公开 Snapshot。**旧接口全保留**为迁移期兼容路径。 + +**P1 适配器(`submit_intent` / `approve` / `answer` / `cancel` / `resume` 新命令)**: + +> **P1 与 P3 的边界**:P1 只做"命令可用 + `submit_intent` 内部判定 steer/start"。`answer`/`approve`/`resume` 在 P1 **只是入口封装**(内部调旧函数),"收到交互该调哪个命令、能否重试"的判定**仍在前端**;到 P3 才把判定收走,前端只剩 `submit`/`respond` 两种动作。 + +- 新增 `src-tauri/src/agent/supervisor_shell/`,内含: + - `intent.rs`:`submit_intent` 内部路由——读当前 runtime → 判定 steer/start(逻辑取自 `swarm_cli/turn_dispatch.rs` 的 steer 判定与前端 `matchingAgentRuntimeForSteer`)→ 调现有 `steer_game_creator_agent_runtime_task_for_profile_at` 或 `start_game_creator_supervisor_background_task_for_session_at` → 返回 `{ mode, accepted_run_id, runtime }`。 + - `interaction.rs`:`approve/answer/cancel` 薄封装现有 `confirm/reject/answer/cancel` 内部函数(P1 只封装,判定仍在前端)。 + - `resume.rs`:`resume_game_creator_agent_project` 吸收 `resume/confirm_resume/retry/confirm_retry/schedule_ready`——Shell 判定是否需 policy 确认、是否需先 cancel 再重试(`needs-reconciliation` 分支,逻辑取自 `App.tsx:6205` `handleProjectSupervisorRetry`)。 +- 新命令与旧命令**同时注册**(`main.rs` invoke_handler)。 +- 前端新增"调新命令 → 后端报 unknown command → 回退旧命令"的版本协商(复用 `isMissing*CommandError` 模式),保证打包版本不一致时旧链路可用。 +- **fallback 边界**:仅"命令不存在(版本不兼容)"确定性回退;写操作的其他错误原样呈现,不做盲目重试(防重复入队)。 + +**P2 Snapshot 投影(状态读取收敛)**: + +> **投影 = 读模型**:把同一份 Runtime durable state(唯一事实来源)按一个稳定、精简、面向消费的 schema 重新导出,作为 Consumer 的权威视图。它**不是新的事实来源**,只是同一份事实的另一种呈现;Consumer 依赖投影,业务真相仍在 durable state。 + +- 新增 `supervisor_shell/snapshot.rs`:`AgentRuntimeSnapshot` 投影。 + - 输入:现有 `AgentRuntimeResult.state` + `recent_events`/`recent_tasks`/`response_stream`/`user_input_request`。 + - 输出:稳定的公开视图(agent/session/run 身份、status/phase、`InteractionRequired`、progress、终态、公开错误)。 + - **内部字段(recentToolCalls、observations、allowedTools、contextUsage 等)不进 Snapshot**。 +- 关键:**后端先补"稳定读取"**——现状前端 `normalizeAgentRuntimeState` 做跨轮 carry-forward,是因为后端 read 在恢复/竞态时字段不稳定。P2 后端投影保证同一 run 身份下字段自洽,前端才能删掉自己的修补。 +- 新增 `read_game_creator_agent_runtime_snapshot(s)` 命令(或改造现有 read 返回 Snapshot),旧 `read_game_creator_agent_runtime(s)` 保留。 +- 前端 `normalizeAgentRuntimeState` / `mergeAgentRuntimeStateIntoMap` / phase→文案映射**依赖 P2 稳定后删除**(本轮先做投影,下一阶段删前端逻辑)。 + +**验收**: +- 新命令与旧命令对同一场景返回的终态一致(用 P0 trace + 确定性 e2e 断言)。 +- 前端在"走新 Snapshot"下渲染与旧路径一致(组件回归)。 +- 现有 `command_runtime.rs` / `collaboration/` 测试全绿(旧逻辑未动)。 + +### P3 Loop 收归 Supervisor Shell + +**目标**:Consumer 只 dispatch 意图,不再持有生命周期判断。 + +- 完成 `submit_intent` / `approve` / `answer` / `cancel` / `resume` 对全部旧分叉的吸收(P1 已建,本轮做全): + - `approve` 吸收 confirm/reject,并按 interaction kind 分派(`UserInput`/`Approval`/`PolicyApproval`)。 + - `resume` 吸收 resume/confirm_resume/retry/confirm_retry/schedule_ready。 +- 建立 `AgentRuntimeInteractionRequired`(见 1.3),Shell 统一暴露"需等待外部回答"。 +- Shell 负责填充 `waiting_on`/`next_step`(从 phase 映射,逻辑上提自 `model.ts:612/644`)。 +- 新增派生事件 `tool_request`(Shell 从 `recentToolCalls` + phase 投影)。 +- **前端删除**: + - `submitProjectSupervisorRuntimeTask` 的 steer/start 决策(`model.ts:849`)。 + - `agentRuntimeCanCancel/CanRetry/CanConfirm` 门禁(`model.ts:1131-1154`)。 + - `App.tsx:5981/6127/6205` 的 confirm/retry/repair 路由逻辑。 +- CLI(`swarm_cli`)改为调用同一 Shell:决策逻辑上提后,CLI 只保留 stdin/stdout 终端交互与 observer 渲染。 + +**验收**: +- CLI 走新协议跑通完整 supervisor+子 Agent 流程(`agent-swarm-test-chat.mjs` / 确定性 e2e)。 +- GUI 提交、确认、重试、恢复均通过统一 `submit_intent/approve/resume`,无 `steer_*`/`confirm_*`/`retry_*` 直接调用。 +- P0 协议级 trace 在"新旧实现各放一遍"下事件序列一致。 + +### P4 Runner 自驱 + +**目标**:工作发现、恢复、继续执行不依赖 Consumer 触发。**这是重构主线的一部分,不是独立项目。** + +- 项目目录簿:`runner/state.rs` 的 `known_roots`(当前进程内)→ 持久化到 AppData(复用 runner 的 `--config-dir`),Runner 重启后仍知道持有过哪些项目。 +- 启动自恢复:`runner/server.rs` 启动完成后,对 known roots 调 `has_recoverable_game_creator_agent_background_tasks_at`(`recovery_scan.rs:425`),有可恢复工作则自动 `resume_game_creator_agent_background_tasks_at`。 +- 空闲自扫描:主 accept loop(`server.rs:275`,已有 25ms `EXTERNAL_AGENT_RUNNER_LOOP_INTERVAL`)内增加"是否有 pending 任务需 wake"的轻量检查,替代 Consumer 调 `schedule_game_creator_agent_ready_tasks` / `wake_pending`。 +- 吸收 `schedule_game_creator_agent_ready_tasks`:manifest ready 任务由 Runner 扫描发现并调度,删除前端 devMode 按钮(`App.tsx:10550`)。 +- **边界(不在本轮)**:Runner 仍由 GUI 启动(`main.rs:2124`),保留 GUI-owner watchdog(`server.rs:169`)与 `game_chat_release` 退出协议(`main.rs:2311`)。"自驱 = 存活期间自调度 + 启动自恢复",**不含**开机自启/无 GUI 常驻。 +- 对应删除前端恢复触发职责:`App.tsx:2799/10341`、`useDeveloperAgentPanel.ts:684` 的启动时 resume、resume 确认卡回调。 + +**验收**: +- Runner 进程内:确认一个 pending 任务后无需任何 Consumer 调用即自动执行;恢复 pending 动作后自动续跑。 +- 确定性 e2e 增加"Runner 独立进程跑完整流程"用例(复用 `agent-runtime-real-e2e/harness/process.mjs` 的二进制编译能力)。 +- `confirm_resume` 恢复确认卡流程改为 `InteractionRequired::PolicyApproval` → `approve`。 + +### P5 迁移 GUI / CLI / Tests + +**目标**:三类 Consumer 全部迁移到统一 Intent、Snapshot、Runtime Output。 + +- GUI: + - 状态渲染改读 `AgentRuntimeSnapshot`;删除 `normalizeAgentRuntimeState` / `mergeAgentRuntimeStateIntoMap` / phase 文案映射。 + - 交互全走 `submit_intent/approve/answer/cancel/resume`;删除 steer/confirm/retry/resume 直接调用与门禁。 + - 保留只读消费形态:response stream 展示、对话合并、画布资产编排(这些不进 Loop 协议)。 +- CLI:`cli.rs` 与 `swarm_cli` 改用 Shell 命令;删除各自状态机(决策已上提)。 +- Tests:`src-tauri/tests/` 迁移到新命令;`agent-runtime-real-e2e` 与确定性 e2e 走同一协议。 +- 迁移顺序:先 CLI(最薄)→ 再 Tests → 最后 GUI(唯一消费 response stream / conversation 合并 / goal CAS / 委派修复路由,工作量最大)。 + +**验收**:GUI、CLI、Tests 调用面收敛到同一组命令,代码差异只剩输入输出形式。 + +### P6 删除旧公开面 + +**目标**:Interaction Contract 成为唯一稳定公开边界。 + +- 删除旧 Tauri 命令:`start_game_creator_agent_runtime_task` / `start_game_creator_supervisor_runtime_task` / `steer_*` / `confirm_*` / `reject_*` / `retry_*` / `confirm_retry_*` / `resume_game_creator_agent_runtime_tasks` / `confirm_resume_*` / `schedule_game_creator_agent_ready_tasks` / `read_game_creator_agent_runtime(s)`(`main.rs:2188-2208`)。 +- 删除对应后端 wrapper 与前端 `app/types.ts` 旧 DTO、旧事件协议、迁移期兼容逻辑。 +- 现有 start / steer / resume / recovery 能力作为 Shell 内部实现保留(改名/内联)。 + +**验收**:`grep` 无旧命令名残留;全量回归(P0 基线 + 单元 + e2e)全绿。 + +--- + +## 4. 迁移策略(新旧并存 + 等价性) + +1. **接口即实现,不留空窗**:新命令从注册第一天起就是真实可用——内部套用现有旧函数(adapter 套旧实现是**常态、透明**,前端不知道也不关心)。不存在"接口先立、实现待填"的中间态;分阶段的不是"接口 vs 实现",而是"谁先切到新接口"。 +2. **新旧并存**:P1 起新命令与旧命令同时注册,Consumer 逐个切换,旧路径逐条下线(strangler fig)。 +3. **版本协商 fallback(仅用于迭代空窗期)**:前端调新命令,**仅当**后端报 unknown command(前端版本 ≠ 后端版本,打包错位)时回退旧命令;其他运行错误**原样呈现,不盲目重试**(防重复入队)。后端新版随应用覆盖到位后,fallback 即死代码,P6 删除。 +4. **等价性保障**: + - 确定性 provider + 协议级 trace(P0)作为新旧实现的对照基线。 + - 进程内集成测试每阶段全跑。 + - 关键迁移点(steer/start 判定、resume 路由、needs-reconciliation 重试)用"同一输入 → 新旧实现输出一致"的单测锁定。 + +--- + +## 5. 里程碑与验收门禁 + +| 里程碑 | 交付 | 门禁 | +|---|---|---| +| M0 | P0 基线 + trace 入库 | 回归三件套全绿 | +| M1 | P1 新命令 + adapter(新旧并存) | 新/旧命令终态一致;现有测试全绿 | +| M2 | P2 Snapshot 投影 + 前端读 Snapshot | 前端删 normalize 后渲染回归一致 | +| M3 | P3 Loop 收归(Intent + InteractionRequired) | CLI 走新协议跑通完整流程;前端无 steer/confirm/retry 直调 | +| M4 | P4 Runner 自驱 | Runner 独立进程自动跑完;resume 确认走统一 approve | +| M5 | P5 全部 Consumer 迁移 | GUI/CLI/Tests 调用面收敛到同一协议 | +| M6 | P6 删旧面 | 无旧命令残留;全量回归绿 | + +--- + +## 6. 风险与未决问题 + +| 风险/问题 | 影响 | 缓解 | +|---|---|---| +| P2 依赖"后端 read 先稳定",否则前端不敢删 normalize | 阶段顺序敏感 | P2 后端投影先行,前端删逻辑放同一阶段尾 | +| P4 自驱与 GUI-owner 安全模型冲突 | 若误解为"无 GUI 常驻"会引安全评审 | 计划内明确边界,实现不越界 | +| steer/start 判定含 UX 语义(mode/source/runProfile、steerId 生成、acceptedRunId 对账) | 收归 Shell 后前端展示可能退化 | Shell 返回 `{ mode, accepted_run_id, steer_decision }` 补足展示信息 | +| `tool_request` 无现成单一落点 | 需 Shell 派生投影 | 提前排进 P2/P3 投影工作量 | +| 协议级 trace 的随机 ID 归一化 | 基线易碎 | 复用确定性 provider,归一化规则集中一处 | + +## 7. 建议实施顺序(一句话) + +P0 建安全网 → P1+P2(adapter + Snapshot,纯增量、旧接口全留、fallback 兜底)→ P3 收 Loop → P5 迁移(先 CLI 后 GUI)→ P6 删旧面;P4(Runner 自驱)作为主线中心件贯穿 M4,不单独立项,但边界(存活期间自调度,不含无 GUI 常驻)在计划内写死。 + +--- + +## 8. 相对原方案的调整点 + +本计划在原方案基础上做了以下调整,均基于 master 现状与既有安全模型: + +1. **P0 基线**:原方案的 Golden Replay 改为"确定性 e2e + 协议级事件 trace"——在既有确定性 provider e2e(`deterministic-lane-defense-provider.mjs`)上扩展,录制协议级事件序列并归一化 timestamp 与随机 ID,不重建基线体系。 + +2. **P4 定位与边界**:原方案将"Supervisor Lifecycle Coordinator"列为独立阶段;现作为主线组成部分(里程碑 M4,不单独立项)。自驱限于"Runner 存活期间自调度 + 启动自恢复",排除"开机自启 / 无 GUI 常驻",与既有 GUI-owner 安全模型一致。 + +3. **术语收敛**:原方案"Interaction Contract / Intent / Snapshot"统一为本计划"交互 Loop 协议"(5 入向命令 + 7 出向事件)与"投影"(读模型),含义不变。 + +4. **迁移原则显式化**:接口即实现(adapter 套旧逻辑为常态);fallback 仅在版本空窗期、只认 unknown command。原方案未明确此点。 -- 2.52.0 From 784facbdb309f2ecf3e1911205d350a2e434817c Mon Sep 17 00:00:00 2001 From: suzmii Date: Thu, 13 Aug 2026 14:53:26 +0800 Subject: [PATCH 2/4] =?UTF-8?q?=E6=A0=B9=E6=8D=AEReview=E6=84=8F=E8=A7=81?= =?UTF-8?q?=E5=AE=8C=E5=96=84Game=20Agent=20Runtime=E4=BA=A4=E4=BA=92?= =?UTF-8?q?=E5=8D=8F=E8=AE=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 补齐公开协议版本、事件身份、有序性、cursor与Snapshot revision规则 统一五个公开写命令的request ledger、请求指纹、幂等冲突、结果读回与崩溃恢复 补充Interaction identity、response去重、项目级PolicyApproval与锁内策略复核 拆分Public/Developer Snapshot,冻结公开字段白名单、稳定枚举与结构化错误 明确project/Runner/GUI owner、projection journal、恢复矩阵与自动调度门禁 调整分阶段实施边界、旧公开面检查范围并补充技术文档索引 --- docs/README.md | 5 + ...作Agent Runtime交互边界重构实施计划-2026-08-12.md | 723 +++++++++++++----- 2 files changed, 529 insertions(+), 199 deletions(-) diff --git a/docs/README.md b/docs/README.md index 11600184b..731b66111 100644 --- a/docs/README.md +++ b/docs/README.md @@ -34,6 +34,11 @@ - [浏览器内 AI Web 工程沙箱预览](./technical/【技术方案】浏览器内AIWeb工程沙箱预览方案-2026-06-13.md) - [AI Web 工程 Runner 安全模型](./technical/【安全模型】AIWeb工程Runner与预览隔离威胁模型-2026-06-13.md) +### AI 游戏创作 Runtime + +- [AI 游戏创作 Agent Runtime 交互边界重构实施计划](./technical/【技术方案】AI游戏创作Agent%20Runtime交互边界重构实施计划-2026-08-12.md) +- [AI 游戏创作 Agent Runtime V1.1](./technical/【技术方案】AI游戏创作Agent%20Runtime%20V1.1-2026-07-12.md) + ### 后端与公开数据 - [外部生成 Worker 化方案](./technical/【后端架构】外部生成Worker化方案-2026-06-03.md) diff --git a/docs/technical/【技术方案】AI游戏创作Agent Runtime交互边界重构实施计划-2026-08-12.md b/docs/technical/【技术方案】AI游戏创作Agent Runtime交互边界重构实施计划-2026-08-12.md index d3dfe8d0c..a3628b63c 100644 --- a/docs/technical/【技术方案】AI游戏创作Agent Runtime交互边界重构实施计划-2026-08-12.md +++ b/docs/technical/【技术方案】AI游戏创作Agent Runtime交互边界重构实施计划-2026-08-12.md @@ -1,13 +1,13 @@ # AI 游戏创作 Agent Runtime 交互边界重构实施计划 -更新时间:`2026-08-12` -状态:评审中 +更新时间:`2026-08-13` +状态:评审中(协议冻结前禁止进入工程编码) ## 0. 目标与范围 统一 Consumer(GUI / CLI / 自动化测试)与 Runtime 之间的公开交互边界,达到: -- **Consumer 只做两件事**:`render(state)` 与 `dispatch(intent)`,中间不保留决策。 +- **Consumer 对 Runtime 生命周期只做两件事**:`render(snapshot)` 与 `dispatch(command)`,不保留跨轮业务真相或生命周期决策;conversation/response stream 仍是独立展示通道,但不得反向推导 Runtime 状态。 - **交互 Loop 收归后端 Supervisor Shell**:Consumer 不再根据 Runtime 状态自行选择 start / steer / confirm / retry / resume。 - **GUI、CLI、测试夹具是同一套协议的平等 Consumer**,GUI 没有任何特权通道。 - **Runner 自驱**:工作发现、恢复、继续执行不依赖 Consumer 在线或主动触发。 @@ -24,102 +24,395 @@ ## 1. 交互 Loop 协议(Interaction Contract) -这是 Consumer 与 Supervisor Shell 之间唯一的公开协议。协议不绑定 transport(Tauri 命令 / Runner 协议 / 进程内函数均可用同一套 DTO)。 +这是 Consumer 与 Supervisor Shell 之间唯一的公开 Runtime 控制协议。协议不绑定 transport(Tauri 命令、Runner 协议和进程内测试均复用同一语义),但 transport 必须把调用来源传给 Shell,不能信任 Consumer 自报权限。 -### 1.1 出向事件(Shell → Consumer) +权威执行拓扑固定为:Tauri/CLI 只是 transport adapter;启用 External Runner 时,五个写命令和 Public projector 的权威 Shell handler 必须在已经取得 project execution owner 的 Runner 内执行并写项目 ledger,GUI 不得先行写一份平行 ledger。Public read/订阅通过 Runner 返回已修复投影;Runner 不可达时 transport 返回 `TRANSIENT_UNAVAILABLE`,但不能把可能陈旧的 Snapshot 伪装成成功响应。非 owner 进程不得修复 dirty journal。未启用 Runner 的进程内模式和测试使用同一 handler,并先取得等价 project owner。Developer read 在 Tauri/受信任开发 CLI 内只读内部状态并经过宿主来源 capability,不承担 Public 投影修复。 -| 事件 | 含义 | 现状落点 | 是否新增 | -|---|---|---|---| -| `progress` | 运行推进 | `status/phase` + `recentEvents` + `waitingOn/nextStep`,经 `read_game_creator_agent_runtimes` 与 `game-creator-agent-progress` 事件 | 收敛 | -| `needs_input` | 等待澄清回答 | `userInputRequest` / phase `waiting-for-user-input`(`AgentRuntimeUserInputRequest`) | 收敛 | -| `approval_required` | 等待开发者批准工具动作 | `pendingToolAction` / phase `waiting-for-confirmation`(`AgentRuntimePendingToolActionSummary`) | 收敛 | -| `tool_request` | 工具已请求/执行中 | 现散在 `recentToolCalls` + phase `action` | **新增派生** | -| `artifact` | 产物 / manifest 变化 | `game-creator-manifest-invalidated` + finalization journal | 收敛 | -| `done` | 本轮终态 | `completed` + `AgentRuntimeFinalizationJournal` + responseStream `committed` | 收敛 | -| `error` | 失败 / 需人工核对 | `failed` / `needs-reconciliation` + `error` | 收敛 | +V1 初始 `schemaVersion`(Rust 字段 `schema_version`)固定为 `game-creator-agent-interaction.v1`。Snapshot、事件 envelope、命令和公开错误必须携带该值,或由同一 transport 在调用前明确协商到该值;不支持的 major 返回 `PROTOCOL_VERSION_UNSUPPORTED`,写命令失败关闭。Rust 字段按 camelCase 序列化;公开枚举使用本文冻结的 lowerCamelCase wire value;所有公开 ID 均为不透明字符串,Consumer 不得从 ID 推导路径、run 或时序。V1 写命令使用严格字段合同:缺少必填字段、未知字段、重复字段、错误类型或超出长度/数量上限均返回 `INVALID_REQUEST`,不执行任何副作用;不通过“忽略未知字段”实现协议兼容,后续字段只能通过新 schemaVersion 引入。 -协议 DTO(camelCase 序列化,与现有一致): +### 1.1 项目身份、事实源与双 Snapshot + +- 项目 manifest 的稳定 `projectId` 是公开协议身份;`projectPath` 只作为本地 transport locator。Shell 每次调用都先 canonicalize locator、验证项目已在现有本地项目授权/目录簿中、取得并重读 manifest,再验证 `projectId` 一致。仅持有任意路径字符串不构成授权;符号链接、替换目录和 TOCTOU 按现有安全 path resolver/目录句柄约束处理。路径不进入公开 DTO、错误、事件、指纹或报告。 +- Runtime durable state 及其同事务/同锁持久投影是业务事实;`SupervisorPublicSnapshot` 是 Consumer 唯一可见的完整状态事实。事件、命令 ack、错误和 response stream 都不能被合并成另一份 Runtime 状态。 +- V1 每个项目只有一个 Public Snapshot、一个 `snapshotRevision` 和一个项目级事件流;Snapshot 只包含当前 Project Supervisor。专业 Agent/动态 child 只折叠为协作数量,不公开身份或列表。 ```rust -enum AgentRuntimeOutboundEvent { - Progress(AgentRuntimeProgress), - NeedsInput { request: AgentRuntimeUserInputRequestView }, - ApprovalRequired { action: AgentRuntimePendingToolActionSummary }, - ToolRequest(AgentRuntimeToolRequest), // 新增:Shell 从 recentToolCalls+phase 投影 - Artifact(AgentRuntimeArtifactEvent), // 收敛 manifest-invalidated + finalization - Done(AgentRuntimeDoneEvent), - Error(AgentRuntimePublicError), -} - -struct AgentRuntimeProgress { - project_path: String, agent_id: String, run_id: String, - status: String, phase: String, - current_task: String, current_action: String, - plan_steps: Vec, active_plan_step_index: Option, - waiting_on: String, next_step: String, // Shell 负责填充,Consumer 不再映射 phase→文案 +struct SupervisorPublicSnapshot { + schema_version: String, + snapshot_revision: u64, + event_cursor: String, + project_id: String, + supervisor: Option, + interactions: Vec, updated_at: u64, } -``` -> 与现状的关键差异:`progress` 里的 `waiting_on`/`next_step` 由 **Shell 填充**。当前是前端在 `model.ts:612/644` 硬编码 phase→中文文案,收归 Shell 后前端删除该映射。 +struct SupervisorRuntimeSummary { + agent_id: String, + session_id: String, + run_id: String, + status: AgentRuntimePublicStatus, + stage: AgentRuntimePublicStage, + completed_step_count: u32, + total_step_count: u32, + current_step_summary: Option, + waiting_on: Option, + next_step: Option, + collaborator_count: u32, + outcome: Option, + error: Option, + updated_at: u64, +} -### 1.2 入向命令(Consumer → Shell) - -| 命令 | 吸收的旧命令 | 说明 | -|---|---|---| -| `submit_intent` | `start_*` / `steer_*` | 一条消息可能 steer 进现有 run,也可能 start 新 run,由 Shell 判定 | -| `answer` | `answer_game_creator_agent_runtime_user_input` | 回答澄清 | -| `approve` | `confirm_*` / `reject_*` | 批准或拒绝,含 policy 确认卡 | -| `cancel` | `cancel_game_creator_agent_runtime_task` | 取消 | -| `resume` | `resume_*` / `confirm_resume_*` / `retry_*` / `confirm_retry_*` / `schedule_game_creator_agent_ready_tasks` | 恢复/重试/调度统一入口 | - -命令签名: - -```rust -#[tauri::command] -async fn submit_game_creator_agent_intent( - project_path: String, session_id: String, intent: String, - run_profile: Option, source: Option, -) -> Result; - -#[tauri::command] -async fn answer_game_creator_agent_interaction( - project_path: String, run_id: String, action_id: String, - request_id: String, response_id: String, answers: BTreeMap, -) -> Result; - -#[tauri::command] -async fn approve_game_creator_agent_interaction( - project_path: String, run_id: String, action_id: String, - approved: bool, note: String, -) -> Result; - -#[tauri::command] -async fn cancel_game_creator_agent_run( - project_path: String, agent_id: String, run_id: String, -) -> Result; - -#[tauri::command] -async fn resume_game_creator_agent_project( +struct DeveloperRuntimeSnapshot { + // 独立开发 DTO,不嵌入 Public 类型或 event cursor: + schema_version: String, + project_id: String, + source_snapshot_revision: u64, project_path: String, -) -> Result, String>; -``` - -协议外 API(不进交互 Loop,作为管理面保留在 Shell 上):goal CRUD、`compact`、会话管理、配置读写。 - -### 1.3 统一 InteractionRequired 语义 - -Shell 向 Consumer 暴露"必须等待外部回答"的单一概念,替代现在分散的 `pendingToolAction` / `userInputRequest` / policy 确认卡: - -```rust -enum AgentRuntimeInteractionRequired { - UserInput { request: AgentRuntimeUserInputRequestView }, - Approval { action: AgentRuntimePendingToolActionSummary }, - PolicyApproval { policy: String }, // 新增:吸收 confirm_resume/confirm_retry 的 agent.resume 确认卡 + selected_agent_id: String, + selected_session_id: String, + selected_run_id: String, + current_task: String, + current_action: String, + plan_revision: u64, + plan_steps: Vec, + active_plan_step_index: Option, + recent_tool_calls: Vec, + interaction_records: Vec, } ``` -> 现状的 `confirm_resume`(`commands.rs:1148`)与 `confirm_retry`(`commands.rs:1020`)是"要求用户确认策略"的产物,由 GUI 弹确认卡实现。收归 Shell 后,Shell 评估 `enforce_project_auto_permission_policy`(`verification.rs:813`),需要确认时返回 `InteractionRequired::PolicyApproval`,用户确认后经统一的 `approve` 命令继续。 +Public Snapshot 白名单固定为:稳定项目与当前 Project Supervisor 身份、紧凑阶段、完成数/总数、当前步骤摘要、等待对象、下一步、协作数量、正式用户可回答的最小交互、终态摘要和稳定公开错误。它不得包含项目绝对路径、完整任务/action/plan、动态 child 身份、原始 observation、工具名称/参数/计划、Provider 原文、`recentToolCalls` 或内部 interaction fingerprint。`tool_request` 不进入正式公开 Snapshot 或事件。 + +Developer Snapshot 使用独立命令和 Rust DTO,不嵌入 Public Snapshot,避免开发调用方误订阅正式事件后把两种投影合并。仅前端 `devMode`、query/hash 或调用者提供的布尔值不构成授权;Tauri 端只允许 debug 构建中受信任的 `developer` 窗口标签,受信任开发 CLI 使用显式本地 capability,进程内测试使用 test capability;release/client/supervisor-chat 和 Runner 普通 Consumer 一律返回 `PERMISSION_DENIED`。后续若开放其它开发调用方,必须新增等价的服务端 capability,不得复用 Public read 权限。 + +`snapshotRevision` 仅在 Public 白名单字段的规范化值真实变化并成功持久化时递增;Developer-only 变化不推进。数组按稳定 identity 排序、枚举和缺省值统一规范化后再比较,不能因文件遍历顺序产生新 revision。`updatedAt` 是该 Public 投影最后真实变化的持久时间,不直接复制底层 Runtime 每次内部写入的时间,也不参与顺序判断。投影修复若重建出同一规范化 Public Snapshot,不递增 revision、不更新时间,也不创建新逻辑事件。 + + +Projection-dirty 的提交顺序冻结为以下四步,所有会改变 Public 白名单的 Runtime 写入口必须复用,不得各自发明顺序: + +1. 在 project lock 内写入并同步 dirty journal,记录 `projectId`、operation identity、变更前 durable digest、预期写入口和 `journalVersion`。 +2. 调用现有 Runtime durable writer 原子提交业务事实;业务写入失败则将 journal 标记为可关闭的 no-op,不产生 Public revision。 +3. 从已提交的 durable state 生成规范化 Public Snapshot;若 hash 变化,按事件规则一次性持久化新 Snapshot、revision、event record 和 cursor;若 hash 未变化则只关闭 dirty journal。 +4. 同步 projection ledger 后关闭 journal,再进行 best-effort event delivery。任何中间崩溃都由 owner 恢复或 Public read 按 operation identity 幂等重跑第 3/4 步,不重复第 2 步业务副作用。 + +因此,“Runtime durable state 已提交但 projection 未刷新”是可自动补投影状态;“Runtime durable state 是否提交无法证明”不是可补投影状态,必须进入 `needs-reconciliation`。 + +Public status/stage 是稳定枚举,不直接透传内部 phase。映射必须穷尽已知内部状态:排队为 `Queued`;planning/LLM 为 `Running/Planning`;action/observation/协作为 `Running/Executing|Coordinating`;user input、developer approval、确定性 retry/lane/timer、paused 分别为 `Waiting` 下的明确 stage;completed、failed/budget-exhausted、cancelled 和 needs-reconciliation 分别映射稳定终态/核对态。遇到未知或互相矛盾的内部 status/phase 时不得猜成 Running,而要投影 `NeedsReconciliation` 和脱敏 `PUBLIC_STATE_INVALID`。具体映射表与 DTO 同模块维护并做穷尽契约测试。 + +进度只从当前 Supervisor 的可信结构化计划计算:`totalStepCount=planSteps.len()`,`completedStepCount` 只计 completed,当前摘要只取唯一 active step 的脱敏标题;没有结构化计划时为 `0/0`,不得按 tool/action/loop 数猜进度。`collaboratorCount` 只计当前 Supervisor run 的 durable、尚未终结专业协作单元并去重,不包含历史 child。`waitingOn/nextStep/outcome/error` 是有界、脱敏、仅展示的 Shell 文本,Consumer 不得解析它们路由命令;可执行能力只由 interaction view 和写命令结果决定。 + +所有可能改变 Public 白名单的 Runtime 写入都必须经过统一 projection-dirty 协议:先在同一 project lock 下写 durable dirty journal,再提交原 Runtime 变更,随后重建 Public Snapshot/事件并关闭 journal。变更前崩溃可重建为无变化,变更后崩溃可由 Public read、订阅启动、Runner 启动或项目 wake 幂等补投影。P2 必须枚举并接入现有 state、task、interaction、终态和恢复写入口;不允许依赖 Consumer 轮询偶然发现漏掉的内部变更。 + + +### 1.1.1 身份来源与生命周期 + +公开协议中的三类身份不是同一个概念,来源和生命周期固定如下: + +| 身份 | 权威来源 | 生命周期与约束 | +|---|---|---| +| `projectId` | 项目 manifest 的持久字段 | 创建项目时生成一次;迁移时只允许从已验证的旧 manifest 显式导入;写入后不可变。缺失、重复或 manifest 校验失败时项目进入 `needs-reconciliation`,不得按路径或名称猜测身份。 | +| `sessionId` | 现有项目会话管理记录 | 由会话管理面创建并持久化,带项目归属、角色和 `sessionRevision`;Project Supervisor 只能绑定一个当前有效 session。结束或切换 session 后旧 session 不可作为新命令 target。Runtime 命令不隐式创建或切换 session。 | +| `runId` | Runtime durable run 记录 | Shell 在产生 Runtime 副作用前预分配并持久化;一个 runId 只对应一次 run,终态后不可复用。`acceptedRunId` 只是该同一 runId 的公开回显,不是第二套身份。 | + +`actionId`、child instanceId、executor generation 和下游 provider request identity 只属于内部 durable 记录;它们可以参与内部恢复和幂等,但不进入正式 Public Snapshot、公开事件、公开错误或 Consumer 路由。`projectPath` 只在 transport 到 Shell 的第一步作为 locator 使用,完成 canonicalize、目录簿授权、目录句柄绑定和 manifest 复核后丢弃;后续日志和协议只使用 `projectId`。 + +所有身份校验都在取得 project execution owner 后、写入 request ledger 前完成。locator canonicalize 与 manifest 复核必须针对同一已打开目录句柄完成;复核失败返回结构化错误并不产生 ledger 记录。会话或 run 的 revision 只由其权威持久化记录递增,不能使用 Consumer 看到的时间戳或事件 sequence 代替。 + +### 1.1.2 Public 摘要枚举与投影提交合同 + +`status`、`stage`、`waitingOn`、`nextStep` 和 `outcome` 是公开稳定枚举,不向 Consumer 透传内部 phase,也不要求 Consumer 解析自然语言。V1 至少冻结以下值: + +| 字段 | 稳定值 | +|---|---| +| `status` | `idle`、`queued`、`running`、`waiting`、`completed`、`failed`、`cancelled`、`needsReconciliation` | +| `stage` | `idle`、`planning`、`executing`、`coordinating`、`waitingForUserInput`、`waitingForPolicyApproval`、`waitingForDeveloperApproval`、`waitingForTimer`、`reconciling`、`completed`、`failed`、`cancelled` | +| `waitingOn` | `none`、`userInput`、`policyApproval`、`developerApproval`、`timer`、`runner`、`reconciliation` | +| `nextStep` | `none`、`submitIntent`、`answerInteraction`、`approveInteraction`、`cancelRun`、`resumeProject`、`waitForRunner`、`reconcile` | +| `outcome` | `none`、`success`、`failure`、`cancelled`、`unknown` | + +`waitingOn` 和 `nextStep` 只用于展示提示,任何可执行按钮必须来自当前 Public `interactions` 或明确的五命令能力;Consumer 不得根据这两个字段自行拼装命令。文案由 Shell 根据稳定值本地化并做长度、路径、Provider 原文和敏感信息过滤;文案变化不改变协议状态,不单独推进 `snapshotRevision`。 + +Public projector 的一次提交以 `projectId` 为边界,在 `project execution owner → supervisor project lock → projection journal` 的锁序内完成: + +1. 读取并规范化 durable Runtime state、当前 Supervisor session/run 和 User audience interaction;校验身份、枚举和白名单。 +2. 对规范化 Public DTO 计算 `publicSnapshotHash`。与已持久化 hash 相同则关闭 dirty journal,不增加 revision、sequence、cursor 或 `updatedAt`。 +3. 有真实 Public 变化时分配 `snapshotRevision = previous + 1`、`sequence = previousSequence + 1`,并在同一 journal 中预分配 `eventId` 与新 opaque cursor。`eventId = sha256(RFC 8785 canonical JSON([projectId, snapshotRevision, publicSnapshotHash]))` 的编码结果只作为不透明 ID 返回;Consumer 不得解析其构成。 +4. 持久化 Snapshot、事件记录和 journal 状态。底层存储不能提供单文件事务时,使用 journal 恢复保证“旧 Snapshot/无事件”或“新 Snapshot/有唯一事件记录”两种可重建结果,不允许出现新 Snapshot 配旧 cursor 或同 revision 多事件。 +5. 只有 Snapshot 与事件记录均可读后才允许投递;投递失败不回滚事实,后续订阅或 wake 按同一 eventId 补投。 + +`event_cursor` 的流起点为项目专属的不透明 `origin` 游标;每个事件的 cursor 由 Shell 生成并持久化,禁止按时间、路径或可猜测的数字直接编码。读 Snapshot 与建立订阅必须共享一次 project projection lock 的线性化边界:Consumer 先取得 Snapshot 返回的 cursor,再以该 cursor 作为 `afterCursor` 建立订阅;订阅端先补发 cursor 之后已持久化的记录,再接收新投递。这样读取与订阅之间发生的事件不会丢失。 + + +### 1.1.3 Project owner、GUI-owner 与并发栅栏 + +项目执行所有权、Runner 存活所有权和 GUI 存活所有权是三层独立门禁,不得用一个布尔值互相替代: + +| 所有权 | 持有者 | 持久/运行时记录 | 失效行为 | +|---|---|---|---| +| project execution owner | 当前实际执行 Runtime 写入和调度的 Runner/进程 | 项目私有 owner record:`ownerInstanceId`、单调 `ownerGeneration`、lease 到期点 | 取得前不扫描、不写入、不修复;lease 失效后旧 owner 被 fencing,不能继续提交。 | +| Runner owner | 当前 Runner 进程 | Runner boot identity、drain 状态和 heartbeat | Runner drain 或进程失活时停止新调度;恢复只能由新 boot 在重新取得 project owner 后执行。 | +| GUI-owner | 当前授权 Runner 存活的 GUI 会话 | 现有 GUI-owner lease/heartbeat 与 release 协议 | heartbeat 到期或收到 release 后停止新调度;不撤销已持久 Runtime 事实,不把 GUI 断线当作任务取消。 | + +owner record 的取得、续租和释放使用同一项目锁内的 CAS;generation 每次成功换主递增,旧 generation 的写入返回 `OWNER_FENCED`,不得覆盖新 owner 的 ledger、Runtime state 或 projection。lease 到期不能只凭本地时钟判定可接管:新进程必须先取得 owner,再在锁内复核 Runner drain、GUI-owner 和 manifest projectId。时钟只用于 lease 超时提示,CAS/generation 才是权威。 + +调度 worker 的顺序固定为:取得/续租 project owner → 检查 Runner drain 与 GUI-owner → 从 durable wake/runnable 索引取一项 → 在 dequeue 前再次校验 owner generation 和项目状态 → 以同一 operation identity 调用 Runtime。任何检查失败都不得先 dequeue 后补救;旧 worker 在失去 generation 后的结果一律按 fencing 处理并进入既有 reconciliation 路径。 + +### 1.2 出向事件、有序性与重连 + +```rust +struct AgentRuntimeOutboundEnvelope { + schema_version: String, + event_id: String, + sequence: u64, + cursor: String, + snapshot_revision: u64, + project_id: String, + // 项目级事件可为空;有值时只作为当前 Supervisor 的刷新定位提示。 + agent_id: Option, + session_id: Option, + run_id: Option, + event: AgentRuntimeOutboundEvent, +} + +enum AgentRuntimeOutboundEvent { + SnapshotChanged, +} +``` + +V1 将公开事件刻意收窄为项目级失效通知。`agentId/sessionId/runId` 只在事件生成点存在当前 Project Supervisor 且能从同一投影线性化点确定时填充;项目级恢复、空 Supervisor 或多 Run 影响事件保持为空。它们不是状态载荷,也不能替代 Snapshot 中的当前身份。事件不携带 interaction、artifact、terminal 或 error 状态载荷;这些内容全部从完整 Public Snapshot 读取,避免事件类型演变成第二个 read model。规则如下: + +1. `eventId` 标识一次已提交的 Public Snapshot revision;同一项目同一 revision 的重建/重投沿用同一确定性 ID。投递至少一次,Consumer 按 ID 去重。 +2. `sequence` 在 `projectId` 事件流内从 1 严格递增,且一次有效 Public revision 最多对应一个 sequence。初始空投影的 revision 为 0、cursor 为流起点且不产生事件;第一次真实 Public 变化提交 revision/sequence 1。`cursor` 是绑定同一项目和 sequence 的不透明日志位置,不能跨项目使用或解析。 +3. Projection journal 的业务事实与投影提交顺序按 1.1.2 冻结;事件日志追加只允许在 Snapshot 可完整读取后进行。若崩溃在两步之间,恢复只补同一 revision 的唯一事件;不生成第二 revision 或第二 eventId。 +4. 一个 envelope 引用的 revision 发布时必须已经可读。Consumer 收到后读取完整 Snapshot,只接受 `snapshotRevision >= envelope.snapshotRevision`;本地已有更高 revision 时忽略提示。短暂读不到目标 revision 时有界重读,仍失败则显示结构化暂态错误,绝不合并事件载荷。 +5. 首次读取 Snapshot 得到与该读取线性化点一致的 `eventCursor`,随后从 `afterCursor` 订阅;读取与订阅间发生的更新会出现在补读结果中。断线后沿用最后确认处理的 cursor 补读。sequence 回退或缺口只触发补读与全量刷新,不猜测状态。 +6. 每个项目至少保留最近 256 条 envelope。cursor 未知、属于其它项目或已过窗口时返回 `CURSOR_INVALID` / `CURSOR_EXPIRED`;Consumer 重新读取完整 Snapshot,并从新 `eventCursor` 继续订阅。 +7. Snapshot 才是状态事实;事件日志只承担通知、缺口检测和审计定位,不能独立还原 Runtime 状态。 + +### 1.3 五个写命令、结果合同与幂等状态机 + +| 命令 | 吸收的旧命令 | V1 业务输入 | +|---|---|---| +| `submit_intent` | CLI `Reply/Execute/Resume` 与 `start_*` / `steer_*` | 用户消息、目标 Project Supervisor sessionId 与公开 runProfile;Shell 判定 direct reply/start/steer/resume,source 由 transport 派生 | +| `answer` | `answer_*_user_input` | `interactionId + responseId + answers` | +| `approve` | `confirm_*` / `reject_*` | `interactionId + responseId + decision(approve/reject)` | +| `cancel` | `cancel_*` | 当前公开 Project Supervisor `runId` | +| `resume` | `resume_*` / `retry_*` / `schedule_*` | 项目级恢复意图;若需确认则只创建/返回 InteractionRequired | + +所有写命令都包含: + +```rust +struct AgentRuntimeCommandMeta { + schema_version: String, + project_id: String, + request_id: String, +} + +struct InteractionResponseMeta { + interaction_id: String, + response_id: String, + expected_interaction_revision: u64, +} + +struct AgentRuntimeCommandResponse { + request_id: String, + request_fingerprint: String, + replayed: bool, + observed_snapshot_revision: u64, + result: T, +} + +enum AgentRuntimeCommandAck { + IntentAccepted { + disposition: IntentDisposition, // Reply | Start | Steer | Resume + accepted_run_id: Option, + response_message_id: Option, + }, + InteractionAccepted { interaction_id: String, decision: Option }, + CancelAccepted { run_id: String }, + ResumeAccepted { affected_run_count: u32 }, + InteractionRequired { interaction_id: String, interaction_revision: u64 }, +} + +struct AgentRuntimePublicError { + schema_version: String, + code: String, + kind: AgentRuntimePublicErrorKind, + retryable: bool, + message: String, + request_id: Option, + request_fingerprint: Option, + replayed: bool, + observed_snapshot_revision: Option, + interaction_required: bool, +} +``` + +`submit_intent` 要求 Consumer 先经现有会话管理面取得明确的 Project Supervisor `sessionId`;Shell 锁内验证该 session 属于本项目/当前 Supervisor,active session 已变化则返回 `TARGET_STALE`,本轮不把会话 CRUD 隐式塞入 Runtime 命令。`source` 由受信任 transport 固定映射,Consumer 不得自报任意 source;`runProfile` 使用公开白名单枚举并在锁内校验当前项目支持。Shell 复用现有 interaction kernel 决定 direct reply/execute/resume,再对 execute 决定 start/steer;direct reply 仍只写 conversation/response stream,不伪造 Runtime Snapshot 变化,其稳定 responseMessageId 预先写入 request ledger。显式 `resume` 命令服务按钮/自动化的结构化恢复意图,自然语言“继续”也可由 `submit_intent` 路由到同一内部实现。 + +`submit_intent/cancel/resume` 不使用项目级 `snapshotRevision` 作为业务 CAS:无关的进度刷新不能让用户命令无效。它们在锁内以 payload 中的精确目标身份和当前 durable state 校验可执行性;`cancel` 的 run 已切换时返回 `TARGET_STALE`。`answer/approve` 使用 interaction 自身的 `expectedInteractionRevision`,而不是全项目 Snapshot revision;Public Snapshot 中的 interaction view 同时投影该值。命令返回值只是最小 ack 和操作完成时观察到的 revision,不携带 Runtime state;Consumer 成功或 `interactionRequired=true` 后都重读完整 Snapshot。 command ack 的 `responseMessageId` 只用于在既有 conversation/response-stream 管道定位 direct reply;对话正文仍通过原有 durable conversation read/stream 获取,不进入 Snapshot、事件或 command response。`observedSnapshotRevision` 是操作完成时已闭合的 Public revision,不表示命令结果本身是一份状态。 + +公开错误使用稳定 `code/kind/retryable/message/interactionRequired`,命中 ledger 的错误还返回原 `requestFingerprint` 和 `replayed`。Consumer 不解析中文 message。最小错误矩阵如下;未知错误码按不可重试失败关闭: + +| code | 语义 | retryable / Consumer 动作 | +|---|---|---| +| `PROTOCOL_VERSION_UNSUPPORTED` / `INVALID_REQUEST` / `PERMISSION_DENIED` | ledger 前的版本、格式或权限拒绝 | false;修正客户端/权限,不能原请求盲重试 | +| `TARGET_STALE` / `INTERACTION_STALE` / `INTERACTION_ALREADY_RESOLVED` | 精确目标或 interaction 已变化 | false;重读 Snapshot,若仍需操作则新 requestId | +| `IDEMPOTENCY_KEY_REUSED` | 同 requestId/responseId 被不同内容复用 | false;视为调用方错误 | +| `COMMAND_IN_PROGRESS` | 同请求已有 live executor 或同 response 正在 Resolving | true;同 payload/requestId 读回或重试,不启动第二 executor | +| `COMMAND_RESULT_UNKNOWN` / `NEEDS_RECONCILIATION` | 已受理操作的外部结果无法证明 | false;重读并进入人工核对,禁止换 ID 自动重放 | +| `OWNER_UNAVAILABLE` / `OWNER_FENCED` / `TRANSIENT_UNAVAILABLE` | 尚未受理,或当前执行者已失去 owner generation | true;完全相同 requestId 可重试;旧 owner 不得继续写入 | +| `REQUEST_NOT_FOUND` | 当前 project ledger 没有该 requestId 的受理记录 | false;不泄漏项目存在性;调用方根据原 transport 结果决定是否用原 requestId 重试 | +| `CURSOR_INVALID` / `CURSOR_EXPIRED` | 事件补读起点非法或过期 | 不适用于写重试;全量读取 Snapshot 后换新 cursor | +| `INTERNAL` | 已脱敏的未分类内部失败 | false,除非未来细分为明确暂态 code | + +`retryable=true` 仅表示可用同一 requestId 重试同一请求;需要基于新 Snapshot 改变 payload 时必须使用新 requestId。 + + +### 1.3.1 请求结果读回与崩溃语义 + +`read_game_creator_agent_command_result(projectId, requestId)` 是只读恢复接口,不是第六个 Runtime 写命令。它返回项目内 request ledger 的以下稳定投影: + +```rust +struct AgentRuntimeCommandResultView { + schema_version: String, + project_id: String, + request_id: String, + request_fingerprint: String, + status: CommandLedgerStatus, + replayable: bool, + result: Option, + error: Option, + observed_snapshot_revision: Option, +} +``` + +读回先做 locator/projectId/调用来源复核,再在 project command lock 内查询;不能跨项目按 requestId 搜索。`prepared`/`executing` 返回 `COMMAND_IN_PROGRESS` 或等价的 `status`,Consumer 继续用同一 requestId 读回;`succeeded`/`rejected` 永久返回已保存结果;`outcome-unknown` 返回 `COMMAND_RESULT_UNKNOWN` 并标记 `needs-reconciliation`。如果 requestId 从未被受理,返回不泄漏项目存在性的 `REQUEST_NOT_FOUND`;该错误只表示“本次调用没有留下受理记录”,调用方仍需根据原 transport 响应决定是否使用同一 requestId 重试,不能据此生成新 requestId 重放未知副作用。已进入 ledger 的确定性业务拒绝必须通过 `rejected` 结果读回,而不是依赖错误文字重新判断。 + +request ledger 的状态转移冻结为: + +```text +prepared -> executing -> succeeded | rejected | outcome-unknown +prepared -> rejected (可证明尚未产生副作用的业务拒绝) +executing -> succeeded | rejected (有权威 durable 证据) +outcome-unknown -> needs-reconciliation(终态,不自动回退) +``` + +每条记录保存 `ledgerVersion`、创建/更新时间、请求指纹、操作身份、执行者 generation、状态和完整结果引用;写入使用临时文件/同步/原子替换,恢复时按版本校验,损坏记录保留原始证据并阻止同 requestId 再执行。`replayed=true` 仅表示返回已持久化的同一结果,不代表再次执行。任何“Shell 已写入 prepared 但 transport 未收到响应”的情况都必须先读回;不能以 unknown-command fallback 或新 requestId 规避 ledger。 + +request ledger 是项目级私有 durable 记录,状态为 `prepared / executing / succeeded / rejected / outcome-unknown`,并保存规范请求指纹、预分配的内部 operation identity、executor boot/generation 及完整权威成功或错误结果。恢复所需的用户消息/answers 只保存有界私有 payload 或指向既有 durable conversation/interaction record 的稳定引用,沿用现有内容安全、权限和脱敏规则;绝不复制到 Public Snapshot、事件或错误。requestId 提供幂等受理与结果读回边界,不承诺无法判定的外部副作用 exactly-once;这种窗口必须显式 outcome-unknown。处理顺序固定: + +1. transport 先 canonicalize root、验证本地项目授权、manifest `projectId` 与调用权限;版本/身份/权限失败发生在 ledger 之前,保证攻击者不能向任意项目写记录。 +2. Shell 用 RFC 8785 canonical JSON 规范化“schema version + command kind + projectId + 完整业务 payload(含 interaction/response identity、decision、answers 和任何精确 target)”,计算小写 SHA-256。`requestId`、路径、时间戳及 transport 字段不进指纹。 +3. 在 project command lock 内先按 `requestId` 查 ledger,再做任何当前状态校验。相同 ID/相同指纹的 `succeeded` 或 `rejected` 直接返回原结果;不同指纹返回 `IDEMPOTENCY_KEY_REUSED`;`prepared/executing` 返回 `COMMAND_IN_PROGRESS`(可同 ID重试/读回);`outcome-unknown` 返回原 `COMMAND_RESULT_UNKNOWN`,不再执行。 +4. 只有 ledger 未命中时才验证 target/interaction 当前状态,并在副作用前原子写 `prepared`;`submit_intent` 的 acceptedRunId/steerId 以及下游 Runner requestId 必须在 prepared 中预分配并在恢复时复用,不能在重试中生成第二身份。业务拒绝也原子落为 `rejected`,使同一请求重放得到相同结果。进入内部执行前转为 `executing` 并绑定 executor;完成内部状态写后必须先闭合对应 Public projection,再写入并回读 `succeeded/rejected` 权威结果和 `observedSnapshotRevision`。 +5. 崩溃恢复只能依据 ledger、executor 生命状态、Runtime journal 和既有 durable identity 前向闭合;仍有 live executor 时不得由第二执行者接管。能证明未产生副作用可用同一 operation identity 继续;能证明结果则幂等补投影/结果;外部结果不明则原子转为 `outcome-unknown` 并使项目进入 `needs-reconciliation`,禁止自动换 requestId 或重复入队。 +6. Consumer 对 transport 超时、`COMMAND_IN_PROGRESS` 或明确暂态错误只可重发完全相同 payload 和同一 requestId,或调用 `read_game_creator_agent_command_result(projectId, requestId)`;读回接口同样先完成 locator/project identity/权限复核,禁止跨项目扫描。 +7. V1 ledger 跟随项目 Runtime durable archive 生命周期保存,不按时间或条数隐式淘汰。若将来压缩,必须先设计持久 tombstone,使已淘汰 requestId 仍能失败关闭。 + +现有 Runner 内存 request cache、`acceptedRunId` 和 Goal CAS 只作为内部附加护栏,不替代公开 ledger。goal CRUD、`compact`、会话管理和配置读写属于管理面,不进入这五个 Runtime Loop 命令;它们若是公开写操作,继续遵守各自现行 CAS/权限合同,不能借本次重构降级。 + + +### 1.3.2 五命令请求体冻结 + +五个公开写命令使用严格 tagged union;除 `schemaVersion/projectId/requestId` 外,业务字段如下。未列出的字段不属于 V1,transport 不得透传额外字段参与执行;未知字段、重复字段和错误字段类型统一返回 `INVALID_REQUEST`,不得通过忽略未知字段实现兼容。后续新增字段必须提升 schemaVersion 或经过显式兼容协议协商,并同步更新请求指纹规则。 + +```rust +struct SubmitIntentCommand { + meta: AgentRuntimeCommandMeta, + session_id: String, + message: String, + run_profile: AgentRuntimeRunProfile, +} + +struct AnswerCommand { + meta: AgentRuntimeCommandMeta, + interaction: InteractionResponseMeta, + answers: Vec, +} + +struct ApproveCommand { + meta: AgentRuntimeCommandMeta, + interaction: InteractionResponseMeta, + decision: ApprovalDecision, // Approve | Reject,必填 +} + +struct CancelCommand { + meta: AgentRuntimeCommandMeta, + session_id: String, + run_id: String, +} + +struct ResumeCommand { + meta: AgentRuntimeCommandMeta, + resume_scope: AgentRuntimeResumeScope, // Project + reason: ResumeReason, +} +``` + +`submit_intent.message` 不能为空,长度上限沿用现有 Runtime 输入限制;`sessionId` 必须是当前 Project Supervisor 的 session。`cancel` 必须同时提交当前 `sessionId + runId`,防止旧 runId 被新会话误取消;run 已终结、session 已切换或绑定关系不一致均为 `TARGET_STALE`。V1 `resumeScope` 只允许 `project`,`reason` 使用稳定枚举 `userRequested | ownerRecovered | timerElapsed | interactionResolved`;`ownerRecovered/timerElapsed/interactionResolved` 只能由受信任 Shell/Runner 生成,Consumer 只能提交 `userRequested`。`runProfile` 只允许已注册的公开 profile 名称和版本,不能携带 Provider、模型、工具、提示词或路径配置。 + +`AnswerCommand` 的答案结构只允许 Public Snapshot 当前 interaction view 中声明的 question/option/自由回答约束;缺失、重复、越界或不符合约束返回 `INVALID_REQUEST`,不产生部分写入。`ApproveCommand` 的 decision 不允许由 UI button、命令名或缺省值推断。`requestFingerprint` 覆盖上述规范化业务请求体以及 `schemaVersion/commandKind/projectId`,不覆盖 transport source、locator、时间戳、重试次数或响应展示文案。通过身份、权限和版本校验的请求,即使因 target stale、interaction stale、当前状态不允许或策略拒绝而没有 Runtime 副作用,也必须在 ledger 中以 `rejected` 持久化;只有格式、版本、身份或权限失败且请求尚未进入项目 ledger 的情况才返回 `REQUEST_NOT_FOUND`。 + +### 1.4 InteractionRequired 身份、版本和解决状态机 + +```rust +// Shell 内部 durable record;不直接作为正式公开 DTO。 +struct AgentRuntimeInteractionRecord { + interaction_id: String, + interaction_revision: u64, + kind: AgentRuntimeInteractionKind, + scope: AgentRuntimeInteractionScope, + audience: AgentRuntimeInteractionAudience, + project_id: String, + agent_id: Option, + session_id: Option, + run_id: Option, + action_id: Option, + request_fingerprint: String, + bound_state_fingerprint: String, + status: AgentRuntimeInteractionStatus, + resolution: Option, + private_presentation: AgentRuntimeInteractionPrivatePresentation, +} + +struct AgentRuntimeInteractionView { + interaction_id: String, + interaction_revision: u64, + kind: AgentRuntimeInteractionKind, + scope: AgentRuntimeInteractionScope, + status: AgentRuntimeInteractionPublicStatus, + presentation: AgentRuntimePublicInteractionPresentation, +} + +enum AgentRuntimePublicInteractionPresentation { + UserInput { + questions: Vec, + allow_freeform: bool, + }, + PolicyApproval { + title: String, + summary: String, + allowed_decisions: Vec, + }, +} + +enum AgentRuntimeInteractionKind { UserInput, ToolApproval, PolicyApproval } +enum AgentRuntimeInteractionScope { Project, Run, Action } +enum AgentRuntimeInteractionAudience { User, Developer } +enum AgentRuntimeInteractionStatus { Open, Resolving, Resolved, Superseded } +enum AgentRuntimeInteractionPublicStatus { Open, Resolving } +``` + +`interactionId` 在项目内稳定唯一,`interactionRevision` 从 1 开始并只在该 interaction 的可回答内容、约束或状态变化时递增;`responseId` 在单个 interaction 内唯一。`boundStateFingerprint` 绑定创建交互的 durable 对象与策略前提,不能用全项目 revision 替代。项目级 resume/retry 的 PolicyApproval 使用 `scope=Project`,可在锁内覆盖重新枚举出的多个 run,因此 agent/session/run/action 均可为空;ToolApproval 使用 Action scope 并绑定精确 action;UserInput 按真实落点使用 Run 或 Action scope。 + +Public Snapshot 只投影 `audience=User` 且状态为 Open/Resolving 的最小 view;Open 可回答,Resolving 只显示处理中并禁用再次提交,Resolved/Superseded 从公开列表移除。正式写命令仍在执行时重新校验当前调用来源与项目策略,不公开 audience、request fingerprint、bound fingerprint、内部 actionId、策略字符串、工具名称/参数或动态 child 身份。`audience=Developer`(包括现行 ToolApproval)只进入 Developer Snapshot 的脱敏 debug interaction view;正式 Public 仅通过 Supervisor summary 的 `waitingOn=developer-approval` 表示暂停,不创建可点击 interaction。内部 private presentation 与 Public presentation 使用不同 DTO;Public presentation 采用严格 tagged union 和长度上限:UserInput 从本地私有 user-input sidecar 经过敏感信息/路径过滤后,复用现行最多 3 题、每题 2–3 选项与自由回答约束;若问题或选项不能安全公开则转为 `audience=Developer`/needs-reconciliation,不把原文带入 Public。用户 PolicyApproval 只含有界、脱敏的行为影响摘要和允许 decision。Public view 只允许 UserInput/UserInput 和 PolicyApproval/PolicyApproval 两种 kind/variant 组合,未知或不匹配的 variant 按不支持协议失败关闭;presentation 不是可执行 payload。Developer ToolApproval 使用独立 debug DTO。 + +`answer` 仅接受 UserInput,`approve` 仅接受 ToolApproval/PolicyApproval;命令与 kind 不匹配返回 `INVALID_REQUEST` 且零副作用。`answer/approve` 的处理顺序为:先走 request ledger 重放检查,再取得 project execution owner 与 interaction lock,重读 record,校验 `interactionId + expectedInteractionRevision`、状态为 Open、bound fingerprint 仍与 durable 对象一致,然后重新执行当前权限和策略检查。通过后以 responseId 和 response fingerprint 原子转为 Resolving,调用既有内部 answer/confirm/reject/resume 实现,最后写 Resolved 和权威 command result;response fingerprint 覆盖 interactionId、interactionRevision、responseId、decision/answers,不能只散列自由文本。`approve` 必须显式携带 `decision=approve|reject`;禁止从按钮、命令名或缺省值猜测。 + +即使 Consumer 更换 command requestId,同一 `responseId`、相同 response fingerprint 也只创建新 command ledger 的幂等成功结果并返回原 interaction resolution,不重复消费;同一 responseId 不同内容失败为 `IDEMPOTENCY_KEY_REUSED`。已由其它 response 解决返回 `INTERACTION_ALREADY_RESOLVED` 并携带 `interactionRequired=false`;interaction revision、绑定对象或策略前提漂移返回 `INTERACTION_STALE`,零副作用。项目其它无关 Runtime/Snapshot 更新不使 interaction stale。禁止只凭持久化的 `"agent.resume"` 字符串或旧 policy 文案直接恢复。 --- @@ -133,7 +426,7 @@ enum AgentRuntimeInteractionRequired { | Runner 自驱续跑定时器 | `runtime_driver/provider_recovery.rs` 的 `schedule_waiting_provider_retry_wake_after_lane_release` 等 | 已存在,P4 直接复用 | | 确定性 e2e | `scripts/agent-runtime-deterministic-playable-e2e.mjs` + `deterministic-lane-defense-provider.mjs`(`expectedProviderStats`/`expectedChildReport` 断言) | P0 基线扩展(协议级 trace) | | 版本协商 fallback | 前端 `model.ts:1186-1226` `isMissing*CommandError` | 迁移期新旧并存的标准模式 | -| 逐命令幂等 | `accepted_run_id`(`runtime_state.rs:1548`)、runner requestId 缓存(`runner/protocol.rs:353`)、goal CAS | P1 设计直接沿用 | +| 内部幂等护栏 | `accepted_run_id`(`runtime_state.rs:1548`)、runner requestId 缓存(`runner/protocol.rs:353`)、goal CAS | 继续复用;P1 建 request ledger 底座,P3 接入五个公开写命令 | | 进程内集成测试 | `src-tauri/tests/`(`command_runtime.rs`、`runtime_actions/`、`collaboration/`、`goal.rs`) | P1-P6 每步回归的护栏 | ### 2.2 需要收敛/改造的点 @@ -151,169 +444,201 @@ enum AgentRuntimeInteractionRequired { ## 3. 分阶段实施计划 -> 主线:**先建安全网 → 建统一入口 → 统一状态读取 → 收回决策 → Runner 自驱 → 迁移全部 Consumer → 删旧面**。 +> 依赖顺序:**先建安全网 → 建持久协议底座 → 建唯一读模型 → 一次性开放完整写协议并收回决策 → Runner 自驱 → 迁移 Consumer → 删除旧公开面**。后续阶段不得反向依赖尚未落地的公开 DTO。 -### P0 行为基线(安全网) +### P0 行为基线与协议验证框架 -**目标**:在改动前建立可判断"公开行为是否变化"的验证能力。 +**目标**:在改动前建立可判断迁移语义和新协议安全性的验证入口,不冻结旧 DTO。 -- 复用 `deterministic-lane-defense-provider.mjs`,在现有 `agent-runtime-deterministic-playable-e2e.mjs` 基础上**增加协议级事件 trace**: - - 录制一条确定性完整会话的**出向事件序列**(progress/needs_input/approval_required/done/error 的顺序与关键载荷),归一化 timestamp、`run_id`/`steer_id`/`request_id` 随机 ID、`accepted_run_id` 对账值。 - - 断言当前 master 的 trace 与预期一致(快照 diff)。 -- 建立统一的回归命令,P1-P6 每阶段结束必跑: - - 确定性 e2e(功能正确性) - - `src-tauri/tests/` 进程内集成测试(单元级护栏) - - 协议级 trace(迁移等价性) -- **不动 Runtime 代码**,只建安全网。 +- 复用确定性 Provider 与现有进程内 Runtime 测试,记录当前 master 的用户可见语义:提交模式、目标 run、等待/终态、批准/拒绝、取消、恢复及重复副作用计数;统一归一化随机 ID 和时间戳。 +- 为第 1 节建立尚未启用的契约 fixture:项目身份、Public 白名单、Developer capability、revision/cursor、request ledger 状态机、Interaction 状态机、结构化错误和崩溃点。 +- 基线比较只要求业务语义和副作用一致,不要求旧 DTO、旧事件名字或旧命令调用序列与新协议相同。 +- P0 不修改 Runtime 生产行为,也不以空 handler、ignored 断言或永真 stub 让新契约提前通过。 -**验收**:上述三条命令在当前 master 全绿,trace 基线文件入库。 +**完成门禁**:当前 master 基线可重复通过;每个协议不变量都有明确测试入口和预期失败原因,能区分“尚未实现”与“错误通过”。 -### P1+P2 统一入口 + 状态读取(adapter + Snapshot 投影) +### P1 持久协议底座 -**目标**:Consumer 改走新协议调用旧实现;状态读取收敛为稳定的公开 Snapshot。**旧接口全保留**为迁移期兼容路径。 +**目标**:先落不依赖公开命令和 Snapshot 的共享基础设施,避免 P1 命令反向依赖 P2/P3。P1 不注册五个新公开命令。 -**P1 适配器(`submit_intent` / `approve` / `answer` / `cancel` / `resume` 新命令)**: +- 新增 `agent/supervisor_shell/` 内部模块,集中处理 canonical root、manifest project identity、调用来源 capability、结构化公开错误映射和 project lock 顺序;`schemaVersion`、五命令、错误码及 Snapshot/Interaction DTO 放入同一共享契约模块,由 Rust 与 TypeScript 绑定共同生成/校验,避免两端手抄漂移。 +- 实现带 schema 的 command ledger、interaction ledger、projection journal 基础读写:私有目录、原子写/回读、损坏隔离、锁、状态转移校验、同 ID 指纹冲突和 archive 生命周期。 +- 将 RFC 8785 + SHA-256 规范化、request/response fingerprint、公开文本脱敏和稳定 ID 生成收敛为单一实现;禁止各命令自行拼接字符串做指纹。 +- 明确锁顺序为 `project execution owner → supervisor project lock → command/interaction/projection 子记录`;不得持有文件锁等待 Consumer,也不得绕过现有 Runtime 的 run/action 锁顺序。长时间内部执行使用 ledger ownership 标记而非长期占用 transport 线程锁。 +- 现有公开命令和 Runner 内存 request cache 行为不变;P1 只通过存储/状态机单测和 crash-point 测试验证底座。 -> **P1 与 P3 的边界**:P1 只做"命令可用 + `submit_intent` 内部判定 steer/start"。`answer`/`approve`/`resume` 在 P1 **只是入口封装**(内部调旧函数),"收到交互该调哪个命令、能否重试"的判定**仍在前端**;到 P3 才把判定收走,前端只剩 `submit`/`respond` 两种动作。 +**完成门禁**:ledger 状态转移、相同/不同指纹、torn write、损坏记录、权限拒绝和锁竞争均失败关闭;尚无新公开调用面,旧行为基线不变。 -- 新增 `src-tauri/src/agent/supervisor_shell/`,内含: - - `intent.rs`:`submit_intent` 内部路由——读当前 runtime → 判定 steer/start(逻辑取自 `swarm_cli/turn_dispatch.rs` 的 steer 判定与前端 `matchingAgentRuntimeForSteer`)→ 调现有 `steer_game_creator_agent_runtime_task_for_profile_at` 或 `start_game_creator_supervisor_background_task_for_session_at` → 返回 `{ mode, accepted_run_id, runtime }`。 - - `interaction.rs`:`approve/answer/cancel` 薄封装现有 `confirm/reject/answer/cancel` 内部函数(P1 只封装,判定仍在前端)。 - - `resume.rs`:`resume_game_creator_agent_project` 吸收 `resume/confirm_resume/retry/confirm_retry/schedule_ready`——Shell 判定是否需 policy 确认、是否需先 cancel 再重试(`needs-reconciliation` 分支,逻辑取自 `App.tsx:6205` `handleProjectSupervisorRetry`)。 -- 新命令与旧命令**同时注册**(`main.rs` invoke_handler)。 -- 前端新增"调新命令 → 后端报 unknown command → 回退旧命令"的版本协商(复用 `isMissing*CommandError` 模式),保证打包版本不一致时旧链路可用。 -- **fallback 边界**:仅"命令不存在(版本不兼容)"确定性回退;写操作的其他错误原样呈现,不做盲目重试(防重复入队)。 +### P2 Public/Developer Snapshot 与项目级事件流 -**P2 Snapshot 投影(状态读取收敛)**: +**目标**:先建立稳定、完整、可重连的唯一公开读模型,继续保留旧 read 接口供迁移。 -> **投影 = 读模型**:把同一份 Runtime durable state(唯一事实来源)按一个稳定、精简、面向消费的 schema 重新导出,作为 Consumer 的权威视图。它**不是新的事实来源**,只是同一份事实的另一种呈现;Consumer 依赖投影,业务真相仍在 durable state。 +- 从现有 Runtime state、task/event、response stream 元数据和 pending sidecar 生成第 1.1 节双投影;Public 只包含当前 Project Supervisor,Developer 通过独立受信任 capability 读取选定内部 run。 +- 在 shadow/read-only 语义下把既有 user-input、pending tool confirmation 和可识别的 policy confirm 物化为稳定 Interaction record/view:首次投影在 owner/lock 内持久化 identity,后续按 bound fingerprint 复用;旧 sidecar 缺少可信绑定时投影 needs-reconciliation,不能每次读取生成新 interactionId。P2 只建立/刷新 record,不改变旧命令的交互行为。 +- project projection ledger 原子维护当前规范化 Public Snapshot、revision、event cursor、最近 256 条 envelope 及恢复 journal;每次 Public read 先在 projection lock 内修复未闭合 journal,再返回同一线性化点的 Snapshot/cursor。 +- 提供 `read_game_creator_agent_runtime_snapshot`、Public 事件订阅和 `afterCursor` 有界补读;Developer read 使用独立命令/DTO,不与 Public 返回 union。 +- Public revision 只由白名单变化推进;事件只发布 `SnapshotChanged`,同 revision 的恢复沿用同 eventId。测试不得依赖 Tauri best-effort event 自身保存补读历史。 +- P2 不删除 Consumer normalize/merge,也不注册五个写命令;只允许测试或 shadow observer 对照旧 read 与新 Snapshot。 -- 新增 `supervisor_shell/snapshot.rs`:`AgentRuntimeSnapshot` 投影。 - - 输入:现有 `AgentRuntimeResult.state` + `recent_events`/`recent_tasks`/`response_stream`/`user_input_request`。 - - 输出:稳定的公开视图(agent/session/run 身份、status/phase、`InteractionRequired`、progress、终态、公开错误)。 - - **内部字段(recentToolCalls、observations、allowedTools、contextUsage 等)不进 Snapshot**。 -- 关键:**后端先补"稳定读取"**——现状前端 `normalizeAgentRuntimeState` 做跨轮 carry-forward,是因为后端 read 在恢复/竞态时字段不稳定。P2 后端投影保证同一 run 身份下字段自洽,前端才能删掉自己的修补。 -- 新增 `read_game_creator_agent_runtime_snapshot(s)` 命令(或改造现有 read 返回 Snapshot),旧 `read_game_creator_agent_runtime(s)` 保留。 -- 前端 `normalizeAgentRuntimeState` / `mergeAgentRuntimeStateIntoMap` / phase→文案映射**依赖 P2 稳定后删除**(本轮先做投影,下一阶段删前端逻辑)。 +**完成门禁**:重复、乱序、缺口、读订阅竞态、cursor 非法/过期、投影崩溃窗口和本地高 revision 均通过完整 Snapshot 收敛;正式 Public 零路径、完整任务/action/plan、动态 child、原始工具计划和 Provider 正文;Developer capability 服务端拒绝未授权来源。 -**验收**: -- 新命令与旧命令对同一场景返回的终态一致(用 P0 trace + 确定性 e2e 断言)。 -- 前端在"走新 Snapshot"下渲染与旧路径一致(组件回归)。 -- 现有 `command_runtime.rs` / `collaboration/` 测试全绿(旧逻辑未动)。 +### P3 完整写协议与 Interaction Loop 收归 -### P3 Loop 收归 Supervisor Shell +**目标**:在 P1 底座和 P2 唯一读模型都可用后,一次性注册真实可用的五命令;不发布“DTO 已存在但仍要求 Consumer 选择旧分支”的半成品协议。 -**目标**:Consumer 只 dispatch 意图,不再持有生命周期判断。 +- `submit_intent` 将 CLI 的 Reply/Execute/Resume interaction kernel 和 GUI 的 start/steer 判定上提到 Shell:先决定 direct reply/execute/resume,execute 再读取当前 Project Supervisor 与 run profile 决定 start/steer;source 由 transport 派生,最终调用现有内部实现。 +- 接管 P2 已物化的 user-input/tool-confirm Interaction record,并为项目 resume/retry policy confirm 创建稳定 record;`answer/approve` 只按 interaction response meta 路由,approve 显式处理 approve/reject。 +- `cancel` 锁内校验精确当前 Supervisor run;`resume` 统一处理 pending、确定性 retry/timer、ready task 和 reconciliation policy,遇到需人工确认时创建项目级 PolicyApproval 而不是执行。 +- 五命令全部先走 request ledger,再进行 target/interaction 校验和内部调用;成功、业务拒绝、并发 in-progress、崩溃可证明结果及 outcome unknown 都按第 1.3 节闭合。 +- Shell 负责生成 Public `stage/waitingOn/nextStep` 和安全 Interaction presentation;CLI 的 Reply/Execute/Resume、Consumer 的 start/steer/confirm/retry/resume 判断在此阶段只作为旧公开路径的兼容实现存在,不作为新命令输入。 +- 新旧公开命令并存,但新命令从注册之日起即具备完整生产语义。所有旧写 wrapper 同时改为经过同一 Shell project lock 和 projection-dirty/Interaction 同步 adapter:旧接口可以没有新 requestId 保证,但不能绕过 Interaction 状态、Public 投影或与新命令并发写出矛盾事实。P3 通过进程内调用和专用协议 harness 验证,不提前迁移正式 CLI/GUI 调用点。 -- 完成 `submit_intent` / `approve` / `answer` / `cancel` / `resume` 对全部旧分叉的吸收(P1 已建,本轮做全): - - `approve` 吸收 confirm/reject,并按 interaction kind 分派(`UserInput`/`Approval`/`PolicyApproval`)。 - - `resume` 吸收 resume/confirm_resume/retry/confirm_retry/schedule_ready。 -- 建立 `AgentRuntimeInteractionRequired`(见 1.3),Shell 统一暴露"需等待外部回答"。 -- Shell 负责填充 `waiting_on`/`next_step`(从 phase 映射,逻辑上提自 `model.ts:612/644`)。 -- 新增派生事件 `tool_request`(Shell 从 `recentToolCalls` + phase 投影)。 -- **前端删除**: - - `submitProjectSupervisorRuntimeTask` 的 steer/start 决策(`model.ts:849`)。 - - `agentRuntimeCanCancel/CanRetry/CanConfirm` 门禁(`model.ts:1131-1154`)。 - - `App.tsx:5981/6127/6205` 的 confirm/retry/repair 路由逻辑。 -- CLI(`swarm_cli`)改为调用同一 Shell:决策逻辑上提后,CLI 只保留 stdin/stdout 终端交互与 observer 渲染。 +**完成门禁**:五命令的同 requestId 重放、同键异内容、并发重复、业务拒绝重放、Runner 强杀读回和 unknown outcome 全部闭合;无关 Snapshot 更新不使 interaction 失效,interaction/策略漂移失败关闭;项目级 PolicyApproval 可安全覆盖锁内重新枚举的多个 run;新旧路径用户可见终态与副作用计数等价。 -**验收**: -- CLI 走新协议跑通完整 supervisor+子 Agent 流程(`agent-swarm-test-chat.mjs` / 确定性 e2e)。 -- GUI 提交、确认、重试、恢复均通过统一 `submit_intent/approve/resume`,无 `steer_*`/`confirm_*`/`retry_*` 直接调用。 -- P0 协议级 trace 在"新旧实现各放一遍"下事件序列一致。 +### P4 Runner 自驱与安全恢复 -### P4 Runner 自驱 +**目标**:Runner 合法存活期间,工作发现、确定性唤醒和安全恢复不依赖 Consumer 调用,同时保持 owner、drain 和未知外部结果边界。 -**目标**:工作发现、恢复、继续执行不依赖 Consumer 触发。**这是重构主线的一部分,不是独立项目。** +**项目目录簿**: -- 项目目录簿:`runner/state.rs` 的 `known_roots`(当前进程内)→ 持久化到 AppData(复用 runner 的 `--config-dir`),Runner 重启后仍知道持有过哪些项目。 -- 启动自恢复:`runner/server.rs` 启动完成后,对 known roots 调 `has_recoverable_game_creator_agent_background_tasks_at`(`recovery_scan.rs:425`),有可恢复工作则自动 `resume_game_creator_agent_background_tasks_at`。 -- 空闲自扫描:主 accept loop(`server.rs:275`,已有 25ms `EXTERNAL_AGENT_RUNNER_LOOP_INTERVAL`)内增加"是否有 pending 任务需 wake"的轻量检查,替代 Consumer 调 `schedule_game_creator_agent_ready_tasks` / `wake_pending`。 -- 吸收 `schedule_game_creator_agent_ready_tasks`:manifest ready 任务由 Runner 扫描发现并调度,删除前端 devMode 按钮(`App.tsx:10550`)。 -- **边界(不在本轮)**:Runner 仍由 GUI 启动(`main.rs:2124`),保留 GUI-owner watchdog(`server.rs:169`)与 `game_chat_release` 退出协议(`main.rs:2311`)。"自驱 = 存活期间自调度 + 启动自恢复",**不含**开机自启/无 GUI 常驻。 -- 对应删除前端恢复触发职责:`App.tsx:2799/10341`、`useDeveloperAgentPanel.ts:684` 的启动时 resume、resume 确认卡回调。 +- 将进程内 `known_roots` 扩展为 AppData 私有 `game-creator-known-roots.v1`。记录稳定 project identity、canonical root、首次/末次登记时间和有效状态,不保存用户输入、Provider 内容或凭据。 +- Unix 父目录/文件权限分别为 `0700/0600`;Windows 使用仅当前用户可访问的等价 ACL。写入使用同目录临时文件、文件同步、原子替换及目录同步(平台支持时);读取校验 schema、普通文件/非链接、owner/ACL、重复 identity 和重复 canonical path。 +- root 每次使用前重新 canonicalize 并重读 manifest identity。目录消失只标记失效;搬迁仅在新的、已授权 locator 注册并能唯一证明同一 projectId 时更新,不主动遍历磁盘寻找项目。identity/path 冲突或目录簿损坏保留证据并进入 reconciliation。 -**验收**: -- Runner 进程内:确认一个 pending 任务后无需任何 Consumer 调用即自动执行;恢复 pending 动作后自动续跑。 -- 确定性 e2e 增加"Runner 独立进程跑完整流程"用例(复用 `agent-runtime-real-e2e/harness/process.mjs` 的二进制编译能力)。 -- `confirm_resume` 恢复确认卡流程改为 `InteractionRequired::PolicyApproval` → `approve`。 +**wake、扫描与 owner**: -### P5 迁移 GUI / CLI / Tests +- Shell 成功提交工作、解决 interaction、写入确定性 timer、lane 释放、manifest ready 或 owner 状态变化后发送按 projectId 去重的有界进程内 wake;队列已满时只合并同 project wake,不阻塞持久提交。durable runnable state 本身是丢 wake 后的恢复依据,wake 不是事实源。 +- Runner 启动时及兜底轮询前,逐 root 完成 canonicalize/identity 校验并取得既有 project execution owner;未取得 owner 时不得扫描该项目 durable task、修改 Runtime 或标记活跃。每个 wake/扫描批次都有时间和工作量预算,同一热项目完成一批后重新排队,不能饿死其它 root。 +- 现有 25ms socket accept loop 继续只处理连接与 heartbeat,不在该线程中扫描目录或执行 Runtime 工作。另建阻塞式调度 worker(或等价专用 Runtime task)消费进程内 wake/队列信号;兜底扫描每个 Runner 最快 30 秒一轮,带抖动、轮转且每轮最多 8 个 root;启动恢复也使用同一有界批次并持续轮转,不能启动瞬间全量扫盘。 +- draining、GUI-owner 丢失或 project owner 释放开始后立即停止新 Runtime 调度;不得把已经受理的 cancel、读回、状态查询和 reconciliation 操作误判为普通新调度。除取消/收束类命令外,尚未写入 prepared 的会触发 Runtime 执行的新命令返回 `OWNER_UNAVAILABLE`;已开始的内部工作按现行 interruption/handoff/reconciliation 合同收束。 -**目标**:三类 Consumer 全部迁移到统一 Intent、Snapshot、Runtime Output。 +**恢复矩阵**: -- GUI: - - 状态渲染改读 `AgentRuntimeSnapshot`;删除 `normalizeAgentRuntimeState` / `mergeAgentRuntimeStateIntoMap` / phase 文案映射。 - - 交互全走 `submit_intent/approve/answer/cancel/resume`;删除 steer/confirm/retry/resume 直接调用与门禁。 - - 保留只读消费形态:response stream 展示、对话合并、画布资产编排(这些不进 Loop 协议)。 -- CLI:`cli.rs` 与 `swarm_cli` 改用 Shell 命令;删除各自状态机(决策已上提)。 -- Tests:`src-tauri/tests/` 迁移到新命令;`agent-runtime-real-e2e` 与确定性 e2e 走同一协议。 -- 迁移顺序:先 CLI(最薄)→ 再 Tests → 最后 GUI(唯一消费 response stream / conversation 合并 / goal CAS / 委派修复路由,工作量最大)。 +| durable 状态 / 条件 | 自动动作 | 禁止动作 | +|---|---|---| +| command `prepared` 且可证明未产生副作用 | 取得 owner 后继续同一 request | 不创建替代 requestId | +| command `executing` 且结果有可信 durable 证据 | 幂等补 command result / Public 投影 | 不重复内部或外部副作用 | +| command `executing` 且外部结果未知 | `outcome-unknown` + `needs-reconciliation` | 不自动重放 | +| Runtime `pending` 且身份可信、owner 已取得 | 可调度一次 | 不跨 owner 重复调度 | +| 等待确定性 timer / lane release | 到期或 wake 后继续 | 未到期不轮询重放 | +| Open user input / tool approval / policy approval | 保持 InteractionRequired,只刷新投影 | 不自动批准或把权限拒绝当恢复失败 | +| interaction `resolving` 且结果可证明 | 幂等补 Resolved 和 command result | 不重新消费 response | +| interaction `resolving` 且外部结果未知 | supersede/reconciliation,保留 response 证据 | 不以第二 response 自动解决 | +| Runtime `executing` 且 Provider/工具/进程结果未知 | `needs-reconciliation` | 不自动重放 request/action slot | +| journal、ledger、interaction 或 run 身份损坏/冲突 | `needs-reconciliation` | 不猜测、不覆盖证据 | +| 业务已完成、revision 尚未分配 | 由 dirty journal 生成唯一下一 revision/eventId | 不回滚业务事实、不跳号 | +| revision 已持久化但事件未闭合 | 幂等补同 revision/eventId | 不生成第二事件或第二终态 | +| project owner 未取得 | 不扫描 durable task、不执行 | 不越权读取后调度 | +| Runner draining / GUI-owner 丢失 | 停止新调度并收束进行中工作;允许只读、结果读回、取消和 reconciliation | 不接受新的自动恢复或新的 Runtime dispatch | +| root 不存在、搬迁或 identity 冲突 | 可证明失效则标记;其余 reconciliation | 不按旧路径执行 | -**验收**:GUI、CLI、Tests 调用面收敛到同一组命令,代码差异只剩输入输出形式。 +**边界**:Runner 仍由 GUI 启动,保留 GUI-owner watchdog 与 `game_chat_release` 退出协议;本轮不实现开机自启或无 GUI 常驻。 -### P6 删除旧公开面 -**目标**:Interaction Contract 成为唯一稳定公开边界。 +自动调度只允许处理以下状态:身份可信、已取得 project execution owner、项目不处于 draining、任务为 `pending` 且其依赖已满足,或确定性 timer/lane 到期且可证明尚未执行;投影补偿只允许重复生成已确定的 Snapshot/event 结果。`waiting for user input`、`waiting for policy approval`、`waiting for developer approval` 永不自动推进;`executing`、Provider/工具/进程结果未知、状态身份冲突和 `needs-reconciliation` 永不自动重放。Runner 的调度 worker 在 owner lease、GUI-owner lease 或 drain 状态任一失效时先停止 dequeue,再决定进行中工作如何收束;不得先取出任务后再补做 owner 检查。 -- 删除旧 Tauri 命令:`start_game_creator_agent_runtime_task` / `start_game_creator_supervisor_runtime_task` / `steer_*` / `confirm_*` / `reject_*` / `retry_*` / `confirm_retry_*` / `resume_game_creator_agent_runtime_tasks` / `confirm_resume_*` / `schedule_game_creator_agent_ready_tasks` / `read_game_creator_agent_runtime(s)`(`main.rs:2188-2208`)。 -- 删除对应后端 wrapper 与前端 `app/types.ts` 旧 DTO、旧事件协议、迁移期兼容逻辑。 -- 现有 start / steer / resume / recovery 能力作为 Shell 内部实现保留(改名/内联)。 +所有自动动作都必须记录可恢复的 wake reason 和 operation identity。wake 只负责唤醒,不能证明任务仍可执行;worker 每次 dequeue 前重新读取 durable state、owner generation 和 project drain 状态,校验通过后才创建或继续同一 operation。兜底扫描发现不满足上述条件的 root 时只记录跳过原因,不改变 Runtime 状态;扫描过程不得为了“发现新项目”而遍历未登记目录。 -**验收**:`grep` 无旧命令名残留;全量回归(P0 基线 + 单元 + e2e)全绿。 +**完成门禁**:目录簿安全合同和恢复矩阵逐行验证;25ms socket loop 零持久目录扫描/Runtime 执行;丢 wake 可由有界兜底恢复;未知结果、owner 冲突、drain 和 GUI-owner 丢失均零自动重放/新调度。 + +### P5 迁移 CLI、Tests、GUI + +**目标**:只迁移 Consumer,不在本阶段新增协议语义。顺序固定为 CLI → 面向公开协议的 Tests → GUI。 + +- 面向用户/自动化的 Supervisor CLI 改为 Public Snapshot + 五命令 + event cursor;现有 `--swarm-chat` 若继续展示完整专业 Agent 状态,必须明确归类为受信任开发 CLI 并走 Developer capability,不能一边读取内部字段一边宣称是正式 Public Consumer。 +- 公开 e2e/fixture 迁移到同一协议;直接调用内部 start/steer/resume 的单元和恢复回归继续保留,不把内部能力误算为 Consumer。 +- GUI 正式 Supervisor 只使用 Public Snapshot;删除 normalize/merge、phase 文案、steer/start、confirm/retry/resume 和启动 schedule-ready 决策。开发窗口显式走 Developer read capability;开发者对当前 Project Supervisor 的写操作仍走同一五命令,专业/child Agent 的直接调试控制属于既有受信任管理面,不伪装成正式 Supervisor 协议。 +- Consumer 只按结构化 code/kind/retryable/interactionRequired 分流;事件去重、缺口和 cursor 过期都只触发完整 Snapshot 读取。 +- 迁移期 fallback 仅处理 transport 明确的 unknown command:该错误证明新 handler 未执行,才可调用旧命令。任何已到达新 handler 的结构化错误或超时都不得 fallback;超时只可同 requestId 重试/读回。 + +**完成门禁**:三类正式 Consumer 的 Runtime 调用面一致,差异只剩输入输出形态;Public/Developer 类型无交叉;迁移前后用户语义和副作用计数等价。 + +### P6 删除旧公开面并最终收口 + +**目标**:Interaction Contract 成为唯一稳定公开 Runtime 控制边界。 + +- 从 Tauri invoke handler 和其它正式 transport 删除旧 start/steer/confirm/reject/answer/cancel/retry/resume/schedule/read 注册、旧公开 DTO、旧事件和 migration fallback。 +- 删除 Consumer 旧调用点与生命周期分支;管理面 goal/compact/session/config 按第 1.3 节边界保留。 +- Shell 内部 start/steer/resume/recovery 函数、Runner 内部方法及验证这些能力的回归测试允许保留或重命名,不设置全仓旧名称为零的伪门禁。 +- 同步 Runtime V1.1、智能体 App 实施计划、文档索引和长期架构记忆,确保本计划不成为与权威 Runtime 并行的冲突事实源。 + +**完成门禁**:定向静态检查证明 invoke handler、正式 transport、Consumer 调用点和公开 DTO 不再引用旧协议;协议契约、进程内、确定性、独立进程恢复、真实 Runner 和前端验证覆盖最终调用面。 --- -## 4. 迁移策略(新旧并存 + 等价性) +## 4. 迁移与兼容原则 -1. **接口即实现,不留空窗**:新命令从注册第一天起就是真实可用——内部套用现有旧函数(adapter 套旧实现是**常态、透明**,前端不知道也不关心)。不存在"接口先立、实现待填"的中间态;分阶段的不是"接口 vs 实现",而是"谁先切到新接口"。 -2. **新旧并存**:P1 起新命令与旧命令同时注册,Consumer 逐个切换,旧路径逐条下线(strangler fig)。 -3. **版本协商 fallback(仅用于迭代空窗期)**:前端调新命令,**仅当**后端报 unknown command(前端版本 ≠ 后端版本,打包错位)时回退旧命令;其他运行错误**原样呈现,不盲目重试**(防重复入队)。后端新版随应用覆盖到位后,fallback 即死代码,P6 删除。 -4. **等价性保障**: - - 确定性 provider + 协议级 trace(P0)作为新旧实现的对照基线。 - - 进程内集成测试每阶段全跑。 - - 关键迁移点(steer/start 判定、resume 路由、needs-reconciliation 重试)用"同一输入 → 新旧实现输出一致"的单测锁定。 +1. **依赖先行,不发布半协议**:P1 只建内部底座,P2 先稳定 read,P3 才同时注册完整五命令;公开 handler 出现时必须可真实执行、读回和恢复。 +2. **新旧并存只发生在 P3–P5**:旧公开命令服务尚未迁移的 Consumer,新协议服务已迁移 Consumer;两者调用同一内部 Runtime 能力并共享 Shell project lock、Interaction 和 projection 同步,只有新协议承诺 request ledger 的安全重试/读回合同。 +3. **fallback 不处理不确定结果**:只有 transport 的 unknown command 可走旧命令;超时、崩溃、结构化错误和结果未知必须沿新 request ledger 收敛。 +4. **等价比较看语义,不冻结旧结构**:比较用户可见状态、目标 run、interaction 结果、终态和副作用唯一性;不要求命令名、事件名、DTO 或中间调用序列一致。 +5. **旧接口删除前先完成调用图证明**:区分正式 Consumer、内部实现、恢复工具和回归测试,P6 只删除公开注册及调用,不误伤 Runtime 内部能力。 --- ## 5. 里程碑与验收门禁 -| 里程碑 | 交付 | 门禁 | +| 里程碑 | 交付 | 必须证明 | |---|---|---| -| M0 | P0 基线 + trace 入库 | 回归三件套全绿 | -| M1 | P1 新命令 + adapter(新旧并存) | 新/旧命令终态一致;现有测试全绿 | -| M2 | P2 Snapshot 投影 + 前端读 Snapshot | 前端删 normalize 后渲染回归一致 | -| M3 | P3 Loop 收归(Intent + InteractionRequired) | CLI 走新协议跑通完整流程;前端无 steer/confirm/retry 直调 | -| M4 | P4 Runner 自驱 | Runner 独立进程自动跑完;resume 确认走统一 approve | -| M5 | P5 全部 Consumer 迁移 | GUI/CLI/Tests 调用面收敛到同一协议 | -| M6 | P6 删旧面 | 无旧命令残留;全量回归绿 | +| M0 | P0 行为基线与协议 fixture | 旧语义可复验;每个新不变量有非伪造测试入口 | +| M1 | P1 持久协议底座 | ledger/锁/权限/崩溃状态机闭合,尚无半成品公开命令 | +| M2 | P2 双 Snapshot 与事件流 | Public 唯一事实、Developer 服务端授权、revision/cursor 可恢复 | +| M3 | P3 五命令与 Interaction Loop | 幂等/冲突/读回/Interaction/策略复核闭合,新旧语义等价 | +| M4 | P4 Runner 自驱 | 目录簿与恢复矩阵闭合,owner/drain/未知结果失败关闭 | +| M5 | P5 Consumer 迁移 | CLI/Tests/GUI 只经统一协议,正式/开发读模型隔离 | +| M6 | P6 旧公开面删除 | 正式注册/调用/DTO 无旧协议,内部能力与回归测试保留 | + +任一阶段只能依赖已完成的前序里程碑;不能以“后续阶段会补”为理由放行当前公开合同缺口。 --- -## 6. 风险与未决问题 +## 6. 关键风险与强制约束 -| 风险/问题 | 影响 | 缓解 | -|---|---|---| -| P2 依赖"后端 read 先稳定",否则前端不敢删 normalize | 阶段顺序敏感 | P2 后端投影先行,前端删逻辑放同一阶段尾 | -| P4 自驱与 GUI-owner 安全模型冲突 | 若误解为"无 GUI 常驻"会引安全评审 | 计划内明确边界,实现不越界 | -| steer/start 判定含 UX 语义(mode/source/runProfile、steerId 生成、acceptedRunId 对账) | 收归 Shell 后前端展示可能退化 | Shell 返回 `{ mode, accepted_run_id, steer_decision }` 补足展示信息 | -| `tool_request` 无现成单一落点 | 需 Shell 派生投影 | 提前排进 P2/P3 投影工作量 | -| 协议级 trace 的随机 ID 归一化 | 基线易碎 | 复用确定性 provider,归一化规则集中一处 | +| 风险 | 强制约束 | +|---|---| +| Snapshot 与 durable state 在文件崩溃窗口不同步 | projection journal + 单一锁序;Public read 先修复,事件永远只作提示 | +| 全项目 revision 导致无关更新误杀交互 | 回答 CAS 使用 interactionRevision;精确 target 命令锁内重读 durable identity | +| 重试先做当前状态校验而失去首次结果 | 固定先查 request ledger,再做状态/策略校验;成功和业务拒绝都持久化 | +| responseId 换 requestId 造成二次消费 | interaction resolution 同时保存 responseId/fingerprint,独立于 command requestId 去重 | +| command/interaction executing 的外部结果未知 | outcome-unknown / reconciliation;禁止自动重放或换 ID | +| Tauri 前端 devMode 被当作权限 | Developer read 只认服务端构建、窗口标签或显式受信任 capability | +| Public interaction 正文可能泄漏私有问题、工具计划或路径 | interaction audience + kind 白名单 + 公共内容安全过滤;不安全内容降为 Developer/reconciliation,原始信息仅私有 sidecar/开发 capability | +| 目录簿泄漏本地路径或扫描失控 | 私有权限、仅 AppData 持久化、公开零路径、wake 优先和有界低频轮转 | +| P3/P5 重复迁移导致阶段不可独立审查 | P3 只完成后端协议与 harness;P5 只切换 Consumer 和删 Consumer 决策 | +| 旧名称静态检查误删内部能力 | P6 只检查正式 transport、Consumer 和公开 DTO | + +--- ## 7. 建议实施顺序(一句话) -P0 建安全网 → P1+P2(adapter + Snapshot,纯增量、旧接口全留、fallback 兜底)→ P3 收 Loop → P5 迁移(先 CLI 后 GUI)→ P6 删旧面;P4(Runner 自驱)作为主线中心件贯穿 M4,不单独立项,但边界(存活期间自调度,不含无 GUI 常驻)在计划内写死。 +P0 建语义基线与契约 fixture → P1 建身份/权限/ledger/锁底座 → P2 建 Public/Developer Snapshot 与项目事件流 → P3 一次性开放完整五命令并收归 Interaction Loop → P4 按恢复矩阵实现 Runner 自驱 → P5 按 CLI、Tests、GUI 迁移 → P6 定向删除旧公开面。 --- -## 8. 相对原方案的调整点 +## 8. 协议冻结输出 -本计划在原方案基础上做了以下调整,均基于 master 现状与既有安全模型: +本方案提交后,以下内容视为 V1 编码前的冻结合同,不在实现 PR 中由 Consumer 或 transport 自行解释: -1. **P0 基线**:原方案的 Golden Replay 改为"确定性 e2e + 协议级事件 trace"——在既有确定性 provider e2e(`deterministic-lane-defense-provider.mjs`)上扩展,录制协议级事件序列并归一化 timestamp 与随机 ID,不重建基线体系。 +- 身份:manifest `projectId` 不可变;`sessionId` 来自会话管理面;`runId` 由 Shell 在 prepared 阶段预分配;所有 locator、owner generation 和内部 operation identity 不进入 Public 协议。 +- 所有权:project execution owner、Runner owner、GUI-owner 分层;owner generation 是 fencing 权威;未取得 owner、draining 或 GUI-owner 失效时不扫描、不执行、不接受自动恢复。 +- 投影:先 durable 业务事实,再按 dirty journal 补 Public projection;已提交事实但投影未刷新可幂等补偿,事实提交结果未知必须 reconciliation。 +- 事件:项目级 at-least-once `SnapshotChanged`;`snapshotRevision`、`sequence`、`eventId`、opaque `cursor` 的持久关系不可改变;事件只通知,Consumer 只能重读完整 Snapshot。 +- 写入:五命令统一 request ledger;相同 requestId/指纹回放原结果,异指纹冲突;结果未知不换 ID 重放;结果读回只按 `(projectId, requestId)` 查询。 +- 交互:`interactionId + interactionRevision + responseId` 独立于全项目 revision;Shell 在锁内重读交互和策略;项目级 PolicyApproval 不要求绑定单一 run。 +- 公开面:Public 字段白名单和稳定枚举是唯一正式 Supervisor 展示合同;Developer Snapshot 必须经过服务端 capability;`tool_request` 不进入 Public。 -2. **P4 定位与边界**:原方案将"Supervisor Lifecycle Coordinator"列为独立阶段;现作为主线组成部分(里程碑 M4,不单独立项)。自驱限于"Runner 存活期间自调度 + 启动自恢复",排除"开机自启 / 无 GUI 常驻",与既有 GUI-owner 安全模型一致。 +任何实现若无法满足上述合同,必须先修改本技术方案并重新评审,不得通过新增 Consumer fallback、缓存或隐式状态字段绕过。 -3. **术语收敛**:原方案"Interaction Contract / Intent / Snapshot"统一为本计划"交互 Loop 协议"(5 入向命令 + 7 出向事件)与"投影"(读模型),含义不变。 +--- -4. **迁移原则显式化**:接口即实现(adapter 套旧逻辑为常态);fallback 仅在版本空窗期、只认 unknown command。原方案未明确此点。 +## 9. 本轮设计修订结论 + +1. Snapshot 是唯一公开 Runtime 状态事实;事件收窄为项目级 `SnapshotChanged`,不再公开可被误合并的 run/interaction/tool 状态载荷。 +2. 项目 manifest `projectId` 是协议身份,`projectPath` 只是每次都要 canonicalize 和复核的私有 locator。 +3. Public Snapshot 只含当前 Project Supervisor 紧凑摘要与协作数量;Developer Snapshot 使用独立 DTO 和服务端 capability,前端 devMode 不算授权。 +4. 五命令不再统一滥用全项目 Snapshot revision:interaction 回答使用独立 interactionRevision,cancel 使用精确 run target,其余命令锁内校验 durable state。 +5. `approve` 显式携带 approve/reject decision;requestId 负责命令幂等受理/结果读回,responseId 负责 interaction response 去重,两层身份不可互相替代,未知外部结果不虚假承诺 exactly-once。 +6. request ledger 明确“身份/权限 → 指纹 → 先查 ledger → 再校验状态 → 写 prepared → 执行 → 权威结果”的顺序,并持久化成功和业务拒绝;外部结果未知统一 reconciliation。 +7. P1 改为内部持久协议底座,P2 先提供唯一 read model,P3 才注册完整可用的五命令;P3 不迁移 Consumer,P5 不再重复设计后端 Loop。 +8. Runner 自驱补齐跨平台目录权限、丢 wake 恢复、启动有界轮转、interaction resolving 和 command executing 恢复矩阵。 +9. P6 只删除正式 transport、Consumer 和公开 DTO 的旧协议引用,内部 start/steer/resume/recovery 能力和回归测试保留。 -- 2.52.0 From 17684223ab16759937c01e62d75c0015bd15b76f Mon Sep 17 00:00:00 2001 From: suzmii Date: Sun, 16 Aug 2026 12:09:38 +0800 Subject: [PATCH 3/4] =?UTF-8?q?=E5=AE=8C=E5=96=84=20Agent=20Runtime=20?= =?UTF-8?q?=E4=BA=A4=E4=BA=92=E8=BE=B9=E7=95=8C=E9=87=8D=E6=9E=84=E5=8D=8F?= =?UTF-8?q?=E8=AE=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 冻结 Public Snapshot、五命令和 Runtime 事件的权威边界 统一 Public wire schema、字段限制和 Rust 到 TypeScript 生成合同 补齐请求幂等、Interaction、审批、取消、恢复和 retry lineage 状态机 明确 submit、same-run steer、Goal Contract 与 slash management 路由 引入 durable record envelope、owner fencing 和跨平台恢复门禁 完善 Session rotation、handoff target 与 continuation set 恢复合同 拆分 direct reply、Runtime final reply、status 和 public event 交付 新增 Public conversation message、分页、去重和历史完整性合同 调整分阶段实施计划、兼容策略和编码前证据验收门禁 --- ...作Agent Runtime交互边界重构实施计划-2026-08-12.md | 1879 ++++++++++++++++- 1 file changed, 1796 insertions(+), 83 deletions(-) diff --git a/docs/technical/【技术方案】AI游戏创作Agent Runtime交互边界重构实施计划-2026-08-12.md b/docs/technical/【技术方案】AI游戏创作Agent Runtime交互边界重构实施计划-2026-08-12.md index a3628b63c..0feeeaef9 100644 --- a/docs/technical/【技术方案】AI游戏创作Agent Runtime交互边界重构实施计划-2026-08-12.md +++ b/docs/technical/【技术方案】AI游戏创作Agent Runtime交互边界重构实施计划-2026-08-12.md @@ -1,7 +1,7 @@ # AI 游戏创作 Agent Runtime 交互边界重构实施计划 -更新时间:`2026-08-13` -状态:评审中(协议冻结前禁止进入工程编码) +更新时间:`2026-08-15` +状态:评审中(已完成 #168、持续边界复核及冻结前补充审计;第 10 节证据门禁闭合前禁止进入生产实现编码,允许 P0 只读 fixture/test harness 用于验证本方案) ## 0. 目标与范围 @@ -10,7 +10,7 @@ - **Consumer 对 Runtime 生命周期只做两件事**:`render(snapshot)` 与 `dispatch(command)`,不保留跨轮业务真相或生命周期决策;conversation/response stream 仍是独立展示通道,但不得反向推导 Runtime 状态。 - **交互 Loop 收归后端 Supervisor Shell**:Consumer 不再根据 Runtime 状态自行选择 start / steer / confirm / retry / resume。 - **GUI、CLI、测试夹具是同一套协议的平等 Consumer**,GUI 没有任何特权通道。 -- **Runner 自驱**:工作发现、恢复、继续执行不依赖 Consumer 在线或主动触发。 +- **Runner 在 owner/lifecycle 门禁有效期间自驱**:工作发现、确定性唤醒和安全恢复不依赖 Consumer 轮询或主动触发;本轮仍保留 GUI-owner/Runner 生命周期门禁,不宣称无 GUI 常驻或无限制 headless 自驱。 本重构**不重新设计 Runtime 内部执行模型**(Part D 保持黑盒),只补充 Shell 需要的边界能力。 @@ -19,6 +19,7 @@ - Agent 执行状态机(main_loop / task_queue / recovery)内部重构。 - Runner 进程生命周期策略(开机自启 / 无 GUI 常驻)——自驱只限于"Runner 存活期间",保留 GUI 启动 + GUI-owner watchdog。 - LLM / Provider / 提示词体系改动。 +- 现有项目资源上传/登记、`preview.start`/`preview.validate`、本地预览 Registry 和 Session 管理面不在本协议内重定义;它们继续使用各自现行的项目权限、immutable revision、preview authorization 和 exactly-once 合同,不得因删除 Consumer 的 Runtime 生命周期分支而被误删或由前端从 Snapshot 自行推导。 --- @@ -28,13 +29,42 @@ 权威执行拓扑固定为:Tauri/CLI 只是 transport adapter;启用 External Runner 时,五个写命令和 Public projector 的权威 Shell handler 必须在已经取得 project execution owner 的 Runner 内执行并写项目 ledger,GUI 不得先行写一份平行 ledger。Public read/订阅通过 Runner 返回已修复投影;Runner 不可达时 transport 返回 `TRANSIENT_UNAVAILABLE`,但不能把可能陈旧的 Snapshot 伪装成成功响应。非 owner 进程不得修复 dirty journal。未启用 Runner 的进程内模式和测试使用同一 handler,并先取得等价 project owner。Developer read 在 Tauri/受信任开发 CLI 内只读内部状态并经过宿主来源 capability,不承担 Public 投影修复。 -V1 初始 `schemaVersion`(Rust 字段 `schema_version`)固定为 `game-creator-agent-interaction.v1`。Snapshot、事件 envelope、命令和公开错误必须携带该值,或由同一 transport 在调用前明确协商到该值;不支持的 major 返回 `PROTOCOL_VERSION_UNSUPPORTED`,写命令失败关闭。Rust 字段按 camelCase 序列化;公开枚举使用本文冻结的 lowerCamelCase wire value;所有公开 ID 均为不透明字符串,Consumer 不得从 ID 推导路径、run 或时序。V1 写命令使用严格字段合同:缺少必填字段、未知字段、重复字段、错误类型或超出长度/数量上限均返回 `INVALID_REQUEST`,不执行任何副作用;不通过“忽略未知字段”实现协议兼容,后续字段只能通过新 schemaVersion 引入。 +V1 初始 `schemaVersion`(Rust 字段 `schema_version`)固定为 `game-creator-agent-interaction.v1`。Snapshot、事件 envelope、命令和公开错误必须携带该值,或由同一 transport 在调用前明确协商到该值;不支持的 major 返回 `PROTOCOL_VERSION_UNSUPPORTED`,写命令失败关闭。Rust 字段按 camelCase 序列化;公开业务枚举使用本文冻结的 lowerCamelCase wire value,结构化 `error.code` 使用本文冻结的 SCREAMING_SNAKE code;所有公开 ID 均为不透明字符串,Consumer 不得从 ID 推导路径、run 或时序。所有带数据的公开 tagged union 统一使用扁平的内部 tag 形式:对象必须包含 `kind` 字段,variant 使用 lowerCamelCase wire value,其余 variant 字段与 `kind` 同级;不使用 serde 默认的外部 variant 包装,也不允许同一 union 在不同 transport 使用不同 tag。无数据的枚举仍序列化为 lowerCamelCase 字符串。V1 写命令使用严格字段合同:缺少必填字段、未知字段、重复字段、错误类型或超出长度/数量上限均返回 `INVALID_REQUEST`,不执行任何副作用;不通过“忽略未知字段”实现协议兼容,后续字段只能通过新 schemaVersion 引入。请求处理顺序固定为:先完成 schema/version/结构/类型/大小校验,再完成 locator、项目身份与来源权限校验,最后执行 session/rotation phase gate 和 ledger 查询;因此 rotation 期间 malformed request 仍返回 `INVALID_REQUEST`,只有结构合法且未命中既有 requestId 的新请求才返回 `TARGET_BUSY`。 + +带数据 variant 的 canonical JSON 形态固定为 `{ "kind": "", ...variantFields }`。例如 `SubmitIntentPayload::Conversation` 使用 `{"kind":"conversation","sessionId":"...","expectedSessionRevision":1,"message":"...","attachments":[],"intentKind":"createFromPrompt","entryBinding":null,"runProfile":{"name":"standard","version":"v1"}}`,`BuiltinCommand` 使用 `{"kind":"builtinCommand","commandLine":"/status","expectedParserVersion":"..."}`;`AgentRuntimePublicInteractionPresentation`、`AgentRuntimeIntentEntryBinding`、`AgentRuntimeResumeIntent`、`AgentRuntimeCommandAck` 等其它带数据 union 也必须遵守同一形态。`kind` 是必填 discriminator,variant 不得再携带同名字段;未知 kind、缺失 kind、variant 字段多带/缺失、错误类型和重复字段均按 `INVALID_REQUEST` 处理,并纳入 Rust→TypeScript schema 生成与 golden fixture。 + +V1 统一边界常量必须由 Rust 单一来源生成 TypeScript schema、fixture 和 transport validator,不能由各 Consumer 各自复制: + +| 项目 | V1 上限与校验 | 超限行为 | +|---|---|---| +| 通用 Public 不透明 ID(适用于所有 Public DTO/command/event 中的 `*Id` 字段与 `cursor`,包括 `projectId/agentId/sessionId/runId/requestId/interactionId/responseMessageId/conversationUserMessageId/runtimeStatusMessageId/steerId/cancelOperationId/eventId/collaborationId`;下列 UserInput ID 与已有 resource/artifact binding identity 是显式例外) | `1–128` 个 UTF-8 字节;禁止控制字符、空白首尾和路径分隔符;新增 Public `*Id` 默认继承该规则,不能靠名称枚举遗漏校验 | ledger 前 `INVALID_REQUEST` | +| UserInput `responseId` | 沿用现有 answer 合同,最多 `160` 个 Unicode scalar、`640` 个 UTF-8 字节;禁止控制字符,trim 后不能为空;该 ID 仍是不透明值,不得用于路径或时序推导 | ledger 前 `INVALID_REQUEST`;若未来收紧上限必须提升 `schemaVersion` | +| 单个 command JSON | `64 KiB`(UTF-8,包含 `schemaVersion/projectId/requestId`) | ledger 前 `INVALID_REQUEST` | +| `submit_intent.Conversation.message` | 最多 `4,000` 个 Unicode scalar,且最多 `16 KiB` UTF-8 字节 | ledger 前 `INVALID_REQUEST` | +| `submit_intent.BuiltinCommand.commandLine` | 最多 `512` 个 Unicode scalar,且最多 `4 KiB` UTF-8 字节;规范化后必须仍以 `/` 开头 | ledger 前 `INVALID_REQUEST`;未知命令只允许固定长度 direct reply | +| same-run steer | 每个 run 最多 `16` 条追加指令、累计最多 `16 KiB`;单条最多 `4 KiB` UTF-8(沿用现有 steer ledger 合同),不得因 Conversation message 上限为 `16 KiB` 而放宽单条上限 | ledger 前或 steer ledger 受理前 `INVALID_REQUEST` / `TARGET_BUSY` | +| 输入附件 | V1 仅允许随非空 message 提交,最多 `8` 个既有项目资源 binding;不得携带原始字节、绝对路径或临时上传 token | attachment-only 或超限在 ledger 前 `INVALID_REQUEST`;binding 漂移为 `ARTIFACT_BINDING_UNAVAILABLE` | +| `requestChanges.feedback` | 最多 `2,000` 个 Unicode scalar,且最多 `8 KiB` UTF-8 字节;过滤、Unicode 规范化后重新计数 | ledger 前 `INVALID_REQUEST`;不保存原文 | +| UserInput | 最多 `3` 个问题;单个答案最多 `4,000` 个字符;全部答案最多 `8,000` 个字符、`32 KiB` UTF-8 字节 | ledger 前 `INVALID_REQUEST` | +| Public presentation 文本 | `title/summary/currentStepSummary/currentTaskSummary/checkSummary/latestReworkSummary` 各最多 `512` 个 Unicode scalar、`2 KiB` UTF-8;单个 interaction presentation 最多 `8 KiB` | ledger 前 `INVALID_REQUEST`;投影超限为 `PUBLIC_STATE_INVALID` | +| Public UserInput presentation | `question.id` 沿用现有唯一 snake_case 合同,最多 `64` 个 ASCII/UTF-8 字节;`header` 最多 `12` 个 Unicode scalar、`48` 字节;`question` 最多 `400` 个 Unicode scalar、`1,600` 字节;每个 option 的 `id` 最多 `64` 个 UTF-8 字节,`label` 最多 `60` 个 Unicode scalar、`240` 字节,`description` 最多 `240` 个 Unicode scalar、`960` 字节;均沿用现有单行、控制字符和 trim 后非空合同;问题数为 `1–3`,每题 option 数为 `2–3` | ledger 前 `INVALID_REQUEST`;投影超限、不安全或结构不完整为 `PUBLIC_STATE_INVALID` | +| Public conversation `directReply.publicText` | 最多 `4,000` 个 Unicode scalar、`16 KiB` UTF-8 字节;复用现有有界安全文本常量,不另建平行正文机制 | conversation append/commit 前确定性拒绝:现有 response delivery 进入 `rejected`,command 返回有界 `INTERNAL` 安全摘要;不得提交 message、sequence 或 cursor,也不得留下无法分页补读的 committed 消息 | +| Runtime status / public event message | 脱敏 status/publicText 各最多 `512` 个 Unicode scalar、`2 KiB` UTF-8;status 使用固定模板,publicText 只来自 Rust allowlist projector | 超限或不安全内容不投递;status 硬门失败/unknown 阻止 Run 推进,event message 以带原因的 `discarded` tombstone 丢弃并保留私有审计 | +| Local transport reply / local error message | reply 最多 `4,000` 个 Unicode scalar、`16 KiB` UTF-8;error message 最多 `512` 个 Unicode scalar、`2 KiB` UTF-8;均拒绝控制字符并只允许安全摘要/受信任 resolver 展示值 | ledger/response 前 `INVALID_REQUEST`;已持久化记录损坏为 `CORRUPT_RECORD`;不得截断后当作完整路径、错误或结果提交 | +| Public interactions / 专业组 / progress / intent options | 同时最多 `16` 个 User audience interaction;专业组固定最多 `6` 个公开 slot;progress `activeGroups` 最多 `6`、`latestChecks` 固定最多 `4`;submit intent option 最多 `16` 个,动态 child 只计数不列身份 | 超出表示投影不变量破坏,进入 `PUBLIC_STATE_INVALID`,不得静默截断 | +| Project approval target set | 最多 `16` 个精确 action target;按 `(agentId,parentRunId,runId,actionId,actionFingerprint)` 去重并固化 digest | 超限或集合漂移为业务拒绝 / `INTERACTION_STALE`,不得扩展为“当前全部任务” | +| Public Snapshot JSON | 最多 `256 KiB` UTF-8;数组按稳定 identity 排序 | 超限进入 `PUBLIC_STATE_INVALID`,不得返回部分 Snapshot | +| Public event 补读 | 单次最多 `256` 条 envelope;`afterCursor` 最多 `128` 字节 | `CURSOR_INVALID` / `CURSOR_EXPIRED`,不自动扩大批次 | +| Public conversation 补读 | 单页最多 `256` 条 committed message、最多 `1 MiB` JSON;`limit` 为 `1–256`,`afterCursor` 最多 `128` 字节;committed message/cursor 在 session 生命周期内逻辑保留 | `CURSOR_INVALID` 或 `CONVERSATION_HISTORY_INCOMPLETE` 时不返回部分页;该接口不返回 `CURSOR_EXPIRED`,不能扩大批次、静默截断历史或改读私有 ledger | +| response stream | 沿用现有 `AGENT_RUNTIME_RESPONSE_STREAM_MAX_CHARS=32,000`;sidecar 最多 `256 KiB`(`AGENT_RUNTIME_RESPONSE_STREAM_SIDECAR_MAX_BYTES`) | 进入 response operation 的确定性失败或 `outcome-unknown`,不得截断后提交 | + +长度均按 UTF-8 字节和 Unicode scalar **同时**检查;组合 JSON 大小优先于单字段上限。上限改变属于 breaking contract,必须提升 `schemaVersion`,不能在实现 PR 中悄悄放宽。 ### 1.1 项目身份、事实源与双 Snapshot - 项目 manifest 的稳定 `projectId` 是公开协议身份;`projectPath` 只作为本地 transport locator。Shell 每次调用都先 canonicalize locator、验证项目已在现有本地项目授权/目录簿中、取得并重读 manifest,再验证 `projectId` 一致。仅持有任意路径字符串不构成授权;符号链接、替换目录和 TOCTOU 按现有安全 path resolver/目录句柄约束处理。路径不进入公开 DTO、错误、事件、指纹或报告。 - Runtime durable state 及其同事务/同锁持久投影是业务事实;`SupervisorPublicSnapshot` 是 Consumer 唯一可见的完整状态事实。事件、命令 ack、错误和 response stream 都不能被合并成另一份 Runtime 状态。 -- V1 每个项目只有一个 Public Snapshot、一个 `snapshotRevision` 和一个项目级事件流;Snapshot 只包含当前 Project Supervisor。专业 Agent/动态 child 只折叠为协作数量,不公开身份或列表。 +- V1 每个项目只有一个 Public Snapshot、一个 `snapshotRevision` 和一个项目级事件流。Snapshot 的控制主体仍只有当前 Project Supervisor,但必须同时投影当前 run 的 Runtime-owned progress 与六个静态专业组的有界只读状态;动态 child 仍只折叠为数量,不公开 instance 身份。仅给出 `collaboratorCount` 无法满足现有工作台底栏、专业 Agent 确认和失败重试合同,因此不再作为完整 Public read model。 ```rust struct SupervisorPublicSnapshot { @@ -42,11 +72,72 @@ struct SupervisorPublicSnapshot { snapshot_revision: u64, event_cursor: String, project_id: String, + session_context: AgentRuntimePublicSessionContext, supervisor: Option, + collaborators: Vec, interactions: Vec, + command_capabilities: AgentRuntimeCommandCapabilities, updated_at: u64, } +enum AgentRuntimePublicSessionContext { + Ready { session_id: String, session_revision: u64 }, + NeedsBootstrap, + HandoffInProgress, + NeedsReconciliation, +} + +enum AgentRuntimeCollaboratorProjectionSource { + Runtime, + ManifestFallback, +} + +enum AgentRuntimePublicStatus { + Idle, + Preparing, // 已受理但 Public status message 尚未提交,不可 dequeue + Queued, + Running, + Waiting, + Paused, + Cancelling, + Completed, + Failed, + Cancelled, + TerminalPending, // 终态摘要尚未完成唯一 Runtime status message + NeedsReconciliation, +} + +enum AgentRuntimePublicStage { + Idle, + Preparing, + PublicStatusPending, + Planning, + Executing, + Coordinating, + WaitingForUserInput, + WaitingForUserApproval, + WaitingForPolicyApproval, + WaitingForDeveloperApproval, + WaitingForTimer, + WaitingForRunner, + PausedByUser, + Cancelling, + Finalizing, + Reconciling, + Completed, + Failed, + Cancelled, + TerminalPending, +} + +enum AgentRuntimePublicOutcome { + None, + Success, + Failure, + Cancelled, + Unknown, +} + struct SupervisorRuntimeSummary { agent_id: String, session_id: String, @@ -56,14 +147,408 @@ struct SupervisorRuntimeSummary { completed_step_count: u32, total_step_count: u32, current_step_summary: Option, - waiting_on: Option, - next_step: Option, - collaborator_count: u32, - outcome: Option, - error: Option, + progress: Option, + waiting_on: AgentRuntimePublicWaitingOn, + next_step: AgentRuntimePublicNextStep, + collaborator_count: u32, // 当前 Supervisor run 中已登记且未终结的协作单元;不含 manifestFallback、历史 child 或静态占位组 + outcome: AgentRuntimePublicOutcome, + error: Option, updated_at: u64, } +struct AgentRuntimeSupervisorProgressView { + run_id: String, + loop_iteration: u32, + task_progress: AgentRuntimePublicProgressCount, + plan_progress: AgentRuntimePublicProgressCount, + active_groups: Vec, + latest_checks: Vec, + latest_rework_summary: Option, + updated_at: u64, +} + +struct AgentRuntimePublicProgressCount { + completed: u32, + total: u32, +} + +enum AgentRuntimePublicCheckKind { + Playtest, + StaticVerification, + CodeMutation, + ScreenshotVerification, +} + +enum AgentRuntimePublicCheckOutcome { + Pending, + Passed, + Failed, + Unknown, +} + +struct AgentRuntimePublicCheckSummary { + kind: AgentRuntimePublicCheckKind, + outcome: AgentRuntimePublicCheckOutcome, + summary: Option, + evidence_count: u32, +} + +struct AgentRuntimeCollaboratorSummary { + collaboration_id: String, // 公开不透明身份,不能解析为内部 agentId + group: AgentRuntimePublicCollaboratorGroup, + source: AgentRuntimeCollaboratorProjectionSource, // runtime | manifestFallback + parent_run_id: Option, + run_id: Option, + status: AgentRuntimePublicStatus, + stage: AgentRuntimePublicStage, + completed_step_count: u32, + total_step_count: u32, + current_task_summary: Option, + outcome: AgentRuntimePublicOutcome, + error: Option, + recovery: Option, + updated_at: u64, +} + +enum AgentRuntimePublicCollaboratorGroup { + Design, Art, Code, Balance, Audio, Publishing +} + +enum AgentRuntimeCollaboratorRecoveryView { + RetryAvailable, + RepairApprovalPending { + interaction_id: String, + interaction_revision: u64, + }, +} + +// RepairApprovalPending 不是新的写 capability;它只引用同一份 Public +// PolicyApproval interaction。Consumer 必须从 interactions 原样构造 approve, +// 不得从 recovery view 生成第二个 repair/approval 命令。 + +struct AgentRuntimeCollaboratorRunTarget { + collaboration_id: String, + parent_run_id: String, + run_id: String, + expected_terminal_revision: u64, +} + +struct AgentRuntimeCommandCapabilities { + submit_intent: Option, + cancel: Option, + resume: Vec, +} + +struct AgentRuntimeSubmitIntentCapability { + session_id: String, + expected_session_revision: u64, + conversation_options: Vec, + builtin_command: Option, +} + +struct AgentRuntimeBuiltinCommandCapability { + parser_version: String, + supported_commands: Vec, +} + +struct AgentRuntimeBuiltinCommandSummary { + name: String, + route: AgentRuntimeBuiltinCommandRoute, + accepts_attachments: bool, // V1 永远为 false;显式字段用于拒绝漂移 +} + +enum AgentRuntimeBuiltinCommandRoute { + DirectReply, + Interaction, + ManagementAction, + RuntimeCommand, +} + +enum AgentRuntimeSlashParseResult { + RuntimeBuiltinCommand, + LocalManagementRoute, + TransportOnly, + UnknownCommand, +} + +// 本地/全局 route(尤其是无 projectId/sessionId 的命令)不复用 Public +// Snapshot 中的 submit_intent capability;它们通过受信任 local transport +// 单独取得 parser capability。宿主来源由 transport 绑定,Consumer 不能自报 localScope。 +struct AgentRuntimeLocalManagementCapability { + schema_version: String, + capability_id: String, + parser_version: String, + scope: AgentRuntimeLocalCapabilityScope, + supported_commands: Vec, +} + +enum AgentRuntimeLocalCapabilityScope { + Global, + Project { + project_id: String, + session_id: Option, + expected_session_revision: Option, + }, +} + +// `Project` 是 capability 的上界,不代表每个命令都可以省略 session 或 +// target。Shell 在锁内按命令再次校验 route-specific invariant:Goal mutation +// (`/goal <目标>`、`edit`、`pause`、`resume`、`clear`) 必须同时携带 +// `session_id + expected_session_revision`,并在 command target 中携带 Goal ID +// 与 Goal revision;Goal read/status 可以只使用 project read capability。 +// `/resume` 必须绑定 session、run 以及 recovery target revision,不能只靠 +// project scope;`/project` 的初始解析和 `/config` 使用 Global scope,二者 +// 不得携带 project/session target;其它 project-scoped 命令若要求 active session +// 也必须显式声明并验证这两个字段。字段缺失返回 `SCOPE_MISMATCH`,不能以 +// `None` 放宽 mutation 权限或从当前窗口猜目标。 + +struct AgentRuntimeLocalCommandSummary { + name: String, + route: AgentRuntimeLocalCommandRoute, + // 服务端私有绑定的精确目标;Goal/Runtime/legacy trace 等 revision 变化时 + // 整个 capability 失效,Consumer 不解析 targetRef。 + target_ref: Option, +} + +enum AgentRuntimeLocalCommandRoute { + DirectReply, + ManagementAction, + TransportOnly, +} + +enum AgentRuntimeLocalManagementOperationStatus { + Prepared, + Executing, + Succeeded, + Rejected, + OutcomeUnknown, + NeedsReconciliation, +} + +// Local operation 与项目 request ledger 使用同一未知结果闭合规则: +// `prepared -> executing | rejected | outcome-unknown`,其中无法证明是否已 +// 开始执行时必须走 `outcome-unknown`;`executing -> succeeded | rejected | +// outcome-unknown`,`outcome-unknown -> needs-reconciliation`;只有受信任 +// reconciliation 流程能把 `needs-reconciliation` 推进为确定的 succeeded/rejected。 +// unknown/reconciliation 期间同一 (localScopeId, requestId) 只能读回或核对, +// 不能换 handle/requestId 重做;Local readback 对前者返回 COMMAND_RESULT_UNKNOWN, +// 对后者返回 NEEDS_RECONCILIATION,并始终保留原 operation identity。 + +// Local command 的 commandLine 只在受信任 transport → parser → resolver 的 +// 短链路中出现;含 host locator 时原始路径由 resolver 拆出并绑定临时 handle, +// command durable record 和 response 只引用 locatorHandleRef/digest,不保存原文。 +struct AgentRuntimeLocalManagementCommand { + schema_version: String, + capability_id: String, + request_id: String, + command_line: String, + expected_parser_version: String, +} + +struct AgentRuntimeLocalTransportReply { + text: String, +} + +struct AgentRuntimeLocalManagementResponseMeta { + schema_version: String, + // malformed/missing requestId 的 ledger 前错误使用 None;transport 自身 + // 仍按调用上下文关联该响应,不能伪造业务 requestId。 + request_id: Option, + request_fingerprint: Option, + replayed: bool, +} + +enum AgentRuntimeLocalManagementResponse { + Succeeded { + meta: AgentRuntimeLocalManagementResponseMeta, + result: AgentRuntimeLocalManagementResult, + // Succeeded 的 meta.request_fingerprint 必须为 Some;TransportClosed + // 也使用规范化 command fingerprint,不创建项目 ledger。 + }, + Failed { + meta: AgentRuntimeLocalManagementResponseMeta, + error: AgentRuntimeLocalManagementError, + }, +} + +enum AgentRuntimeLocalManagementResult { + Reply { reply: AgentRuntimeLocalTransportReply }, + ManagementAccepted { + operation_id: String, + status: AgentRuntimeLocalManagementOperationStatus, + resolved_project_id: Option, + project_operation_ref: Option, + }, + TransportClosed, +} + +// 本地 route 的错误是独立合同;Consumer/GUI/CLI 只能按 code/retryable 和 +// readback 处理,不能解析中文 message。错误响应不伪造 projectId,也不携带 +// observedSnapshotRevision 或 interactionRequired;前者只在成功结果的 +// `resolved_project_id` 有真实解析结果时出现,后者永远不是 local route 的字段。 +// ResponseMeta 是唯一的 requestId/fingerprint/replayed 来源;Succeeded/Failed +// 不得再复制这些字段。缺少/非法 requestId 的 ledger 前错误使用 +// `meta.request_id = None`,不因为错误本身无法构造而退回 transport 文本。 +struct AgentRuntimeLocalManagementError { + code: AgentRuntimeLocalManagementErrorCode, + retryable: bool, + // 只允许安全、有限的摘要;Consumer 不解析该字段。 + message: String, +} + +enum AgentRuntimeLocalManagementErrorCode { + CapabilityNotFound, + ProtocolVersionUnsupported, + InvalidRequest, + TargetStale, + ScopeMismatch, + PermissionDenied, + LocatorUnavailable, + CommandInProgress, + CommandResultUnknown, + NeedsReconciliation, + IdempotencyKeyReused, + CorruptRecord, + RequestNotFound, + Internal, +} + +// AgentRuntimeLocalTransportReply 可在受信任本地 UI/CLI 展示本地路径;它没有 +// observedSnapshotRevision,也不能进入 Public conversation/response stream。 +// `Succeeded` 与 `Failed` 是严格互斥的 tagged union;不存在 result/error +// 同时为空或同时存在的第三种形态。`requestFingerprint` 仅在完成规范化且 +// 未含 host locator 原文时返回;错误 message 只允许安全摘要,并遵守上方 +// Local transport reply / local error message 的字符、字节和控制字符上限。 + +// 不使用 intent/profile/binding 三个独立数组,避免 Consumer 误把它们做 +// 笛卡尔积。每个 option 是一个已经由 Shell 注册并校验过的组合;实际 +// Template/ExistingDesign 资源仍由资源管理面返回 immutable binding, +// capability 只声明所需 binding kind,不把资源目录复制进 Snapshot。 +struct AgentRuntimeSubmitIntentOption { + intent_kind: AgentRuntimeIntentKind, + run_profile: AgentRuntimeRunProfile, + entry_binding_kind: AgentRuntimeIntentEntryBindingKind, + input_policy: AgentRuntimeSubmitInputPolicy, + allowed_attachment_media_kinds: Vec, +} + +enum AgentRuntimeSubmitInputPolicy { + TextOnly, // textOnly + TextWithOptionalAttachments, // textWithOptionalAttachments;仍要求 message 非空 +} + +enum AgentRuntimeIntentEntryBindingKind { + None, + Template, + ExistingDesign, +} + +struct AgentRuntimeCancelCapability { + session_id: String, + expected_session_revision: u64, + run_id: String, +} + +enum AgentRuntimeResumeCapability { + ContinueSupervisorRun { + session_id: String, + expected_session_revision: u64, + run_id: String, + expected_run_revision: u64, + }, + RetrySupervisorRun { + session_id: String, + expected_session_revision: u64, + run_id: String, + expected_terminal_revision: u64, + }, + RetryCollaboratorRun { + session_id: String, + expected_session_revision: u64, + target: AgentRuntimeCollaboratorRunTarget, + }, +} + +enum AgentRuntimePublicWaitingOn { + None, + PublicStatusMessage, + UserInput, + UserApproval, + PolicyApproval, + DeveloperApproval, + Timer, + Runner, + Reconciliation, +} + +enum AgentRuntimePublicNextStep { + None, + WaitForPublicStatus, + SubmitIntent, + AnswerInteraction, + ApproveInteraction, + CancelRun, + ResumeRun, + RetryTerminalRun, + WaitForRunner, + Reconcile, +} + +enum AgentRuntimeCommandErrorCode { + ProtocolVersionUnsupported, // PROTOCOL_VERSION_UNSUPPORTED + InvalidRequest, // INVALID_REQUEST + PermissionDenied, // PERMISSION_DENIED + TargetStale, // TARGET_STALE + TargetBusy, // TARGET_BUSY + InteractionStale, // INTERACTION_STALE + InteractionAlreadyResolved, // INTERACTION_ALREADY_RESOLVED + CancelAlreadyTerminal, // CANCEL_ALREADY_TERMINAL + ArtifactBindingUnavailable, // ARTIFACT_BINDING_UNAVAILABLE + IdempotencyKeyReused, // IDEMPOTENCY_KEY_REUSED + CommandInProgress, // COMMAND_IN_PROGRESS + CommandResultUnknown, // COMMAND_RESULT_UNKNOWN + NeedsReconciliation, // NEEDS_RECONCILIATION + OwnerUnavailable, // OWNER_UNAVAILABLE + TransientUnavailable, // TRANSIENT_UNAVAILABLE + OwnerFenced, // OWNER_FENCED + RequestNotFound, // REQUEST_NOT_FOUND + CursorInvalid, // CURSOR_INVALID + CursorExpired, // CURSOR_EXPIRED + Internal, // INTERNAL +} + +enum AgentRuntimeCommandErrorKind { + Protocol, Validation, Authorization, Target, Interaction, + Idempotency, InProgress, UnknownOutcome, Ownership, Cursor, Internal, +} + +enum AgentRuntimeSnapshotErrorCode { + ConfigurationRequired, // CONFIGURATION_REQUIRED + AuthenticationFailed, // AUTHENTICATION_FAILED + RateLimited, // RATE_LIMITED + TransportFailed, // TRANSPORT_FAILED + ProviderFailed, // PROVIDER_FAILED + VerificationFailed, // VERIFICATION_FAILED + BudgetExhausted, // BUDGET_EXHAUSTED + SandboxDenied, // SANDBOX_DENIED + ArtifactUnavailable, // ARTIFACT_UNAVAILABLE + ReconciliationRequired, // RECONCILIATION_REQUIRED + PublicStateInvalid, // PUBLIC_STATE_INVALID + Internal, // INTERNAL +} + +enum AgentRuntimeSnapshotErrorKind { + Configuration, Authentication, RateLimit, Transport, Provider, + Verification, Budget, Sandbox, Artifact, Reconciliation, PublicState, Internal, +} + +struct AgentRuntimeSnapshotError { + code: AgentRuntimeSnapshotErrorCode, + kind: AgentRuntimeSnapshotErrorKind, + retryable: bool, + interaction_required: bool, +} + struct DeveloperRuntimeSnapshot { // 独立开发 DTO,不嵌入 Public 类型或 event cursor: schema_version: String, @@ -81,9 +566,64 @@ struct DeveloperRuntimeSnapshot { recent_tool_calls: Vec, interaction_records: Vec, } + +struct AgentRuntimeToolCallDebugView { + tool_call_id: String, + tool_name: String, + status: AgentRuntimeDebugStatus, + request_summary: Option, + response_summary: Option, + started_at: u64, + finished_at: Option, +} + +struct AgentRuntimeInteractionDebugView { + interaction_id: String, + interaction_revision: u64, + kind: AgentRuntimeInteractionKind, + status: AgentRuntimeInteractionStatus, + presentation: AgentRuntimeInteractionPrivatePresentation, +} + +struct AgentRuntimeInteractionPrivatePresentation { + title: String, + summary: String, + questions: Vec, + allowed_decisions: Vec, + private_context_summary: Option, +} + +struct AgentRuntimePrivateQuestion { + id: String, + header: String, + question: String, + options: Vec, + allow_freeform: bool, +} + +struct AgentRuntimePrivateQuestionOption { + id: String, + label: String, + description: String, +} + +enum AgentRuntimeDebugStatus { + Started, // started + Succeeded, // succeeded + Failed, // failed + OutcomeUnknown, // outcomeUnknown +} ``` -Public Snapshot 白名单固定为:稳定项目与当前 Project Supervisor 身份、紧凑阶段、完成数/总数、当前步骤摘要、等待对象、下一步、协作数量、正式用户可回答的最小交互、终态摘要和稳定公开错误。它不得包含项目绝对路径、完整任务/action/plan、动态 child 身份、原始 observation、工具名称/参数/计划、Provider 原文、`recentToolCalls` 或内部 interaction fingerprint。`tool_request` 不进入正式公开 Snapshot 或事件。 +Developer-only DTO 也必须沿用 V1 的字符串、数量、字节和内容安全上限;`request_summary`、`response_summary`、`private_context_summary` 只允许有界脱敏摘要,禁止 Provider 原文、凭据、完整工具参数、绝对路径和未经过滤的 observation。Developer capability 只允许读取这些 DTO,不能把它们作为 Public interaction 或命令 target 发送回 Shell。 + +Public Snapshot 白名单固定为:稳定项目与当前 Project Supervisor 身份、紧凑阶段、完成数/总数、当前步骤摘要、稳定等待枚举、稳定下一步枚举、六个静态专业组的有界只读状态、动态 child 数量、正式用户可回答的最小交互、可由服务端生成的 command capabilities、终态摘要和不含命令私有字段的 Snapshot error。命令错误(包括 requestId、requestFingerprint、replayed 和 observedSnapshotRevision)只出现在命令响应/结果读回,不得嵌入 Snapshot。它不得包含项目绝对路径、完整任务/action/plan、动态 child 身份、原始 observation、工具名称/参数/计划、Provider 原文、`recentToolCalls` 或内部 interaction fingerprint。`collaborationId`、`runId` 和 capability target 都是不透明句柄,不能由 Consumer 推导 agent/task/path;`tool_request` 不进入正式公开 Snapshot 或事件。 + +`sessionContext=NeedsBootstrap` 只表示现有项目确实没有可绑定的 Project Supervisor Session。若 active Session 索引缺失但项目内存在身份完整的当前 `project-supervisor` durable Runtime,projector 必须先按 1.1.1.b 的 project/session/run/lineage 证据恢复 Session record/handoff 并继续展示该 Runtime;证据冲突则 `NeedsReconciliation`,不能误报 NeedsBootstrap、隐藏已落盘失败事实或创建第二 Session。只有确无 Runtime/session 证据时,Consumer 才调用既有 Session 管理面创建/恢复 active session 后重新读取 Snapshot;不能向五命令伪造 sessionId 或把首次启动隐式塞入 submit_intent。 + +Session rotation 的 `Prepared` 只表示唯一 operation 已持久化、active index 尚未 fenced;它单独存在时继续按旧 active session 投影 `sessionContext=Ready`,不得投影 `HandoffInProgress`,也不得创建 successor session、handoff、target/continuation set、manifest 或其它 rotation 副作用。Prepared 后若旧 session revision、run creation epoch 或 active index 已变化,该 operation 必须以 `Rejected` tombstone 结束,不能用旧预期强行提交 fence。只有 operation 的 `phase=FenceCommitted`、`rotationFenceCommitMarker` 与 active index 的 `rotationFenceOperationId + rotationFenceCommitMarker` 在同一 journal 线性化提交并可一致回读后,才开始投影 `sessionContext=HandoffInProgress`。`FenceCommitted`、`HandoffManifestCommitted`、`SuccessorSessionCommitted` 和 `HandoffsCommitted` 均只允许读取 Snapshot、已有 command result/conversation 和 reconciliation 证据,`commandCapabilities` 为空,不能提交 submit/answer/approve/cancel/resume/retry,旧 session 的 interaction 也不能在切换期间写入;同时所有 interaction view 的 `actionable=false` 且 `allowedActions=[]`,Consumer 不得仅因 interaction 仍出现在 Snapshot 就渲染或派发 answer/approve。只有 `sessionContext=Ready`、interaction 为 Open 且对应 action 通过 capability/锁内复核时,Shell 才返回 `actionable=true`。rotation fence 生效后,新 requestId 的五命令统一在 ledger 前返回 `TARGET_BUSY`;已经存在的 requestId 仍可只读回原结果,但不能借重放推进新的 session-bound 副作用。只有 operation 为 `ActiveSessionCommitted`、`activeSessionCommitMarker` 可读,且同一线性化点满足 `activeIndex.committedRotationOperationId == operation.operationId`、`activeIndex.activeSessionId == operation.successorSessionId == successorSession.sessionId`、`activeIndex.activeSessionRevision == successorSession.sessionRevision`、manifest 的 operation/predecessor/successor/session revision 均与 operation 相等、`manifest.runCreationEpoch == operation.expectedRunCreationEpoch`、`activeIndex.runCreationEpoch == operation.expectedRunCreationEpoch + 1`,并且完整 handoff manifest、Run target set 与 continuation set 均可读时,才投影 `Ready { successorSessionId, successorSessionRevision }` 并生成新 capability;任一关系不成立统一 `NeedsReconciliation`,不得扫描其它 operation 猜测已提交者。Open/Resolving interaction、未终结 command/input envelope、DirectReply/RuntimeFinalReply、Runtime status/public-event delivery 的原 `sessionId` 保持创建时 lineage,不改写为 successor;successor 只能凭 manifest continuation item 继续授权、投影或恢复同一 record identity。未被 continuation set 覆盖的旧 session record 必须失败关闭。`Rejected` 在旧 active session 未改变时恢复为旧 `Ready`;`ReconciliationRequired` 或 index/manifest/target/continuation digest 不一致统一投影 `NeedsReconciliation`,清空全部写 capability。`HandoffInProgress` 期间只读,`NeedsReconciliation` 不产生写 capability。`source=manifestFallback` 只表示“没有可匹配 Runtime 的历史 manifest 摘要”,不得伪造 running、进度百分比、完成或失败结论;该状态的 command capability 必须为空,直到真实 Runtime Snapshot 建立。Transport 暂时读失败时,Consumer 可以在本地短暂保留最后可信 Snapshot 作为 stale display,但必须标记本地 stale、禁用除上一份精确 cancel capability 外的其它写按钮并继续重试;stale cancel 只是用户止损尝试,Shell 仍重新授权和锁内复核,且当前 rotation/drain/reconciliation phase gate 优先于 stale 止损例外。不得把 stale display 当作新的 Runtime 事实,也不得清空或倒退已有专业 Agent 状态。Shell 不接受 Consumer 传来的 stale 标记。 + +专业组投影必须按 `parentRunId == 当前 Project Supervisor runId` 精确筛选,并按固定六组顺序输出;旧父 run、其它项目、动态 child、没有父绑定的 Runtime 记录不能冒充当前协作。只存在 manifest 任务的静态组可以输出 `source=manifestFallback + parentRunId=None + runId=None + idle/idle/none` 的占位 slot,但不得携带 Runtime error、进度、当前任务或 recovery,也不能计作活动 collaborator;存在当前父 run 的真实 Runtime 记录后必须由 `source=runtime` 唯一替换。每个公开 `collaborationId` 都由 Shell 绑定真实 `agentId + parentRunId + runId`,失败重试和 ToolApproval 重新读回时必须同时核对该三元身份。`recovery=RetryAvailable` 只是展示“当前 commandCapabilities.resume 中存在同 collaborationId 的 RetryCollaboratorRun”;`RepairApprovalPending` 只引用 Public `interactions` 中同一 interactionId/revision 的审批,不是第二个写 capability。两处任一缺失或不一致都按 `PUBLIC_STATE_INVALID` 失败关闭。`expectedArtifacts`、artifact SHA、verification gate 和文本回执只以安全摘要/数量/结果类别投影,详情仍通过既有项目资源/文档 read model 读取,不能把专业 Agent 内部对话或私有 observation 塞入 Snapshot。 Developer Snapshot 使用独立命令和 Rust DTO,不嵌入 Public Snapshot,避免开发调用方误订阅正式事件后把两种投影合并。仅前端 `devMode`、query/hash 或调用者提供的布尔值不构成授权;Tauri 端只允许 debug 构建中受信任的 `developer` 窗口标签,受信任开发 CLI 使用显式本地 capability,进程内测试使用 test capability;release/client/supervisor-chat 和 Runner 普通 Consumer 一律返回 `PERMISSION_DENIED`。后续若开放其它开发调用方,必须新增等价的服务端 capability,不得复用 Public read 权限。 @@ -99,9 +639,11 @@ Projection-dirty 的提交顺序冻结为以下四步,所有会改变 Public 因此,“Runtime durable state 已提交但 projection 未刷新”是可自动补投影状态;“Runtime durable state 是否提交无法证明”不是可补投影状态,必须进入 `needs-reconciliation`。 -Public status/stage 是稳定枚举,不直接透传内部 phase。映射必须穷尽已知内部状态:排队为 `Queued`;planning/LLM 为 `Running/Planning`;action/observation/协作为 `Running/Executing|Coordinating`;user input、developer approval、确定性 retry/lane/timer、paused 分别为 `Waiting` 下的明确 stage;completed、failed/budget-exhausted、cancelled 和 needs-reconciliation 分别映射稳定终态/核对态。遇到未知或互相矛盾的内部 status/phase 时不得猜成 Running,而要投影 `NeedsReconciliation` 和脱敏 `PUBLIC_STATE_INVALID`。具体映射表与 DTO 同模块维护并做穷尽契约测试。 +Public status/stage 是稳定枚举,不直接透传内部 phase。映射必须穷尽已知内部状态:Start 在 user message 已提交但 Runtime-owned status message 尚未提交时为 `Preparing/PublicStatusPending`,`waitingOn=publicStatusMessage`、`nextStep=waitForPublicStatus`,绝不显示 `queued`;status commit marker 成功后才为 `Queued`。planning/LLM 为 `Running/Planning`;action/observation/协作为 `Running/Executing|Coordinating`;user input、user/tool/policy approval、确定性 retry/lane/timer 分别为 `Waiting` 下的明确 stage;Runner/owner 暂不可用或等待 Runner 恢复时为 `Waiting/WaitingForRunner`,`waitingOn=runner`、`nextStep=waitForRunner`;paused、cancelling 分别为 `Paused/PausedByUser`、`Cancelling/Cancelling`;根终态事实已确定但唯一 terminal failure status 尚未提交时为 `TerminalPending/TerminalPending`,`waitingOn=publicStatusMessage`,`outcome=Unknown`,不得提前显示 `Failed` 或 `Failure`;只有 status commit marker 可读回后才投影 `Failed/Failure`。completed、failed/budget-exhausted、cancelled 和 needs-reconciliation 分别映射稳定终态/核对态。遇到未知或互相矛盾的内部 status/phase 时不得猜成 Running,而要投影 `NeedsReconciliation` 和脱敏 `PUBLIC_STATE_INVALID`。Runtime 失败原因使用独立 `AgentRuntimeSnapshotErrorCode`,不能复用命令协议错误码;配置缺失、鉴权、限流、Provider/验证/sandbox/预算失败都必须映射固定 code,不把 transport/Provider 原文带到 Public。具体映射表与 DTO 同模块维护并做穷举契约测试。 -进度只从当前 Supervisor 的可信结构化计划计算:`totalStepCount=planSteps.len()`,`completedStepCount` 只计 completed,当前摘要只取唯一 active step 的脱敏标题;没有结构化计划时为 `0/0`,不得按 tool/action/loop 数猜进度。`collaboratorCount` 只计当前 Supervisor run 的 durable、尚未终结专业协作单元并去重,不包含历史 child。`waitingOn/nextStep/outcome/error` 是有界、脱敏、仅展示的 Shell 文本,Consumer 不得解析它们路由命令;可执行能力只由 interaction view 和写命令结果决定。 +进度只从当前 Supervisor run 的可信结构化事实计算:顶层 `totalStepCount=planSteps.len()`,`completedStepCount` 只计 completed,当前摘要只取唯一 active step 的脱敏标题;没有结构化计划时为 `0/0`,不得按 tool/action 数猜进度。现有工作台要求的 Runtime-owned 进度卡不能继续由客户端跨 manifest/plan/event 自行拼装,因此 `progress` 由 Shell 投影同 run 的 `loopIteration`、任务/计划完成数、当前活跃专业组,以及最多四类最新校验(试玩、静态检查、代码 mutation、截图检查)的稳定 outcome/安全摘要/证据数量;返工只提供有界安全摘要。没有可信 receipt/verification evidence 就投影 `unknown` 或省略,绝不根据日志文字猜通过。该 progress 只更新 Snapshot 同一 run 的卡片,不写 conversation;run 切换时由新 Snapshot 原子替换,Consumer 不能合并旧 run 证据。`collaboratorCount` 只计当前 Supervisor run 的 durable、尚未终结专业协作单元并去重,不包含历史 child 或 manifestFallback slot。`waitingOn/nextStep/outcome/error` 是有界、脱敏、仅展示的 Shell 字段,Consumer 不得解析它们路由命令;可执行能力只由 interaction view 与 command capabilities 决定。 + +进度字段关系冻结如下:`SupervisorRuntimeSummary.completed_step_count/total_step_count` 在 `progress` 存在且 `plan_progress` 可用时必须逐字等于 `plan_progress.completed/total`;无结构化计划、plan receipt 缺失或 task/plan receipt 不一致时,Shell 省略 `progress` 并将顶层计数固定为 `0/0`,不得用 task 计数替代或把数字伪装成 unknown。`task_progress` 只来自当前 Supervisor run 的 Runtime task journal,`plan_progress` 只来自同一 run 的 immutable plan journal,二者不互相推导、不得跨 run 合并;同一 revision 内若两者 receipt 不一致,进入 reconciliation,而不是选择较新或较大数字;单个 check 缺少 receipt 时仍可保留该 check,但 outcome 必须为 `Unknown` 且 evidenceCount 为 `0`。`active_groups` 只列当前 run 的 active group,`latest_checks` 每种 check 最多一条且必须带同一 run 的 evidence receipt;任何上述字段变化都推进同一 Snapshot revision。 所有可能改变 Public 白名单的 Runtime 写入都必须经过统一 projection-dirty 协议:先在同一 project lock 下写 durable dirty journal,再提交原 Runtime 变更,随后重建 Public Snapshot/事件并关闭 journal。变更前崩溃可重建为无变化,变更后崩溃可由 Public read、订阅启动、Runner 启动或项目 wake 幂等补投影。P2 必须枚举并接入现有 state、task、interaction、终态和恢复写入口;不允许依赖 Consumer 轮询偶然发现漏掉的内部变更。 @@ -120,19 +662,289 @@ Public status/stage 是稳定枚举,不直接透传内部 phase。映射必须 所有身份校验都在取得 project execution owner 后、写入 request ledger 前完成。locator canonicalize 与 manifest 复核必须针对同一已打开目录句柄完成;复核失败返回结构化错误并不产生 ledger 记录。会话或 run 的 revision 只由其权威持久化记录递增,不能使用 Consumer 看到的时间戳或事件 sequence 代替。 +### 1.1.1.a Durable record envelope + +所有项目级 Shell durable record 先嵌入同一个 envelope;尚未解析 `projectId` 的本地 locator/窗口操作使用独立 local envelope,不能把 `projectId` 改成空字符串或可空字段来复用项目 envelope: + +```rust +// 项目级 durable record 使用;envelope.projectId 必须与 record body 中重复出现的 +// projectId 一致,不一致即损坏,不能选择其一继续。 +struct AgentRuntimeDurableEnvelope { + schema_version: String, + record_id: String, + // 记录自身的 CAS/recovery revision;每次该 record 的 durable 状态变化递增, + // continuation item 的 record_revision 固定取这里,不能用时间或文件版本猜测。 + record_revision: u64, + // 项目 ledger 的追加顺序;它不是 record_revision,不可单独证明某条记录未变化。 + ledger_version: u64, + project_id: String, + checksum: String, + owner_boot_id: String, + owner_generation: u64, + created_at: u64, + updated_at: u64, +} + +// 仅用于尚未解析 projectId 的受信任本地管理面。localScopeId 由宿主的 +// app profile/control lease 派生,不接受 Consumer 自报,也不包含路径。 +struct AgentRuntimeLocalDurableEnvelope { + schema_version: String, + record_id: String, + // local record 自身的 CAS/recovery revision;与项目 envelope 使用同一递增规则。 + record_revision: u64, + ledger_version: u64, + local_scope_id: String, + checksum: String, + owner_boot_id: String, + owner_generation: u64, + created_at: u64, + updated_at: u64, +} +``` + +两类 envelope 的 `recordId` 都与业务 identity 一一对应;`recordRevision` 从 `1` 开始并在同一 record 的每次状态变化时单调递增,`ledgerVersion` 只表示项目 ledger 追加顺序;`checksum` 均按“去掉 checksum 字段后的完整 record”计算 RFC 8785 canonical JSON SHA-256,owner boot/generation 参与 fencing。local envelope 不能进入 Public Snapshot、项目事件或 Runtime conversation;一旦解析出项目身份,后续项目级副作用必须在 project owner/lock 下建立或关联项目级 operation record,并把稳定引用写入 local record 的 `projectOperationRef`;两边状态无法唯一对应时进入 outcome-unknown/reconciliation,不能继续只靠 local record 执行或重复副作用。 + +### 1.1.1.b Session handoff 映射 + +现有 Runtime/Process Session 的 `conversation_session_id` 只能作为候选 `sessionId`,不能直接当作 Supervisor session lineage;它没有证明“新窗口可以控制旧 Run”的关系。P1 必须新增与既有会话管理记录一对一关联的 durable handoff 记录(不复制 conversation 正文): + +```rust +enum ActiveSessionStatus { + Pending, // pending;rotation 尚未提交 active-session marker + Active, // active + Superseded, // superseded + Closed, // closed +} + +enum SessionHandoffReason { + Rotation, // rotation + OwnerRecovery, // ownerRecovery + Migration, // migration +} + +struct ProjectSupervisorSessionRecord { + envelope: AgentRuntimeDurableEnvelope, + project_id: String, + supervisor_lineage_id: String, // 首次创建后不可变 + session_id: String, // 每次 rotation 新建,永不复用 + session_revision: u64, + predecessor_session_id: Option, + status: ActiveSessionStatus, // pending | active | superseded | closed + issued_by_control_lease_id: String, + created_at: u64, + closed_at: Option, +} + +struct ProjectSupervisorRunHandoff { + envelope: AgentRuntimeDurableEnvelope, + project_id: String, + supervisor_lineage_id: String, + run_id: String, + rotation_operation_id: String, + handoff_manifest_id: String, + predecessor_session_id: String, + successor_session_id: String, + target_run_set_digest: String, + handoff_revision: u64, + handoff_reason: SessionHandoffReason, +} + +// rotation 开始时固化的非终态 Run 集合。目标集合本身也是 durable record,不能 +// 只把 target_run_set_ref 当作未定义的 sidecar;恢复不得重新扫描当前 Run 集合。 +struct ProjectSupervisorRunHandoffTargetSetRecord { + envelope: AgentRuntimeDurableEnvelope, + target_set_id: String, + rotation_operation_id: String, + manifest_id: String, + project_id: String, + supervisor_lineage_id: String, + predecessor_session_id: String, + successor_session_id: String, + // 按 runId 排序的不可变 payload;每个 item 同时固化捕获时的 session/run revision。 + sorted_targets_ref: String, + target_run_count: u32, + target_run_set_digest: String, + commit_marker: Option, + status: HandoffTargetSetStatus, +} + +struct ProjectSupervisorRunHandoffTargetPayload { + envelope: AgentRuntimeDurableEnvelope, + target_set_id: String, + chunk_index: u32, + items: Vec, // 每 chunk 最多 256 条 + checksum: String, +} + +struct ProjectSupervisorRunHandoffTargetItem { + run_id: String, + predecessor_session_id: String, + captured_run_revision: u64, +} + +enum HandoffTargetSetStatus { + Reserved, + Committed, + Corrupt, +} + +// target set payload 使用同一 owner 下有界、带 checksum 的私有 record/chunk;V1 +// 每个 chunk 最多 256 条、整个 target set 最多 4096 条,超过上限拒绝 rotation +// 并保留原 active session。digest 固定为 sha256(RFC 8785 canonical JSON(sorted +// target items)),不允许按文件名或最新 updatedAt 选择。target set/chunk 的保留与 +// 隔离规则和其它 durable record 一并冻结;payload 缺失、checksum 不一致或 commit +// marker 不可读直接 reconciliation。 +struct ProjectSupervisorRunHandoffManifest { + envelope: AgentRuntimeDurableEnvelope, + manifest_id: String, + rotation_operation_id: String, + project_id: String, + supervisor_lineage_id: String, + predecessor_session_id: String, + successor_session_id: String, + source_session_revision: u64, + target_run_set_ref: String, + target_run_count: u32, + target_run_set_digest: String, + run_creation_epoch: u64, + continuation_set_ref: String, + continuation_count: u32, + continuation_set_digest: String, +} + +// 除 Run 本身外,rotation 还必须固化所有会跨 session 继续的交互/投递记录。 +// 这些记录的 session_id 是创建时的 lineage/provenance,不在 handoff 中改写; +// successor 只能凭该 continuation set 取得一次性的当前 session 授权,不能 +// 通过“当前 session + 最近记录”重新猜测要继续哪条消息或 interaction。 +struct ProjectSupervisorSessionHandoffContinuationSetRecord { + envelope: AgentRuntimeDurableEnvelope, + continuation_set_id: String, + rotation_operation_id: String, + manifest_id: String, + project_id: String, + supervisor_lineage_id: String, + predecessor_session_id: String, + successor_session_id: String, + sorted_continuations_ref: String, + continuation_count: u32, + continuation_set_digest: String, + commit_marker: Option, + status: HandoffContinuationSetStatus, +} + +struct ProjectSupervisorSessionHandoffContinuationItem { + record_kind: HandoffContinuationRecordKind, + record_id: String, + // 必须等于被引用 record 的 envelope.record_revision;不得使用 + // interactionRevision、ledgerVersion、时间戳或文件版本替代。 + record_revision: u64, + session_id: String, + run_id: Option, +} + +struct ProjectSupervisorSessionHandoffContinuationPayload { + envelope: AgentRuntimeDurableEnvelope, + continuation_set_id: String, + chunk_index: u32, + items: Vec, // 每 chunk 最多 256 条 + checksum: String, +} + +enum HandoffContinuationRecordKind { + Interaction, + CommandOperation, + InputEnvelope, + DirectReplyDelivery, + RuntimeFinalReply, + RuntimeStatusMessage, + PublicEventMessage, +} + +enum HandoffContinuationSetStatus { + Reserved, + Committed, + Corrupt, +} + +// continuation payload 同样使用有界、带 checksum 的 chunk;V1 最多 8192 条, +// digest 固定为 sha256(RFC 8785 canonical JSON(sorted continuation items))。 +// 缺失记录、revision/record_kind/session/run 不匹配、checksum 或 commit marker +// 不一致,均直接进入 reconciliation,不按 ledger 当前“最新状态”补猜集合。 + +// Session rotation 跨多个 session/handoff record,不能靠“最后写入的文件”判断 +// 成功。rotation operation 是唯一恢复事实;active-session commit marker 之前 +// 旧 active session 仍然有效,新的 session/handoff 只能作为待提交事实存在。 +struct ProjectSupervisorSessionRotationRecord { + envelope: AgentRuntimeDurableEnvelope, + operation_id: String, + project_id: String, + supervisor_lineage_id: String, + predecessor_session_id: String, + successor_session_id: String, + expected_session_revision: u64, + // Prepared 时尚未建立 manifest,必须为 None;只有 target/continuation set 与 + // manifest 均提交并回读成功后,才在 HandoffManifestCommitted 中补为 Some。 + handoff_manifest_ref: Option, + expected_run_creation_epoch: u64, + phase: SessionRotationPhase, + // 与 active index 的 rotation_fence_commit_marker 一致;Prepared 时必须为 None。 + // fence 与该 marker/phase 必须在同一 journal 线性化提交。 + rotation_fence_commit_marker: Option, + active_session_commit_marker: Option, +} + +enum SessionRotationPhase { + Prepared, // operation 已持久化,active index 尚未 fenced + FenceCommitted, // operation phase/marker 与 active-index fence 已原子提交 + HandoffManifestCommitted, + SuccessorSessionCommitted, + HandoffsCommitted, + ActiveSessionCommitted, + ReconciliationRequired, + Rejected, +} + +// 当前 active session 只能从该项目唯一 index 读取;不能扫描 session 文件 +// 或按 updatedAt/文件名选择“最新”记录。 +struct ProjectSupervisorActiveSessionIndex { + envelope: AgentRuntimeDurableEnvelope, + project_id: String, + supervisor_lineage_id: String, + active_session_id: String, + active_session_revision: u64, + run_creation_epoch: u64, + // 非空表示 rotation fence 已提交;所有五命令、新 Run/child/delegation、 + // interaction 和 conversation delivery identity 的分配都必须在同一 project + // lock 内拒绝或延后,不能继续落到 predecessor session。 + rotation_fence_operation_id: Option, + // 与 rotation operation 的同名 marker 一致;operationId/marker 必须同时为空或 + // 同时存在,禁止出现 active-index fence 找不到唯一 operation recovery fact。 + rotation_fence_commit_marker: Option, + // 最近一次成功切换 active session 的 operationId。最终 active-session journal + // 必须把它更新为当前 operationId;任何 Rejected 都保留历史成功值,不得清空或覆盖。 + committed_rotation_operation_id: Option, +} +``` + +Session、handoff、target set 和 lineage 身份记录同样嵌入统一 durable envelope;`status`、`closedAt`、target-set commit marker 和 lineage 状态迁移必须通过 checksum 与 owner-generation fencing。`Prepared` recovery 只允许校验唯一 operation、predecessor/successor 预分配身份、expected session revision/epoch 和“active index 尚未引用该 operation”;它不能创建 successor session、manifest、handoff 或外部入队。若预期仍成立,可以在重新取得 owner/project lock 后继续同一 operation 的 fence commit;预期已漂移则写 `Rejected` tombstone。若 active index 已引用该 operation,但 operation 仍为 `Prepared`、两边 marker 缺失或不一致,则属于不可达的 torn commit,必须 `ReconciliationRequired`,不能把 Prepared 当作 fence 已成功。 + +session rotation 必须在项目锁内先校验当前 active index 没有其它 fence,分配唯一 `rotation_operation_id`、`successor_session_id` 并固化 `expected_session_revision + expected_run_creation_epoch`,首先持久化 `ProjectSupervisorSessionRotationRecord(Prepared)`;此时 `handoff_manifest_ref=None`、两个 commit marker 均为 `None`,active index 不变,旧 session 继续 `Ready`。随后重新读取并校验 active index、session revision、run creation epoch 和 owner generation,在同一 projection/rotation journal 线性化提交中同时写入 operation 的 `phase=FenceCommitted + rotation_fence_commit_marker` 与 active index 的 `rotation_fence_operation_id + rotation_fence_commit_marker`;任一侧缺失、operationId/marker 不一致或只能读到半边时进入 `ReconciliationRequired`,不得自动清除 fence。只有该 journal 可一致回读后 fence 才生效并投影 `HandoffInProgress`。fence 同时阻止新 requestId 的五命令、新 Run/retry successor/child/delegation/Runtime-owned schedule run、Interaction 和 conversation delivery identity 分配;不得先分配 identity 后再补 handoff。fence 前已进入 prepared/executing 的 answer/approve/cancel/resume/submit 必须停止创建新的下游 identity,并收束到 handoff-safe durable boundary:能证明权威结果则闭合原 command result;尚未产生未知外部结果但需要跨 session 继续的 operation 与其已分配 response/status/interaction identity 一并写入 continuation set;结果已未知则 rotation 进入 reconciliation。已分配 identity 的 response/status/public-event delivery 可补到稳定 commit marker、handoff-safe operation boundary 或稳定 `outcome-unknown`,但不得创建替代 message identity。所有目标 Run 到达不再产生未登记 session-bound identity 的 durable handoff barrier 后,才在同一锁序内按当前 active session 和 supervisor lineage 固化、排序并 digest 非终态 Run 集合,以及全部 Open/Resolving interaction、未终结 command/input envelope、DirectReply/RuntimeFinalReply、Runtime status/public-event delivery,分别写入 committed target set 与 continuation set,并提交 manifest;manifest 可读且 refs/count/digest 全部匹配后,才把同一 operation 的 `handoff_manifest_ref` 从 `None` 补为 `Some(manifestId)` 并推进到 `HandoffManifestCommitted`。`Prepared` 或 `FenceCommitted` 不得保存一个声称已提交但尚不可回读的 manifest ref。无法在有界期限内到达 barrier 时,按同一 operation 写 `Rejected` tombstone;若 fence 已提交则通过 journal 隔离 pending records、清除 active-index fence/marker 并递增 epoch,恢复旧 active session,不能不带 fence 强切。 + +manifest 的 `target_run_set_ref + target_run_count + target_run_set_digest + run_creation_epoch` 和 `continuation_set_ref + continuation_count + continuation_set_digest` 共同构成本次 handoff 的不可变目标边界,rotation fence 生效后没有新 session-bound identity 可以落到 predecessor;若检测到 epoch 漂移、barrier 后 predecessor record revision 变化或集合摘要漂移,rotation 必须失效并进入 reconciliation,不能静默排除新 Run、interaction 或 delivery。所有 `ProjectSupervisorRunHandoff` 必须一一匹配 manifest/target set 的 operation、manifest、predecessor/successor session、run、lineage、captured revision 和 digest;所有跨 session 继续的 command/input/interaction/delivery 必须一一匹配 continuation item 的 record kind/id/revision/session/run,不能恢复时重新扫描当前 ledger 集合。`ActiveSessionCommitted` marker 之前,旧 session 保持 `active`,不得先落 `superseded`;`Prepared` 单独存在时仍可按旧 session 受理写入,但提交 fence 前必须重新校验 revision/epoch,`FenceCommitted` 之后不得再受理新写命令或 session-bound identity。阶段按 `Prepared → FenceCommitted → HandoffManifestCommitted → SuccessorSessionCommitted → HandoffsCommitted` 推进;只有新 session、manifest、全部 Run handoff 与 continuation record 均可读且身份/lease/revision 校验通过后,才在同一 active-session journal commit 中切换 active index、将旧 session 标记 `superseded`、把 `activeIndex.committedRotationOperationId` 同步写为当前 `operation.operationId`、清除当前 operation 的 rotation fence/marker、把 `activeIndex.runCreationEpoch` 从 `operation.expectedRunCreationEpoch` 精确递增为 `expected + 1`,并提交 operation 的 `active_session_commit_marker`,再进入 `ActiveSessionCommitted`。该 journal 恢复和 `Ready` projector 必须同时校验 committed operationId、predecessor/successor session、source/successor session revision、manifest operation/session/epoch、active index epoch 和 active-session marker;不得只凭 activeSessionId 或最新文件判定成功。任一步崩溃或写失败都由 rotation operation 恢复,并严格区分两类 Rejected:`Prepared-before-fence Rejected` 仅在 active index 没有引用该 operation 时成立;恢复只幂等写 operation 的 `Rejected` tombstone,不清理任何 fence、不递增 active-index epoch、不创建或隔离本来就不允许存在的 successor/manifest/handoff/set,并原样保留 active index 既有 `committed_rotation_operation_id`,然后继续使用旧 active session。`FenceCommitted-after-fence Rejected` 只在 operation/index 的 operationId 与 fence marker 一致、active-session marker 尚未提交且没有未知外部结果时成立;恢复先隔离该 operation 的 pending successor/handoff/target-set/continuation,再在同一 journal 中清除且只清除该 operation 的 active-index fence/marker,把 epoch 从捕获值按规则递增一次,保留历史 `committed_rotation_operation_id` 不变,并继续使用旧 active session。operation/index marker 不一致、active-session marker 已提交或结果未知时不得走 Rejected,统一 `ReconciliationRequired`。active-session marker 之后发现 committed operationId、session/manifest/epoch 关系、任一集合/manifest、digest、handoff/continuation 缺失或不一致,同样进入 `ReconciliationRequired`,保留 fence/commit 证据,禁止两个 session 控制或继续同一 record,不能按“最新文件”猜成功。新 session 只有在同一 `supervisorLineageId`、项目归属、control lease、单调 revision 和对应 handoff/continuation proof 全部成立时,才可以控制旧 session 遗留 Run 或继续旧 session record;旧 session 永远不能重新变为 active。没有 proof、lineage 不匹配、revision 回退或双窗口竞争时返回 `TARGET_STALE`,不能根据 `conversation_session_id`、路径或时间猜测归属。迁移前已有 session 必须先生成显式 migration handoff/continuation fixture,无法证明 lineage 的旧 Run、command/input envelope、interaction 或 delivery 暂停在 `needs-reconciliation`,不自动开放 answer/approve/cancel/resume。 + ### 1.1.2 Public 摘要枚举与投影提交合同 `status`、`stage`、`waitingOn`、`nextStep` 和 `outcome` 是公开稳定枚举,不向 Consumer 透传内部 phase,也不要求 Consumer 解析自然语言。V1 至少冻结以下值: | 字段 | 稳定值 | |---|---| -| `status` | `idle`、`queued`、`running`、`waiting`、`completed`、`failed`、`cancelled`、`needsReconciliation` | -| `stage` | `idle`、`planning`、`executing`、`coordinating`、`waitingForUserInput`、`waitingForPolicyApproval`、`waitingForDeveloperApproval`、`waitingForTimer`、`reconciling`、`completed`、`failed`、`cancelled` | -| `waitingOn` | `none`、`userInput`、`policyApproval`、`developerApproval`、`timer`、`runner`、`reconciliation` | -| `nextStep` | `none`、`submitIntent`、`answerInteraction`、`approveInteraction`、`cancelRun`、`resumeProject`、`waitForRunner`、`reconcile` | +| `status` | `idle`、`preparing`、`queued`、`running`、`waiting`、`paused`、`cancelling`、`completed`、`failed`、`cancelled`、`terminalPending`、`needsReconciliation` | +| `stage` | `idle`、`preparing`、`publicStatusPending`、`planning`、`executing`、`coordinating`、`waitingForUserInput`、`waitingForUserApproval`、`waitingForPolicyApproval`、`waitingForDeveloperApproval`、`waitingForTimer`、`waitingForRunner`、`pausedByUser`、`cancelling`、`finalizing`、`reconciling`、`completed`、`failed`、`cancelled`、`terminalPending` | +| `waitingOn` | `none`、`publicStatusMessage`、`userInput`、`userApproval`、`policyApproval`、`developerApproval`、`timer`、`runner`、`reconciliation` | +| `nextStep` | `none`、`waitForPublicStatus`、`submitIntent`、`answerInteraction`、`approveInteraction`、`cancelRun`、`resumeRun`、`retryTerminalRun`、`waitForRunner`、`reconcile` | | `outcome` | `none`、`success`、`failure`、`cancelled`、`unknown` | -`waitingOn` 和 `nextStep` 只用于展示提示,任何可执行按钮必须来自当前 Public `interactions` 或明确的五命令能力;Consumer 不得根据这两个字段自行拼装命令。文案由 Shell 根据稳定值本地化并做长度、路径、Provider 原文和敏感信息过滤;文案变化不改变协议状态,不单独推进 `snapshotRevision`。 +`waitingOn` 和 `nextStep` 只用于展示提示,任何可执行按钮必须来自当前 Public `interactions` 或 `commandCapabilities`;专业组 `recovery` 只是与这两处互相校验的展示索引,不能独立构造命令。Consumer 不得根据状态、错误文案或这两个字段自行拼装命令。capability 是 Snapshot 在同一 revision 上投影的精确命令目标:无 capability 就禁用入口,有 capability 才原样提交其中的 session/run/revision/interaction target;Shell 仍在写锁内重读事实,过期 capability 返回 `TARGET_STALE`。`submit_intent.conversationOptions` 按可用组合而不是多个独立白名单字段输出,避免 Consumer 误组合 intentKind、runProfile 和 entryBinding;resource identity 仍由既有资源管理 read model 提供。该 capability 只声明 Conversation 请求形态可提交,不预先承诺 interaction kernel 的 `execute` 一定可转为 start/steer:active Project Supervisor 根 Run 已绑定冻结 Goal Contract 时仍可保留合法 Conversation option 以承接 direct reply,但 Shell 不投影、也不接受任何从该 capability 推导出的 replacement-steer 能力;若同一请求在锁内被判定为 `execute`,稳定返回 `TARGET_BUSY` 并指向显式 Goal management mutation。短暂 Public read 失败时,Consumer 可以继续显示上一份已验证 Snapshot 并明确标记 stale;除上一份精确 `cancel` capability 外所有写入口禁用。为避免失去用户止损能力,stale cancel 可继续提交原 session/sessionRevision/run target,但它只在最后可信 Snapshot 的 `sessionContext=Ready` 且本地没有观测到 `HandoffInProgress`/draining/reconciliation 时作为例外;Shell 必须先在 project lock 内检查当前 phase/fence,再重新授权和锁内复核,phase gate 优先时返回 `TARGET_BUSY`/`TARGET_STALE`,目标变化返回 `TARGET_STALE`,transport/owner 不可用则返回 `TRANSIENT_UNAVAILABLE/OWNER_UNAVAILABLE`。已有 cancel request 的结果读回仍可进行,但不得借 stale capability 创建第二个 cancel operation。这解决“nextStep 不可路由、但 UI 又需要启停 submit/cancel/resume/retry”以及“读短暂失败时仍要能尝试取消”的矛盾。文案由 Shell 根据稳定值本地化并做长度、路径、Provider 原文和敏感信息过滤;Snapshot hash 必须比较除 `snapshotRevision/eventCursor/updatedAt` 这些提交元数据外的所有实际序列化 Public 业务字段,包括规范化展示文案,不能出现业务响应字节变化但 `snapshotRevision` 不变。只有不进入 DTO 的本地化资源或渲染变化不推进 revision。 Public projector 的一次提交以 `projectId` 为边界,在 `project execution owner → supervisor project lock → projection journal` 的锁序内完成: @@ -157,6 +969,16 @@ Public projector 的一次提交以 `projectId` 为边界,在 `project executi owner record 的取得、续租和释放使用同一项目锁内的 CAS;generation 每次成功换主递增,旧 generation 的写入返回 `OWNER_FENCED`,不得覆盖新 owner 的 ledger、Runtime state 或 projection。lease 到期不能只凭本地时钟判定可接管:新进程必须先取得 owner,再在锁内复核 Runner drain、GUI-owner 和 manifest projectId。时钟只用于 lease 超时提示,CAS/generation 才是权威。 +调用 capability 与生命周期 owner 是两套轴: + +| 调用方 | 可读 | 可写/自动动作 | +|---|---|---| +| Public User GUI/CLI | Public Snapshot、Public event、自己的 command result/conversation | `submit_intent/answer`、用户 audience 的 `approve`、cancel、ContinueRun/RetryTerminalRun;必须通过项目权限和当前 active session 校验 | +| Developer capability | Public + Developer Snapshot | Developer audience ToolApproval、显式 ReconcileRun 和受信任管理面;仍受 project owner/fencing 约束 | +| Runner internal capability | Public projector、durable runnable/recovery records | 只执行已持久 wake/recovery intent;不能伪装 Public Consumer 生成用户 answer/approve/requestChanges | + +GUI-owner 是 GUI 启动 Runner 的生命周期 lease,不是 GUI 的业务特权。V1 明确**不提供无 GUI CLI 写入的 headless control lease**:CLI 可以读取 Public Snapshot、事件和自己的 command result;CLI 写入只有在已存在且有效的 GUI-owner/Runner 上转发时才可执行,否则在写入 `prepared` 前返回 `OWNER_UNAVAILABLE`。不得为让 CLI “可用”而绕过 GUI-owner,也不得把本轮协议平等表述成无 GUI 常驻 Runner;后续若实现 headless lease,必须提升/协商生命周期能力合同并新增 fencing fixture。 + 调度 worker 的顺序固定为:取得/续租 project owner → 检查 Runner drain 与 GUI-owner → 从 durable wake/runnable 索引取一项 → 在 dequeue 前再次校验 owner generation 和项目状态 → 以同一 operation identity 调用 Runtime。任何检查失败都不得先 dequeue 后补救;旧 worker 在失去 generation 后的结果一律按 fencing 处理并进入既有 reconciliation 路径。 ### 1.2 出向事件、有序性与重连 @@ -195,11 +1017,11 @@ V1 将公开事件刻意收窄为项目级失效通知。`agentId/sessionId/runI | 命令 | 吸收的旧命令 | V1 业务输入 | |---|---|---| -| `submit_intent` | CLI `Reply/Execute/Resume` 与 `start_*` / `steer_*` | 用户消息、目标 Project Supervisor sessionId 与公开 runProfile;Shell 判定 direct reply/start/steer/resume,source 由 transport 派生 | +| `submit_intent` | CLI `Reply/Execute` 与 `start_*` / `steer_*` | `Conversation` payload 为用户消息、目标 Project Supervisor sessionId、入口 `intentKind` 与公开 runProfile;`BuiltinCommand` payload 为 command line 与 expected parser version;Shell 按 parser/intent policy matrix 判定 direct reply/start/steer/reject,source 由 transport 派生 | | `answer` | `answer_*_user_input` | `interactionId + responseId + answers` | -| `approve` | `confirm_*` / `reject_*` | `interactionId + responseId + decision(approve/reject)` | +| `approve` | `confirm_*` / `reject_*` | `interactionId + responseId + decision(approve/reject/requestChanges)`;`requestChanges` 可携带有界意见 | | `cancel` | `cancel_*` | 当前公开 Project Supervisor `runId` | -| `resume` | `resume_*` / `retry_*` / `schedule_*` | 项目级恢复意图;若需确认则只创建/返回 InteractionRequired | +| `resume` | 旧 `resume_*` / `retry_*` 生命周期入口 | 明确 tagged intent:继续同一 durable run、针对终态失败创建 successor run,或由受信任 capability 进入人工 reconciliation;timer/lane/schedule 属于 Runner 内部 wake,不是公开 resume | 所有写命令都包含: @@ -214,9 +1036,34 @@ struct InteractionResponseMeta { interaction_id: String, response_id: String, expected_interaction_revision: u64, + session_id: String, + expected_session_revision: u64, +} + +enum AgentRuntimeIntentKind { + CreateFromPrompt, // createFromPrompt + ContinueProject, // continueProject + CreateFromTemplate, // createFromTemplate + ImportExistingDesign, // importExistingDesign +} + +// Shell 私有路由审计;不要求 Consumer 解析 command name。 +enum AgentRuntimeIntentRoute { + BuiltinCommand { + parser_version: String, + command_name: String, + }, + Conversation, +} + +enum ApprovalDecision { + Approve, // approve + Reject, // reject,终止当前审批链 + RequestChanges, // requestChanges,带意见退回同一工作链重做 } struct AgentRuntimeCommandResponse { + schema_version: String, request_id: String, request_fingerprint: String, replayed: bool, @@ -224,22 +1071,40 @@ struct AgentRuntimeCommandResponse { result: T, } +enum IntentDisposition { + DirectReply, // directReply + Start, // start + Steer, // steer:可在当前安全 Provider 边界中接管 + SteerDeferred, // steerDeferred:保留当前 action/approval,receipt/barrier 后消费 +} + enum AgentRuntimeCommandAck { IntentAccepted { - disposition: IntentDisposition, // Reply | Start | Steer | Resume + disposition: IntentDisposition, // directReply | start | steer | steerDeferred accepted_run_id: Option, + steer_id: Option, // Shell prepared 后为 Steer 映射的现有 steer ledger 身份 response_message_id: Option, + runtime_status_message_id: Option, // Start 才有;Runtime-owned status message + }, + InteractionAccepted { + interaction_id: String, + decision: Option, + follow_up_interaction_id: Option, + }, + CancelAccepted { run_id: String, cancel_operation_id: String }, + ResumeAccepted { + mode: AgentRuntimeResumeMode, // continueRun | retryTerminalRun | reconcileRun + predecessor_run_id: String, + accepted_run_id: Option, // RetryTerminalRun 才分配 successor runId + affected_run_count: u32, }, - InteractionAccepted { interaction_id: String, decision: Option }, - CancelAccepted { run_id: String }, - ResumeAccepted { affected_run_count: u32 }, InteractionRequired { interaction_id: String, interaction_revision: u64 }, } -struct AgentRuntimePublicError { +struct AgentRuntimeCommandError { schema_version: String, - code: String, - kind: AgentRuntimePublicErrorKind, + code: AgentRuntimeCommandErrorCode, + kind: AgentRuntimeCommandErrorKind, retryable: bool, message: String, request_id: Option, @@ -250,20 +1115,101 @@ struct AgentRuntimePublicError { } ``` -`submit_intent` 要求 Consumer 先经现有会话管理面取得明确的 Project Supervisor `sessionId`;Shell 锁内验证该 session 属于本项目/当前 Supervisor,active session 已变化则返回 `TARGET_STALE`,本轮不把会话 CRUD 隐式塞入 Runtime 命令。`source` 由受信任 transport 固定映射,Consumer 不得自报任意 source;`runProfile` 使用公开白名单枚举并在锁内校验当前项目支持。Shell 复用现有 interaction kernel 决定 direct reply/execute/resume,再对 execute 决定 start/steer;direct reply 仍只写 conversation/response stream,不伪造 Runtime Snapshot 变化,其稳定 responseMessageId 预先写入 request ledger。显式 `resume` 命令服务按钮/自动化的结构化恢复意图,自然语言“继续”也可由 `submit_intent` 路由到同一内部实现。 +`submit_intent` 仍必须保留现有项目的 `/` 内置命令语义,但解析权不能留在 GUI/CLI。Shell 在 interaction kernel 和 intent policy matrix 之前,使用 capability 声明版本的同一 Rust parser 对规范化 message 做一次确定性路由:普通文本只能进入 `Conversation`;不含 absolute host locator、`file://` 或本地句柄的项目级 slash 才能进入 `SubmitIntentPayload::BuiltinCommand`。经项目相对路径安全校验的 resource path 不属于 host locator,但仍必须拒绝绝对路径、`..` 和 symlink escape。路径、窗口和全局配置等本地管理命令必须在同一 parser 中先产出 `LocalManagementRoute`,先进入 `AgentRuntimeLocalManagementOperationRecord`,不能伪装成 `submit_intent` 或用空 `projectId/sessionId` 进入 Runtime ledger;若该 local route 在解析后确实产生项目级领域副作用,则必须在同一 project lock 下建立关联的 `AgentRuntimeBuiltinManagementOperationRecord`,把 `projectOperationRef` 双向固化,但仍不得伪造 conversation user message 或进入 Public conversation。BuiltinCommand 只提交 `command_line + expected_parser_version`,不再伪装成 `continueProject + runProfile`,也不携带 attachments、entry binding 或 Runtime intent/profile;项目级 BuiltinCommand 才进入 `submit_intent` 的 command ledger。命令需要的 project/session scope 由 Shell 在锁内从当前 capability/管理面取得。expected parser version 必须是调用方从当前 capability 原样读回的版本:格式/版本不支持返回 `PROTOCOL_VERSION_UNSUPPORTED`,与当前 capability 不一致返回 `TARGET_STALE`;一旦写入 prepared,parser version、规范化 command line 和 route 固化在 ledger,恢复/重放不得重新按新 parser 解释同一文本。未知命令仍可产生有界、持久 direct reply,不落成 Runtime task,但只有在 host-locator/path safety 检查通过且不含绝对路径、`file://` 或无法分类的本地句柄时才允许保存原 command line;未知命令携带这类输入必须在 ledger 前 `INVALID_REQUEST`,不能以 direct reply 回显或持久化原文。只读/说明类内置命令可走 `DirectReplyDelivery`;需要预览、文件、工具或其它副作用的内置命令必须创建既有类型的稳定 Interaction,由 `answer/approve` 或受信任管理面继续,不能在 parser 内直接绕过确认;若最终需要 start/steer/resume/cancel,必须调用同一 Shell 内部能力与 project lock/ledger,不得回退旧公开命令。存在 Open/Resolving interaction 时,内置只读命令是否可回复、会产生新 interaction/副作用的命令是否 `TARGET_BUSY`,由逐命令 fixture 冻结;V1 禁止嵌套第二个 User interaction。这样 `/preview` 仍只生成 `preview.start` 确认而不会成为自主构建任务,GUI/CLI 也不再各自维护 slash 分支。 + +现役 slash catalog 按当前 `apps/ai-game-creator-shell/src/App.tsx` 与 `projectSummaryConstants.ts` 冻结为以下路由;新增命令必须先更新 Rust parser、capability catalog、GUI/CLI golden fixture 和本表,不能落入未知命令的偶然行为: + +| 路由类别 | 现役命令(完整命令名;带参数的参数形态保持现有帮助文案) | 协议行为 | +|---|---|---| +| 只读 direct reply | `/help`、`/capabilities`、`/能力`、`/audit`、`/审计`、`/status`、`/llm-status`、`/llm-routes`、`/brief`、`/goal`、`/progress`、`/spec`、`/mvp`、`/pitch`、`/demo`、`/rules`、`/tutorial`、`/mobile`、`/compatibility`、`/accessibility`、`/localization`、`/performance`、`/polish`、`/risks`、`/blockers`、`/ready`、`/evidence`、`/deps`、`/revise`、`/privacy`、`/audience`、`/invite`、`/bug-report`、`/survey`、`/cover`、`/screenshots`、`/trailer`、`/faq`、`/post`、`/store`、`/media-kit`、`/release-notes`、`/known-issues`、`/criteria`、`/groups`、`/balance`、`/budget`、`/qa`、`/changes`、`/review`、`/context`、`/timeline`、`/handoff`、`/next`、`/guide`、`/plan`、`/todo`、`/publish`、`/listing`、`/playtest`、`/test-plan`、`/feedback`、`/retention`、`/share`、`/tasks`、`/agents`、`/agent-conversations`、`/agent-memories`、`/trace`、`/loop`、`/agent-status`、`/history`、`/files`、`/assets`、`/credits`、`/art`、`/audio`、`/artifacts`、`/run-artifacts`、`/passes`、`/runs`、`/run-files`、`/internals`、`/logs`、`/checkpoints`、`/diff `、`/policy`、`/read `、`/exports`、`/preview-status`、`/memory [scope]`、`/commands`、`/limited-commands` | 按 parser route 选择交付:`RuntimeBuiltinCommand` 的 direct-reply 分支先持久化一次用户 command line,再提交 `DirectReplyDelivery`;`LocalManagementRoute` 改用 `AgentRuntimeLocalManagementResponse::Succeeded { meta, result: Reply }`,不写 Public conversation/response stream;两者均不创建 Runtime run。文件/Runtime 摘要按各自 read capability 脱敏;有 Open/Resolving interaction 时不得偷偷推进副作用。 | +| 交互或管理动作 | `/project <绝对路径>`、`/generate `、`/draft `、`/index`、`/checkpoint`、`/smoke`、`/restore `、`/policy-deny `、`/policy-allow `、`/policy-confirm `、`/policy-auto `、`/agent-policy-deny `、`/agent-policy-allow `、`/agent-policy-confirm `、`/agent-policy-auto `、`/asset-register [kind] [mediaType]`、`/run`、`/export`、`/open-project`、`/show-project`、`/switch-project`、`/open-preview`、`/preview-stop`、`/remember [scope] `、`/memory-set [scope] `、`/forget-memory [scope]`、`/canvas `、`/sync-canvas-project `、`/generate-art `、`/import-canvas-asset `、`/import-canvas-export ` | `/project`、`/open-project`、`/switch-project`、`/config` 是本地管理面;其中 `/project <绝对路径>`、`/import-canvas-export ...` 等 host-locator 参数只能进入 trusted local locator resolver,不能进入 Public conversation、Snapshot、事件、错误或 request fingerprint 原文;`/asset-register`、`/import-canvas-asset`、`/read` 的 path 必须先证明是项目内安全相对路径,不能接受绝对路径、`..` 或符号链接逃逸。其它命令按现有管理面权限/CAS/确认合同执行。资源登记、文件导入、记忆写入和 External Editor 调用都必须有稳定 operation identity,失败/重放不得生成第二副作用;不得把这些管理动作伪装成 Runtime task。`/diff`、`/policy`、`/read` 已列入只读 direct reply,不创建 Interaction。 | +| CLI-only 管理/观察别名 | `/resume`、`/goal <目标>`、`/goal status`、`/goal edit <目标>`、`/goal pause`、`/goal resume`、`/goal clear`、`/compact`、`/mcp`、`/quit`、`/exit` | 这些命令来自 `swarm_cli/input.rs`,必须纳入同一 Rust parser 的显式 catalog,不能依赖 GUI 未知命令 fallback。精确 `/goal` 已在上一行作为 direct read 列出;`/goal status` 同样是 direct read。`/resume` 是既有 Runtime recovery scan + observe 入口,不等于模糊的 `ResumeCommand`,只能按当前 session/run capability 恢复并观察;`/goal <目标>`、`/goal edit <目标>`、`/goal resume` 是带 Goal ID+revision CAS 的管理 mutation,其中现有 CLI 会在 mutation 成功后继续等待/启动该 Goal 对应 Runtime turn,迁移后必须通过同一 Shell start/steer/lineage handler,不能旁路创建第二个 Supervisor run;`/goal pause`/`/goal clear` 只改变 Goal 状态,不自动取消或终止 Runtime;`/compact` 只调用既有 context-compaction 管理合同,不创建 task/provider;`/mcp` 是 direct reply;`/quit`/`/exit` 只关闭 CLI 观察 transport,不写 conversation/Runtime ledger。 | +| 本地配置/窗口动作 | `/config` | 只打开现有独立配置面板,不写 Runtime ledger、不持久化为 Runtime task;配置保存继续走现有 config CAS/secret boundary。 | +| Agent run control 管理动作 | `/agent-kill`、`/agent-retry`、`/agent-resume [说明]` | 这些 slash 在当前 `App.tsx` 中调用既有 `control_agent_run`:`agent.kill` 只更新 legacy run trace/activity/output/context,`agent.retry` 和 `agent.resume` 从最近 trace 的 goal 启动新的本地生成 run;它们不能伪装成五命令的 `cancel`、`RetryTerminalRun` 或 same-run `ContinueRun`。parser 只生成带精确 legacy trace target、action 和有界 detail 的 management operation;如果迁移后确实要控制正式 Runtime,必须另有 capability 显式选择五命令,并写 `RunLineageRecord`,否则只返回管理动作结果。`/agent-resume` 的说明作为 prompt detail 保存,不能从文字猜 Runtime mode。 | +| Preview sibling contract | `/preview` | 只进入既有 `preview.start` authorization/confirmation 链;不得进入 `submit_intent` Conversation、自动创建 Supervisor run 或从 `nextStep` 猜授权。`/run` 若同时请求自检和预览,先执行既有静态检查,预览部分仍复用同一 `preview.start` 链;“准备启动预览”的 UI 提示只能来自该 Interaction/管理面状态,不再额外持久化一条 assistant ack。 | + +关键命令必须同时满足以下“单一协议落点”矩阵;表外别名只能先进入同一 parser catalog,不能由 GUI/CLI 增加第二种解释: + +| 命令族 | parser route | scope | durable 事实 | delivery / recovery | +|---|---|---|---|---| +| `/help`、`/llm-status`、`/mcp` | `LocalManagementRoute` | 无 project/session | 纯 direct reply;`/mcp` 不落 ledger | `AgentRuntimeLocalManagementResponse::Succeeded { meta, result: Reply }`;不读写 Public Snapshot | +| `/config` | `LocalManagementRoute` | 无 project/session | 既有 config CAS/secret boundary | 独立配置面板结果;不进入 Runtime ledger 或 conversation | +| `/quit`、`/exit` | `TransportOnly` | 无 project/session | 无 durable record | `TransportClosed`;不写 conversation/Runtime ledger | +| `/goal`、`/goal status` | `LocalManagementRoute` | 当前 project/session(缺失时只返回 scope 错误) | Goal read model;不创建 Runtime operation | local/CLI readback;不触发 Runtime turn | +| `/goal <目标>`、`/goal edit/pause/resume/clear` | `LocalManagementRoute` | project + active session + Goal ID/revision | 先写 `AgentRuntimeLocalManagementOperationRecord`(无 conversation user message),需要项目副作用时再以唯一 `projectOperationRef` 关联 `AgentRuntimeBuiltinManagementOperationRecord`;会产生 Runtime turn 时关联 `lineageRef`;现有冻结 Goal Contract 根 Run replacement primitive 仅允许由该管理 route 调用 | operation readback;同 requestId 重放,未知结果不重做;Runtime admission/replacement 必须走同一 project lock、operation identity 和 lineage,普通 `submit_intent` 不得复用 | +| `/resume` | `LocalManagementRoute` | project + session/run recovery target | recovery-observe operation;不创建 Public `ResumeCommand` | 按精确 session/run/revision 扫描并观察;同一 operation identity 恢复,不能借自然语言或最近 run 猜 target | +| `/agent-kill`、`/agent-retry`、`/agent-resume` | `LocalManagementRoute` | project + 精确 legacy trace target | legacy management operation;retry/resume 只有取得正式 lineage 后才关联 successor | operation readback;不得映射为 cancel/ContinueRun/RetryTerminalRun | +| `/project `、`/import-canvas-export ` | `LocalManagementRoute` | 初始无 project;解析后绑定 project | `AgentRuntimeLocalManagementOperationRecord`,解析后关联 project operation | local resolver/OS handle 结果;路径原文不入 Public/Runtime delivery,local→project link 必须唯一 | +| `/asset-register`、`/import-canvas-asset` | `RuntimeBuiltinCommand` 的 `ManagementAction` 分支 | project + safe project-relative path | resource/External Editor 既有 CAS + project management operation | project operation readback;相对路径不等于 host locator,仍须拒绝绝对路径、`..` 和 symlink escape | +| `/read `、`/diff`、`/policy` | `RuntimeBuiltinCommand` 的 direct-reply 分支 | project read capability | command ledger + 一次 user message;不创建 Runtime run | `DirectReplyDelivery`;超时按同 requestId 读回,不把读取结果写入 Snapshot | + +该矩阵明确:没有 project/session 的命令不伪造五命令 `meta.projectId`,有 project scope 的管理命令也不伪装成 Runtime run;resource path 只有在项目相对路径安全校验通过后才可属于 RuntimeBuiltinCommand,不得把 absolute host locator、`file://` 或本地句柄混入该 route;任何命令若无法唯一落到 route、scope、durable record 和 delivery/recovery 四项,必须在协议冻结前补充 fixture,而不是由 Consumer fallback。local/global command 在调用前必须从受信任 transport 取得 `AgentRuntimeLocalManagementCapability`,并原样提交 `capabilityId + expectedParserVersion`;Shell 重新校验 capability scope、project/session/target revision 和 principal。capability 不存在返回 `CAPABILITY_NOT_FOUND`,targetRef 漂移返回 `TARGET_STALE`,parser version 不支持返回 `PROTOCOL_VERSION_UNSUPPORTED`,不得从当前窗口、最近 Goal 或最近 run 猜目标。local operation record 必须保存 originatingCapabilityId(仅审计)与 authorizationPrincipalRef/authorizationScopeFingerprint;读回时使用当前 capability 重新授权,不要求 capabilityId 跨 owner boot 稳定;不同权限调用方不能借相同 requestId 读取本地路径或管理结果。 + +项目级 management route 在自身领域 CAS 之前先写 `AgentRuntimeBuiltinManagementOperationRecord`;Goal mutation 若会产生后续 Runtime turn,operation result 必须同时保存 `goalId + goalRevision + resultingRunId/lineageRef`,并由同一 project lock 串起 Goal CAS 与 Runtime admission。现有 `goal_contract_root_steer`/replacement primitive 只作为该 Goal management operation 的内部实现:必须复用同一 `projectOperationRef`、预分配的 resulting run identity 和 lineage,不能从 `submit_intent` 的 `Steer` disposition 进入,也不能把 replacement 回显成 same-run steer;同 requestId/同 argument fingerprint 回放已保存结果,异指纹返回 `IDEMPOTENCY_KEY_REUSED`,`prepared/executing` 返回 `COMMAND_IN_PROGRESS`,`outcome-unknown` 禁止重新执行;核对流程只能沿同一 `projectOperationRef` 推进 `needsReconciliation`,并写入 `reconciliationOperationId` 后闭合为确定的 `succeeded/rejected`。领域 writer 成功但 operation result 未闭合时由 owner 按 operation identity 补结果;不能以现有 UI pending state 代替 durable operation。 + +Agent run control management action 的 prepared payload 必须至少保存 `legacyTraceIdentity`(项目、trace/run 标识及其 revision/digest)、`action=kill|retry|resume`、`detail`(仅 resume,按同一 message 上限过滤)和新 generation operation identity。`kill` 的成功只表示 legacy trace 已写入 killed,不得向 Public Runtime Snapshot 投影 `cancelled`;`retry/resume` 只有新 generation 已取得正式 Runtime lineage 并完成唯一 predecessor/successor 记录后,才能向 Runtime read model 暴露 successor,否则只作为管理面结果。旧 trace 缺失、被替换或 revision/digest 漂移时返回 `TARGET_STALE`,禁止对“最近 run”重新猜 target。 + +所有进入项目/对话协议的 slash route 都拒绝 attachments、entry binding 和自然语言补偿;参数缺失/重复/未知字段在对应 ledger 前 `INVALID_REQUEST`,未知命令则仅生成固定长度 direct reply。进入 `submit_intent`/DirectReplyDelivery 的 command line,其用户消息只持久化一次,后续 delivery/operation 均引用该 `conversationUserMessageId`;`LocalManagementRoute`/`LocalTransportReply` 不写 Public conversation,原始 path 只留在 resolver/OS handle 边界内;`/quit`、`/exit` 这类 transport-only 命令是显式例外,不写 conversation/Runtime ledger。GUI 和 CLI 必须使用同一 Rust parser 和相同规范化/错误合同,公共命令得到相同 route、interaction kind、operation identity 规则和副作用计数;CLI-only 别名只能通过上表显式映射到不同 management/observation route,不能在 GUI/CLI 各自增加隐含解析分支。 + +`submit_intent` 的 `Conversation` payload 要求 Consumer 先经现有会话管理面取得明确的 Project Supervisor `sessionId + expectedSessionRevision`;Shell 锁内验证该 session/revision 属于本项目/当前 Supervisor,active session 已变化则返回 `TARGET_STALE`,本轮不把会话 CRUD 隐式塞入 Runtime 命令。BuiltinCommand 的项目级命令由 Shell 根据当前 capability/管理面 scope 取得同一上下文;无项目上下文的 `/help`、`/config`、`/llm-status` 等本地/全局命令不得伪造 sessionId。请求中的 `intentKind` 是 Consumer 对用户实际入口的业务陈述,不是权限或 source 主张;V1 固定为 `createFromPrompt`、`continueProject`、`createFromTemplate`、`importExistingDesign`,并与执行方式正交:`runProfile` 只表示怎么执行,`intentKind` 表示从哪个业务入口开始。Shell 在锁内按项目 manifest、当前 session 和策略校验 intent,可接受、拒绝或将其归一到受支持的内部分支;Consumer 不能自报 source。受信任 transport 使用 `(transport, intentKind) -> source` 的后端映射,映射和否决权始终在 Shell。这样同一 GUI 的不同入口即使 `message` 与 `runProfile` 相同,请求也不会逐字节相同。Shell 复用现有 interaction kernel 只决定 direct reply/execute,再由冻结 intent policy matrix 对 execute 决定 start/steer/reject;direct reply 仍只写 conversation/response stream,不伪造 Runtime Snapshot 变化,其稳定 responseMessageId 预先写入 request ledger。显式 `resume` 命令服务按钮/自动化的结构化生命周期意图;自然语言“继续”只作为普通 message 参与 reply/steer,不得触发 ContinueRun、RetryTerminalRun 或 reconciliation。现有 `agent/interaction.rs` 的 `runtime_resume` tool / `AgentInteractionAction::Resume` 属于迁移前内部分支:P5 必须禁止模型输出直接升级为 Public ResumeCommand;若保留该内部 action,只能在同一 Shell 内归一为普通 steer/reply 或显式 recovery-observe,并保留原 request/message identity,不能绕过新 capability、ledger 和 target revision。 + +V1 的并发不变量是:一个 projectId 同一时刻最多只有一个非终态 Project Supervisor run;专业 Agent/child run 由该 Supervisor 管理,不计作第二个公开 Supervisor。`BuiltinCommand` 先按上一段冻结规则收束,不进入下面的 Runtime matrix;所有 `Conversation` 路由的 `submit_intent` 在 project lock 内串行读取当前 run,再按已注册、带版本的 intent policy matrix 决定 `directReply | start | steer | reject`。对于已由 interaction kernel 判定为 `directReply` 的消息,Shell 先执行下表的“开放交互门禁”:存在 Open/Resolving interaction 时统一 `TARGET_BUSY`,否则不产生 Runtime run,直接走第 1.5 节交付合同;只有 `execute` 分支进入下表。这样 direct reply 不是 Consumer 自己绕过 matrix 的第二条路径。 + +V1 execute 分支的 intent policy matrix 冻结如下;表内每格只有一个 disposition,`steer` 表示在同一 Project Supervisor run 内调整当前执行,必须保持原 runId,不得创建 replacement 或第二个并行 Supervisor run: + +| `intentKind` | 无 run | 活动 run(`running`) | 等待交互(`waiting`) | 已有终态(`completed/failed/cancelled`) | `needs-reconciliation` | +|---|---|---|---|---|---| +| `createFromPrompt` | `start` | `steer` | `TARGET_BUSY` | `start` | `NEEDS_RECONCILIATION` | +| `continueProject` | `start` | `steer` | `TARGET_BUSY` | `start` | `NEEDS_RECONCILIATION` | +| `createFromTemplate` | `start` | `TARGET_BUSY` | `TARGET_BUSY` | `start` | `NEEDS_RECONCILIATION` | +| `importExistingDesign` | `start` | `TARGET_BUSY` | `TARGET_BUSY` | `start` | `NEEDS_RECONCILIATION` | + +`createFromTemplate` 必须携带且锁内复核 `entry_binding=Template`;`importExistingDesign` 必须携带且锁内复核 `entry_binding=ExistingDesign`;另外两种必须省略 `entry_binding`。绑定的 revision/digest 不存在、已失效或不能证明来源时返回 `ARTIFACT_BINDING_UNAVAILABLE`,而不是从 `message` 猜测模板/设计。所有 `start` 的 `runId` 只在 request ledger 的 `prepared` 阶段分配;matrix、绑定校验和并发竞争均纳入 P0 每格 fixture。 + +上表的 `活动 run/等待交互` 还必须按现有 V1.13/V1.23 barrier 细分,不能把所有 `waiting` 当成同一种状态: + +| durable barrier | `createFromPrompt/continueProject` | 其它新入口 | 规则 | +|---|---|---|---| +| active root Run 已绑定冻结 Goal Contract | `TARGET_BUSY` | `TARGET_BUSY` | 该行优先于其它 barrier;普通 `submit_intent` 不调用现有 replacement steer,返回稳定错误并引导使用 `/goal edit` 等显式 Goal management mutation;interaction kernel 判为 direct reply 时仍按开放交互门禁交付 | +| planning/final-reply Provider await | `steer` | `TARGET_BUSY` | 只中断可中断的纯 Provider await;不 abort worker,不重放 action | +| pending confirmation / approved / executing action | `steerDeferred` | `TARGET_BUSY` | 保留原 action fingerprint、确认和 process session;terminal receipt 后再消费 steer | +| `waiting-for-user-input` | `TARGET_BUSY` | `TARGET_BUSY` | 必须精确 `answer`,不能用普通自然语言绕过问题 | +| `waiting-for-isolated-join` | `steerDeferred` | `TARGET_BUSY` | 保留 join barrier,不创建 joinRun/旁路 Provider | +| timer/lane/schedule wait | `steerDeferred` | `TARGET_BUSY` | 只写 steer ledger,唤醒后按 cursor 消费,不把 timer 当作用户 resume | +| `pausedByUser` / `cancelling` / `finalizing` | `TARGET_BUSY` | `TARGET_BUSY` | 必须分别使用 ContinueRun、cancel 读回或等既有 finalization 收束 | + +当 matrix 选择 `steer` 时,Shell 必须先在 project lock 内证明 active root Run 未绑定冻结 Goal Contract;命中冻结合同一律以 `TARGET_BUSY` 持久化业务拒绝,不能进入现有会取消旧树并创建 replacement Run 的特殊 steer primitive。通过该门禁后,Shell 才在同一 `prepared` 记录中由 `sha256(RFC 8785 canonical JSON(["runtime-steer", projectId, requestId, targetRunId]))` 确定性派生并保存 `steerId`,同时保存并复核 V1.13 要求的 `agentId/taskId/sessionId/runId/source` 完整身份;该 seed 只负责稳定生成公开 steerId,不能替代 V1.13 的 source/audience 校验。再通过现有 V1.13 `steer` ledger 的 `prepared → conversation-persisted → queued → applied → closed` 合同提交;重试永远复用这个派生身份。`requestId` 负责公开命令幂等,`steerId` 负责同一 run 的追加指令身份,二者不能互相替代。Steer 的容量、正文脱敏、`appliedSteerCursor`、Provider 中断和 finalization 竞态沿用 V1.13;已有 action/confirmation/process session/side effect 不因普通 steer 被暗中取消,旧计划只能在安全边界失效。 + +`source` 只用于受信任归因、权限和审计,不能替代 intentKind 或改变 start/steer/retry 业务路由。source 不进入 requestFingerprint,但 command ledger 必须保存内部 `authorizationScopeFingerprint`;每次重试/读回仍先重新授权,只有当前 principal 对同一项目、同一 audience 拥有等价或更高有效 capability 时才允许回放,否则返回 `PERMISSION_DENIED`。这样 GUI 与 CLI 可以用同一 requestId 安全恢复,但低权限调用方不能借已存在 ledger 结果越权读回。 + +`cancel` 是持久化取消意图,不等同于调用返回时 Run 已经进入终态;`CancelAccepted` 只表示取消受理并返回同一 runId 与稳定 `cancelOperationId`,Consumer 必须继续读 Snapshot/事件观察最终收束。`cancelling` 是独立公开状态,不能让 UI 把仍在执行的 Run 显示成 `cancelled`,也不能在取消期间再次发起普通 submit/retry。V1 取消矩阵冻结如下: + +| 目标状态 | Shell 行为 | 结果边界 | +|---|---|---| +| `queued/pending` 且尚未开始执行 | 写 cancel tombstone,阻止 dequeue,收束为 cancelled | 不产生 Runtime/Provider 副作用 | +| `running`、等待 child、等待 lane 或等待 timer | 写同一 operation identity 的 cancel intent,锁内固化当前 parent/child/action/process-session target set,向非终态 child 写 cancel tombstone,通知 Driver 中断并等待 durable finalization | 取消请求可先成功受理,不能提前伪造终态;child 未收束不能把 parent 标成 cancelled | +| `waitingForUserInput`、`waitingForPolicyApproval`、`waitingForDeveloperApproval` | cancel interaction 为 superseded/cancelled,再收束 Run | 不自动提交 answer/approve/reject | +| pending action、Provider/工具执行中或 `finalizing` | 只允许走现有 interruption/finalization 合同;无法证明副作用结果时进入 `needs-reconciliation` | 不因 cancel 创建第二 action、第二 Provider 请求或虚假 cancelled 终态 | +| `cancelling` 且已有 cancel operation | 读取并返回已有 `cancelOperationId`;同 requestId 回放原 ack,不创建第二 tombstone | 新旧 requestId 都指向同一取消操作,不重复中断或写第二终态 | +| `needs-reconciliation` / 外部结果未知 | 记录取消请求但不自动宣称已取消;由 reconciliation owner 判定 parent、child、action 和 process session 是否可安全收束 | 任一 target 外部结果未知时返回 `COMMAND_RESULT_UNKNOWN` 或等价结构化状态,禁止只收束 parent 掩盖 child 未知 | +| 已经是 `completed/failed/cancelled` | 同 requestId 回放原结果;不同 requestId 返回 `CANCEL_ALREADY_TERMINAL` | 零副作用、不可重新取消或隐式 retry | + +`CancelCommand.sessionId` 表示当前调用方的 active project session,不要求等于 Run 创建时的旧 session;只要 session handoff 已由会话管理面持久化并证明属于同一 Project Supervisor lineage,新的 active session 可以取消旧 session 遗留的 Run。session 切换不会复用旧 sessionId,也不会自动解绑仍在运行的 Run;没有有效 handoff/lineage 时返回 `TARGET_STALE`。没有 active session 的 owner recovery 只能走受信任 Runner/Developer reconciliation capability,不能由 Public Consumer 猜测恢复身份。 `submit_intent/cancel/resume` 不使用项目级 `snapshotRevision` 作为业务 CAS:无关的进度刷新不能让用户命令无效。它们在锁内以 payload 中的精确目标身份和当前 durable state 校验可执行性;`cancel` 的 run 已切换时返回 `TARGET_STALE`。`answer/approve` 使用 interaction 自身的 `expectedInteractionRevision`,而不是全项目 Snapshot revision;Public Snapshot 中的 interaction view 同时投影该值。命令返回值只是最小 ack 和操作完成时观察到的 revision,不携带 Runtime state;Consumer 成功或 `interactionRequired=true` 后都重读完整 Snapshot。 command ack 的 `responseMessageId` 只用于在既有 conversation/response-stream 管道定位 direct reply;对话正文仍通过原有 durable conversation read/stream 获取,不进入 Snapshot、事件或 command response。`observedSnapshotRevision` 是操作完成时已闭合的 Public revision,不表示命令结果本身是一份状态。 -公开错误使用稳定 `code/kind/retryable/message/interactionRequired`,命中 ledger 的错误还返回原 `requestFingerprint` 和 `replayed`。Consumer 不解析中文 message。最小错误矩阵如下;未知错误码按不可重试失败关闭: +命令错误使用稳定 `code/kind/retryable/message/interactionRequired`,命中 ledger 的错误还返回原 `requestFingerprint` 和 `replayed`;SnapshotError 只投影稳定 code/kind/retryable/interactionRequired,不携带命令身份或正文。Consumer 不解析中文 message。最小错误矩阵如下;未知错误码按不可重试失败关闭: | code | 语义 | retryable / Consumer 动作 | |---|---|---| | `PROTOCOL_VERSION_UNSUPPORTED` / `INVALID_REQUEST` / `PERMISSION_DENIED` | ledger 前的版本、格式或权限拒绝 | false;修正客户端/权限,不能原请求盲重试 | -| `TARGET_STALE` / `INTERACTION_STALE` / `INTERACTION_ALREADY_RESOLVED` | 精确目标或 interaction 已变化 | false;重读 Snapshot,若仍需操作则新 requestId | +| `TARGET_STALE` / `TARGET_BUSY` / `INTERACTION_STALE` / `INTERACTION_ALREADY_RESOLVED` / `CANCEL_ALREADY_TERMINAL` | 精确目标、并发槽位或 interaction 已变化 | false;重读 Snapshot,若仍需操作则新 requestId | +| `ARTIFACT_BINDING_UNAVAILABLE` | entry binding、输入附件或 requestChanges 的目标产物没有可复核的 immutable revision + `sha256` digest | false;只能等待既有资源/artifact lineage 补齐或改用不需要该 binding 的合法入口,不得猜测当前最新版本或静默使用最新资源 | | `IDEMPOTENCY_KEY_REUSED` | 同 requestId/responseId 被不同内容复用 | false;视为调用方错误 | | `COMMAND_IN_PROGRESS` | 同请求已有 live executor 或同 response 正在 Resolving | true;同 payload/requestId 读回或重试,不启动第二 executor | | `COMMAND_RESULT_UNKNOWN` / `NEEDS_RECONCILIATION` | 已受理操作的外部结果无法证明 | false;重读并进入人工核对,禁止换 ID 自动重放 | -| `OWNER_UNAVAILABLE` / `OWNER_FENCED` / `TRANSIENT_UNAVAILABLE` | 尚未受理,或当前执行者已失去 owner generation | true;完全相同 requestId 可重试;旧 owner 不得继续写入 | +| `OWNER_UNAVAILABLE` / `TRANSIENT_UNAVAILABLE` | 尚未写入 prepared,当前没有合法 owner/transport | true;完全相同 payload/requestId 可重试 | +| `OWNER_FENCED` | 已受理 executor 失去 owner generation | false;Consumer 先读回同 requestId,由新 owner 按 ledger 恢复;若副作用无法证明则转 `COMMAND_RESULT_UNKNOWN`,旧 owner 不得继续写入 | | `REQUEST_NOT_FOUND` | 当前 project ledger 没有该 requestId 的受理记录 | false;不泄漏项目存在性;调用方根据原 transport 结果决定是否用原 requestId 重试 | | `CURSOR_INVALID` / `CURSOR_EXPIRED` | 事件补读起点非法或过期 | 不适用于写重试;全量读取 Snapshot 后换新 cursor | | `INTERNAL` | 已脱敏的未分类内部失败 | false,除非未来细分为明确暂态 code | @@ -284,35 +1230,91 @@ struct AgentRuntimeCommandResultView { status: CommandLedgerStatus, replayable: bool, result: Option, - error: Option, + error: Option, observed_snapshot_revision: Option, } + +enum CommandLedgerStatus { + Prepared, // prepared + Executing, // executing + Succeeded, // succeeded + Rejected, // rejected + OutcomeUnknown, // outcomeUnknown;外部结果无法证明 + NeedsReconciliation, // needsReconciliation;仅由核对流程推进,不自动重试 +} ``` -读回先做 locator/projectId/调用来源复核,再在 project command lock 内查询;不能跨项目按 requestId 搜索。`prepared`/`executing` 返回 `COMMAND_IN_PROGRESS` 或等价的 `status`,Consumer 继续用同一 requestId 读回;`succeeded`/`rejected` 永久返回已保存结果;`outcome-unknown` 返回 `COMMAND_RESULT_UNKNOWN` 并标记 `needs-reconciliation`。如果 requestId 从未被受理,返回不泄漏项目存在性的 `REQUEST_NOT_FOUND`;该错误只表示“本次调用没有留下受理记录”,调用方仍需根据原 transport 响应决定是否使用同一 requestId 重试,不能据此生成新 requestId 重放未知副作用。已进入 ledger 的确定性业务拒绝必须通过 `rejected` 结果读回,而不是依赖错误文字重新判断。 +`result/error` 是随 `status` 绑定的严格 tagged-union 投影,不能出现未定义组合:`succeeded` 必须是 `result=Some、error=None`;`rejected` 必须是 `result=None、error=Some` 且 error 为已持久化的确定性业务拒绝;`prepared/executing` 必须是 `result=None、error=None`;`outcomeUnknown` 必须是 `result=None、error.code=COMMAND_RESULT_UNKNOWN`;`needsReconciliation` 必须是 `result=None、error.code=NEEDS_RECONCILIATION`。`observedSnapshotRevision` 只有在对应 Public projection 已闭合并可读回时才为 `Some`。任一组合不满足该不变量都按 corrupt record 隔离并进入 `outcome-unknown/needs-reconciliation`,不得让 Consumer 猜测结果。 + +`CommandLedgerStatus` 的 wire value 固定为注释中的 lowerCamelCase。`prepared`/`executing` 的 `replayable=true`,表示只能使用同一 requestId 继续或读回;`succeeded`/`rejected`/`outcomeUnknown`/`needsReconciliation` 为不可自动重做的结果,`replayable=false`。`outcomeUnknown` 不是可执行状态:它必须由受信任 reconciliation 流程转为 `needsReconciliation`;`needsReconciliation` 在核对完成前只能读回并返回 `NEEDS_RECONCILIATION`,核对流程确认权威结果后才可推进为确定的 `succeeded/rejected`。在 `outcomeUnknown` 或 `needsReconciliation` 下,任何同 requestId 调用都不得执行命令或创建新的副作用;所有转移都必须保留原 operation identity,并记录 reconciliation operation identity。 + +读回先做 locator/projectId/调用来源复核,再在 project command lock 内查询;不能跨项目按 requestId 搜索。`prepared`/`executing` 返回 `COMMAND_IN_PROGRESS` 或等价的 `status`,Consumer 继续用同一 requestId 读回;`succeeded`/`rejected` 永久返回已保存结果;`outcome-unknown` 返回 `COMMAND_RESULT_UNKNOWN` 并标记项目 `needs-reconciliation`;已进入核对终态的记录返回 `NEEDS_RECONCILIATION`。如果 requestId 从未被受理,返回不泄漏项目存在性的 `REQUEST_NOT_FOUND`;该错误只表示“本次调用没有留下受理记录”,调用方仍需根据原 transport 响应决定是否使用同一 requestId 重试,不能据此生成新 requestId 重放未知副作用。已进入 ledger 的确定性业务拒绝必须通过 `rejected` 结果读回,而不是依赖错误文字重新判断。 request ledger 的状态转移冻结为: ```text -prepared -> executing -> succeeded | rejected | outcome-unknown -prepared -> rejected (可证明尚未产生副作用的业务拒绝) -executing -> succeeded | rejected (有权威 durable 证据) -outcome-unknown -> needs-reconciliation(终态,不自动回退) +prepared -> executing | rejected (后者仅限可证明尚未产生副作用) +prepared -> outcome-unknown (无法证明是否已开始执行) +executing -> succeeded | rejected (有权威 durable 证据) +executing -> outcome-unknown (外部结果无法证明) +outcome-unknown -> needs-reconciliation(仅受信任核对流程) +needs-reconciliation -> succeeded | rejected(仅核对确认权威结果) ``` -每条记录保存 `ledgerVersion`、创建/更新时间、请求指纹、操作身份、执行者 generation、状态和完整结果引用;写入使用临时文件/同步/原子替换,恢复时按版本校验,损坏记录保留原始证据并阻止同 requestId 再执行。`replayed=true` 仅表示返回已持久化的同一结果,不代表再次执行。任何“Shell 已写入 prepared 但 transport 未收到响应”的情况都必须先读回;不能以 unknown-command fallback 或新 requestId 规避 ledger。 +每条记录保存 `recordRevision`、`ledgerVersion`、创建/更新时间、请求指纹、操作身份、执行者 generation、状态和完整结果引用;写入使用临时文件/同步/原子替换,恢复时按 record revision 与 ledger version 双重校验,损坏记录保留原始证据并阻止同 requestId 再执行。`replayed=true` 仅表示返回已持久化的同一结果,不代表再次执行。任何“Shell 已写入 prepared 但 transport 未收到响应”的情况都必须先读回;不能以 unknown-command fallback 或新 requestId 规避 ledger。 -request ledger 是项目级私有 durable 记录,状态为 `prepared / executing / succeeded / rejected / outcome-unknown`,并保存规范请求指纹、预分配的内部 operation identity、executor boot/generation 及完整权威成功或错误结果。恢复所需的用户消息/answers 只保存有界私有 payload 或指向既有 durable conversation/interaction record 的稳定引用,沿用现有内容安全、权限和脱敏规则;绝不复制到 Public Snapshot、事件或错误。requestId 提供幂等受理与结果读回边界,不承诺无法判定的外部副作用 exactly-once;这种窗口必须显式 outcome-unknown。处理顺序固定: +所有 durable 记录统一使用 `DurabilityCapability`,不把“rename 成功”当成跨平台持久化证明;V1 至少覆盖 command ledger、Builtin management operation、interaction、rework、projection、input envelope、Runtime status message、public event message、response delivery、RunLineage、Session/Handoff/ActiveSessionIndex/SessionRotation/HandoffManifest 和 local management operation: + +1. Unix:同目录创建临时普通文件 → 写完整 envelope → `fsync(file)` → 原子 rename/replace → `fsync(parent directory)`;临时文件、目标文件或目录是 symlink、类型错误或 schema/ledgerVersion 不完整时隔离为 `.corrupt.`,不覆盖旧证据。 +2. Windows:同目录临时文件 → `FlushFileBuffers` → 使用平台原子 replace(保留目标备份/ACL)→ 对目标句柄再次 `FlushFileBuffers`;若平台 API 或文件系统不能证明 replace 后持久化,能力报告为 `unsupported`,禁止 prepared→executing。 +3. 恢复只接受完整 envelope、单调 `recordRevision`/`ledgerVersion`、匹配 request/operation identity 和完整 checksum;发现新旧两个版本都存在时按 journal 状态选择唯一合法前缀,无法唯一选择就进入 `outcome-unknown/needs-reconciliation`,不“取最新文件”。 + +能力报告必须在 Runner boot 时持久化并绑定 `ownerBootId`;运行期间能力降级会停止新受理,但不回滚已提交事实。上述规则是安全门禁,不以“目标平台通常支持”替代 Unix/Windows crash-point 实测。 + +request ledger 是项目级私有 durable 记录,状态为 `prepared / executing / succeeded / rejected / outcome-unknown / needs-reconciliation`,并保存规范请求指纹、预分配的内部 operation identity、executor boot/generation 及完整权威成功或错误结果。恢复所需的用户消息/answers 只保存有界私有 payload 或指向既有 durable conversation/interaction record 的稳定引用,沿用现有内容安全、权限和脱敏规则;绝不复制到 Public Snapshot、事件或错误。requestId 提供幂等受理与结果读回边界,不承诺无法判定的外部副作用 exactly-once;这种窗口必须显式 outcome-unknown,核对完成后才可进入 needs-reconciliation 或确定结果。处理顺序固定: 1. transport 先 canonicalize root、验证本地项目授权、manifest `projectId` 与调用权限;版本/身份/权限失败发生在 ledger 之前,保证攻击者不能向任意项目写记录。 -2. Shell 用 RFC 8785 canonical JSON 规范化“schema version + command kind + projectId + 完整业务 payload(含 interaction/response identity、decision、answers 和任何精确 target)”,计算小写 SHA-256。`requestId`、路径、时间戳及 transport 字段不进指纹。 -3. 在 project command lock 内先按 `requestId` 查 ledger,再做任何当前状态校验。相同 ID/相同指纹的 `succeeded` 或 `rejected` 直接返回原结果;不同指纹返回 `IDEMPOTENCY_KEY_REUSED`;`prepared/executing` 返回 `COMMAND_IN_PROGRESS`(可同 ID重试/读回);`outcome-unknown` 返回原 `COMMAND_RESULT_UNKNOWN`,不再执行。 -4. 只有 ledger 未命中时才验证 target/interaction 当前状态,并在副作用前原子写 `prepared`;`submit_intent` 的 acceptedRunId/steerId 以及下游 Runner requestId 必须在 prepared 中预分配并在恢复时复用,不能在重试中生成第二身份。业务拒绝也原子落为 `rejected`,使同一请求重放得到相同结果。进入内部执行前转为 `executing` 并绑定 executor;完成内部状态写后必须先闭合对应 Public projection,再写入并回读 `succeeded/rejected` 权威结果和 `observedSnapshotRevision`。 +2. Shell 在 ledger 前完成严格结构/数量/长度/UTF-8 字节上限和内容安全校验;失败返回 `INVALID_REQUEST` 且不落原始正文。通过后,项目级 Runtime command 用 RFC 8785 canonical JSON 规范化“schema version + command kind + projectId + 完整业务 payload(含 Conversation/BuiltinCommand variant、规范化 commandLine、expectedParserVersion、intentKind、entryBinding、immutable attachments、runProfile、resume tagged intent、interaction/response identity、decision、feedback、answers 和任何精确 target)”,计算小写 SHA-256。LocalManagementRoute 不进入项目 request ledger;它只对去掉 locator 的参数计算 `argumentFingerprint`,并把不含原文的 `locatorDigest` 单独固化。任何 fingerprint 都不得保存或回显原始路径;`requestId`、时间戳及 transport/source 字段不进项目业务指纹;当前有效 capability 另存内部 authorizationScopeFingerprint。Local command 的幂等键为 `(localScopeId, requestId)`;同 scope/同 requestId 必须保持 parser version、route、commandName、targetRef、locatorDigest 和 path-free arguments 这组**历史操作 identity**完全相同,才可回放;不同则 `IDEMPOTENCY_KEY_REUSED`。首次受理的 capabilityId 只作为 `originatingCapabilityId` 审计记录,不进入历史操作 identity;重试/读回可以使用 capability rotation 后的新 capabilityId,但当前 capability 必须重新证明同一 localScope、principal、route/target 的授权范围未被撤销且覆盖该操作,否则返回 `PERMISSION_DENIED`/`TARGET_STALE`。这些 parser/route/target 字段必须原样写入 `AgentRuntimeLocalManagementOperationRecord`,成为 prepared 后的历史 identity;当前 capability 只能用于重新授权,不能替代或重新解释已保存的 parser/route/target。恢复时任一历史字段不一致返回 `TARGET_STALE`,不得仅按新的 capability 或 commandLine 继续执行。local command 结果通过 `read_local_management_result(localScopeId, requestId)` 读回并重新授权;`prepared/executing/outcome-unknown/needs-reconciliation` 的恢复语义与项目 operation 相同,但绝不按 commandLine 原文或当前窗口重新解析路径。 +3. 在 project command lock 内先按 `requestId` 查 ledger,再做任何当前业务状态校验;但每次重放仍必须通过 project identity、principal 和 authorizationScopeFingerprint 兼容性复核。相同 ID/相同指纹且授权等价的 `succeeded` 或 `rejected` 直接返回原结果;不同指纹返回 `IDEMPOTENCY_KEY_REUSED`;`prepared/executing` 返回 `COMMAND_IN_PROGRESS`(可同 ID重试/读回);`outcome-unknown` 返回原 `COMMAND_RESULT_UNKNOWN`,不再执行;`needs-reconciliation` 返回 `NEEDS_RECONCILIATION`,只能读回或进入受信任核对流程,不得执行命令。 +4. 只有 ledger 未命中时才验证 target/interaction 当前状态,并在副作用前原子写 `prepared`;`submit_intent` 的 inputEnvelopeId/acceptedRunId/steerId、RetryTerminalRun 的 successorRunId、requestChanges 的 reworkOperationId、direct reply 的 responseMessageId 以及下游 Runner/Provider request identity 必须在 prepared 中预分配并在恢复时复用,不能在重试中生成第二身份。业务拒绝也原子落为 `rejected`,使同一请求重放得到相同结果。进入内部执行前转为 `executing` 并绑定 executor;完成内部状态写后必须先闭合对应 Public projection,再写入并回读 `succeeded/rejected` 权威结果和 `observedSnapshotRevision`。 5. 崩溃恢复只能依据 ledger、executor 生命状态、Runtime journal 和既有 durable identity 前向闭合;仍有 live executor 时不得由第二执行者接管。能证明未产生副作用可用同一 operation identity 继续;能证明结果则幂等补投影/结果;外部结果不明则原子转为 `outcome-unknown` 并使项目进入 `needs-reconciliation`,禁止自动换 requestId 或重复入队。 6. Consumer 对 transport 超时、`COMMAND_IN_PROGRESS` 或明确暂态错误只可重发完全相同 payload 和同一 requestId,或调用 `read_game_creator_agent_command_result(projectId, requestId)`;读回接口同样先完成 locator/project identity/权限复核,禁止跨项目扫描。 7. V1 ledger 跟随项目 Runtime durable archive 生命周期保存,不按时间或条数隐式淘汰。若将来压缩,必须先设计持久 tombstone,使已淘汰 requestId 仍能失败关闭。 -现有 Runner 内存 request cache、`acceptedRunId` 和 Goal CAS 只作为内部附加护栏,不替代公开 ledger。goal CRUD、`compact`、会话管理和配置读写属于管理面,不进入这五个 Runtime Loop 命令;它们若是公开写操作,继续遵守各自现行 CAS/权限合同,不能借本次重构降级。 +`RetryTerminalRun` 的 successor lineage 不再直接复用现有 `acceptedRunId` 或调用方传入的 `nextRunId`。当前实现中的 `agent.runtime.background_task.retry` 记录、`retryRunId` 和 `accepted_run_id` 只能作为迁移 adapter 的输入:adapter 必须在 project lock 内验证 `(projectId, agentId, taskId, predecessorRunId, retryRunId, sessionId)` 唯一且 predecessor 确为终态,然后写入新的 `RunLineageRecord`;`acceptedRunId` 仍只是响应中的实际 successor 回显,不是 lineage 事实。V1 `RetryTerminalRun` 不接受 `nextRunId`,successor runId 由 prepared 阶段生成并写入: + +```rust +struct RunLineageRecord { + envelope: AgentRuntimeDurableEnvelope, + project_id: String, + supervisor_lineage_id: String, + agent_id: String, + session_id: String, + task_id: String, + parent_run_id: Option, // Supervisor=None;专业 Agent 必须为当前父 run + delegation_id: Option, // 专业 Agent 必填并锁内复核 + predecessor_run_id: String, + successor_run_id: String, + predecessor_terminal_revision: u64, + retry_operation_id: String, + source_record_ref: Option, // 仅迁移旧 retry sidecar/DB record + status: SuccessorLineageStatus, // prepared | enqueued | active | terminal | rejected | outcomeUnknown | needsReconciliation +} + +enum SuccessorLineageStatus { + Prepared, + Enqueued, + Active, + Terminal, + Rejected, + OutcomeUnknown, + NeedsReconciliation, +} +``` + +唯一约束使用 `(projectId, agentId, predecessorRunId)`,同一 predecessor 在其整个 lineage 生命周期内最多有一个 successor(不论 successor 当前是 prepared、active 还是 terminal);不能假设不同 Agent 的 runId 全局唯一。并发 retry 通过 predecessor terminal revision + lineage 唯一约束收敛为同一 successor。successor 已终态后不得再次从旧 predecessor 分叉;后续 retry 必须针对该 successor 的最新终态记录创建下一条 lineage,重复提交同一 retryOperationId 才能回放原 successor。Supervisor lineage 的 `parentRunId/delegationId` 必须为空;专业 Agent lineage 必须同时保存并复核当前 `parentRunId + delegationId + taskId + sessionId`,且父 Supervisor 仍是允许该 recovery 的当前 run。旧 `nextRunId` 与新生成 ID 冲突、旧 retry 记录缺字段、父 Supervisor 已终态或任一 lineage 证据不完整时,业务拒绝并进入 `TARGET_STALE`/`NEEDS_RECONCILIATION`,不能另造 successor。`ContinueRun` 不写 `RunLineageRecord` 且始终保持原 runId。 + +现有 Runner 内存 request cache、`acceptedRunId` 和 Goal CAS 只作为内部附加护栏,不替代公开 ledger。goal CRUD、`compact`、会话管理、资源上传/登记、`preview.start`/`preview.validate` 和配置读写属于管理面,不进入这五个 Runtime Loop 命令;它们若是公开写操作,继续遵守各自现行 CAS/权限合同,不能借本次重构降级。 ### 1.3.2 五命令请求体冻结 @@ -322,9 +1324,193 @@ request ledger 是项目级私有 durable 记录,状态为 `prepared / executi ```rust struct SubmitIntentCommand { meta: AgentRuntimeCommandMeta, + payload: SubmitIntentPayload, +} + +enum SubmitIntentPayload { + Conversation { + session_id: String, + expected_session_revision: u64, + message: String, + attachments: Vec, + intent_kind: AgentRuntimeIntentKind, + // 只有 createFromTemplate/importExistingDesign 使用;不能把模板或设计身份 + // 藏在自然语言 message 中。 + entry_binding: Option, + run_profile: AgentRuntimeRunProfile, + }, + BuiltinCommand { + command_line: String, + expected_parser_version: String, + // slash 不允许 attachments、entry binding 或 Runtime intent/profile; + // project/session scope 由 Shell 从 capability/管理面取得。 + }, +} + +// 项目级 slash management action 的私有幂等记录;不把 management action 伪装成 Runtime run。 +struct AgentRuntimeBuiltinManagementOperationRecord { + envelope: AgentRuntimeDurableEnvelope, + command_request_id: String, + // RuntimeBuiltinCommand 的项目级 slash action 必须引用已经幂等提交的 + // conversation user message;LocalManagementRoute(例如 CLI /goal)不写 + // Public conversation,此字段必须为 None,不能伪造 message identity。 + conversation_user_message_id: Option, + command_name: String, + target_identity_fingerprint: String, + argument_fingerprint: String, + private_argument_ref: Option, + // 领域副作用一旦被受理,必须绑定同一 project operation;核对期间保留 + // 原 operation identity,不能通过新 requestId 或新的领域 operation 重做。 + project_operation_ref: Option, + reconciliation_operation_id: Option, + status: AgentRuntimeBuiltinManagementOperationStatus, + result_ref: Option, + error_ref: Option, +} + +enum AgentRuntimeBuiltinManagementOperationStatus { + Prepared, + Executing, + Succeeded, + Rejected, + OutcomeUnknown, + NeedsReconciliation, +} + +// 状态迁移:prepared -> executing | rejected;prepared -> outcomeUnknown; +// executing -> succeeded | rejected | outcomeUnknown; +// outcomeUnknown -> needsReconciliation; +// needsReconciliation -> succeeded | rejected。最后两条只能由受信任核对流程 +// 执行,并必须写入 reconciliation_operation_id 与确定的 result/error ref。 +// 只有需要项目身份/副作用的 Builtin management route 建该记录;/mcp、/quit、/exit +// 等纯 CLI transport 动作不进入项目 ledger。其 target/argument fingerprint 在 +// prepared 后固定,重试未知结果只能读回或核对,不能换 requestId 再做副作用。 + +// LocalManagementRoute 的 path-free 参数恢复规则:`private_argument_ref` 指向 +// 同一 localScopeId 下有界、脱敏、带 checksum 的私有 payload。恢复只能读取该 +// ref,并同时校验 payload checksum 与 `argument_fingerprint`;缺失、篡改或二者 +// 不一致进入 `CORRUPT_RECORD`,不得重新解析 commandLine。locator 原文永不入 +// payload、fingerprint、result/error 或 Public delivery。 + +// LocalManagementResponse 的错误闭合规则:capability 不存在返回 +// `CAPABILITY_NOT_FOUND`;parser 版本不支持返回 `PROTOCOL_VERSION_UNSUPPORTED`; +// targetRef 漂移返回 `TARGET_STALE`,scope 字段缺失/多带或 session revision +// 不匹配返回 `SCOPE_MISMATCH`,权限不足返回 `PERMISSION_DENIED`,locator +// 不可用/过期/撤销返回 `LOCATOR_UNAVAILABLE`,活跃执行返回 +// `COMMAND_IN_PROGRESS`,结果无法证明返回 `COMMAND_RESULT_UNKNOWN`;已进入 +// reconciliation 的记录返回 `NEEDS_RECONCILIATION`,同一 `(localScopeId, +// requestId)` 换指纹返回 `IDEMPOTENCY_KEY_REUSED`,损坏记录 +// 返回 `CORRUPT_RECORD`。确定性校验失败不得依赖中文 message;已进入 local +// record 的拒绝和未知结果必须可由 `read_local_management_result(localScopeId, +// requestId)` 读回。Local response 的 `requestFingerprint` 在成功时必有,错误 +// 仅在已完成无路径原文的规范化时有值;`projectId` 只通过成功结果的 +// `resolved_project_id` 返回,`observedSnapshotRevision` 和 +// `interactionRequired` 对所有 local response 均不存在。 + +enum AgentRuntimeLocalLocatorHandleState { + NotAcquired, + Acquired, + Released, + ReconciliationHeld, +} + +// locator_handle_ref 的生命周期与 projectOperationRef 绑定:resolver 先验证 +// locator 类型、digest、权限和 symlink/reparse 安全,再取得绑定 +// `(localScopeId, requestId, ownerGeneration)` 的临时 handle。local-only route +// 在 local record 已进入 terminal 且 `project_operation_ref = None` 时释放; +// project-linked route 必须等 local record 与关联 project operation 都到达 +// terminal 且结果已 durable commit 后释放。`prepared -> executing` 前 handle +// 过期/撤销返回 `LOCATOR_UNAVAILABLE`;executing 或 project operation 已受理 +// 后失去 handle 必须进入 `outcome-unknown/reconciliation`,状态固定为 +// `ReconciliationHeld`,不能换 handle 或 requestId 重做。owner 重启只能用同一 +// 稳定 resolver identity 重新绑定并复核 locator digest、target revision 和 +// operation identity,不能从原 commandLine 重新解析。`Released` 只表示 handle +// 已撤销,record 可保留不透明 ref 供审计但不得再次使用。至少覆盖 +// `prepared → handle acquired → crash`、`executing → handle expired`、 +// `outcome-unknown → retry/readback`、无 project operation 的 resolver rejection +// 和“project resolve 成功但 project operation 失败”的 fixture;所有分支最终 +// 都必须释放或明确保留待 reconciliation 的 handle。 + +// 尚未解析出 projectId 的本地 locator / path-bearing action 使用独立私有记录, +// 不强行伪造 projectId,也不把绝对路径写入 Public/Runtime ledger。 +struct AgentRuntimeLocalManagementOperationRecord { + envelope: AgentRuntimeLocalDurableEnvelope, + command_request_id: String, + originating_capability_id: String, // 仅审计;不作为重放的历史业务 identity + authorization_principal_ref: String, + authorization_scope_fingerprint: String, + expected_parser_version: String, + route: AgentRuntimeLocalCommandRoute, + target_ref: Option, + command_name: String, + resolved_project_id: Option, + project_operation_ref: Option, + locator_digest: Option, + // 仅引用受信任 resolver/OS bookmark/handle store;不能包含原始路径。 + locator_handle_ref: Option, + locator_handle_state: AgentRuntimeLocalLocatorHandleState, + // 只覆盖去掉 locator 后的规范化参数;路径身份单独由 locatorDigest 约束。 + argument_fingerprint: String, + // `/goal <目标>`、`/goal edit` 和 `/agent-resume [说明]` 等 path-free + // 参数保存为有界私有 payload;host path 原文禁止进入该 payload。 + private_argument_ref: Option, + status: AgentRuntimeLocalManagementOperationStatus, + result_ref: Option, + error_ref: Option, +} + +struct AgentRuntimeInputAttachmentBinding { + attachment_namespace: String, + attachment_id: String, + attachment_revision: u64, + digest_algorithm: String, // V1 固定 sha256 + content_digest: String, + media_kind: AgentRuntimePublicMediaKind, +} + +// Shell 私有 durable payload;不是 Public Snapshot 或 conversation 正文。 +struct AgentRuntimeInputEnvelopeRecord { + envelope: AgentRuntimeDurableEnvelope, + input_envelope_id: String, + status: AgentRuntimeInputEnvelopeStatus, // reserved | committed | outcome-unknown | corrupt + command_request_id: String, + project_id: String, session_id: String, + session_revision: u64, + conversation_user_message_id: String, + conversation_commit_marker: Option, message: String, - run_profile: AgentRuntimeRunProfile, + attachments: Vec, + input_fingerprint: String, +} + +enum AgentRuntimeInputEnvelopeStatus { + Reserved, + Committed, + OutcomeUnknown, + Corrupt, +} + +`Committed` 必须同时具有可回读的 `conversation_commit_marker`;`OutcomeUnknown`/`Corrupt` 禁止进入 Provider/Runtime context,恢复只能保留同一 envelope identity 并进入 reconciliation。 + +enum AgentRuntimeIntentEntryBinding { + Template { + template_id: String, + template_revision: u64, + digest_algorithm: String, // V1 固定 sha256 + content_digest: String, + }, + ExistingDesign { + design_id: String, + design_revision: u64, + digest_algorithm: String, // V1 固定 sha256 + content_digest: String, + }, +} + +struct AgentRuntimeRunProfile { + name: String, + version: String, } struct AnswerCommand { @@ -336,33 +1522,87 @@ struct AnswerCommand { struct ApproveCommand { meta: AgentRuntimeCommandMeta, interaction: InteractionResponseMeta, - decision: ApprovalDecision, // Approve | Reject,必填 + decision: ApprovalDecision, // approve | reject | requestChanges,必填 + feedback: Option, // 仅 requestChanges;有界、脱敏,其他 decision 必须省略 } struct CancelCommand { meta: AgentRuntimeCommandMeta, session_id: String, + expected_session_revision: u64, run_id: String, } +enum AgentRuntimeResumeMode { + ContinueRun, // continueRun + RetryTerminalRun, // retryTerminalRun + ReconcileRun, // reconcileRun +} + +enum AgentRuntimeResumeIntent { + ContinueRun { + session_id: String, + expected_session_revision: u64, + run_id: String, + expected_run_revision: u64, + }, + RetryTerminalRun { + session_id: String, + expected_session_revision: u64, + target: AgentRuntimeRetryTarget, + }, + ReconcileRun { + // 没有 active session 时允许受信任 reconciliation capability 置空; + // Public User/CLI 不能使用该分支。 + session_id: Option, + run_id: String, + expected_run_revision: u64, + reconciliation_id: String, + }, +} + +enum AgentRuntimeRetryTarget { + Supervisor { + run_id: String, + expected_terminal_revision: u64, + }, + Collaborator { + target: AgentRuntimeCollaboratorRunTarget, + }, +} + struct ResumeCommand { meta: AgentRuntimeCommandMeta, - resume_scope: AgentRuntimeResumeScope, // Project - reason: ResumeReason, + intent: AgentRuntimeResumeIntent, } ``` -`submit_intent.message` 不能为空,长度上限沿用现有 Runtime 输入限制;`sessionId` 必须是当前 Project Supervisor 的 session。`cancel` 必须同时提交当前 `sessionId + runId`,防止旧 runId 被新会话误取消;run 已终结、session 已切换或绑定关系不一致均为 `TARGET_STALE`。V1 `resumeScope` 只允许 `project`,`reason` 使用稳定枚举 `userRequested | ownerRecovered | timerElapsed | interactionResolved`;`ownerRecovered/timerElapsed/interactionResolved` 只能由受信任 Shell/Runner 生成,Consumer 只能提交 `userRequested`。`runProfile` 只允许已注册的公开 profile 名称和版本,不能携带 Provider、模型、工具、提示词或路径配置。 +V1 `submit_intent` 的 `Conversation.message` 必须是非空、非纯空白文本并按第 1 节统一常量校验;attachment-only 在 ledger 前返回 `INVALID_REQUEST`。slash route 携带 attachment 或 entryBinding 同样在 ledger 前返回 `INVALID_REQUEST`,不得先执行 parser 再忽略多余输入。这是对现有 Runtime `task` 非空合同的显式继承,Consumer 不得为了绕过门禁自动合成“请处理附件”等自然语言。attachment 只能在 capability 对应 option 的 `inputPolicy=textWithOptionalAttachments` 时随 message 提交,`mediaKind` 必须命中同一 option 的 `allowedAttachmentMediaKinds` 且由资源 registry 的真实类型复核;`Other` 不属于 V1 可输入媒体。attachment 必须已经通过现有上传/项目资源管理面进入 manifest/resource lineage。Runtime 命令不接收文件字节、绝对路径、`file://`、浏览器临时 URL 或上传 token;Shell 在锁内重读 revision 和 `sha256`,任一漂移整条请求失败关闭。attachment 顺序、完整 binding 和 mediaKind 均进入 request fingerprint,不能只散列文件名。V1 所有 `digestAlgorithm` 必须等于 `sha256`,`contentDigest` 必须是 64 位小写十六进制;namespace/id、revision 和实际重读内容均在 project lock 内复核,缺失/格式错误在 ledger 前 `INVALID_REQUEST`,内容不一致为 `ARTIFACT_BINDING_UNAVAILABLE`。 -`AnswerCommand` 的答案结构只允许 Public Snapshot 当前 interaction view 中声明的 question/option/自由回答约束;缺失、重复、越界或不符合约束返回 `INVALID_REQUEST`,不产生部分写入。`ApproveCommand` 的 decision 不允许由 UI button、命令名或缺省值推断。`requestFingerprint` 覆盖上述规范化业务请求体以及 `schemaVersion/commandKind/projectId`,不覆盖 transport source、locator、时间戳、重试次数或响应展示文案。通过身份、权限和版本校验的请求,即使因 target stale、interaction stale、当前状态不允许或策略拒绝而没有 Runtime 副作用,也必须在 ledger 中以 `rejected` 持久化;只有格式、版本、身份或权限失败且请求尚未进入项目 ledger 的情况才返回 `REQUEST_NOT_FOUND`。 +附件不能只做“校验后丢弃”。对于 `Conversation` payload,Shell 在 command `prepared` 时将规范化 message 与有序 immutable bindings 写入同一私有 `AgentRuntimeInputEnvelopeRecord`,direct reply/start/steer 三条路径都引用同一 envelope identity;`BuiltinCommand` 不创建 Runtime input envelope,而是将规范化 command line 固化在 command ledger,并只引用一次 `conversationUserMessageId`;现有 Runtime 继续以原 message 作为非空 task/steer 文本,Provider/context adapter 另以结构化 attachment context 注入 resource identity、revision、digest 和 mediaKind,工具读取仍只经既有 manifest/resource resolver。不得把资源路径、签名 URL 或自动生成的自然语言拼进 task;恢复时必须重读同一 envelope 和 binding,资源已替换/删除则在 Provider/Runtime 副作用前失败关闭。conversation user message 可以显示用户原文和安全附件摘要,但摘要不是第二份资源事实。对 `Start`,同一 prepared 记录必须同时预分配 `conversationUserMessageId + runId + runtimeStatusMessageId`;`runtimeStatusMessageId = runtime-public-status- + lowerHex(sha256(RFC 8785 canonical JSON(["runtime-status", projectId, runBoundSessionId, runId, statusKind])))`;其中 `runBoundSessionId` 是 prepared/Runtime run 固化的 session 身份,不随后续 active session handoff 改写,只在该 prepared identity 下生成一次:先幂等提交用户原文/安全附件摘要的 conversation message 并回读 commit marker,再以 `runtime-public-status-*` 前缀将脱敏“任务已接收,正在启动处理”作为 Runtime-owned status message 原子写入 conversation;status message 的 commit marker 可读回后才允许把 Run 从 `preparing/public-status-pending` 提升为 `queued/pending` 并让 Runner dequeue。崩溃在 user message committed 与 status committed 之间时,恢复只用同一三组 identity 补 status;conversation 已提交而辅助 audit 尚未提交时,以 conversation commit marker 为公开真相继续补 audit/入队,不得留下“已接收但永不执行”或写第二条 user/status message。status 写入失败则持久化 `failed/public-status-write-failed`,不得执行或继续重试 Provider;写入结果未知则 Run 保持不可执行并进入 reconciliation,恢复只能复用同一 message/run identity。Steer、DirectReply 和 RuntimeFinalReply 不重复写该 Start status message。根 Supervisor 的正式失败、预算耗尽或硬期限 reconciliation 也必须先用唯一 Runtime-owned failure status message 写入用户可理解摘要,再提交其它 task/event/state 终态;status 写入失败/结果未知时不把 Public outcome 提前投影为 `failed`,而是保持 terminal-pending/reconciliation,直到同一 status identity 可读回或由 owner 明确核对;status message 必须被 prompt builder 按固定前缀排除,前端按其来源顺序展示且不得二次持久化。现有需要进入聊天的安全 Runtime 事件另走 `AgentRuntimePublicEventMessageRecord`:Rust 只在同一父 run 下生成稳定 `eventId + publicText`(`eventId = runtime-public-event- + lowerHex(sha256(RFC 8785 canonical JSON(["runtime-public-event", projectId, parentRunId, sourceRunId, durableEventId, eventKind, normalizedPublicPayload])))`,其指纹输入包含 project/parent/source run、底层 durable event identity、allowlisted event kind 和规范化 public payload),前端按该二元身份去重和展示;无 eventId、空 publicText、legacy/raw tool/provider/runner payload 一律丢弃。它不进入 `SnapshotChanged` envelope、Public Snapshot 或 Runtime status message,也不改变 Runtime 状态事实;只允许现有 allowlist 的专业 Agent/公开 child 事件进入该 delivery;根 Supervisor 的 `turn.started/turn.failed/turn.budget_exhausted` 不再生成第二条 event message,终态失败/预算摘要只走上面的唯一 Runtime-owned status message。当前工作台对 Supervisor/主 Agent/直接 child 的最近 4/最多 20 条聚合只是该 conversation read model 的展示限制,不能在 Consumer 自行从 raw event 重算。 + +`Conversation.intentKind` 必填且只能取 V1 稳定枚举值,不能从 `message` 启发式推断,也不能塞入 `runProfile`。`entry_binding` 与 `intentKind` 必须是严格一一对应的 tagged union,且 `(intentKind, runProfile, entryBindingKind, inputPolicy, attachmentMediaKind)` 必须命中当前 `conversationOptions` 的同一个 option;禁止从独立列表做笛卡尔积、通过未知字段或自然语言补充身份。实际 entry binding 由既有模板/资源管理面提供,不把所有模板或设计 identity 复制到 Snapshot。Shell 必须在锁内校验该入口对当前项目、session 和状态是否可用;校验失败为持久化业务拒绝,不产生副作用。`sessionId + expectedSessionRevision` 必须命中当前 Project Supervisor active session。`cancel` 必须同时提交当前 `sessionId + expectedSessionRevision + runId`,防止旧 capability 被新会话误用;session handoff 已明确绑定同一 lineage 时只接受新 active session 的 capability,run 已终结、session 已切换或绑定关系不一致均为 `TARGET_STALE`。V1 `resume` 不再使用模糊的 project scope/reason。`ContinueRun` 只允许 Snapshot capability 明确声明的 `pausedByUser` 同一 durable Supervisor run,必须携带 `expectedRunRevision`;running、waiting interaction、pending/executing action、timer/lane、finalizing 和 needs-reconciliation 均不得用 ContinueRun 越过各自门禁。`RetryTerminalRun` 必须精确绑定 capability 给出的失败终态 run 和 terminal revision,在 prepared 阶段预分配新的 successor runId,并持久化 predecessor/successor lineage,绝不复用旧 runId;completed 不可 retry,cancelled 只有产品策略明确给出 retry capability 时才可 retry。专业 Agent retry 使用 `AgentRuntimeCollaboratorRunTarget`,同时复核 collaborationId、parentRunId、runId 和 delegation/repair 状态;若 Supervisor 已准备唯一 contract repair,Snapshot 只给 `recovery=RepairApprovalPending` 并指向同一 Public PolicyApproval interaction;在该 interaction 解决前不得创建平行 successor 或开放 `RetryCollaboratorRun`。`ReconcileRun` 只允许受信任 Developer/Runner reconciliation capability,且允许没有 active session,但必须由 capability 通过 Project Supervisor lineage 精确定位目标,不能以空 session 放宽权限,也不能自动重放未知 Provider/工具副作用。timer、lane release、ownerRecovered、interactionResolved 和 schedule-ready 都是 Shell/Runner 内部 recovery intent,不进入 Public ResumeCommand。自然语言“继续”不得路由为任何 resume tagged intent,更不能静默触发 RetryTerminalRun;Consumer 必须显式发送结构化 ResumeCommand。`runProfile` 的 wire 形态为 Snapshot capability 中已注册的公开 profile 名称和版本,不能携带 Provider、模型、工具、提示词或路径配置。 + +`AnswerCommand` 的答案结构只允许 Public Snapshot 当前 interaction view 中声明的 question/option/自由回答约束;缺失、重复、越界或不符合约束返回 `INVALID_REQUEST`,不产生部分写入。`ApproveCommand` 的 decision 不允许由 UI button、命令名或缺省值推断;`requestChanges` 只在当前 interaction 的 `allowed_decisions` 声明该值时可用,V1 公开支持的场景仅为 `scope=Run | Action`、绑定单一不可变 targetArtifact 的用户 `PolicyApproval`;Project scope PolicyApproval 与 ToolApproval 仍只允许 `approve/reject`。选择 `requestChanges` 时必须携带 `feedback`,长度限制为最多 2,000 个 Unicode 字符;选择 `approve/reject` 时必须省略该字段。Shell 对 feedback 复用公开内容安全过滤,拒绝绝对路径、密钥/Token、Provider 原文和超长内容;过滤失败返回 `INVALID_REQUEST`,不产生部分写入。`requestFingerprint` 覆盖上述规范化业务请求体以及 `schemaVersion/commandKind/projectId`,不覆盖 transport source、locator、时间戳、重试次数或响应展示文案。通过身份、权限和版本校验的请求,即使因 target stale、interaction stale、当前状态不允许或策略拒绝而没有 Runtime 副作用,也必须在 ledger 中以 `rejected` 持久化;只有格式、版本、身份或权限失败且请求尚未进入项目 ledger 的情况才返回 `REQUEST_NOT_FOUND`。 ### 1.4 InteractionRequired 身份、版本和解决状态机 ```rust +struct AgentRuntimeArtifactBinding { + artifact_namespace: String, + artifact_id: String, + artifact_revision: u64, + digest_algorithm: String, // V1 固定 sha256 + content_digest: String, +} + // Shell 内部 durable record;不直接作为正式公开 DTO。 struct AgentRuntimeInteractionRecord { + envelope: AgentRuntimeDurableEnvelope, interaction_id: String, interaction_revision: u64, + // continuation item 的 record_revision 取 envelope.record_revision;interaction_revision + // 只表示交互业务版本,不能替代 durable record revision。 kind: AgentRuntimeInteractionKind, scope: AgentRuntimeInteractionScope, audience: AgentRuntimeInteractionAudience, @@ -371,6 +1611,11 @@ struct AgentRuntimeInteractionRecord { session_id: Option, run_id: Option, action_id: Option, + action_fingerprint: Option, + policy_snapshot_fingerprint: Option, + approval_target_set: Option, + // 私有绑定:保证审批意见针对创建交互时看到的确切不可变产物版本。 + target_artifact: Option, request_fingerprint: String, bound_state_fingerprint: String, status: AgentRuntimeInteractionStatus, @@ -378,18 +1623,86 @@ struct AgentRuntimeInteractionRecord { private_presentation: AgentRuntimeInteractionPrivatePresentation, } +// Shell 内部 durable resolution;UserInput 与 Approval 使用严格 tagged union。 +enum AgentRuntimeInteractionResolution { + UserInput { + response_id: String, + response_fingerprint: String, + answers: Vec, + }, + Approval { + response_id: String, + response_fingerprint: String, + decision: ApprovalDecision, + // 只保存经过公开内容安全过滤的副本。 + feedback: Option, + target_artifact: Option, + rework_operation_id: Option, + follow_up_interaction_id: Option, + }, +} + +struct AgentRuntimeApprovalTargetSet { + targets: Vec, + target_set_fingerprint: String, +} + +struct AgentRuntimeApprovalTarget { + agent_id: String, + parent_run_id: Option, // Supervisor=None;专业 Agent 必须为当前父 run + run_id: String, + action_id: String, + action_fingerprint: String, +} + +`parent_run_id=None` 仅允许 Supervisor action;专业 Agent/child action 必须为当前 Public Supervisor run 的 Some(parentRunId),并在 target-set fingerprint 与 approve 锁内复核。 + struct AgentRuntimeInteractionView { interaction_id: String, interaction_revision: u64, kind: AgentRuntimeInteractionKind, scope: AgentRuntimeInteractionScope, + // Consumer 只能在 actionable=true 且 allowed_actions 非空时渲染 answer/approve; + // Shell 仍须在锁内重新校验,字段不是绕过服务端权限的凭据。 + actionable: bool, + allowed_actions: Vec, status: AgentRuntimeInteractionPublicStatus, + context: AgentRuntimePublicInteractionContext, presentation: AgentRuntimePublicInteractionPresentation, } +enum AgentRuntimePublicInteractionContext { + Supervisor, + Collaborator { + collaboration_id: String, + group: AgentRuntimePublicCollaboratorGroup, + }, +} + +struct AgentRuntimePublicQuestion { + id: String, // 沿用现有唯一 snake_case,最多 64 个 ASCII/UTF-8 字节 + header: String, + question: String, + options: Vec, +} + +struct AgentRuntimePublicQuestionOption { + id: String, // Shell 在 interaction 创建时分配并持久化的不透明 option identity + label: String, + description: String, +} + +enum AgentRuntimeAnswer { + Option { question_id: String, option_id: String }, + Freeform { question_id: String, text: String }, +} + +enum AgentRuntimePublicMediaKind { Image, Audio, Video, Document, Code, ProjectVersion, Other } + enum AgentRuntimePublicInteractionPresentation { UserInput { questions: Vec, + // V1 固定为 true;false 是 PUBLIC_STATE_INVALID,不是关闭自由回答的能力。 allow_freeform: bool, }, PolicyApproval { @@ -397,22 +1710,330 @@ enum AgentRuntimePublicInteractionPresentation { summary: String, allowed_decisions: Vec, }, + ToolApproval { + title: String, + summary: String, + risk_level: AgentRuntimePublicRiskLevel, + allowed_decisions: Vec, // V1 只能 approve/reject + }, } +enum AgentRuntimePublicRiskLevel { Low, Medium, High } + enum AgentRuntimeInteractionKind { UserInput, ToolApproval, PolicyApproval } enum AgentRuntimeInteractionScope { Project, Run, Action } enum AgentRuntimeInteractionAudience { User, Developer } -enum AgentRuntimeInteractionStatus { Open, Resolving, Resolved, Superseded } +enum AgentRuntimeInteractionStatus { Open, Resolving, Resolved, Superseded, Cancelled } enum AgentRuntimeInteractionPublicStatus { Open, Resolving } ``` -`interactionId` 在项目内稳定唯一,`interactionRevision` 从 1 开始并只在该 interaction 的可回答内容、约束或状态变化时递增;`responseId` 在单个 interaction 内唯一。`boundStateFingerprint` 绑定创建交互的 durable 对象与策略前提,不能用全项目 revision 替代。项目级 resume/retry 的 PolicyApproval 使用 `scope=Project`,可在锁内覆盖重新枚举出的多个 run,因此 agent/session/run/action 均可为空;ToolApproval 使用 Action scope 并绑定精确 action;UserInput 按真实落点使用 Run 或 Action scope。 +`interactionId` 在项目内稳定唯一,`interactionRevision` 从 1 开始并只在该 interaction 的可回答内容、约束或状态变化时递增;`responseId` 在单个 interaction 内唯一。UserInput 的 Public presentation 必须保持现有合同:question id 为唯一 snake_case 且最多 64 个 ASCII/UTF-8 字节,问题数 1–3,每题有 2–3 个 option,所有题都必须回答且自由输入始终可选,模型不能关闭“其他”。option identity 不从 UI label 推导:Shell 在 interaction 创建时按规范化 question id 与 option ordinal 分配 `optionId = "opt_" + lowerHex(sha256(canonicalJson({interactionId, questionId, ordinal})))[:32]`,将该 ID 与 option 顺序同时写入 private presentation、interaction request fingerprint、continuation item 和 resolution;同一 interaction 内 option 顺序不可变,label/description 或顺序变化必须递增 interactionRevision 并使旧 response stale。答案必须恰好覆盖当前全部 question id,每个 question 只能是一个 option 或一个 freeform;`Option` 必须提交 Public view 中的稳定 option id,不能提交 UI label。`boundStateFingerprint` 绑定创建交互的 durable 对象与策略前提,不能用全项目 revision 替代;若交互针对产物审批,`targetArtifact.artifactNamespace + artifactId + artifactRevision + digestAlgorithm + contentDigest` 必须同时绑定并在锁内复核,不能只靠用户意见正文或当前最新产物猜测目标版本。V1 只为已有稳定 immutable revision 且能在同一锁内计算 `sha256` 的 manifest/resource/artifact lineage 生成该 binding;当前只有 `resourceId`、可变路径、无 revision 或无法重读内容计算 digest 的对象不得开放 `requestChanges`,返回 `ARTIFACT_BINDING_UNAVAILABLE`,不新建一套平行 artifact identity。项目级 PolicyApproval 使用 `scope=Project`,必须把锁内重新枚举的精确 `agentId + parentRunId + runId + actionId + actionFingerprint` 集合固化为 `approval_target_set`,可一次覆盖多个 run 但只允许 `approve/reject`,不能把“当前所有 run”当作隐含目标;目标集合变更必须创建新 interaction。允许 `requestChanges` 的 PolicyApproval 必须使用 Run/Action scope 并绑定单一不可变 targetArtifact;User audience ToolApproval 使用 Action scope 并绑定精确 action,只允许 `approve/reject`;Developer audience ToolApproval 继续走 Developer Snapshot。UserInput 按真实落点使用 Run 或 Action scope。 -Public Snapshot 只投影 `audience=User` 且状态为 Open/Resolving 的最小 view;Open 可回答,Resolving 只显示处理中并禁用再次提交,Resolved/Superseded 从公开列表移除。正式写命令仍在执行时重新校验当前调用来源与项目策略,不公开 audience、request fingerprint、bound fingerprint、内部 actionId、策略字符串、工具名称/参数或动态 child 身份。`audience=Developer`(包括现行 ToolApproval)只进入 Developer Snapshot 的脱敏 debug interaction view;正式 Public 仅通过 Supervisor summary 的 `waitingOn=developer-approval` 表示暂停,不创建可点击 interaction。内部 private presentation 与 Public presentation 使用不同 DTO;Public presentation 采用严格 tagged union 和长度上限:UserInput 从本地私有 user-input sidecar 经过敏感信息/路径过滤后,复用现行最多 3 题、每题 2–3 选项与自由回答约束;若问题或选项不能安全公开则转为 `audience=Developer`/needs-reconciliation,不把原文带入 Public。用户 PolicyApproval 只含有界、脱敏的行为影响摘要和允许 decision。Public view 只允许 UserInput/UserInput 和 PolicyApproval/PolicyApproval 两种 kind/variant 组合,未知或不匹配的 variant 按不支持协议失败关闭;presentation 不是可执行 payload。Developer ToolApproval 使用独立 debug DTO。 +Public Snapshot 只投影 `audience=User` 且状态为 Open/Resolving 的最小 view;Open 可回答,Resolving 只显示处理中并禁用再次提交,Resolved/Superseded 从公开列表移除。正式写命令仍在执行时重新校验当前调用来源与项目策略,不公开 audience、request fingerprint、bound fingerprint、内部 actionId、策略字符串、工具名称/参数或动态 child 身份;`context=Collaborator` 只使用公开不透明 `collaborationId` 和普通用户组名,Shell 内部仍必须复核真实 `agentId + runId + parentRunId + actionId`。`audience=User` 的 ToolApproval 必须进入 Public Snapshot,提供脱敏行为摘要和风险等级,满足现有工作台“确认”入口;`audience=Developer` 的 ToolApproval 才只进入 Developer Snapshot 的 debug interaction view。内部 private presentation 与 Public presentation 使用不同 DTO;Public presentation 采用严格 tagged union 和长度上限:UserInput 从本地私有 user-input sidecar 经过敏感信息/路径过滤后,复用现行最多 3 题、每题 2–3 选项与自由回答约束,`allow_freeform` 在 V1 必须为 `true`,不能由模型或 sidecar 关闭;若问题或选项不能安全公开则转为 `audience=Developer`/needs-reconciliation,不把原文带入 Public。用户 PolicyApproval 只含有界、脱敏的行为影响摘要和允许 decision。Public view 允许 `kind=UserInput` 搭配 `UserInput`、`kind=ToolApproval` 搭配 `ToolApproval`、`kind=PolicyApproval` 搭配 `PolicyApproval` 三种组合,未知或不匹配的 variant 按不支持协议失败关闭;presentation 不是可执行 payload。Developer ToolApproval 使用独立 debug DTO。 -`answer` 仅接受 UserInput,`approve` 仅接受 ToolApproval/PolicyApproval;命令与 kind 不匹配返回 `INVALID_REQUEST` 且零副作用。`answer/approve` 的处理顺序为:先走 request ledger 重放检查,再取得 project execution owner 与 interaction lock,重读 record,校验 `interactionId + expectedInteractionRevision`、状态为 Open、bound fingerprint 仍与 durable 对象一致,然后重新执行当前权限和策略检查。通过后以 responseId 和 response fingerprint 原子转为 Resolving,调用既有内部 answer/confirm/reject/resume 实现,最后写 Resolved 和权威 command result;response fingerprint 覆盖 interactionId、interactionRevision、responseId、decision/answers,不能只散列自由文本。`approve` 必须显式携带 `decision=approve|reject`;禁止从按钮、命令名或缺省值猜测。 +`answer` 仅接受 UserInput,`approve` 仅接受 User audience ToolApproval/PolicyApproval 或 Developer capability 的 Developer ToolApproval;命令与 kind 不匹配返回 `INVALID_REQUEST` 且零副作用。`answer/approve` 的处理顺序为:先走 request ledger 重放检查,再取得 project execution owner 与 interaction lock,重读 record,校验 `interactionId + expectedInteractionRevision`、状态为 Open、bound fingerprint 仍与 durable 对象一致,并复核 `targetArtifact` 的 identity、revision 和 content digest 仍指向创建交互时的不可变产物版本,然后重新校验当前 principal capability、不可放宽的 hard-deny/sandbox/安全策略和交互绑定的 `policy_snapshot_fingerprint`。业务策略快照不因普通 live policy 文案变化而重新解释;若安全 hard-deny 收紧、目标 action/actionFingerprint 漂移或 policy snapshot 不可读,则零副作用返回 `INTERACTION_STALE` 或进入 `NEEDS_RECONCILIATION`,不能把旧 approve 施加到新 action。通过后以 responseId 和 response fingerprint 原子转为 Resolving,调用既有内部 answer/confirm/reject 实现;UserInput/approve-reject 可在权威结果闭合后写 Resolved,requestChanges 只有在 rework operation 已 durable prepared/enqueued 后才能写 Resolved,否则保留 Resolving/unknown;response fingerprint 覆盖 interactionId、interactionRevision、responseId、decision、feedback 和 answers 的完整规范化响应,不能只散列自由文本。`approve` 必须显式携带 `decision=approve|reject|requestChanges`;禁止从按钮、命令名或缺省值猜测。 -即使 Consumer 更换 command requestId,同一 `responseId`、相同 response fingerprint 也只创建新 command ledger 的幂等成功结果并返回原 interaction resolution,不重复消费;同一 responseId 不同内容失败为 `IDEMPOTENCY_KEY_REUSED`。已由其它 response 解决返回 `INTERACTION_ALREADY_RESOLVED` 并携带 `interactionRequired=false`;interaction revision、绑定对象或策略前提漂移返回 `INTERACTION_STALE`,零副作用。项目其它无关 Runtime/Snapshot 更新不使 interaction stale。禁止只凭持久化的 `"agent.resume"` 字符串或旧 policy 文案直接恢复。 +`answer/approve` 的 `InteractionResponseMeta` 还必须提交当前 `sessionId + expectedSessionRevision`。interaction record 的 `session_id` 保留创建时的 lineage/provenance;正常情况下二者必须相同,rotation 成功后只有 continuation set 中精确匹配 `interactionId + interactionRevision + recordRevision` 的 item 才允许 successor session 重新授权同一 interaction。该 proof 只授予继续原 interaction operation 的权限,不改变 `interactionRevision`、`boundStateFingerprint`、target artifact 或 response identity;Open interaction 可在 successor Snapshot 中继续展示并回答,Resolving interaction 只能恢复/读回原 response,不能接受第二 response。proof 缺失、interaction 未被捕获、session/revision 不一致或 rotation phase 不是 `Ready`,统一零副作用返回 `TARGET_BUSY`/`TARGET_STALE`/`NEEDS_RECONCILIATION`,不能按当前项目最新 Open interaction 猜目标。 + +`requestChanges` 与 `reject` 不是同一语义:`reject` 终止当前审批链并收束 run;`requestChanges` 是带意见的非终止工作流转移。V1 只允许 `scope=Run | Action`、恰好绑定一个不可变 targetArtifact 的用户 PolicyApproval 声明 `requestChanges`;覆盖多个 run 的 Project scope PolicyApproval 仍只允许 `approve/reject`,避免一段 feedback 模糊作用于多个目标。 + +处理 `requestChanges` 时,Shell 必须在 interaction lock 内先把 interaction 原子认领为 Resolving,同时预分配并保存 `reworkOperationId`、response fingerprint、过滤后的 feedback 和 target artifact binding;随后通过同一 project operation journal 创建/查找唯一 rework operation。只有 rework operation 已 durable `prepared/enqueued` 后才能把旧 interaction 写为 Resolved;崩溃恢复按 `reworkOperationId` 幂等补入队,不能重新解释 feedback 或创建第二操作。外部执行结果未知时旧 interaction 保持 Resolving 或进入 needs-reconciliation,不得伪造 Resolved。旧产物版本保持不可变;新版本生成并通过 lineage 校验后才创建下一次 PolicyApproval interaction,并写回 followUpInteractionId。若 rework 尚未物化,`InteractionAccepted` 返回 `follow_up_interaction_id=None`,Consumer 只读取 Snapshot/事件等待后续交互,不得重复提交;同一 requestId 或 responseId 重放按 ledger 当前状态返回成功、in-progress 或 unknown,不能把 unknown 伪装成原 resolution 成功。 + +即使 Consumer 更换 command requestId,同一 `responseId`、相同 response fingerprint 也不能重复消费:interaction 已 Resolved 时新 command ledger 返回原 resolution;仍为 Resolving 时返回 `COMMAND_IN_PROGRESS`;对应 operation 已 outcome-unknown 时返回 `COMMAND_RESULT_UNKNOWN`。同一 responseId 不同内容失败为 `IDEMPOTENCY_KEY_REUSED`。已由其它 response 解决返回 `INTERACTION_ALREADY_RESOLVED` 并携带 `interactionRequired=false`;interaction revision、绑定对象或策略前提漂移返回 `INTERACTION_STALE`,零副作用。项目其它无关 Runtime/Snapshot 更新不使 interaction stale。禁止只凭持久化的 `"agent.resume"` 字符串或旧 policy 文案直接恢复。 + + +### 1.5 Conversation / response stream 交付合同 + +conversation/response stream 仍是独立展示通道,但现有“Runtime final-reply stream”和新协议 `submit_intent` 的 direct reply 不是同一种交付,且 Start/根终态的 Runtime-owned status message 也不是 Provider response;不能共用一个没有来源标签的 ledger。V1 冻结两条 response 分支、一条 status-message 分支和一条不进入 Public conversation 的 LocalTransport 分支: + +1. `DirectReplyDelivery`:interaction kernel 判定为 direct reply 时,在 request ledger `prepared` 阶段预分配稳定 `responseMessageId`,绑定 projectId、sessionId、requestId 和 response operation identity;消息记录至少具有 `reserved | streaming | committed | outcome-unknown` 状态。只有该分支的 `IntentAccepted.responseMessageId` 可直接定位 command reply;Start/Steer 的最终 assistant 属于下面的 RuntimeFinalReply,不在受理 ack 中假装已经存在。 +2. `RuntimeFinalReply`:已有 durable Runtime run 的最终回复继续由现有 finalization v4、`responseRequestSlot`、Agent/Session/run 和 response fingerprint 约束;Shell 不另造一套 Runtime message identity,必须读取/复用 finalization journal 已生成的 `finalizationId + messageId`(当前实现由 `game_creator_agent_runtime_finalization_id` 与 `game_creator_agent_runtime_finalization_message_id` 生成),并在 adapter 中校验二者与 run/session 完全一致。Runtime state、conversation assistant 和 response stream 三者以同一 finalization identity 闭合。Runtime final reply 的流中间态不能被 submit_intent command 结果代替,最终 assistant 也不能反向改变 Snapshot 的生命周期结论。 + +3. `LocalTransportReply / LocalManagement`:现有 CLI `project_location`、GUI/CLI 的 `/project`、`/open-project`、`/switch-project`、绝对导出包路径和本地配置操作只在受信任本地 transport/独立面板显示;它们不是 Public conversation message,不进入 Runtime prompt、Snapshot、事件或 Runtime response stream。所有 absolute host-locator operation(包括 `/project`、`/import-canvas-export`、本地文件选择器结果及 CLI `project_location`)只保存 `locatorDigest + locatorHandleRef + path-free argumentFingerprint + projectId(若已解析)`,原始路径留在受信任 path resolver/OS handle 边界内;路径需要展示时只能作为本地 UI/CLI transport result 返回,不能由 assistant message 或公共错误承载。`project_location` 现有 `ProjectLocation` action 若继续保留,P5 必须改为该分支,禁止把绝对路径写入 `conversation`。 + +三条会话写入顺序也冻结:DirectReply(包括未知 slash command)必须使用 `AgentRuntimeConversationUserMessageBinding::Present`,先用其中的 `conversationUserMessageId` 幂等提交原始用户文本(slash 保留规范化 command line),回读 user commit marker 后再写 assistant response,最后才关闭 command ledger;Steer 必须先完成同一 `conversationUserMessageId` 的 `conversation-persisted`,再推进既有 steer ledger 的 `queued`,不得在 steer ledger 与 input envelope 各写一条用户消息;若恢复发现 user marker 已存在,只能复用它,若正文/来源不一致则 `IDEMPOTENCY_KEY_REUSED`/`NEEDS_RECONCILIATION`。Start 也必须使用 `Present`,沿用 user message → Runtime status message → queued/dequeue。RuntimeFinalReply 或 terminal failure status 若属于没有原始用户轮次的后台恢复,才可使用 `NotApplicable`;该分支仍必须绑定 `source_record_ref`,不得借此补写第二条用户消息。 + +Public conversation 使用独立于 Snapshot event、Provider response stream 和各私有 delivery ledger 的**单一会话全局序列**。每个 `(projectId, sessionId)` 在 conversation append lock 内为待提交的规范 message identity 预留严格递增且永不复用的 `sequence` 与不透明 `cursor`;只有提交并回读成功后才把该 cursor 接入私有 committed cursor chain 并对 Public 可见,预留后失败可以留下永久 sequence 空洞,但失败 reservation 不进入 committed cursor chain,同一 `messageId` 不得获得第二个 sequence/cursor。cursor chain 与 `afterCursor/nextCursor` 分页是 committed message 补读完整性的唯一权威依据;`sequence` 只用于同一会话内稳定排序和诊断,数值不连续是合法终态,不表示漏读,也不得触发 Consumer 全量重读或无限恢复。只有 committed cursor chain 断链、重复链接或漏掉已 committed message 才表示历史不完整。response stream 的 `sequence` 只表示同一 Provider/final-reply stream 的 chunk/source revision,**绝不等于 conversation sequence**,也不能作为 Public conversation `afterCursor`。Runtime status message 不进入 response stream,但以独立 commit marker、唯一 `runtimeStatusMessageId` 和 conversation read-back 恢复。Consumer 只消费下面冻结的 Public conversation read DTO,按 `(projectId, sessionId, deliveryKind, messageId)` 去重,并按 `sequence` 稳定展示;不得读取私有 status/event/response ledger、解析不透明 ID 前缀或把 chunk 合并成 Runtime Snapshot。只有对应分支的私有 commit marker 已提交、conversation message 以同一 message key/正文/作用域回读成功且 delivery/input record 已进入 `committed` 后,Public read 才能返回该消息;`reserved/streaming/failed/rejected/discarded/outcome-unknown` 均不返回。commit marker、finalizationId、Provider request/slot/chunk、locator/path 和内部 observation 永远不进入 Public DTO。任一 Provider 已调用但正文提交无法证明时,仅将该分支置为 `outcome-unknown` 并进入 reconciliation,禁止换 requestId 或新 finalization identity 生成第二条回答。公开正文沿用同一权限、长度和内容安全过滤;其中 user 复用 `Conversation.message` 上限,runtimeFinalReply 复用 response stream 上限,runtimeStatus/publicEvent 复用 Runtime status/public event message 上限,directReply 统一复用上表 `4,000` Unicode scalar/`16 KiB` UTF-8 有界安全文本常量。Shell 必须在 conversation append lock 内、提交 Public message/sequence/cursor 前完成 directReply 正文规范化、内容安全和双上限校验;超限或不安全正文只能使既有 response delivery 确定性进入 `rejected` 并返回有界 `INTERNAL` 安全摘要,不得截断或提交正文,也不得留下无法分页补读的 committed 消息。原始 Provider chunk、凭据、绝对路径和内部 observation 不进入 Public Snapshot、Public conversation、公开错误或审计摘要。 + +下面的 Public conversation request/message/page/provenance/error/enums 与五命令、Public Snapshot 使用同一 Rust 权威 wire 模块:字段统一 camelCase,无数据枚举统一 lowerCamelCase 字符串。Rust request decoder 必须拒绝缺失字段、未知字段、重复字段、错误类型、非法 `schemaVersion`、越界 `limit/afterCursor` 和超限 request JSON,不能依赖 serde 默认忽略未知字段;结构或类型错误在任何 transport 查询 conversation 前统一返回 `INVALID_REQUEST`,不支持的版本统一返回 `PROTOCOL_VERSION_UNSUPPORTED`。GUI、CLI 和测试不得自行放宽、补默认值或回退读取私有 ledger。`AgentRuntimePublicConversationReadRequest`、`AgentRuntimePublicConversationMessage`、`AgentRuntimePublicConversationReadPage`、`AgentRuntimePublicConversationProvenance`、`AgentRuntimePublicConversationReadError` 及其全部枚举必须从该 Rust 单一来源生成 TypeScript schema/decoder、transport validator、golden fixture 和 negative fixture;Public response 的 success page 与 error envelope 必须分别进入同一生成和验证链路,decoder 对缺失/未知/重复字段、错误类型、未知枚举/code 或错误 schemaVersion 必须确定性报协议错误,不能静默丢字段后继续渲染。新增字段或改变必填性、wire value、文本/ID/cursor/数组/JSON 上限均须提升 `schemaVersion`,不能借 Public read 的只读性质绕过版本规则。 + +Session rotation 不会重写上述 delivery/status/event record 的 `session_id`、`responseMessageId`、`finalizationId`、`runtimeStatusMessageId`、`eventId` 或 `conversationMessageKey`。rotation barrier 捕获的 `DirectReplyDelivery`、`RuntimeFinalReply`、Start/terminal `RuntimeStatusMessage` 和 `PublicEventMessage` 必须由 continuation item 精确授权给 successor 继续同一 operation:`reserved/streaming` 只能复用原 message identity 继续提交,`outcome-unknown` 只能读回或 reconciliation,Start status 未提交前不得 dequeue,terminal failure status 未提交前不得投影 `failed/failure`。若 record 已在 barrier 后发生 revision 变化、缺少 continuation proof 或 provider call evidence 不明,successor 不得重建 response/finalization/status/event identity;统一保留证据并进入 reconciliation。 + +```rust +enum AgentRuntimePublicConversationDeliveryKind { + User, // user + DirectReply, // directReply + RuntimeFinalReply, // runtimeFinalReply + RuntimeStatus, // runtimeStatus + PublicEvent, // publicEvent +} + +enum AgentRuntimePublicConversationRole { + User, // user + Assistant, // assistant + System, // system +} + +// 仅允许公开、安全且渲染所需的 lineage;不包含 finalizationId、Provider、 +// locator、path、tool/action 或私有 ledger identity。 +struct AgentRuntimePublicConversationProvenance { + run_id: Option, + source_agent_id: Option, + source_run_id: Option, +} + +struct AgentRuntimePublicConversationMessage { + schema_version: String, + project_id: String, + session_id: String, + delivery_kind: AgentRuntimePublicConversationDeliveryKind, + message_id: String, + // 同一 project/session 的 conversation-global 顺序;不是 response-stream sequence。 + sequence: u64, + cursor: String, + role: AgentRuntimePublicConversationRole, + public_text: String, + // 仅 publicEvent 为 Some,且必须逐字节等于 message_id;其它类型必须为 None。 + event_id: Option, + provenance: Option, +} + +struct AgentRuntimePublicConversationReadRequest { + schema_version: String, + project_id: String, + session_id: String, + // None 表示从 session Public conversation 的 committed origin 开始全量分页。 + after_cursor: Option, + limit: u32, +} + +enum AgentRuntimePublicConversationHistoryState { + // 已覆盖 session origin 以来全部 retained committed message。 + Complete, // complete + // V1 committed history 完整,但存在无法证明 identity 的 pre-V1 私有隔离项。 + LegacyEntriesIsolated, // legacyEntriesIsolated +} + +enum AgentRuntimePublicConversationReadErrorCode { + CursorInvalid, // CURSOR_INVALID + HistoryIncomplete, // CONVERSATION_HISTORY_INCOMPLETE + Internal, // INTERNAL +} + +struct AgentRuntimePublicConversationReadError { + schema_version: String, + code: AgentRuntimePublicConversationReadErrorCode, + retryable: bool, +} + +struct AgentRuntimePublicConversationReadPage { + schema_version: String, + project_id: String, + session_id: String, + messages: Vec, + // 本页最后一条 committed 消息的 cursor;该 cursor 在 session 生命周期内持续有效。 + // 空页沿用 afterCursor,首次空历史为 None。 + next_cursor: Option, + has_more: bool, + history_state: AgentRuntimePublicConversationHistoryState, +} + +// 不属于 Provider response stream;它是 Runtime-owned conversation status message。 +enum AgentRuntimeInteractionAction { + Answer, // answer + Approve, // approve +} + +// 用户消息可能来自 submit_intent/input envelope;后台恢复的既有 Runtime final reply +// 可以没有可证明的用户轮次,但不得伪造一个 user message identity。 +enum AgentRuntimeConversationUserMessageBinding { + Present { + conversation_user_message_id: String, + // reserved 阶段可以为 None;用户消息 commit 成功并回读后必须补齐, + // delivery committed 前不得仍为 None。 + conversation_commit_marker: Option, + }, + NotApplicable { + reason: AgentRuntimeConversationUserMessageAbsenceReason, + source_record_ref: String, + }, +} + +enum AgentRuntimeConversationUserMessageAbsenceReason { + BackgroundRuntimeRecovery, // 仅 RuntimeFinalReply/terminal failure status 可用 +} + +struct AgentRuntimeStatusMessageRecord { + envelope: AgentRuntimeDurableEnvelope, + runtime_status_message_id: String, + project_id: String, + session_id: String, + run_id: String, + command_request_id: Option, + conversation_user_message: AgentRuntimeConversationUserMessageBinding, + status_kind: RuntimeStatusMessageKind, // startAccepted | terminalFailure; 固定 runtime-public-status- 前缀 + conversation_message_key: String, + status: RuntimeStatusMessageStatus, // reserved | committed | failed | outcome-unknown + failure_code: Option, + commit_marker: Option, + message_sequence: Option, + conversation_cursor: Option, +} + +enum RuntimeStatusMessageKind { + StartAccepted, // startAccepted + TerminalFailure, // terminalFailure +} + +enum RuntimeStatusMessageStatus { + Reserved, + Committed, + Failed, + OutcomeUnknown, +} + +// 现有“进入聊天的安全 Runtime 事件”也不是 SnapshotChanged envelope。 +// 由 Rust event projector 生成,前端只消费 eventId + publicText。 +struct AgentRuntimePublicEventMessageRecord { + envelope: AgentRuntimeDurableEnvelope, + // Shell 私有 delivery record;正式 conversation delivery 只投影 eventId + publicText。 + event_id: String, + project_id: String, + session_id: String, // 创建该 event 的 session provenance;rotation 后保持不变 + parent_run_id: String, + source_agent_id: String, + source_run_id: String, + durable_event_id: String, + event_kind: String, + public_text: String, + public_payload_digest: String, + conversation_message_key: String, + commit_marker: Option, + message_sequence: Option, + conversation_cursor: Option, + discard_reason: Option, + status: PublicEventMessageStatus, // reserved | committed | discarded | outcome-unknown +} + +enum PublicEventMessageStatus { + Reserved, // reserved + Committed, // committed + Discarded, // discarded;安全/大小策略确定拒绝,不再重试 + OutcomeUnknown, // outcomeUnknown +} + +enum AgentRuntimePublicEventDiscardReason { + PayloadTooLarge, + UnsafePayload, + NotAllowlisted, +} + +struct AgentRuntimeResponseDeliveryRecord { + envelope: AgentRuntimeDurableEnvelope, + delivery_kind: ResponseDeliveryKind, // directReply | runtimeFinalReply + response_message_id: String, // DirectReply 预分配;RuntimeFinalReply 复用 finalization.messageId + command_request_id: Option, + conversation_user_message: AgentRuntimeConversationUserMessageBinding, + finalization_id: Option, + response_operation_id: String, + project_id: String, + session_id: String, + run_id: Option, + response_request_slot: Option, + // RuntimeFinalReply legacy adapter 必须保存捕获时的完整 source identity; + // 不能只保存可重新指向当前 sidecar 的 key。 + legacy_source_binding: Option, + conversation_message_key: String, + commit_marker: Option, + status: ResponseDeliveryStatus, // reserved | streaming | committed | rejected | outcome-unknown + message_sequence: Option, + conversation_cursor: Option, + provider_call_evidence: ProviderCallEvidence, +} + +struct AgentRuntimeLegacyResponseStreamBinding { + legacy_stream_key: String, + task_id: String, + session_id: String, + run_id: String, + request_kind: String, + request_slot: String, + applied_steer_cursor: u64, + response_revision: u64, + source_sequence: u64, + source_status: AgentRuntimeLegacyResponseStreamStatus, + // 按捕获时完整 legacy source record(含正文/finish reason)计算; + // 恢复时 sidecar key、全部 identity、status、sequence 或 digest 任一漂移 + // 都不得继续 finalization,必须进入 reconciliation。 + source_record_digest: String, +} + +enum AgentRuntimeLegacyResponseStreamStatus { + Streaming, + Ready, + Committed, + Discarded, + Failed, +} + +enum ResponseDeliveryKind { + DirectReply, + RuntimeFinalReply, +} + +enum ResponseDeliveryStatus { + Reserved, + Streaming, + Committed, + Rejected, + OutcomeUnknown, +} + +struct ProviderCallEvidence { + request_slot: Option, + provider_request_id: Option, + call_status: ProviderCallStatus, +} + +enum ProviderCallStatus { + NotCalled, + Started, + Completed, + OutcomeUnknown, +} +``` + +五类 Public conversation message 的映射固定如下,adapter 不得按正文、文件位置、时间戳或 ID 前缀猜类型: + +| `deliveryKind` | `role` | Public `messageId` 来源 | `eventId` | Public provenance | +|---|---|---|---|---| +| `user` | `user` | `conversationUserMessageId` | 必须为 `None` | 默认 `None`;不得暴露 command/input envelope 私有 identity | +| `directReply` | `assistant` | prepared 时预分配的 `responseMessageId` | 必须为 `None` | 默认 `None`;request/operation identity 保持私有 | +| `runtimeFinalReply` | `assistant` | 现有 finalization journal 的 `messageId` | 必须为 `None` | `runId` 必填;`sourceAgentId/sourceRunId` 仅在已有公开 lineage 可证明时填写 | +| `runtimeStatus` | `system` | `runtimeStatusMessageId` | 必须为 `None` | `runId` 必填;不公开 failure 内部堆栈或 status ledger identity | +| `publicEvent` | `system` | 与 `eventId` **同一不透明 identity、同一 wire value** | 必须为 `Some(messageId)` | `runId/sourceAgentId/sourceRunId` 按 allowlist event projector 的已验证公开 lineage 填写 | + +Public 去重键固定为 `(projectId, sessionId, deliveryKind, messageId)`;同键正文、role、eventId、provenance 或 sequence/cursor 任一不一致都表示 durable identity 冲突,必须阻断该 session 的增量投影并进入 reconciliation,不能选择“最新”副本。`sequence` 只用于 conversation-global 排序和诊断,不参与业务 identity或分页完整性判断;同一去重键的 at-least-once 重放必须返回完全相同的 message。不同 deliveryKind 即使偶然得到同一 messageId 也不是同一消息,但 Public event 的 eventId/messageId 等值规则是显式例外,不得再生成第二 conversation message identity。 + +`read_public_conversation` 返回 `Result`。合法 `afterCursor` 必须属于同一 project/session 且指向 committed cursor chain 中已确认的 Public conversation 位置,返回链上其后的 committed message;单页同时受 `limit<=256` 和 `1 MiB` JSON 上限约束,下一条完整消息会使任一上限超出时在该消息前结束本页,只有链上仍存在未返回的 committed message 时 `hasMore=true`,`nextCursor` 指向本页最后一条 committed message。任一单消息仍必须先满足自身 Public 正文上限,不允许为满足页大小而截断正文。`afterCursor=None` 表示从该 session 的 committed origin 开始全量分页,不表示“只读最新一页”;全量读取期间新 committed message 只追加到后续 cursor 页,不得改写已经返回的 sequence/cursor。 + +V1 选择与现有本地 append-only/durable conversation 一致的**逻辑永久保留**方案:一条消息首次对 Public 可见后,其 committed message、cursor、私有 predecessor/successor chain link 以及 session conversation origin/tail marker 必须保留到该 session 被显式删除;物理压缩、checkpoint 或文件合并只有在仍能按原 cursor 读出完全相同的逻辑链时才允许执行。因此 `nextCursor` 在 session 生命周期内持续有效,Public conversation read **不得返回 `CURSOR_EXPIRED`**;`CURSOR_EXPIRED` 只保留给有界 Public Snapshot event 日志。格式非法、属于其它 project/session 或从未由该 session 签发的 cursor 返回 `CURSOR_INVALID`,不夹带部分 page;Consumer 可以在确认本地 cursor 损坏或丢失后以 `afterCursor=None` 重新全量分页,但不能把服务端曾签发 cursor 的消失当作普通 invalid 后静默重置。 + +若任一已签发 cursor、committed message、chain link 或 origin/tail marker 丢失、物理截断,或者 chain 漏掉已 committed message,服务端必须返回不可重试的 `CONVERSATION_HISTORY_INCOMPLETE`,不返回 page、`nextCursor` 或 `historyState`,并进入 reconciliation;不得从“当前最早文件”继续并伪装成 `complete`。`CURSOR_INVALID` 对同一 cursor 不可重试;`INTERNAL` 在 V1 也不可重试,后续只有通过新错误码才能声明明确暂态语义。成功 page 的 `historyState=complete` 表示从 session origin 起的 V1 committed history 完整,`legacyEntriesIsolated` 只表示完整 V1 history 之外另有无法证明 identity 的 pre-V1 隔离项,绝不能表示 retention truncation。Consumer 遇到 `CONVERSATION_HISTORY_INCOMPLETE` 只能停止增量/全量合并并显示有界恢复状态,不能回退读取私有 ledger、`LocalConversationResult` 或用 `afterCursor=None` 掩盖服务端截断。 + +现有 `LocalConversationResult` 是受信任本地/Developer 管理 DTO,其 `path`、本地 session catalog 和磁盘顺序不能复用为 Public read 合同。Public adapter 必须从已 read-back 的 conversation commit 生成上述 path-free message/page,绝不返回 `path/absolutePath/finalizationId/commitMarker/providerRequestId/requestSlot/chunk/tool observation`。commit marker 只保存在 Shell 私有 delivery/input record 中,用于证明 Public message 已 committed;Consumer 既不能读取也不能提交它。 + +legacy conversation message 若没有 `messageId`,只有在现有 finalization/audit/input/steer 证据能唯一证明 project/session、五类 deliveryKind、规范 message identity、role、正文和 lineage 时,migration adapter 才能以该规范 identity 分配一次 conversation-global sequence/cursor,并保存不可变 migration binding;不得按数组位置、正文 hash、mtime 或相邻消息猜 ID。无法唯一证明的 legacy record 必须移入只读私有隔离索引,保留原证据且不进入 Public incremental/full read,page 返回 `historyState=legacyEntriesIsolated`;它只能在受信任 Developer/local history 中查看,不能阻塞新 committed message 继续使用新的 sequence/cursor,也不能在以后重新投影成另一个 Public identity。 + +`Present.conversation_commit_marker=None` 只允许存在于尚未提交用户消息的 reserved/准备阶段;`conversation_commit_marker=Some(...)` 必须经过 conversation read-back 校验。DirectReply、Start 和有用户轮次的 RuntimeFinalReply 在 delivery/status committed 前必须使用 `Present(Some(marker))`;只有有明确 durable 证据证明不存在原始用户轮次的后台 RuntimeFinalReply 或 terminal failure status 才允许使用 `NotApplicable`。 + +所有 delivery record 的 `committed` 状态都必须同时具有可读回的 `commit_marker + conversation_message_key + message_sequence + conversation_cursor`,并与 Public conversation message 的 project/session/deliveryKind/messageId/正文逐项校验;`reserved/streaming` 不得被 Consumer 当作消息已交付,`outcome-unknown` 不得通过新 message key 或新 sequence 补写第二条消息。Public event 的 `discarded` 状态必须具有 `discard_reason`、私有审计证据且不可重新投递,且不得带有已提交的 `commit_marker/message_sequence/conversation_cursor`;其它状态的 `discard_reason` 必须为空。Public event 的 `public_payload_digest` 必须等于 eventId 派生时使用的规范化 public payload digest,不能只依赖可变 `publicText`。 + +所有上述 record 的 `AgentRuntimeInputEnvelopeRecord`、`AgentRuntimeInteractionRecord`、`AgentRuntimeBuiltinManagementOperationRecord`、`RunLineageRecord`、Session/Handoff/ActiveSessionIndex/SessionRotation/HandoffManifest、status、public-event、response 以及 LocalManagement record 的 `envelope.record_id` 必须与业务 identity 一一对应;每条 record 的 `envelope.record_revision` 是该 record 的唯一 CAS revision,`checksum` 是去掉 checksum 字段后的 RFC 8785 canonical JSON SHA-256,`ledger_version` 单调递增,`owner_boot_id + owner_generation` 用于 fencing,`created_at/updated_at` 只作审计不能决定恢复胜负。校验失败统一隔离为 corrupt record 并保留原证据,不能用“最新文件”覆盖或静默降级;状态枚举之外的值、缺失的 commit marker、身份不匹配和重复 message key 都按 `outcome-unknown/needs-reconciliation` 处理。 + +现有 `response-streams/{agentIdHash}/{runIdHash}.json` 实际属于 `RuntimeFinalReply`:它按 `agentId/runId` 定位,记录还包含 `taskId/sessionId/requestKind/requestSlot/appliedSteerCursor/responseRevision/sequence` 等 lineage 与流身份字段;它没有 direct reply 的 `responseMessageId` 或 conversation message cursor。adapter 在 prepared 阶段必须把捕获时的这些字段、完整 source status/sequence 和含正文/finish reason 的 `source_record_digest` 写入 `legacy_source_binding`;恢复时只能校验同一 binding,不能按 key 重新读取当前 sidecar 猜测 lineage。状态是 `streaming/ready/committed/discarded/failed`,legacy adapter 必须对五种状态穷举映射:`streaming` 只能映射为 `streaming`;`ready` 只能映射为“待 finalization commit”,不能直接映射为 `committed`;`committed` 只有在 conversation layer 以同一 finalization journal 的 `finalizationId + messageId` 原子写入并可读回最终消息后才可提交;`discarded`/`failed` 只有在 durable evidence 证明没有 conversation commit、没有未决 Provider/进程副作用且可确定拒绝原因时才映射为 `rejected` 并保留不可重试 tombstone,否则一律映射为 `outcome-unknown` 并进入 reconciliation。legacy `committed` 若找不到对应 conversation commit marker、`AgentRuntimeConversationUserMessageBinding`,或其 messageId 与现有 finalization 不一致,也必须转 `outcome-unknown`,不得凭 sidecar 正文补写第二条消息。只有存在明确 durable 证据证明历史 finalization 没有原始用户轮次时,legacy adapter 才能生成 `NotApplicable { source_record_ref }` 的后台恢复记录;用户消息缺失、存在多个候选、正文/来源冲突或 lineage 无法唯一解析时,必须进入 `outcome-unknown/reconciliation`,不得使用 NotApplicable 掩盖歧义。DirectReplyDelivery 必须新增独立 ledger/stream adapter,不能把现有 final-reply sidecar 路径或 `ready` 状态冒充 direct reply 身份。 + +P0/P3 fixture 必须分别覆盖 direct reply 和 Runtime final reply 在预分配 messageId/finalizationId 后崩溃、stream 中断、正文 committed 但 command/finalization result 未写、同 requestId 重放、跨 transport 读回、steer/取消使旧 finalization 失效和 content-filter 拒绝;另覆盖 Start status message committed 前禁止 queued/dequeue、status write failure/unknown、根终态 failure status 先于 task/event/state 终态、prompt 排除固定前缀、public event 的 eventId/publicText 去重和恢复不重复 status message;并覆盖五类 Public message mapping、eventId/messageId 等值、conversation-global sequence/cursor 预留崩溃形成永久合法空洞且 Consumer 不重启补读、同 identity 重放、committed cursor chain 不漏消息、跨页并发追加、`afterCursor=None` 从 session origin 全量分页、已签发 cursor 经物理压缩后仍有效、格式/跨 scope cursor 返回 `CURSOR_INVALID`、message/cursor/chain/origin/tail 人为截断返回无 partial page 的 `CONVERSATION_HISTORY_INCOMPLETE`、conversation read 永不返回 `CURSOR_EXPIRED`、legacy message 唯一迁移或隔离、LocalConversationResult.path 与 finalization/Provider 私有字段零泄漏,以及 response-stream sequence 不能冒充 conversation sequence。验收以每条 delivery 分支最终 durable 消息唯一、cursor chain 补读覆盖全部 retained committed message、合法 sequence 空洞不造成恢复循环、截断历史不伪装完整和 Provider 调用计数不重复为准。 --- @@ -448,10 +2069,10 @@ Public Snapshot 只投影 `audience=User` 且状态为 Open/Resolving 的最小 ### P0 行为基线与协议验证框架 -**目标**:在改动前建立可判断迁移语义和新协议安全性的验证入口,不冻结旧 DTO。 +**目标**:在改动前建立可判断迁移语义和新协议安全性的验证入口,不冻结旧 DTO。P0 允许新增只读 fixture、golden input/output、crash-point harness 和测试辅助代码;禁止新增生产 handler、Consumer fallback 或改变现有 Runtime 生产行为。 - 复用确定性 Provider 与现有进程内 Runtime 测试,记录当前 master 的用户可见语义:提交模式、目标 run、等待/终态、批准/拒绝、取消、恢复及重复副作用计数;统一归一化随机 ID 和时间戳。 -- 为第 1 节建立尚未启用的契约 fixture:项目身份、Public 白名单、Developer capability、revision/cursor、request ledger 状态机、Interaction 状态机、结构化错误和崩溃点。 +- 为第 1 节建立尚未启用的契约 fixture:项目/session handoff 身份、不可变 Run target set/continuation set/manifest/rotation phase/active-session index 与 Public `sessionContext` 映射、`Prepared` operation 先于 fence 持久化、`FenceCommitted` operation/index marker 原子提交、barrier 后 Open/Resolving interaction、command/input envelope 和 direct/final/status/event delivery 的 successor 重新授权、LocalManagement parser/route/target 历史 identity 恢复、Snapshot/Command error 类型隔离、六个专业组父 run 投影与 retry/repair、Runtime-owned progress view、command capabilities、attachment binding、UserInput/User ToolApproval/PolicyApproval、slash built-in parser/交互门禁、intent/steer policy matrix、cancel matrix、ContinueRun/RetryTerminalRun/ReconcileRun、revision/cursor、request/response/status-message/public-event ledger、Interaction/rework 状态机、结构化错误和崩溃点。 - 基线比较只要求业务语义和副作用一致,不要求旧 DTO、旧事件名字或旧命令调用序列与新协议相同。 - P0 不修改 Runtime 生产行为,也不以空 handler、ignored 断言或永真 stub 让新契约提前通过。 @@ -462,7 +2083,7 @@ Public Snapshot 只投影 `audience=User` 且状态为 Open/Resolving 的最小 **目标**:先落不依赖公开命令和 Snapshot 的共享基础设施,避免 P1 命令反向依赖 P2/P3。P1 不注册五个新公开命令。 - 新增 `agent/supervisor_shell/` 内部模块,集中处理 canonical root、manifest project identity、调用来源 capability、结构化公开错误映射和 project lock 顺序;`schemaVersion`、五命令、错误码及 Snapshot/Interaction DTO 放入同一共享契约模块,由 Rust 与 TypeScript 绑定共同生成/校验,避免两端手抄漂移。 -- 实现带 schema 的 command ledger、interaction ledger、projection journal 基础读写:私有目录、原子写/回读、损坏隔离、锁、状态转移校验、同 ID 指纹冲突和 archive 生命周期。 +- 实现带 schema 的 command ledger、response message ledger、interaction/rework ledger、projection journal 基础读写:私有目录、原子写/回读、损坏隔离、锁、状态转移校验、同 ID 指纹冲突、authorization scope replay guard 和 archive/tombstone 生命周期。 - 将 RFC 8785 + SHA-256 规范化、request/response fingerprint、公开文本脱敏和稳定 ID 生成收敛为单一实现;禁止各命令自行拼接字符串做指纹。 - 明确锁顺序为 `project execution owner → supervisor project lock → command/interaction/projection 子记录`;不得持有文件锁等待 Consumer,也不得绕过现有 Runtime 的 run/action 锁顺序。长时间内部执行使用 ledger ownership 标记而非长期占用 transport 线程锁。 - 现有公开命令和 Runner 内存 request cache 行为不变;P1 只通过存储/状态机单测和 crash-point 测试验证底座。 @@ -473,27 +2094,28 @@ Public Snapshot 只投影 `audience=User` 且状态为 Open/Resolving 的最小 **目标**:先建立稳定、完整、可重连的唯一公开读模型,继续保留旧 read 接口供迁移。 -- 从现有 Runtime state、task/event、response stream 元数据和 pending sidecar 生成第 1.1 节双投影;Public 只包含当前 Project Supervisor,Developer 通过独立受信任 capability 读取选定内部 run。 -- 在 shadow/read-only 语义下把既有 user-input、pending tool confirmation 和可识别的 policy confirm 物化为稳定 Interaction record/view:首次投影在 owner/lock 内持久化 identity,后续按 bound fingerprint 复用;旧 sidecar 缺少可信绑定时投影 needs-reconciliation,不能每次读取生成新 interactionId。P2 只建立/刷新 record,不改变旧命令的交互行为。 +- 从现有 Runtime state、task/event、response stream 元数据和 pending sidecar 生成第 1.1 节双投影;Public 包含当前 Project Supervisor、同 run 的 Runtime-owned progress view、父 run 精确匹配的六个静态专业组、interaction 和 command/recovery capabilities,waitingOn/nextStep 使用稳定枚举,Snapshot error 与 Command error 使用不同 code/DTO;Developer 通过独立受信任 capability 读取选定内部 run。 +- 在 shadow/read-only 语义下把既有 user-input、User/Developer pending tool confirmation 和可识别的 policy confirm 物化为稳定 Interaction record/view:首次投影在 owner/lock 内持久化 identity、audience、context、action/policy fingerprint 和 target set,后续按 bound fingerprint 复用;旧 sidecar 缺少可信绑定时投影 needs-reconciliation,不能每次读取生成新 interactionId。P2 只建立/刷新 record,不改变旧命令的交互行为。 - project projection ledger 原子维护当前规范化 Public Snapshot、revision、event cursor、最近 256 条 envelope 及恢复 journal;每次 Public read 先在 projection lock 内修复未闭合 journal,再返回同一线性化点的 Snapshot/cursor。 - 提供 `read_game_creator_agent_runtime_snapshot`、Public 事件订阅和 `afterCursor` 有界补读;Developer read 使用独立命令/DTO,不与 Public 返回 union。 - Public revision 只由白名单变化推进;事件只发布 `SnapshotChanged`,同 revision 的恢复沿用同 eventId。测试不得依赖 Tauri best-effort event 自身保存补读历史。 -- P2 不删除 Consumer normalize/merge,也不注册五个写命令;只允许测试或 shadow observer 对照旧 read 与新 Snapshot。 +- P2 不删除 Consumer normalize/merge,也不注册五个写命令;只允许测试或 shadow observer 对照旧 read 与新 Snapshot。stale display 可以保留上一份 Snapshot 供展示,但只有上一份精确 cancel capability 可作为止损入口;其它旧 capability 必须禁用,且 Snapshot 恢复后不得把旧 capability 当作当前事实。 -**完成门禁**:重复、乱序、缺口、读订阅竞态、cursor 非法/过期、投影崩溃窗口和本地高 revision 均通过完整 Snapshot 收敛;正式 Public 零路径、完整任务/action/plan、动态 child、原始工具计划和 Provider 正文;Developer capability 服务端拒绝未授权来源。 +**完成门禁**:重复、乱序、缺口、读订阅竞态、cursor 非法/过期、投影崩溃窗口和本地高 revision 均通过完整 Snapshot 收敛;progress 不跨 run 合并且校验证据缺失不猜通过,六个专业组按当前 parentRunId 稳定排序,manifest fallback 不伪造 Runtime 状态,stale display 只允许上一份精确 cancel capability 且服务端重新校验;User ToolApproval/Needs input 可定位到 Supervisor 或专业组且不泄漏内部 action;正式 Public 零路径、完整任务/action/plan、动态 child、原始工具计划和 Provider 正文;Developer capability 服务端拒绝未授权来源。 ### P3 完整写协议与 Interaction Loop 收归 **目标**:在 P1 底座和 P2 唯一读模型都可用后,一次性注册真实可用的五命令;不发布“DTO 已存在但仍要求 Consumer 选择旧分支”的半成品协议。 -- `submit_intent` 将 CLI 的 Reply/Execute/Resume interaction kernel 和 GUI 的 start/steer 判定上提到 Shell:先决定 direct reply/execute/resume,execute 再读取当前 Project Supervisor 与 run profile 决定 start/steer;source 由 transport 派生,最终调用现有内部实现。 -- 接管 P2 已物化的 user-input/tool-confirm Interaction record,并为项目 resume/retry policy confirm 创建稳定 record;`answer/approve` 只按 interaction response meta 路由,approve 显式处理 approve/reject。 -- `cancel` 锁内校验精确当前 Supervisor run;`resume` 统一处理 pending、确定性 retry/timer、ready task 和 reconciliation policy,遇到需人工确认时创建项目级 PolicyApproval 而不是执行。 +- `submit_intent` 先将 GUI/CLI 现有 slash parser 收到 Shell:`/` 输入只走同一版本 parser,只读命令走 direct reply,副作用命令生成 Interaction,禁止成为自主 Runtime task;随后才将 CLI 的 Reply/Execute interaction kernel 和 GUI 的 start/steer 判定上提到 Shell:在 project lock 内按冻结 intent policy matrix 决定 direct reply/start/steer/reject;active root Run 已绑定冻结 Goal Contract 且 kernel 判定为 execute 时必须在旧 steer wrapper 前稳定拒绝为 `TARGET_BUSY`,direct reply 仍按原门禁交付;现有 replacement primitive 仅由显式 Goal management mutation 经其 operation identity/lineage 调用;`(intentKind, entryBindingKind, runProfile, inputPolicy)` 必须命中当前 capability 的同一 option,实际资源 binding 来自既有资源管理面,message 必须非空,附件进入同一 input envelope 且不能被丢弃。steer 在 prepared 阶段绑定现有 V1.13 steerId/cursor,source 只做受信任归因/权限/审计,不能改变业务路由。Continue/Retry/Reconcile 只能走结构化 `resume` tagged intent。 +- 接管 P2 已物化的 user-input/User/Developer tool-confirm Interaction record,并为项目 resume/retry policy confirm 创建稳定 record;`answer/approve` 只按 interaction response meta 路由,approve 显式处理 `approve | reject | requestChanges` 及其 feedback、policy snapshot 和 target set 合同。 +- `cancel` 按第 1.3 节取消矩阵锁内校验精确 Supervisor run;`resume` 只按 tagged intent 区分 ContinueRun、RetryTerminalRun、受信任 ReconcileRun。pending/timer/lane/ready task 的 Runner wake 与项目级自动恢复留在内部 recovery,不由 Public ResumeCommand 模糊触发;需要用户决定时创建项目级 PolicyApproval,而不是执行。 - 五命令全部先走 request ledger,再进行 target/interaction 校验和内部调用;成功、业务拒绝、并发 in-progress、崩溃可证明结果及 outcome unknown 都按第 1.3 节闭合。 -- Shell 负责生成 Public `stage/waitingOn/nextStep` 和安全 Interaction presentation;CLI 的 Reply/Execute/Resume、Consumer 的 start/steer/confirm/retry/resume 判断在此阶段只作为旧公开路径的兼容实现存在,不作为新命令输入。 +- 在同一阶段实现第 1.5 节独立 Public conversation append/read adapter:五类 committed message 共用 project/session conversation-global sequence/cursor,committed cursor chain 是分页完整性的权威,sequence 永久空洞合法且只用于排序/诊断;`read_public_conversation(afterCursor, limit)` 只返回 path-free Public DTO,并将 committed message/cursor/origin/tail 逻辑保留到 session 显式删除。DirectReply、RuntimeFinalReply、RuntimeStatus、PublicEvent 与 user input 的私有 commit marker/read-back 必须先闭合,不能把现有 `LocalConversationResult.path`、response-stream sequence 或私有 delivery ledger 暴露给新 Consumer。legacy 无 messageId 记录只按唯一 durable evidence 迁移,否则进入只读隔离并返回 `legacyEntriesIsolated`;物理截断或 cursor chain 损坏必须返回 `CONVERSATION_HISTORY_INCOMPLETE`,不得返回 partial page、`CURSOR_EXPIRED` 或伪造 `complete`。 +- Shell 负责生成 Public `stage/waitingOn/nextStep`、六个专业组状态、command/recovery capabilities 和安全 Interaction presentation;CLI 的 Reply/Execute/Resume、Consumer 的 start/steer/confirm/retry/resume 判断在此阶段只作为旧公开路径的兼容实现存在,不作为新命令输入。 - 新旧公开命令并存,但新命令从注册之日起即具备完整生产语义。所有旧写 wrapper 同时改为经过同一 Shell project lock 和 projection-dirty/Interaction 同步 adapter:旧接口可以没有新 requestId 保证,但不能绕过 Interaction 状态、Public 投影或与新命令并发写出矛盾事实。P3 通过进程内调用和专用协议 harness 验证,不提前迁移正式 CLI/GUI 调用点。 -**完成门禁**:五命令的同 requestId 重放、同键异内容、并发重复、业务拒绝重放、Runner 强杀读回和 unknown outcome 全部闭合;无关 Snapshot 更新不使 interaction 失效,interaction/策略漂移失败关闭;项目级 PolicyApproval 可安全覆盖锁内重新枚举的多个 run;新旧路径用户可见终态与副作用计数等价。 +**完成门禁**:五命令的同 requestId 重放、同键异内容、并发重复、业务拒绝重放、Runner 强杀读回和 unknown outcome 全部闭合;slash 已知/未知/带附件/开放 interaction 路由与现有语义等价且绝不创建自主任务;入口 intentKind/attachment 的同 requestId 异值命中 `IDEMPOTENCY_KEY_REUSED`,attachment 漂移零副作用,并发 intent 不创建第二个 Supervisor run;same-run steer 保持 runId/steer cursor 且不重复 conversation,冻结 Goal Contract 的 execute 请求返回 `TARGET_BUSY`、不会调用 replacement primitive;Goal management replacement 使用独立 operation identity/lineage,并覆盖旧树已取消但 replacement 未入队的 crash point;cancel matrix 逐行验证、CancelAccepted 不伪造终态且重复 cancel 共用 operation;ContinueRun 保持 runId、Supervisor/专业 RetryTerminalRun 生成唯一 successor lineage、prepared repair 只生成/引用唯一 Public PolicyApproval interaction 且在其解决前不开放 retry、ReconcileRun 不重放未知副作用;UserInput/User ToolApproval/Developer ToolApproval audience 隔离,无关 Snapshot 更新不使 interaction 失效,action/policy/target set 漂移失败关闭;`approve` 的 `requestChanges` 反馈过滤、单一不可变产物绑定、唯一 reworkOperationId 与后续审批交互可读回且不可重复;direct reply 的 durable responseMessageId/stream、Start/terminal Runtime status message 先后顺序与恢复闭合;五类 Public conversation message 的 mapping、全局 sequence/cursor、去重、cursor-chain 分页、合法 sequence 空洞、session-lifetime retention、`CURSOR_INVALID` 与 `CONVERSATION_HISTORY_INCOMPLETE` 失败关闭、legacy 隔离及零 path/私有字段跨 transport fixture 全部通过,且 conversation read 不产生 `CURSOR_EXPIRED`;新旧路径用户可见终态与副作用计数等价。 ### P4 Runner 自驱与安全恢复 @@ -504,6 +2126,7 @@ Public Snapshot 只投影 `audience=User` 且状态为 Open/Resolving 的最小 - 将进程内 `known_roots` 扩展为 AppData 私有 `game-creator-known-roots.v1`。记录稳定 project identity、canonical root、首次/末次登记时间和有效状态,不保存用户输入、Provider 内容或凭据。 - Unix 父目录/文件权限分别为 `0700/0600`;Windows 使用仅当前用户可访问的等价 ACL。写入使用同目录临时文件、文件同步、原子替换及目录同步(平台支持时);读取校验 schema、普通文件/非链接、owner/ACL、重复 identity 和重复 canonical path。 - root 每次使用前重新 canonicalize 并重读 manifest identity。目录消失只标记失效;搬迁仅在新的、已授权 locator 注册并能唯一证明同一 projectId 时更新,不主动遍历磁盘寻找项目。identity/path 冲突或目录簿损坏保留证据并进入 reconciliation。 +- root 注册权属于现有项目创建/打开管理面,不属于五个 Runtime 命令。项目首次创建或授权打开时,必须在发送任何 Runner wake 前写入 manifest projectId 并原子登记 canonical root;登记失败则项目不能进入自动调度。项目关闭只撤销 active control lease,不删除 ledger;归档/删除必须先 drain 并保留 request/interaction tombstone。复制目录若沿用同一 projectId 视为 identity 冲突,必须显式执行 clone-as-new-project 生成新 projectId,不能靠路径先到先得。 **wake、扫描与 owner**: @@ -517,13 +2140,20 @@ Public Snapshot 只投影 `audience=User` 且状态为 Open/Resolving 的最小 | durable 状态 / 条件 | 自动动作 | 禁止动作 | |---|---|---| | command `prepared` 且可证明未产生副作用 | 取得 owner 后继续同一 request | 不创建替代 requestId | +| session rotation `Prepared` 且 active index 未 fenced | 继续按旧 active session 投影 `Ready`;校验 operation 的 predecessor/successor、expected revision/epoch 和 owner generation,预期仍成立时继续同一 operation 的 fence commit,否则写 `Rejected` tombstone | 不投影 `HandoffInProgress`,不创建 successor session/manifest/handoff/集合或其它 rotation 副作用;不得在 index 已引用 operation 时把 Prepared 当作有效 fence | +| session rotation `FenceCommitted` / `HandoffManifestCommitted` / `SuccessorSessionCommitted` / `HandoffsCommitted` | 校验 operation/index 的 operationId 与 fence marker、不可变 target set、continuation set 与 manifest;只读显示旧 interaction/已提交 conversation,阻止新 requestId 和新 delivery identity;仅在所有 handoff、continuation 与 active-session marker 可证明后一次性切换 | 不使用 pending successor/handoff/continuation 控制 Run 或旧 record,不按当前扫描集合补目标,不开放写 capability;stale cancel 例外服从 rotation phase gate | +| session rotation `ActiveSessionCommitted` | 校验 `activeIndex.committedRotationOperationId == operation.operationId`、active-session marker、predecessor/successor session、source/successor revision、manifest operation/session/epoch、`activeIndex.runCreationEpoch == expected + 1` 和全部 handoff/continuation,幂等补 Public `Ready` 投影 | 不按 activeSessionId/最新文件猜已提交 operation,不同时接受旧/新 session,不重复生成 capability | +| session rotation `Prepared-before-fence Rejected` | 校验 active index 从未引用该 operation;只写 operation tombstone,不清 fence、不递增 epoch、不隔离或创建 successor/manifest/handoff/set,保留历史 `committedRotationOperationId`,幂等维持旧 `Ready` | 不修改 active index,不恢复 successor、不发送 Runtime wake、不复用被拒绝的 operation/successor identity | +| session rotation `FenceCommitted-after-fence Rejected` | 校验 operation/index 的 operationId 与 fence marker 一致、active-session marker 未提交且无未知结果;隔离该 operation 的 pending records,在同一 journal 只清除该 operation 的 fence/marker并按规则递增 epoch,保留历史 `committedRotationOperationId`,幂等恢复旧 `Ready` | marker/commit 状态不一致时不清 fence;不覆盖历史 committed id,不恢复 successor、不发送 Runtime wake、不把冻结 interaction 当新交互 | +| session rotation `ReconciliationRequired` 或 manifest/index/continuation 冲突 | 清空写 capability,保留所有 target/continuation、旧 interaction 和 delivery 证据并进入 reconciliation | 不选择最新文件、不自动开放 answer/approve/cancel/resume,不恢复旧/新双写窗口 | | command `executing` 且结果有可信 durable 证据 | 幂等补 command result / Public 投影 | 不重复内部或外部副作用 | | command `executing` 且外部结果未知 | `outcome-unknown` + `needs-reconciliation` | 不自动重放 | | Runtime `pending` 且身份可信、owner 已取得 | 可调度一次 | 不跨 owner 重复调度 | | 等待确定性 timer / lane release | 到期或 wake 后继续 | 未到期不轮询重放 | | Open user input / tool approval / policy approval | 保持 InteractionRequired,只刷新投影 | 不自动批准或把权限拒绝当恢复失败 | | interaction `resolving` 且结果可证明 | 幂等补 Resolved 和 command result | 不重新消费 response | -| interaction `resolving` 且外部结果未知 | supersede/reconciliation,保留 response 证据 | 不以第二 response 自动解决 | +| `requestChanges` rework `prepared/enqueued` 但旧 interaction 未闭合 | 按 operation identity 补齐唯一 rework link,再关闭旧 interaction | 不重解释 feedback、不创建第二 rework | +| interaction `resolving` 且外部结果未知 | 保留 response/target 证据并进入 reconciliation;旧 interaction 不伪造 Resolved | 不以第二 response 自动解决 | | Runtime `executing` 且 Provider/工具/进程结果未知 | `needs-reconciliation` | 不自动重放 request/action slot | | journal、ledger、interaction 或 run 身份损坏/冲突 | `needs-reconciliation` | 不猜测、不覆盖证据 | | 业务已完成、revision 尚未分配 | 由 dirty journal 生成唯一下一 revision/eventId | 不回滚业务事实、不跳号 | @@ -535,7 +2165,7 @@ Public Snapshot 只投影 `audience=User` 且状态为 Open/Resolving 的最小 **边界**:Runner 仍由 GUI 启动,保留 GUI-owner watchdog 与 `game_chat_release` 退出协议;本轮不实现开机自启或无 GUI 常驻。 -自动调度只允许处理以下状态:身份可信、已取得 project execution owner、项目不处于 draining、任务为 `pending` 且其依赖已满足,或确定性 timer/lane 到期且可证明尚未执行;投影补偿只允许重复生成已确定的 Snapshot/event 结果。`waiting for user input`、`waiting for policy approval`、`waiting for developer approval` 永不自动推进;`executing`、Provider/工具/进程结果未知、状态身份冲突和 `needs-reconciliation` 永不自动重放。Runner 的调度 worker 在 owner lease、GUI-owner lease 或 drain 状态任一失效时先停止 dequeue,再决定进行中工作如何收束;不得先取出任务后再补做 owner 检查。 +自动调度只允许处理以下状态:身份可信、已取得 project execution owner、项目不处于 draining、任务为 `pending` 且其依赖已满足,或确定性 timer/lane 到期且可证明尚未执行;这些都是内部 recovery intent,不得伪装为 Public ResumeCommand。投影补偿只允许重复生成已确定的 Snapshot/event 结果。`waiting for user input`、`waiting for policy approval`、`waiting for developer approval` 永不自动推进;`executing`、Provider/工具/进程结果未知、状态身份冲突和 `needs-reconciliation` 永不自动重放。Runner 的调度 worker 在 owner lease、GUI-owner lease 或 drain 状态任一失效时先停止 dequeue,再决定进行中工作如何收束;不得先取出任务后再补做 owner 检查。 所有自动动作都必须记录可恢复的 wake reason 和 operation identity。wake 只负责唤醒,不能证明任务仍可执行;worker 每次 dequeue 前重新读取 durable state、owner generation 和 project drain 状态,校验通过后才创建或继续同一 operation。兜底扫描发现不满足上述条件的 root 时只记录跳过原因,不改变 Runtime 状态;扫描过程不得为了“发现新项目”而遍历未登记目录。 @@ -547,22 +2177,23 @@ Public Snapshot 只投影 `audience=User` 且状态为 Open/Resolving 的最小 - 面向用户/自动化的 Supervisor CLI 改为 Public Snapshot + 五命令 + event cursor;现有 `--swarm-chat` 若继续展示完整专业 Agent 状态,必须明确归类为受信任开发 CLI 并走 Developer capability,不能一边读取内部字段一边宣称是正式 Public Consumer。 - 公开 e2e/fixture 迁移到同一协议;直接调用内部 start/steer/resume 的单元和恢复回归继续保留,不把内部能力误算为 Consumer。 -- GUI 正式 Supervisor 只使用 Public Snapshot;删除 normalize/merge、phase 文案、steer/start、confirm/retry/resume 和启动 schedule-ready 决策。开发窗口显式走 Developer read capability;开发者对当前 Project Supervisor 的写操作仍走同一五命令,专业/child Agent 的直接调试控制属于既有受信任管理面,不伪装成正式 Supervisor 协议。 -- Consumer 只按结构化 code/kind/retryable/interactionRequired 分流;事件去重、缺口和 cursor 过期都只触发完整 Snapshot 读取。 +- 面向用户的 CLI 与 GUI 会话列表统一改读 `AgentRuntimePublicConversationReadPage`:使用 `(projectId,sessionId,deliveryKind,messageId)` 去重、conversation-global sequence 排序、committed cursor chain 与 `afterCursor` 分页;首次读取或本地 cursor 确认丢失时以 `afterCursor=None` 从 session origin 全量补读。sequence 数值空洞是合法诊断信息,不得触发重读;服务端签发的 conversation cursor 在 session 生命周期内必须持续有效,该 read 不处理 `CURSOR_EXPIRED`。`CURSOR_INVALID` 不夹带 partial page;`CONVERSATION_HISTORY_INCOMPLETE` 必须停止合并并显示恢复状态,不能以 `afterCursor=None`、`LocalConversationResult.path` 或私有 ledger 掩盖截断。不得按 response-stream sequence 排序、从 eventId/ID 前缀猜 deliveryKind 或直接访问私有 delivery ledger。受信任 Developer/local history 可以继续使用独立本地 DTO,但必须在类型和调用面上与 Public read 隔离。 +- GUI 正式 Supervisor、Runtime-owned 进度卡、六组底栏状态、用户确认/Needs input 和专业失败重试只使用 Public Snapshot 的 collaborator/interaction/capability;上传先经既有项目资源管理面取得 immutable attachment binding,再以非空 message 调用 submit_intent。attachment-only 在 V1 明确拒绝,不能由 GUI 合成自然语言;附件必须在 input envelope 和 Provider/resource context 中可恢复。`preview.start`/`preview.validate` 继续只消费既有 preview authorization 与 `PreviewRegistry`,不从 Runtime Snapshot 的 `nextStep` 猜测,不被 P6 旧 Runtime 命令删除误伤。删除 normalize/merge、phase 文案、前端 slash 解析、steer/start、confirm/retry/resume 和启动 schedule-ready 决策。开发窗口显式走 Developer read capability;开发者对当前 Project Supervisor 的写操作仍走同一五命令,专业/child Agent 的直接调试控制属于既有受信任管理面,不伪装成正式 Supervisor 协议。 +- Consumer 只按结构化 code/kind/retryable/interactionRequired 分流;此处的事件去重、缺口和 cursor 过期仅指有界 `SnapshotChanged` event 日志,并只触发完整 Snapshot 读取,不得套用到逻辑永久保留的 Public conversation cursor。 - 迁移期 fallback 仅处理 transport 明确的 unknown command:该错误证明新 handler 未执行,才可调用旧命令。任何已到达新 handler 的结构化错误或超时都不得 fallback;超时只可同 requestId 重试/读回。 -**完成门禁**:三类正式 Consumer 的 Runtime 调用面一致,差异只剩输入输出形态;Public/Developer 类型无交叉;迁移前后用户语义和副作用计数等价。 +**完成门禁**:三类正式 Consumer 的 Runtime 调用面和 Public conversation read 合同一致,差异只剩输入输出形态;Public/Developer 类型无交叉,正式 Consumer 返回体不存在 `path/absolutePath/finalizationId/commitMarker/Provider` 私有字段;迁移前后用户语义、消息顺序和副作用计数等价。 ### P6 删除旧公开面并最终收口 **目标**:Interaction Contract 成为唯一稳定公开 Runtime 控制边界。 - 从 Tauri invoke handler 和其它正式 transport 删除旧 start/steer/confirm/reject/answer/cancel/retry/resume/schedule/read 注册、旧公开 DTO、旧事件和 migration fallback。 -- 删除 Consumer 旧调用点与生命周期分支;管理面 goal/compact/session/config 按第 1.3 节边界保留。 +- 删除 Consumer 旧调用点、生命周期分支和把 `LocalConversationResult`/response stream 当作正式会话补读的路径;正式 Public conversation 只保留第 1.5 节 message/read DTO、五类 mapping、committed cursor chain 和 session-lifetime logical retention。任何 cleanup/compact 旧路径都不得删除仍存续 session 的 Public committed message/cursor 或把截断历史重新标记为 `complete`。管理面 goal/compact/session/config 及受信任 Developer/local history 按第 1.3/1.5 节边界保留,但不得重新导出为 Public DTO。 - Shell 内部 start/steer/resume/recovery 函数、Runner 内部方法及验证这些能力的回归测试允许保留或重命名,不设置全仓旧名称为零的伪门禁。 - 同步 Runtime V1.1、智能体 App 实施计划、文档索引和长期架构记忆,确保本计划不成为与权威 Runtime 并行的冲突事实源。 -**完成门禁**:定向静态检查证明 invoke handler、正式 transport、Consumer 调用点和公开 DTO 不再引用旧协议;协议契约、进程内、确定性、独立进程恢复、真实 Runner 和前端验证覆盖最终调用面。 +**完成门禁**:定向静态检查证明 invoke handler、正式 transport、Consumer 调用点和公开 DTO 不再引用旧协议,也不引用 `LocalConversationResult.path` 或私有 delivery/finalization/Provider 字段;协议契约、conversation cursor chain/永久空洞/session-lifetime retention/截断失败关闭、进程内、确定性、独立进程恢复、真实 Runner 和前端验证覆盖最终调用面。 --- @@ -597,15 +2228,26 @@ Public Snapshot 只投影 `audience=User` 且状态为 Open/Resolving 的最小 | 风险 | 强制约束 | |---|---| | Snapshot 与 durable state 在文件崩溃窗口不同步 | projection journal + 单一锁序;Public read 先修复,事件永远只作提示 | +| 进度卡由 Consumer 跨 manifest/plan/event 猜结论 | Shell 投影当前 run 的结构化 progress/check outcome;无可信 receipt/evidence 就 unknown/省略,run 切换原子替换 | | 全项目 revision 导致无关更新误杀交互 | 回答 CAS 使用 interactionRevision;精确 target 命令锁内重读 durable identity | | 重试先做当前状态校验而失去首次结果 | 固定先查 request ledger,再做状态/策略校验;成功和业务拒绝都持久化 | | responseId 换 requestId 造成二次消费 | interaction resolution 同时保存 responseId/fingerprint,独立于 command requestId 去重 | | command/interaction executing 的外部结果未知 | outcome-unknown / reconciliation;禁止自动重放或换 ID | | Tauri 前端 devMode 被当作权限 | Developer read 只认服务端构建、窗口标签或显式受信任 capability | | Public interaction 正文可能泄漏私有问题、工具计划或路径 | interaction audience + kind 白名单 + 公共内容安全过滤;不安全内容降为 Developer/reconciliation,原始信息仅私有 sidecar/开发 capability | +| P5 误删预览/资源管理面或从 Runtime 状态猜授权 | preview/resource/session 管理面保持 sibling contract;只消费现行 authorization/immutable revision/PreviewRegistry,不纳入五命令 | | 目录簿泄漏本地路径或扫描失控 | 私有权限、仅 AppData 持久化、公开零路径、wake 优先和有界低频轮转 | | P3/P5 重复迁移导致阶段不可独立审查 | P3 只完成后端协议与 harness;P5 只切换 Consumer 和删 Consumer 决策 | | 旧名称静态检查误删内部能力 | P6 只检查正式 transport、Consumer 和公开 DTO | +| `resume` 模糊吸收 retry/schedule/reconcile | Public ResumeCommand 使用 tagged intent;ContinueRun 不换 runId,RetryTerminalRun 必须生成 successor lineage,自动 wake 不进入公开命令 | +| slash 命令被当作普通 prompt 或由 Consumer 分叉解析 | submit_intent 在 matrix 前统一调用带版本 Rust parser;只读命令 direct reply,副作用命令生成 Interaction,`/preview` 等绝不进入自主 Runtime task | +| CancelAccepted 被误当成 cancelled 终态 | 冻结取消矩阵;pending action/finalizing/unknown 不能虚假完成,Consumer 继续读 Snapshot | +| Command error 私有字段进入 Snapshot | SnapshotError 与 CommandError 分型,Public projection 零 requestFingerprint/requestId/replayed | +| requestChanges 在 resolution 与 rework 入队之间崩溃 | interaction Resolving 先预分配 reworkOperationId,operation journal 幂等补链,unknown 不伪造 Resolved | +| direct reply stream 超时后生成重复回答 | responseMessageId/response operation identity 在 prepared 预分配,durable message ledger 读回,unknown 禁止换 requestId | +| Runtime status message 与 Run 受理顺序错乱 | Start 先提交唯一 `runtimeStatusMessageId` 的 commit marker,再允许 queued/dequeue;写失败/unknown 不执行,根终态 failure status 先于其它终态投影 | +| 安全 Runtime 事件被前端拼成重复/泄漏消息 | event projector 只交付 Rust 生成的 `eventId + publicText`,不把 raw event 放进 SnapshotChanged;无身份、空正文、legacy/raw payload 丢弃 | +| “Runner 自驱”与 GUI-owner 门禁表述冲突 | 自驱仅指 owner/lifecycle lease 有效时不依赖 Consumer 轮询;无 GUI headless lease 未落地前返回 OWNER_UNAVAILABLE | --- @@ -615,17 +2257,23 @@ P0 建语义基线与契约 fixture → P1 建身份/权限/ledger/锁底座 → --- -## 8. 协议冻结输出 +## 8. 协议候选冻结输出 -本方案提交后,以下内容视为 V1 编码前的冻结合同,不在实现 PR 中由 Consumer 或 transport 自行解释: +本方案提交后,以下内容视为 V1 编码前的候选合同;只有第 10 节证据门禁闭合并重新评审后,才转为正式冻结合同。候选合同期间,Consumer 或 transport 不得自行解释或扩展: - 身份:manifest `projectId` 不可变;`sessionId` 来自会话管理面;`runId` 由 Shell 在 prepared 阶段预分配;所有 locator、owner generation 和内部 operation identity 不进入 Public 协议。 - 所有权:project execution owner、Runner owner、GUI-owner 分层;owner generation 是 fencing 权威;未取得 owner、draining 或 GUI-owner 失效时不扫描、不执行、不接受自动恢复。 - 投影:先 durable 业务事实,再按 dirty journal 补 Public projection;已提交事实但投影未刷新可幂等补偿,事实提交结果未知必须 reconciliation。 - 事件:项目级 at-least-once `SnapshotChanged`;`snapshotRevision`、`sequence`、`eventId`、opaque `cursor` 的持久关系不可改变;事件只通知,Consumer 只能重读完整 Snapshot。 -- 写入:五命令统一 request ledger;相同 requestId/指纹回放原结果,异指纹冲突;结果未知不换 ID 重放;结果读回只按 `(projectId, requestId)` 查询。 -- 交互:`interactionId + interactionRevision + responseId` 独立于全项目 revision;Shell 在锁内重读交互和策略;项目级 PolicyApproval 不要求绑定单一 run。 -- 公开面:Public 字段白名单和稳定枚举是唯一正式 Supervisor 展示合同;Developer Snapshot 必须经过服务端 capability;`tool_request` 不进入 Public。 +- 写入:五命令统一 request ledger;相同 requestId/指纹且 authorization scope 等价才回放原结果,异指纹冲突;结果未知不换 ID 重放;结果读回只按 `(projectId, requestId)` 查询。 +- 生命周期:CancelAccepted 只表示取消受理;ContinueRun 保持原 runId,RetryTerminalRun 生成唯一 successor runId,ReconcileRun 只允许受信任 capability;timer/lane/schedule/recovery wake 不属于 Public ResumeCommand。 +- 入口与边界:slash 输入先由 Shell 同一版本 parser 路由,严格使用 `BuiltinCommand(commandLine, expectedParserVersion)` tagged payload,不能作为普通 prompt/start/steer;普通 `Conversation` 与四种 `intentKind` 使用完整 policy matrix;模板/导入必须提交 typed `entryBinding` 及 immutable revision/digest;V1 ID、JSON、文本、答案和补读上限由 Rust 单一常量源生成,超限在 ledger 前失败。 +- 本地命令:`AgentRuntimeLocalManagementCapability`、`LocalManagementCommand/Response` 与 project-scoped targetRef 是独立候选合同;无 project/session 的命令不伪造五命令 meta,local path 仅在 resolver/OS handle 边界展示;同一 parser route 不能同时落入 Public conversation 和 LocalTransportReply。 +- 会话与 lineage:`conversation_session_id` 不自动等价 Supervisor session;session handoff 依赖持久 `supervisorLineageId + sessionRevision`,Retry 的 `acceptedRunId` 只是回显,唯一 predecessor/successor 由 `RunLineageRecord` 证明;Run target set 之外,Open/Resolving interaction、未终结 command/input envelope 与 response/status/event 还必须由不可变 continuation set 逐条证明,successor 不改写原 record identity。 +- 交互:`interactionId + interactionRevision + responseId` 独立于全项目 revision;UserInput、User ToolApproval 和 PolicyApproval 均有明确 audience/context/presentation 合同;Shell 在锁内重读 action、父子身份、policy snapshot 和 artifact binding。ApprovalDecision 冻结为 `approve | reject | requestChanges`,其中 `requestChanges` 只允许单一不可变产物绑定的 Run/Action PolicyApproval,必须携带有界、脱敏 feedback 和唯一 reworkOperationId;项目级多 Run PolicyApproval 只能针对固化的精确 target set 执行 approve/reject。 +- 交付:direct reply 的 responseMessageId 和 operation identity 在 command prepared 预分配;Runtime final reply 复用现有 finalization journal 的 finalizationId/messageId,不生成第二身份;Start/terminal Runtime status message 使用独立 `runtimeStatusMessageId + commit marker`,先后顺序和 prompt 排除规则固定;安全 Runtime event message 的 Public `messageId` 与 Rust `eventId` 使用同一 wire identity 且独立于 SnapshotChanged。五类 committed conversation message 统一投影为 path-free Public message/read DTO,去重键固定为 `(projectId,sessionId,deliveryKind,messageId)`;committed cursor chain/分页是补读完整性的权威,conversation-global sequence 只排序/诊断且允许永久空洞,response-stream sequence 不是 conversation sequence。committed message/cursor/origin/tail 在 session 生命周期内逻辑保留,conversation read 不返回 `CURSOR_EXPIRED`;物理截断返回无 partial page 的 `CONVERSATION_HISTORY_INCOMPLETE`,不能伪装 `historyState=complete`。commitMarker/finalization/Provider/locator 保持私有,legacy 无 messageId 只能按唯一证据迁移或隔离。conversation/response stream 独立持久、可补读且不参与 Runtime Snapshot,结果未知时不生成第二回答。 +- 兼容与生命周期:现有 response sidecar 必须经 delivery ledger adapter 穷举映射 `streaming/ready/committed/discarded/failed`,并持久化捕获时完整 source identity/digest;legacy `ready/committed` 不能直接当作消息 committed,`discarded/failed` 需有证据才能 rejected 否则 reconciliation;V1 不提供无 GUI CLI headless control lease,无有效 GUI-owner 时写入返回 `OWNER_UNAVAILABLE`。所有 durable journal 受跨平台 `DurabilityCapability` 门禁,无法证明持久化时禁止进入 executing。 +- 公开面:Public 字段白名单和稳定枚举是唯一正式 Supervisor/专业组展示合同;同 run progress、六组 collaborator、interaction 和 command capabilities 都必须受父 run/权限/数量边界约束;waitingOn/nextStep 不使用任意字符串,SnapshotError 与 CommandError 分型;Developer Snapshot 必须经过服务端 capability;`tool_request` 不进入 Public。 任何实现若无法满足上述合同,必须先修改本技术方案并重新评审,不得通过新增 Consumer fallback、缓存或隐式状态字段绕过。 @@ -635,10 +2283,75 @@ P0 建语义基线与契约 fixture → P1 建身份/权限/ledger/锁底座 → 1. Snapshot 是唯一公开 Runtime 状态事实;事件收窄为项目级 `SnapshotChanged`,不再公开可被误合并的 run/interaction/tool 状态载荷。 2. 项目 manifest `projectId` 是协议身份,`projectPath` 只是每次都要 canonicalize 和复核的私有 locator。 -3. Public Snapshot 只含当前 Project Supervisor 紧凑摘要与协作数量;Developer Snapshot 使用独立 DTO 和服务端 capability,前端 devMode 不算授权。 -4. 五命令不再统一滥用全项目 Snapshot revision:interaction 回答使用独立 interactionRevision,cancel 使用精确 run target,其余命令锁内校验 durable state。 -5. `approve` 显式携带 approve/reject decision;requestId 负责命令幂等受理/结果读回,responseId 负责 interaction response 去重,两层身份不可互相替代,未知外部结果不虚假承诺 exactly-once。 +3. Public Snapshot 除当前 Project Supervisor 紧凑摘要外,还必须提供当前父 run 下六个静态专业组的有界只读状态、专业 retry capability 和指向既有 approval interaction 的 repair view、UserInput/ToolApproval/PolicyApproval 和五命令 capabilities;动态 child 仍只计数。Developer Snapshot 使用独立 DTO 和服务端 capability,前端 devMode 不算授权。 +4. 五命令不再统一滥用全项目 Snapshot revision:interaction 回答使用独立 interactionRevision;cancel 使用精确 run target、稳定 cancelOperationId 和 `cancelling` 中间态;resume 使用带 capability target 的 ContinueRun/RetryTerminalRun/ReconcileRun tagged intent;same-run steer 另外保留 V1.13 的 steerId/cursor 合同。冻结 Goal Contract 的 active root Run 不接受普通 execute→steer,稳定返回 `TARGET_BUSY`;replacement 只属于显式 Goal management operation。 +5. `submit_intent` 显式携带稳定 `intentKind`,与表示执行方式的 `runProfile` 正交;capability 按 `(intentKind, runProfile, entryBindingKind, inputPolicy, attachmentMediaKind)` 冻结完整组合,Conversation message 在 V1 必须非空,immutable attachment binding、entry binding 和 input envelope 进入 requestFingerprint,source 只做受信任归因/权限/审计,业务路由由冻结 policy matrix 决定。`approve` 显式携带 `approve | reject | requestChanges` decision;requestChanges 的 feedback 有界、脱敏,绑定单一不可变产物和唯一 reworkOperationId。requestId 负责命令幂等受理/结果读回,steerId 负责 same-run 追加指令,responseId 负责 interaction response 去重,三层身份不可互相替代,未知外部结果不虚假承诺 exactly-once。 6. request ledger 明确“身份/权限 → 指纹 → 先查 ledger → 再校验状态 → 写 prepared → 执行 → 权威结果”的顺序,并持久化成功和业务拒绝;外部结果未知统一 reconciliation。 7. P1 改为内部持久协议底座,P2 先提供唯一 read model,P3 才注册完整可用的五命令;P3 不迁移 Consumer,P5 不再重复设计后端 Loop。 8. Runner 自驱补齐跨平台目录权限、丢 wake 恢复、启动有界轮转、interaction resolving 和 command executing 恢复矩阵。 -9. P6 只删除正式 transport、Consumer 和公开 DTO 的旧协议引用,内部 start/steer/resume/recovery 能力和回归测试保留。 +9. P6 只删除正式 transport、Consumer 和公开 DTO 的旧协议引用,内部 start/steer/resume/retry/recovery 能力和回归测试保留。 +10. direct reply 与 Runtime final-reply 是两条独立 durable response 分支,Start/terminal Runtime-owned status message 与安全 Runtime event message 另有独立 ledger;现有 response sidecar 只通过 finalization adapter 接入,不能把 Runtime `ready` 冒充 direct message;SnapshotError/CommandError、Public/Developer Snapshot 和 Runtime/response stream 均不得混型。 +11. 四次复核进一步补齐了现有项目所需的专业组状态与失败重试、用户确认、Needs input、slash 内置命令、上传附件、command capabilities、Runtime 错误分类、取消中间态、steer identity 和父 run policy snapshot;这些规则已有字段/状态机,但仍需 P0/P1 fixture 与 Unix/Windows 实测证明。 +12. 第五次冻结前审计移除了 slash 对 `continueProject + runProfile` 的伪装,改为严格 `BuiltinCommand` tagged payload,并把 parser version 放进请求、指纹和 prepared ledger;现役 `/preview`、`/agent-resume`、资源/记忆/画板导入和只读命令按当前代码逐项冻结路由,其中 `/agent-kill`/`/agent-retry`/`/agent-resume` 明确保持 legacy Agent run control 语义,不冒充正式五命令;CLI `/resume`、`/goal`、`/compact`、`/mcp`、`/quit`/`/exit` 也按其现役 management/observation 语义显式纳入 catalog。 +13. 新增 `preparing/publicStatusPending` 与 `terminalPending` 的 Public 状态映射,禁止 status message commit marker 之前 dequeue,禁止根失败 status message 之前公开 failed;同时冻结 direct reply、steer 的用户消息单次提交顺序和崩溃恢复。 +14. 补齐 durable input/status/event/response record 的统一 envelope、checksum、owner generation、ledgerVersion 和 corrupt 隔离,并将 status/event identity 改为 RFC 8785 canonical JSON 派生,消除直接字符串拼接的边界碰撞。 +15. 冻结 Supervisor 顶层 plan progress 与 progress view 的等式、task/plan journal 来源及同 run 证据边界;retry successor 改为 predecessor 全生命周期永久唯一,后续 retry 必须针对最新 successor;Supervisor approval target 的 `parentRunId` 允许且仅允许为 None。 +16. Session rotation 改为独立 `ProjectSupervisorSessionRotationRecord` 多记录恢复合同;active-session marker 提交前旧 session 保持 active,其中 operation/index 的 operationId 与 fence marker 一致、active-session marker 尚未提交且无未知外部结果时,incomplete handoff 可按 `FenceCommitted-after-fence Rejected` 合同隔离该 operation 的 pending records、同 journal 清除且只清除该 operation 的 fence/marker、按规则递增 epoch并保留历史 `committedRotationOperationId`,安全回退旧 active session;只有 operation/index marker 冲突、结果未知或 active-session marker 提交后校验失败时进入 reconciliation,不按最新文件猜成功。 +17. Local Management response 使用唯一 `ResponseMeta` 承载 requestId/fingerprint/replayed,ledger 前缺失 requestId 可用 `None` 表达;local reply/error message 增加字符、字节和控制字符边界;local operation 补齐 `outcomeUnknown -> needsReconciliation` 与 `NEEDS_RECONCILIATION` 读回。 +18. locator handle 增加 `NotAcquired/Acquired/Released/ReconciliationHeld` 状态;local-only terminal 与 project-linked terminal 分别定义释放条件,未知结果不得换 handle 或 requestId 重做。 +19. Session rotation 先持久化含 predecessor/successor/expected revision/epoch 的唯一 `ProjectSupervisorSessionRotationRecord(Prepared)`,再以同一 journal 原子提交 `FenceCommitted` operation marker 与 active-index fence marker;之后才固化不可变 `ProjectSupervisorRunHandoffTargetSetRecord + ProjectSupervisorRunHandoffManifest`。目标 payload/chunk 有 envelope、checksum、commit marker、固定 digest 和数量上限;fence 先于集合快照生效,任何新 Run/child/delegation 都拒绝或延后,不会落到 predecessor;manifest ref 在 Prepared/FenceCommitted 时为空,只在 manifest 可回读后补齐,manifest/index/phase 与 Public `sessionContext` 共享线性化边界。 +20. Session rotation 的 `Prepared` 单独存在时继续发布旧 active `Ready`;只有 `FenceCommitted` 及其后 pending phase 才映射为 `HandoffInProgress` 并清空写 capability。最终 active-session journal 必须同步写 `committedRotationOperationId=current operationId`,并以 operation/session/manifest/revision/epoch/active-session marker 全量相等关系作为 successor `Ready` 门禁。Rejected 拆为 Prepared-before-fence(只写 tombstone、不动 active index/epoch、保留历史 committed id)和 FenceCommitted-after-fence(校验 marker、隔离 pending、同 journal 清当前 fence并递增 epoch、保留历史 committed id);marker/commit torn state 进入 reconciliation、fail-closed。 +21. Local Management record 显式保存 `expectedParserVersion + route + targetRef + authorizationPrincipalRef`,`originatingCapabilityId` 仅作审计;恢复只按历史 operation identity 校验,当前 capability 可在 rotation 后重新授权,不得替代历史解析合同。 +22. Session rotation 还必须在 handoff barrier 固化 continuation set,覆盖 interaction、未终结 command/input envelope、DirectReply/RuntimeFinalReply、Runtime status 和 Public event;旧 `sessionId` 作为 provenance 保持不变,successor 只能凭 operation/manifest/record revision proof 继续原 identity,缺失或漂移统一 reconciliation;`HandoffInProgress`/stale cancel 的 phase gate 优先级已冻结。 + +--- + +## 10. 持续边界复核及冻结前补充审计:已修正的合同与编码前证据门禁 + +本轮复核只检查本方案直接承诺的公开交互协议、持久恢复、Runner owner、Consumer 迁移和旧公开面删除,不扩展到 Agent main loop、Provider 选型、提示词或无 GUI 常驻服务。以下不是未来优化,而是进入 P1/P2/P3 编码前必须闭合的合同: + +| 冻结项 | 当前不足 | 冻结证据 | +|---|---|---| +| intent policy matrix | 已冻结四种入口在五类状态下的唯一 disposition,并将 directReply 置于 interaction 门禁之后;模板/导入入口必须有 typed binding;冻结 Goal Contract 是优先于普通 active/waiting 状态的 execute barrier | P0 fixture 逐格断言唯一 disposition/error;冻结合同下 direct reply 正常、execute 稳定 `TARGET_BUSY` 且 replacement 调用计数为 0;并证明并发请求不创建第二 Supervisor run | +| slash built-in 路由 | 现有项目要求 `/preview`、`/agent-resume`、资源/记忆/画板导入和只读命令继续走现役语义;已改为严格 `BuiltinCommand(commandLine, expectedParserVersion)`,不再伪装成 Runtime intent/profile,并区分 legacy Agent run control 与正式五命令,且已按 App/帮助清单冻结完整 catalog | 当前全部 slash 命令逐项 golden fixture;GUI/CLI 同输入同 parser version、route/interaction/管理动作、副作用计数,带附件/entry binding/未知字段或嵌套 interaction 失败关闭,parser 升级与 prepared 恢复不漂移,`/preview` 零自主任务 | +| local management 参数恢复 | path-free Goal 文本、`/agent-resume` detail 等参数原先只有 fingerprint,无法在 owner 重启后恢复同一操作 | `privateArgumentRef` 有界私有 payload 与 fingerprint/checksum 一致性 fixture;host path 原文、secret 和内部 fingerprint 不进入 local/Public delivery;缺失或篡改统一隔离为 `CORRUPT_RECORD` | +| local management 错误响应 | 已改为 `Succeeded/Failed + ResponseMeta`,统一承载 requestId/fingerprint/replayed;缺失 requestId 可用 `None` 表示,并补齐 capability、scope、locator、幂等复用、执行中和 unknown-result 错误合同 | 每个错误 code、retryable、replayed、缺失 requestId 和 readback 行为 golden fixture;GUI/CLI 不解析中文 message;local response 不携带 `observedSnapshotRevision`/`interactionRequired`,projectId 只在成功解析结果中出现 | +| local response 输出边界 | `LocalTransportReply.text` 与 local error message 原先没有独立字符/字节/控制字符合同,路径或异常文本可能超出本地 transport/UI 边界 | reply `4,000` 字符/`16 KiB`、error `512` 字符/`2 KiB` 边界值、控制字符、长路径展示和错误摘要 fixture;不得截断后当作完整结果提交 | +| local durable command identity | 幂等合同要求 parserVersion/route/targetRef 固化,但不能把轮换后的 capabilityId 错当历史 operation identity | prepared/owner 重启/capability 轮换/targetRef 漂移 fixture;record 显式保存 parserVersion、route、targetRef、authorizationPrincipalRef,originatingCapabilityId 仅审计,等价新 capability 可恢复且权限收紧会失败 | +| local capability scope | `Project` scope 的 optional session 字段可能把 Goal mutation、resume 的 active-session 约束放宽,Global 与 project target 也可能混用 | Goal mutation 必须 session+sessionRevision+Goal target;Goal read 可 project-only;`/resume` 必须 session/run/recovery revision;`/project`、`/config` 不带 project/session target 的 scope fixture | +| locator handle 生命周期 | 已补 `NotAcquired/Acquired/Released/ReconciliationHeld`;local-only terminal 在无 project operation 时可释放,project-linked 必须等待双方 terminal,unknown 保留 reconciliation handle | `prepared→handle acquired→crash`、`executing→handle expired`、`outcome-unknown→retry/readback`、无 project operation 的 resolver rejection、project resolve 成功但 operation 失败;禁止换 handle/requestId 重做 | +| 数量与体积上限 | 已冻结 ID、command JSON、message、feedback、answers、补读和 response stream 的字符/字节/数量上限,且规定 Rust 单一来源 | 共享常量生成 TS schema/fixture;边界值、超限、组合爆炸在 ledger 前返回 `INVALID_REQUEST` | +| 现有项目专业 Agent 状态 | 仅有 `collaboratorCount` 无法满足当前工作台六个专业组状态栏、父 run 精确筛选、专业 Agent confirmation 和“当前项目重试”要求;已补 `collaborators`、opaque `collaborationId`、runtime/manifestFallback、retry/repair capability | `parentRunId` 过滤、六组固定顺序、旧父 run 隔离、manifest fallback 不伪造状态、专业 Agent retry/repair 竞争与唯一父子绑定 fixture | +| Runtime-owned 进度卡 | 现有工作台需要轮次、任务/计划、活跃 Agent、试玩/静态/代码/截图校验和返工摘要;若继续由客户端拼接会违反 Consumer 不承接业务真相 | 当前 run 结构化 progress fixture、四类 check receipt 映射、缺证据 unknown、run 切换不合并、路径/Provider/fingerprint 零泄漏 | +| preview/resource sibling contract | `preview.start`/`preview.validate` 与上传/登记不是 Runtime 生命周期命令,不能因统一 Shell 而由 Consumer 依据 nextStep 重造;本方案明确保留现有授权和 PreviewRegistry | preview authorization revision、一次启动/刷新、项目切换/Run 终止/写锁竞争和 P6 静态删除范围 fixture | +| Public command capabilities | 仅有 `nextStep` 会迫使 Consumer 自行推断可执行按钮;已补 `commandCapabilities`,提供按组合冻结的 intent/profile/binding/input target 与精确 session revision/run target,stale capability 失败关闭;冻结 Goal Contract 时 Conversation option 可保留 direct reply 入口,但不产生 replacement-steer capability | Snapshot revision 与 capability 同点、过期 cancel/resume/retry、冻结合同 capability + direct/execute 分流、无 capability 不显示可执行入口、读失败保留 stale display 但仅允许上一份精确 cancel capability 作为止损入口 | +| 用户确认与 Needs input | 原方案把 ToolApproval 全部放到 Developer,且未冻结 question/answer schema;已补 User audience ToolApproval、Collaborator context、1–3 题/2–3 option/自由输入/全量 answers 合同 | 普通工作台用户确认、Developer ToolApproval 隔离、问题重复/缺题/option label 冒充 id、action/parent/run 漂移和 interaction 恢复 fixture | +| 现有项目输入附件 | `submit_intent` 原来只有文本,无法承接工作台上传文件/既有资源引用;已补有界 immutable attachment binding、input envelope 和结构化 resource context,Runtime 不接收字节/path/token;V1 显式要求 message 非空 | 上传后 resource revision/digest 绑定、替换/删除/跨项目拒绝、附件顺序指纹、attachment-only 前置拒绝和恢复后附件不丢失 fixture | +| submit capability 组合完整性 | 独立 intent/profile/binding 数组会允许 Consumer 组合出未注册入口;已改为有限 option,每个 option 固定一个 runProfile、entryBindingKind、inputPolicy 和允许媒体类型 | option 上限、合法/非法笛卡尔组合、TextOnly 携带附件、Other 媒体、入口绑定类型错配均在 ledger 前失败 | +| input envelope 交付 | 现有 Runtime task 必须非空,附件不能校验后丢弃,也不能由前端伪造自然语言;已定义私有 input envelope 与结构化 resource context,direct/start/steer 共用同一身份 | prepared/强杀/恢复、attachment revision/digest 漂移、Provider 前失败、steer 与 direct reply 均能读回同一 envelope 且不泄漏路径/token | +| Runtime 失败与取消中间态 | 原来的公共错误 enum 混合 command error 与 Runtime failure,且没有 `cancelling/paused/finalizing` 的可展示状态;已拆分 SnapshotErrorCode/CommandErrorCode 并补状态 | 配置/鉴权/限流/Provider/验证/sandbox/预算错误脱敏映射;CancelAccepted→cancelling→cancelled/needsReconciliation,重复 cancel 共用 cancelOperationId | +| steer 与同 run 语义 | `submit_intent` 选择 steer 但 ack 未返回现有 steer 身份;已规定 prepared 阶段保存 `steerId`,复用 V1.13 cursor/容量/中断/finalization 合同;现有冻结 Goal Contract replacement 分支不得被普通 submit 调用 | same-run steer 不新建 task/run、steerId 幂等/冲突、冻结合同 execute=`TARGET_BUSY`/replacement 调用计数为 0、Goal management replacement 的 operation identity/lineage 与 cancel-old→start-new crash 恢复、confirmation/process action 不被暗中中断、steer/finalization 双向竞态 | +| approval policy snapshot | 只重新检查 live policy 会与 V1.38 的父 run 持久 policy snapshot 冲突;已补 `policy_snapshot_fingerprint`、精确 Project target set 和 hard-deny 例外 | 普通 policy 文案漂移不重解释;hard-deny 收紧、action fingerprint 漂移和 target set 变化 fail-closed | +| session handoff/lineage | 已定义 target-set payload/chunk、rotation fence、`ProjectSupervisorRunHandoff` 的 manifest/operation/digest identity、interaction/delivery continuation set 和 `ProjectSupervisorSessionRotationRecord`;先持久化 `Prepared` operation,再由同一 journal 提交 `FenceCommitted` operation/index marker;Prepared 的 manifest ref 允许为空且仍投影旧 active `Ready`,FenceCommitted 后才进入 `HandoffInProgress`/无写 capability;最终 journal 同步提交 `committedRotationOperationId=current operationId`,Ready/recovery 校验 operation/session/manifest/revision/epoch/active-session marker;Rejected 按 fence 是否提交拆为两个不混写 active index 的分支 | operation 写入前崩溃不留 fence;Prepared 后/fence 前崩溃继续旧 Ready;operation/index fence marker 半提交必须 reconciliation;Prepared-before-fence Rejected 断言不清 fence、不增 epoch、保留历史 committed id;FenceCommitted-after-fence Rejected 断言 marker 匹配、隔离 pending、只清当前 fence并精确增 epoch、保留历史 committed id;最终 commit 断言 committed operationId、predecessor/successor session、source/successor revision、manifest operation/session/epoch、active index `expected+1` 和 active-session marker 全量匹配;FenceCommitted 后 target/continuation set 缺失/损坏/超限、manifest ref 补写、barrier 后 revision 漂移、rotation fence 与新 Run/interaction/delivery 并发、旧 record successor 重新授权、各阶段崩溃、部分 handoff、Public sessionContext/capability 线性化、active index 缺失、重复/双窗口 rotation、旧 session 永不复活、无 proof 的 `TARGET_STALE` fixture | +| submit payload 与 parser version | 原 slash 方案要求无意义的 `continueProject + runProfile`,且 capability version 未进入请求;已改为 Conversation/BuiltinCommand strict tagged union,BuiltinCommand 显式提交 expected parser version,prepared 后固化 route/version | variant/未知字段/版本漂移、同文本 parser 升级后恢复、同 requestId 异 payload、GUI/CLI 复放只复用已固化结果 fixture | +| interaction kernel 隐式 resume | 当前 `agent/interaction.rs` 仍暴露 `runtime_resume` 工具,CLI active runtime 会把模型返回的 `AgentInteractionAction::Resume` 直接送入 recovery observe;这会让自然语言/模型分类绕过显式 ResumeCommand,且与“继续只走普通 message/steer”冲突 | `runtime_resume` tool 输出、自然语言“继续”、active/non-active Runtime、显式 `/resume` 的 golden fixture;迁移后模型 action 不得直接获得 Resume capability,所有 recovery/steer 都要带显式 target、ledger 和 revision | +| CLI slash catalog 漏项 | `swarm_cli/input.rs` 现役还有 `/resume`、`/goal ...`、`/compact`、`/mcp`、`/quit`/`/exit`,与 GUI 的 `/agent-resume` 和项目摘要命令语义不同;只写 GUI catalog 会造成 CLI fallback/语义漂移 | CLI-only 命令逐项 parser fixture;`/resume` recovery observe、Goal status/CAS mutation 及其后续 Runtime turn、`goal pause/clear` 不取消 Runtime、compact 无 provider、mcp direct reply、quit 无 ledger,GUI/CLI 同 parser 但 route 差异仅来自显式 catalog | +| slash path/classification boundary | 已把项目级 `BuiltinCommand` 与 `LocalManagementRoute` 分开;补齐 local capability、request/response、capabilityId/targetRef 和单一落点矩阵;absolute host locator 只能走 trusted resolver/local envelope,project-relative `/asset-register`、`/import-canvas-asset`、`/read` 先做相对路径/符号链接逃逸校验;未知 slash 携带 host locator 时不进入 direct reply;`/goal` exact 与 `/goal <目标>` 按 arity 分开 | known/unknown slash、绝对路径、`file://`、`..`、symlink escape、project-relative path、无 project/session、targetRef 漂移、local response 不进入 Public conversation、GUI/CLI 同 parser 和原文不进入 Public fingerprint fixture | +| legacy slash lifecycle 误映射 | 当前 `/agent-kill` 只是写 legacy trace,`/agent-retry`/`/agent-resume` 会从最近 goal 启动新 generation;若直接映射 cancel/retry/continue 会丢失旧 target、detail 和 lineage 语义 | legacy trace target revision/digest、kill 不产生 Runtime cancelled、retry/resume 新 generation 的 operation/lineage、旧 trace 替换和跨 transport replay fixture | +| public hard-gate status | `preparing/public-status-pending` 与 `terminal-pending` 原先只是内部文字;已新增稳定 Public status/stage/waitingOn/nextStep 枚举和 commit marker 映射 | user message/status commit/dequeue 崩溃点、status 写失败/unknown、根失败 status 先于 failed/task/event 终态、Consumer 不把 preparing/terminalPending 当 queued/failed fixture | +| conversation user-message 顺序 | Start 已有 status 门,但 DirectReply/Steer 的 user message 可能重复或越过 ledger;已冻结 direct reply 先 user message→assistant→ledger close,steer 复用 V1.13 conversation-persisted 和同一 message identity | direct/slash/steer 崩溃恢复、同正文/同身份回放、重复 user message、未知 commit marker、旧 action/confirmation 不被 steer 越过 fixture | +| 本地 path / project_location 泄漏 | 当前 CLI `ProjectLocation` 会把 `root.display()` 持久化到 conversation;`/project` 与 `/import-canvas-export` 也携带绝对路径,若沿用 DirectReply 会违反 Public 零路径合同 | LocalTransportReply/LocalManagement 分支 fixture;路径只在 trusted resolver/local UI 返回,Public conversation/Snapshot/event/error/prompt/fingerprint 无原文,projectId 未解析时不伪造 ledger target | +| Builtin management operation 幂等 | slash 管理动作原先只有 UI pending command,重试/恢复可能重复 checkpoint、导入、记忆、Agent control 或 External Editor 副作用;已补项目级 management operation record,global CLI action 明确不进 ledger | 每类 management action prepared/执行/结果未知/恢复、同 requestId 异 fingerprint、旧 target revision/digest、domain CAS 与 operation result 不一致 fixture | +| durable delivery envelope | 新增 input/status/event/response record 但需要统一恢复字段;已补 schemaVersion/recordId/ledgerVersion/checksum/owner boot+generation/timestamps 和 corrupt 隔离;interaction 与尚未解析项目身份的 local management 也分别绑定 project/local envelope,response delivery 具备可回读 commit marker | 每类 record 缺字段、checksum/身份/版本冲突、旧新文件并存、owner fencing、跨平台原子替换和结果未知 fixture;local→project operation link 丢失或重复副作用 fixture | +| Public conversation message/read envelope | 私有 delivery record 已有 message key/commit marker,但现有 `LocalConversationResult` 只有 role/content/messageId 并暴露 `path`,Consumer 无法按 deliveryKind 做跨 transport 去重、排序和 cursor 补读;已冻结五类 path-free Public message、eventId/messageId 等值规则、conversation-global sequence/cursor、committed cursor chain、session-lifetime logical retention、read page/historyState、read error envelope、directReply `4,000` scalar/`16 KiB` 双上限和私有字段边界;全部 Public conversation DTO 由 Rust 单一来源生成严格 TS schema/decoder | user/directReply/runtimeFinalReply/runtimeStatus/publicEvent 五类 golden mapping;directReply 分别恰好命中 scalar/UTF-8 边界、任一维度超限、多字节组合、同 requestId 拒绝读回和单消息不阻塞分页;request、success page 与 error envelope 分别覆盖缺失、未知、重复、类型错和版本错误的跨 transport negative fixture,error DTO 另覆盖未知 code;另覆盖同键重复/异正文冲突、乱序返回按 sequence 稳定展示、reservation 崩溃形成永久空洞但不触发重读、cursor chain 不漏 committed message、跨页新写入、空页、256 条/1 MiB 边界、`afterCursor=None` 从 origin 全量补读、物理压缩后旧 cursor 仍有效、格式/跨 scope cursor 为 `CURSOR_INVALID`、message/cursor/chain/origin/tail 截断为无 partial page 的 `CONVERSATION_HISTORY_INCOMPLETE` 且不得返回 `complete`、conversation `CURSOR_EXPIRED` 不可达、response-stream sequence 混淆拒绝、LocalConversationResult.path/finalization/commitMarker/Provider 字段零泄漏 | +| progress invariant | 顶层 step 计数与 progress 内 task/plan 计数可能重复且来源不明;已冻结顶层等于同 run planProgress、task/plan journal 分源、receipt 不一致 unknown/省略 | plan 缺失、task/plan 不一致、run 切换、旧 evidence 混入、check 重复和每种 check 多条 fixture | +| approval parent scope | Supervisor action 没有 parent run,但 ApprovalTarget 曾要求必填;已改为 `Option` 并冻结 Supervisor=None、专业 Agent=当前父 run | Supervisor/专业 Agent target-set 指纹、父 run 漂移、None/Some 错配和 approve/retry 权限 fixture | +| retry successor lineage | 已定义带 `agentId/taskId/sessionId/parentRunId/delegationId` 的 `RunLineageRecord`;`acceptedRunId` 仅为回显,V1 不接受 `nextRunId`,且 predecessor 全生命周期最多一个 successor;旧 retry record 只能经唯一性校验 adapter 导入 | Supervisor/专业 Agent 终态 retry、同 predecessor 并发 retry、崩溃后重放、父 Supervisor 已终态、delegation 漂移和 needs-reconciliation 拒绝测试 | +| response delivery 分支 | 已发现现有 `response-streams` 是 Runtime final-reply,不是 direct reply;已拆 `DirectReplyDelivery` 与 `RuntimeFinalReply`,Runtime 分支复用现有 finalization journal 的 messageId,legacy adapter 穷举 `streaming/ready/committed/discarded/failed` 并持久化 source identity/digest,不能把 `ready/committed` 伪造成 direct message,`discarded/failed` 无充分证据时进入 reconciliation;Start/terminal status 另走 status-message ledger,只有私有 commit marker/read-back 与 conversation-global sequence/cursor 同时闭合后才进入 Public read | user-message→status→queued crash points、两条 response 分支与 status-message 分支分别做 crash-point、Provider 调用计数、finalization/message/status commit marker 唯一性、conversation sequence/cursor 唯一性和跨 transport golden replay | +| stale display 与止损取消 | Public read 暂时失败时既不能把缓存当事实,也不能让用户失去取消入口;已规定只保留上一份精确 cancel capability,但 rotation/draining/reconciliation phase gate 优先,服务端重新授权/锁内校验,其他旧 capability 禁用 | 读失败/恢复、读失败期间开始 rotation、目标已变化、owner 不可用、重复 cancel 和 cancel operation 读回 fixture;不得从 stale Snapshot 推进 submit/resume/retry,不能以 stale cancel 绕过 HandoffInProgress | +| Runtime public event message | 现有项目要求安全 Runtime 事件进入聊天,但不能把 raw event 载荷并入 Snapshot;已定义 Rust 生成 `eventId + publicText` 的独立 conversation delivery,Public `messageId` 必须与 eventId 同值、deliveryKind 固定为 publicEvent、role 固定为 system | Supervisor/主 Agent/直接 child 父 run 过滤、eventId/messageId 不等、eventId/publicText 缺失或重复、同 event 重放保持 sequence/cursor、legacy/raw payload 泄漏和恢复去重 fixture | +| targetArtifact binding | 已扩展为 namespace/id/revision/algorithm/digest;仅允许可重读、不可变且可算 `sha256` 的现有 lineage,其他目标返回 `ARTIFACT_BINDING_UNAVAILABLE`,不造平行身份 | artifact/resource/manifest 适配表及 digest 漂移、删除、替换和旧版本审批 fixture | +| owner 与 headless 能力 | 已冻结 V1 不提供无 GUI CLI headless control lease;CLI 写入只能复用有效 GUI-owner,否则 prepared 前 `OWNER_UNAVAILABLE` | GUI-owner 断线、CLI 读写权限、owner fencing 和“无 GUI 不写入” fixture | +| 文件系统原子性 | 已补齐能力门禁:Unix 要求文件与目录同步;Windows 要求 `FlushFileBuffers` + 原子替换;任一平台无法证明持久性时禁止从 prepared 进入 executing,并隔离 torn record | Unix/Windows crash-point、损坏隔离、旧 owner fencing、恢复后副作用计数与证据保留实测 | + +本次已把上述问题从“泛化待定”修正为可编码的字段、状态机和失败闭合规则,但证据 fixture/跨平台实测尚未完成;在证据完成前,文档状态仍为“评审中”,不得把 P1 基础 DTO 或空 handler 当作协议已完成。停止继续扩展本轮审查的条件是:上述每项都有权威字段/状态机、可达失败路径和验收 fixture;cancel/resume/retry/requestChanges/direct-reply/slash-route/attachment/input-envelope/user-approval/collaborator-status/progress-view/stale-cancel 的崩溃矩阵无未分类窗口;冻结 Goal Contract 的普通 execute→steer 必须稳定 `TARGET_BUSY` 且 replacement 调用计数为零,显式 Goal management replacement 的 operation identity/lineage 与旧树取消后崩溃恢复必须闭合;BuiltinCommand parser version、命令 catalog(含 CLI-only aliases)和 management/runtime route 没有语义漂移;LocalManagement parser/route/target 历史 identity 不被新 capability 替代;SessionRotation Prepared operation、FenceCommitted operation/index marker、manifest ref、最终 committed operationId/session/manifest/revision/epoch/active-session marker、两类 Rejected、phase/continuation 与 Public sessionContext/capability 没有线性化漂移;旧 interaction/delivery 只能凭 continuation proof 继续;legacy Agent run control 和 interaction kernel resume 不伪造五命令状态或 successor;模型 action 不绕过显式 capability;preparing/terminalPending 不越过 status commit marker,顶层 plan progress 不混入旧 run;所有 durable record(含 local management、session rotation、target/continuation set payload/chunk 和 rotation fence)都能按 envelope/checksum/owner generation/commit marker 唯一恢复,且不存在有 active-index fence 而无唯一 rotation operation recovery fact 的崩溃窗口;path-bearing/local reply 不泄漏原文且遵守 local output 上限;predecessor 不产生第二 successor;Public Snapshot 不再暴露命令私有字段;capability 不存在可错误组合的独立白名单;Runtime final reply 不产生第二 message identity,Start status 不越过 commit marker 执行,public event 不重复/不泄漏;Public conversation 五类 mapping、去重键、committed cursor chain、合法 sequence 永久空洞、session-lifetime logical retention、afterCursor origin 全量补读、截断失败关闭、legacy 隔离和零 path/私有字段泄漏均有跨 transport 证据;且没有 P0/P1 级协议矛盾。其它 Runtime 内核技术债、性能优化和无 GUI 常驻能力记录为后续工程,不阻塞本次 Interaction Shell 重构。 -- 2.52.0 From ec565b8d5d5977252fe7b1ee0a007d097a4676da Mon Sep 17 00:00:00 2001 From: suzmii Date: Mon, 17 Aug 2026 21:55:31 +0800 Subject: [PATCH 4/4] =?UTF-8?q?=E9=87=8D=E6=9E=84=20Game=20Agent=20Runtime?= =?UTF-8?q?=20=E4=BA=A4=E4=BA=92=E8=BE=B9=E7=95=8C=E8=AE=BE=E8=AE=A1?= =?UTF-8?q?=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将原交互边界长文档拆分为总览、Contract、迁移矩阵和证据附录 冻结 Snapshot、事件、Capability、Interaction、Conversation 和错误合同 明确 P0–P6 阶段边界、Writer Cutover 与分阶段证据门禁 更新文档索引和四份设计文档的权威阅读顺序 --- docs/README.md | 9 +- ...AI游戏创作Agent Runtime交互合同V1-2026-08-17.md | 1393 +++++++++ ...作Agent Runtime交互边界重构实施计划-2026-08-12.md | 2632 ++--------------- ...作Agent Runtime交互边界证据与决策附录-2026-08-17.md | 432 +++ ...戏创作Agent Runtime交互边界迁移矩阵-2026-08-17.md | 161 + 5 files changed, 2303 insertions(+), 2324 deletions(-) create mode 100644 docs/technical/【技术协议】AI游戏创作Agent Runtime交互合同V1-2026-08-17.md create mode 100644 docs/technical/【设计依据】AI游戏创作Agent Runtime交互边界证据与决策附录-2026-08-17.md create mode 100644 docs/technical/【迁移方案】AI游戏创作Agent Runtime交互边界迁移矩阵-2026-08-17.md diff --git a/docs/README.md b/docs/README.md index 731b66111..11d3dc53d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -36,8 +36,13 @@ ### AI 游戏创作 Runtime -- [AI 游戏创作 Agent Runtime 交互边界重构实施计划](./technical/【技术方案】AI游戏创作Agent%20Runtime交互边界重构实施计划-2026-08-12.md) -- [AI 游戏创作 Agent Runtime V1.1](./technical/【技术方案】AI游戏创作Agent%20Runtime%20V1.1-2026-07-12.md) +1. [AI 游戏创作 Agent Runtime 交互边界重构总览与实施计划](./technical/【技术方案】AI游戏创作Agent%20Runtime交互边界重构实施计划-2026-08-12.md) +2. [AI 游戏创作 Agent Runtime 交互合同 V1(唯一规范性协议)](./technical/【技术协议】AI游戏创作Agent%20Runtime交互合同V1-2026-08-17.md) +3. [AI 游戏创作 Agent Runtime 交互边界迁移矩阵](./technical/【迁移方案】AI游戏创作Agent%20Runtime交互边界迁移矩阵-2026-08-17.md) +4. [AI 游戏创作 Agent Runtime 交互边界证据与决策附录](./technical/【设计依据】AI游戏创作Agent%20Runtime交互边界证据与决策附录-2026-08-17.md) +5. [AI 游戏创作 Agent Runtime V1.1](./technical/【技术方案】AI游戏创作Agent%20Runtime%20V1.1-2026-07-12.md) + +上述四份交互边界文档按“总览 → Contract → 迁移矩阵 → 证据附录”阅读;字段、状态机和错误语义只以 Contract 中的 `IC-*` 为准。 ### 后端与公开数据 diff --git a/docs/technical/【技术协议】AI游戏创作Agent Runtime交互合同V1-2026-08-17.md b/docs/technical/【技术协议】AI游戏创作Agent Runtime交互合同V1-2026-08-17.md new file mode 100644 index 000000000..383d38739 --- /dev/null +++ b/docs/technical/【技术协议】AI游戏创作Agent Runtime交互合同V1-2026-08-17.md @@ -0,0 +1,1393 @@ +# AI 游戏创作 Agent Runtime 交互合同 V1 + +> 文档角色:唯一规范性协议 +> schemaVersion:`game-creator-agent-interaction.v1` +> 状态:评审中;规则已采用默认失败关闭,完成冻结评审前不得进入生产实现 +> 配套总览:[`【技术方案】AI游戏创作Agent Runtime交互边界重构实施计划-2026-08-12.md`](./【技术方案】AI游戏创作Agent%20Runtime交互边界重构实施计划-2026-08-12.md) +> 实现映射:[`【迁移方案】AI游戏创作Agent Runtime交互边界迁移矩阵-2026-08-17.md`](./【迁移方案】AI游戏创作Agent%20Runtime交互边界迁移矩阵-2026-08-17.md) +> 设计证据:[`【设计依据】AI游戏创作Agent Runtime交互边界证据与决策附录-2026-08-17.md`](./【设计依据】AI游戏创作Agent%20Runtime交互边界证据与决策附录-2026-08-17.md) + +## 0. 规范解释 + +本文中的 `MUST`、`MUST NOT`、`SHOULD`、`MAY` 为规范词: + +- `MUST`:实现和测试必须满足; +- `MUST NOT`:实现禁止出现; +- `SHOULD`:除非有重新评审通过的明确理由,否则必须满足; +- `MAY`:允许但不要求。 + +所有正式实现、schema、fixture、迁移矩阵和评审 finding 必须引用稳定 `IC-*` 编号。总览、迁移矩阵和证据附录不得重新定义本文规则。 + +--- + +## 1. 参与者与权威边界 + +### IC-ARC-001:唯一公开控制边界 + +GUI、普通 CLI 和面向公开协议的测试 MUST 作为平等 Consumer,只通过本文 read model 和五个写命令操作 Project Supervisor Runtime。transport 可以是 Tauri、Runner RPC 或进程内测试,但 MUST 复用同一语义和 handler。 + +### IC-ARC-002:Consumer 职责 + +Consumer MUST 只执行: + +```text +render(snapshot) +dispatch(command) +read(conversation) +``` + +Consumer MUST NOT: + +- 根据 Runtime phase、自然语言文案或私有字段选择 start/steer/retry/resume; +- 直接推进 pending、timer、lane、ready task 或 finalization; +- 从 event 或 conversation 推导 Runtime 成功; +- 把 Runtime output 再 append 成第二条正式聊天消息; +- 自报 Developer、Runner internal 或其它受信任 capability。 + +### IC-ARC-003:Supervisor Shell 职责 + +Supervisor Shell MUST: + +- 完成 schema、身份、权限、目标、capability 和幂等校验; +- 将 `submit_intent` 统一路由为 direct reply、start、steer、reject 或 interaction required; +- 通过 Adapter 绑定现有 Runtime identity; +- 生成 Public/Developer projection; +- 提供可重试的 request result read-back。 + +Shell MUST NOT 把 request/projection record 当成 Runtime task、provider、steer、finalization 或 conversation 的第二事实源。 + +### IC-ARC-004:Runtime 事实源 + +现有 Runtime durable state、task、pending/action、provider retry、steer、finalization、conversation 和 event records MUST 继续决定执行事实。新 record MAY 保存协调状态、source binding、projection metadata 和 request read-back,但 MUST NOT 覆盖、重写或重新解释 Runtime 事实。 + +### IC-ARC-005:Public 与 Developer 分离 + +Public DTO 和 Developer DTO MUST 为不同类型和调用面。Developer capability MAY 扩大 read,但 MUST NOT 允许正式 Project Supervisor 写操作绕过五命令或 owner 门禁。专业/child Agent 的直接调试控制只能留在明确的受信任管理面,不得伪装成 Public Supervisor 协议。 + +--- + +## 2. Wire、身份、权限与 owner + +### IC-WIRE-001:版本与序列化 + +所有 Public Snapshot、event envelope、command、command response、command error 和 conversation DTO MUST 携带或协商: + +```text +schemaVersion = game-creator-agent-interaction.v1 +``` + +Rust 字段 MUST 序列化为 camelCase;业务枚举 MUST 使用 lowerCamelCase;结构化错误码 MUST 使用 SCREAMING_SNAKE_CASE。 + +带数据 union MUST 使用扁平内部 tag: + +```json +{ "kind": "conversation", "sessionId": "..." } +``` + +未知、缺失或重复 `kind`,未知字段、重复字段、错误类型和超限字段 MUST 在任何项目副作用前返回 `INVALID_REQUEST`。V1 MUST NOT 通过忽略未知字段兼容未来版本。本文的 Rust 片段是规范性 DTO:除明确标记 `Option`、`Vec` 或“可省略”的字段外,字段均为必填;Public Snapshot 及其嵌套对象不得添加未在 Contract 定义的字段。所有本文引用但非原始标量的 Public 类型必须由本节或后续 `IC-*` 条款冻结其 wire shape/枚举,不得由 Consumer 自行补充。 + +### IC-WIRE-002:统一边界常量 + +Rust MUST 作为边界常量、schema 和 fingerprint 规范的单一来源,并生成或校验 TypeScript schema 与 golden fixture。至少冻结: + +| 对象 | V1 限制 | +|---|---| +| 通用 Public ID/cursor | `1–128` UTF-8 bytes;无控制字符、路径分隔符和首尾空白 | +| 单 command JSON | 最大 `64 KiB` UTF-8 | +| Conversation message | 非空;最大 `4,000` Unicode scalar 且最大 `16 KiB` UTF-8 | +| requestChanges feedback | 非空;最大 `2,000` Unicode scalar,并通过公开内容安全过滤 | +| 单 Public Snapshot | 最大 `256 KiB` UTF-8;collaborators/interactions 各最多 `128`,latestChecks 最多 `32`;超限不得截断或发布 partial DTO | +| Public 摘要/标题/问题/选项/error message 字段 | 最大 `1,000` Unicode scalar 且最大 `4 KiB` UTF-8;Conversation 与 requestChanges 使用各自更严格/专用上限 | +| 单 Developer Snapshot | 最大 `512 KiB` UTF-8;runs/interactions 各最多 `128`,diagnostics 最多 `256` | +| Developer summary | 最大 `1,000` Unicode scalar 且最大 `4 KiB` UTF-8;diagnostic code 最大 `128` UTF-8 bytes | +| UserInput questions | `1–3` | +| 每题 options | `2–3`;freeform 始终可用 | +| digest | V1 仅 `sha256`;64 位小写十六进制 | + +历史 Runtime response stream 的 `32,000` Unicode scalar 兼容限制 MUST NOT 被本文悄悄改成新的 byte cap;若修改必须提升 schema/version 并单独迁移。 + +### IC-ID-001:项目身份 + +`projectId` MUST 来自现有项目 manifest 且在项目生命周期内不可变。`projectPath`、绝对路径、locator handle、导出路径和本地配置目录 MUST NOT 进入 Public DTO、Public conversation 或 Runtime prompt。 + +Shell 的内部入参 MUST 是受信任宿主已解析的 `TrustedProjectContext { projectId, canonicalRoot }`,而不是 Consumer 提交的 root。Tauri/Runner transport 可以使用其宿主保存的 locator 或本地 root;它 MUST 在任何 ledger、owner 或 Runtime 操作前 canonicalize root、读取 manifest,并复核其 `projectId` 与 command `projectId` 一致。找不到或不一致时分别失败为 `PROJECT_NOT_FOUND` / `PROJECT_ID_MISMATCH`,不得把 Public `projectId` 当作文件系统路径或让 Consumer 补传路径。 + +### IC-ID-002:Session 身份 + +`sessionId` MUST 来自现有按 Agent 持久化的 Session catalog。`sessionCatalogVersion` 是对 **Project Supervisor Agent 完整 catalog** 计算的 opaque digest/stale guard: + +- MUST 是字符串,不得由 Consumer 构造; +- MUST NOT 写回 catalog; +- MUST NOT 成为第二 session revision 或 active-session authority; +- MUST NOT 代表 collaborator/child Agent 的 Session catalog。 + +V1 MUST NOT 新增 live Session rotation、handoff manifest、ActiveSessionIndex 或 session control lease。 + +### IC-ID-003:Run 与历史归属 + +每个 command、interaction 和 delivery MUST 显式携带或通过 private binding 保存 `agentId + sessionId + runId`。历史记录 MUST 按已落盘 identity 回读,MUST NOT 根据当前 active Session、文件位置、时间或 GUI 窗口重新归属。 + +对公开的协作组摘要和 `PublicInteractionContext::Collaborator`,Shell MUST 在首次确认同一 parent Supervisor run 下的一项协作执行时,分配并在 Shell ledger 持久化不透明 `collaborationId`。其 private binding 至少固定 `projectId + parentSupervisorAgentId + parentRunId + collaborationId`,并关联 group、original/current Agent 与 run identity、source 和 predecessor/successor lineage。一个 parent run 中同一 group 可以有 `0..N` 个协作 binding;Public 以 `collaborationId` 区分,不得仅以 group 或动态 child `agentId` 去重。retry/successor MUST 保留原 `collaborationId` 并更新 current identity/lineage;isolated 执行是该 binding 的执行方式,不另造公开身份。`manifestFallback` 只可为尚无 Runtime binding 的静态 group 占位;Runtime binding 出现后 MUST 替换该占位,二者不得并列表示同一协作实体。Shell 只可经 private binding 将 Collaborator interaction 解析到真实目标,Public DTO 不得暴露 dynamic child identity。 + +现有 catalog 的“有 live task 时拒绝 create/fork/archive/set-active”语义 MUST 保持不变。 + +### IC-ID-004:Request 身份 + +每个写命令 MUST 包含: + +```rust +struct CommandMeta { + schema_version: String, + project_id: String, + request_id: String, +} +``` + +`requestId` 标识一次 Public command,不等于 runId、steerId、interactionId、messageId 或 finalizationId。 + +### IC-ID-005:Opaque identity + +所有 Public ID 和 cursor MUST 视为不透明值。Consumer MUST NOT 从 ID 前缀、路径、时间、进程号或字符串结构推导对象类型、顺序或权限。 + +### IC-AUTH-001:调用来源 + +transport MUST 从受信任宿主上下文传递 principal/capability;Consumer payload MUST NOT 自报来源权限。权限拒绝 MUST 在创建项目 command ledger 或执行副作用前完成。 + +### IC-OWNER-001:执行权威 + +现有 `.agent/runtime/execution-owner.lock` 的 OS 排他锁 MUST 是项目执行写入权威。`execution-owner.json` 和 Runner `bootId` 只用于诊断关联,MUST NOT 用于 lease 接管或判断旧 owner 已死亡。 + +启用 External Runner 时,正式五命令和投影修复 MUST 在持有 owner lock 的 Runner 内 Shell handler 执行。GUI/CLI MUST 只是 transport adapter。未启用 Runner 的进程内模式和测试 MUST 使用同一 Shell handler,并在创建 Shell record、修复 projection 或调用 Runtime primitive **之前**取得同一 OS owner-lock 实现;per-Agent task/run/action lock 不能替代 project execution owner。 + +失去 owner lock、Runner draining 或无法证明 owner 状态时 MUST 停止新写入并返回 `OWNER_UNAVAILABLE`、`TRANSIENT_UNAVAILABLE` 或进入 reconciliation;MUST NOT 根据诊断 JSON、mtime 或本地时钟接管。 + +### IC-OWNER-002:锁顺序 + +正式写路径 MUST 按以下顺序取得锁: + +```text +project execution owner +→ supervisor project lock +→ command / interaction / projection 子记录锁 +→ 现有 Runtime 自身 run/action 锁 +``` + +这里的 `supervisor project lock` 只在已持有 execution owner 的进程内串行 Shell handler;它不是第二个跨进程 owner、不得自行 stale reclaim,也不得用 PID、mtime、诊断 JSON 或本地时钟接管。现有 `.agent/project.lock` 的 create-new/PID/超时回收语义不是这个锁,MUST NOT 直接复用为 Shell protocol lock。P1 可以用 owner-guard 内的进程内 mutex 或不带回收语义的专用子锁实现它;子记录锁只保护具体 command、interaction 或 projection record,不能提升为执行权威。 + +MUST NOT 持有文件锁等待 Consumer;MUST NOT 绕过现有 Runtime 锁顺序。 + +--- + +## 3. Durable record 与幂等结果 + +### IC-DUR-001:Record envelope + +新增 project durable record MUST 至少包含: + +```rust +struct DurableEnvelope { + schema_version: String, + record_id: String, + record_revision: u64, + ledger_version: u64, + project_id: String, + checksum: String, + owner_boot_id: String, + created_at: u64, + updated_at: u64, +} +``` + +- `recordRevision` 从 1 开始并随同一 record 状态变化递增; +- `ledgerVersion` 只表示追加顺序,不能代替 record revision; +- `checksum` MUST 覆盖除 checksum 字段外的完整 canonical record; +- envelope 与 body 重复出现的 `projectId` 不一致时 MUST 按损坏隔离; +- `ownerBootId` MUST NOT 替代 OS owner lock。 + +本文的 `canonical` 固定为 **RFC 8785 JSON Canonicalization Scheme** 的 UTF-8 bytes;`checksum`、request fingerprint 与 `snapshotHash` 必须各自对其规定的完整值使用同一实现和 sha256。实现 MUST 用 RFC 8785 test vectors 覆盖 Unicode、数字、object key ordering、嵌套对象与 checksum 字段剔除;MUST NOT 用 `serde_json::to_string`、pretty JSON、结构体声明顺序或各模块自定义排序代替 canonicalization。 + +P1 Shell record MUST 存在专用私有 namespace `.agent/runtime/supervisor-shell/`;其 command、interaction/rework mapping 与 projection journal 各有明确 record kind 和 bounded retention/read-back 规则。它 MUST NOT 混入 `.agent/agent.db` 或现有 Runtime task/finalization journal,因为它们的 authority、scan/retention 和损坏隔离边界不同。 + +### IC-DUR-002:原子持久化 + +P1 实现 MUST 使用当前平台可证明的原子写、写后回读和目录/文件持久化语义。无法证明 prepared record 已 durable 时 MUST NOT 进入真实副作用。torn/corrupt record MUST 被隔离并阻止自动重放。现有 JSON sidecar helper 只有在保持临时文件写入、文件同步、原子 replace、父目录同步、回读和 symlink/size 检查的前提下才 MAY 复用;它本身不定义 command ledger 的 record 生命周期。 + +### IC-DUR-003:Shell ledger 的唯一顺序与重建 + +`.agent/runtime/supervisor-shell/ledger.jsonl` MUST 是 Shell coordination record 的唯一追加顺序 authority。每个 durable record transition MUST 作为一条完整、带 `DurableEnvelope` 的 canonical JSON line 追加;`ledgerVersion` 从 1 开始,按该文件最后一条有效 line 的物理追加顺序严格递增。command、interaction/rework mapping、source binding 和 projection transition 共享同一序列,但仍用各自 record kind 和 `recordId` 区分。 + +追加必须在 `IC-OWNER-002` 的 owner/supervisor lock 内完成,并在任何真实副作用前同步、回读并核验 checksum。仅最后一条未完成/torn tail MAY 在同一锁内截除;中间行格式、checksum、projectId、recordRevision 或 ledgerVersion 不连续/冲突时,整个 Shell ledger MUST 隔离为 corrupt,不得跳过坏行继续追加或自动重放。 + +为了避免把 JSONL 扫描上限误当协议语义,read-back index、per-record materialization 和 projection checkpoint MAY 作为派生缓存;它们与 ledger 不一致时 MUST 从有效 ledger 重建或失败关闭,MUST NOT 覆盖 ledger。若未来需要 compact,必须先 durable 写入可验证 checkpoint,并永久保留每个已受理 request 的 `requestId + fingerprint + result/read-back binding`;P3 在此之前不得以删除 terminal record 的方式缩短幂等窗口。 + +### IC-IDEMP-001:Request fingerprint + +request fingerprint MUST 覆盖: + +```text +schemaVersion + commandKind + projectId + 完整规范化业务 payload +``` + +MUST NOT 覆盖 transport source、locator、时间戳、重试次数、日志或响应展示文案。 + +### IC-IDEMP-002:同键重放 + +- 同 `requestId` + 同 fingerprint:MUST 返回原结果或当前 in-progress/unknown 状态,不重复真实副作用; +- 同 `requestId` + 不同 fingerprint:MUST 返回 `IDEMPOTENCY_KEY_REUSED`,零副作用; +- Consumer transport 超时:MUST 使用相同 requestId 重试或 read-back,MUST NOT 换 requestId fallback 到旧命令。 + +### IC-IDEMP-003:结果状态 + +command record MUST 能区分: + +```text +prepared +executing +succeeded +rejected +outcome-unknown +corrupt +``` + +`succeeded/rejected` 只表示该 request 已映射到可读回的 durable source evidence;MUST NOT 由 Shell 自己的协调状态伪造 Runtime 成功或失败。 + +### IC-IDEMP-004:未知结果 + +若真实 Runtime/Provider/工具副作用可能已经发生,但无法唯一证明结果,command MUST 进入 `outcome-unknown` 或 Runtime `needs-reconciliation`。任何 Consumer、Runner recovery 或 retry MUST NOT 自动重放该真实副作用。 + +### IC-IDEMP-005:Result read-back + +Shell MUST 提供按 `projectId + requestId` 的结果读回。身份、权限或结构校验失败且请求从未进入 project ledger 时 MAY 返回 `REQUEST_NOT_FOUND`;已进入 ledger 的业务拒绝 MUST durable `rejected` 并可重放。 + +--- + +## 4. Read model 与出向事件 + +### IC-READ-001:Public Snapshot 是完整 Public 状态 + +Public Snapshot MUST 是正式 Consumer 唯一完整 Runtime 状态视图。它是现有 facts 的可重建投影,不是执行事实源。 + +V1 Public Snapshot 的 wire shape 固定为以下字段;实现不得增加未在本文定义的 Public 字段: + +```rust +struct PublicSnapshot { + schema_version: String, + project_id: String, + supervisor_agent_id: String, + session_context: SessionContext, + session_id: Option, + session_catalog_version: Option, + snapshot_revision: u64, + snapshot_hash: String, + status: PublicStatus, + stage: PublicStage, + waiting_on: PublicWaitingOn, + next_step: PublicNextStep, + outcome: Option, + progress: Option, + collaborators: Vec, + interactions: Vec, + command_capabilities: CommandCapabilities, + error: Option, +} +``` + +Public 投影在规范排序和内容安全过滤后若任一数量、字符串或总 wire size 超出 `IC-WIRE-002`,MUST 返回 `PUBLIC_SNAPSHOT_LIMIT_EXCEEDED` read error;不得截断 collaborator、interaction、capability 或发布 partial/failClosed DTO。 + +每个已发布 Snapshot 都是一个确定 observation outcome:`valid` 或 `failClosed`。只要完整 Public 白名单值发生变化,两个 outcome 都 MUST 推进该 view scope 的 `snapshotRevision`;初次发布从 1 开始。无法确定任何安全 outcome 时,read/transport MUST 返回错误,MUST NOT 伪造或复用 Snapshot。 + +`snapshotHash` MUST 等于 `sha256(RFC8785_CanonicalJSON(SnapshotWithoutSnapshotHashAndSnapshotRevision))`。除 `snapshotHash`、`snapshotRevision` 外,Snapshot 的所有字段都属于 hash 输入;`eventId`、event `sequence`、connection/subscription identifier、delivery timestamp、projection record revision/ledgerVersion 不是 Snapshot 字段,不得进入 hash。所有数组 MUST 按下列稳定 key 排序后再 canonicalize:`activeGroups` 按 group wire value、`latestChecks` 按其公开 id、`collaborators` 按 `collaborationId`、`interactions` 按 `interactionId`、capability 内各数组按其完整 canonical JSON 字节序。`updatedAt` 是由 source record 提供的公开状态更新时间,属于 hash 输入;不得在每次 projection read 时用当前 wall clock 重写。相同规范 Snapshot MUST 得到相同 hash;hash 未变化时 MUST NOT 推进 revision 或发出事件。 + +进度与专业组的最小 Public DTO 冻结为: + +```rust +struct SupervisorProgressView { + supervisor_run_id: String, + loop_iteration: u32, + task_progress: ProgressCount, + plan_progress: ProgressCount, + active_groups: Vec, + latest_checks: Vec, + latest_rework_summary: Option, + updated_at: u64, +} + +struct CollaboratorSummary { + collaboration_id: String, + group: CollaboratorGroup, + source: CollaboratorProjectionSource, // runtime | manifestFallback + status: CollaboratorPublicStatus, + stage: Option, + completed_step_count: Option, + total_step_count: Option, + current_task_summary: Option, + outcome: Option, + error: Option, + updated_at: u64, +} + +struct ProgressCount { completed: u32, total: u32 } +struct PublicCheckSummary { id: String, status: PublicCheckStatus, summary: String } +struct SnapshotError { code: SnapshotErrorCode, message: String } + +enum CollaboratorProjectionSource { Runtime, ManifestFallback } +enum CollaboratorPublicStatus { Pending, Running, Waiting, Completed, Failed, Cancelled, Unknown } +enum CollaboratorPublicStage { Preparing, Executing, Coordinating, WaitingForUserInput, WaitingForUserApproval, Finalizing } +enum PublicCheckStatus { Pending, Passed, Failed, Skipped } +enum CollaboratorGroup { Design, Art, Code, Balance, Audio, Publishing } +enum SessionContext { Ready, NeedsBootstrap, NeedsReconciliation } +enum PublicStatus { Idle, Preparing, Queued, Running, Waiting, Paused, Cancelling, Completed, Failed, Cancelled, TerminalPending, NeedsReconciliation } +enum PublicStage { Idle, Preparing, PublicStatusPending, Planning, Executing, Coordinating, WaitingForUserInput, WaitingForUserApproval, WaitingForPolicyApproval, WaitingForDeveloperApproval, WaitingForTimer, WaitingForRunner, PausedByUser, Cancelling, Finalizing, Reconciling, Completed, Failed, Cancelled, TerminalPending } +enum PublicWaitingOn { None, PublicStatusMessage, UserInput, UserApproval, PolicyApproval, DeveloperApproval, Timer, Runner, Reconciliation } +enum PublicNextStep { None, WaitForPublicStatus, SubmitIntent, AnswerInteraction, ApproveInteraction, CancelRun, ResumeRun, RetryTerminalRun, WaitForRunner, Reconcile } +enum PublicOutcome { Success, Failure, Cancelled, Unknown } +``` + +`CollaboratorGroup` 固定为 `design/art/code/balance/audio/publishing`。`sessionContext=needsBootstrap` 且 `sessionId=None` 时,`collaborators` MUST 为空,不得创建 `manifestFallback`;Session ready 后才按已验证 session identity 建立 fallback,避免 bootstrap/ready 之间重分配或重复公开实体。`manifestFallback` 只表示当前 manifest 静态组信息:它 MUST 使用 `status=pending`,`stage/completedStepCount/totalStepCount/currentTaskSummary/outcome/error` 均为 None,且不得生成 interaction、recovery 或 command capability。它的 `collaborationId` MUST 是 Shell 为 `(projectId, supervisorAgentId, sessionId, manifestDigest, group)` 占位持久化的 ID,并受 `IC-ID-003` 的 Runtime binding 替换规则约束;若已有 parent run,则 binding 还必须记录该 parent run。Runtime source 缺失而按依赖矩阵本应存在时,不得降级为 `manifestFallback`,必须 fail-closed。Runtime binding 出现时应复用该 fallback 的 `collaborationId` 并原子替换其 source/binding;只有 fallback 与 Runtime identity 已冲突时才分配新 ID并从 Public 同时移除旧占位。`RepairApprovalPending` 只能引用同一 Public PolicyApproval interaction,不能自己生成第二写命令。 + +V1 稳定枚举值冻结如下: + +| 字段 | lowerCamelCase wire value | +|---|---| +| `sessionContext` | `ready`、`needsBootstrap`、`needsReconciliation` | +| `status` | `idle`、`preparing`、`queued`、`running`、`waiting`、`paused`、`cancelling`、`completed`、`failed`、`cancelled`、`terminalPending`、`needsReconciliation` | +| `stage` | `idle`、`preparing`、`publicStatusPending`、`planning`、`executing`、`coordinating`、`waitingForUserInput`、`waitingForUserApproval`、`waitingForPolicyApproval`、`waitingForDeveloperApproval`、`waitingForTimer`、`waitingForRunner`、`pausedByUser`、`cancelling`、`finalizing`、`reconciling`、`completed`、`failed`、`cancelled`、`terminalPending` | +| `waitingOn` | `none`、`publicStatusMessage`、`userInput`、`userApproval`、`policyApproval`、`developerApproval`、`timer`、`runner`、`reconciliation` | +| `nextStep` | `none`、`waitForPublicStatus`、`submitIntent`、`answerInteraction`、`approveInteraction`、`cancelRun`、`resumeRun`、`retryTerminalRun`、`waitForRunner`、`reconcile` | +| `outcome` | `success`、`failure`、`cancelled`、`unknown`;无终局时字段为 None,不使用字符串 `none` | + +未知稳定枚举值 MUST 按不支持的协议版本失败关闭,Consumer 不得显示原始值并继续构造命令。 + +### IC-READ-002:Public 白名单 + +Public Snapshot MAY 暴露: + +- 项目和当前 Project Supervisor 的不透明身份; +- 紧凑 status/stage/waitingOn/nextStep/outcome; +- 有界 progress 和公开检查摘要; +- 六个静态专业组的有界状态; +- User audience 的 Open/Resolving interaction; +- 服务端生成的 command/recovery capability; +- 脱敏 Snapshot error。 + +Public Snapshot MUST NOT 暴露: + +- 绝对路径、locator、Provider/model/prompt; +- 完整 task/action/plan/observation/tool 参数; +- 动态 child 身份; +- private interaction fingerprint; +- finalization、commit marker、pending/provider/recovery sidecar; +- 可用于绕过正式命令的内部 operation identity。 + +### IC-READ-003:Developer Snapshot + +Developer Snapshot MUST 使用独立 endpoint、DTO 和受信任 capability。Public 与 Developer 类型 MUST NOT 使用 union 混合返回。V1 Developer wire shape 冻结为: + +```rust +struct DeveloperSnapshot { + schema_version: String, + project_id: String, + snapshot_revision: u64, + snapshot_hash: String, + public_snapshot_revision: Option, + runs: Vec, + interactions: Vec, + diagnostics: Vec, + command_capabilities: DeveloperCommandCapabilities, +} + +struct DeveloperRunSummary { + agent_id: String, + session_id: String, + run_id: String, + status: PublicStatus, + stage: PublicStage, + summary: Option, + updated_at: u64, +} + +struct DeveloperInteractionSummary { + interaction_id: String, + kind: InteractionKind, + scope: InteractionScope, + status: InteractionPublicStatus, + audience: InteractionAudience, + summary: String, +} + +struct DeveloperDiagnosticSummary { + code: String, + severity: DeveloperDiagnosticSeverity, + summary: String, +} + +struct DeveloperCommandCapabilities { + reconcile: Vec, +} + +enum InteractionAudience { User, Developer } +enum DeveloperDiagnosticSeverity { Info, Warning, Error } +``` + +Developer Snapshot 的 `interactions` 只包含当前 Open/Resolving 的脱敏摘要;历史 interaction 走独立 Developer/local history。Developer Snapshot 可以暴露以上选定内部 identity 和脱敏摘要,但 MUST 应用 `IC-WIRE-002` 的数量、长度、总 wire size 和内容安全限制。所有条目先验证并规范化,再按完整 RFC 8785 canonical JSON bytes 排序;完全重复项保留,因字节相同而不影响 hash。任一上限超出时必须返回 `DEVELOPER_SNAPSHOT_LIMIT_EXCEEDED` read error,不得截断或发布 partial Snapshot。 + +Developer view 拥有独立于 Public view 的 revision/hash/event scope;hash 使用 `IC-READ-001` 的 RFC 8785 规则。`IC-READ-004` 的 failClosed 向量只适用于 Public view;Developer 任一 required source 无法闭合、identity/digest 冲突或 projection continuity 无法恢复时,统一返回 read error,不得复用旧 Developer Snapshot、发布 partial DTO 或将 diagnostics 当作 failClosed。Developer capability 只扩大受信任 read 与五命令内的显式 reconcile,不允许正式 Project Supervisor 写操作绕过 owner、幂等或 Shell handler。 + +### IC-READ-004:Projection 一致性 + +project projection journal MAY 缓存 Snapshot、revision、event cursor 和恢复 metadata,但只维护自身一致性。它 MUST NOT 参与 Runtime 事务或将“投影已闭合”解释为 command/Runtime 完成。 + +P2 MUST 经专用 projection reader 建立一次有界 source observation:在 owner/supervisor lock 边界内读取所需 Runtime source、记录每份 source 的稳定 identity/revision/digest,并在发布前复核这些 witness;观察期间发生变化时 MUST 有界重试。现有跨 `runtime.json`、task JSONL、event JSONL、response stream 与 sidecar 的聚合 read result 没有该 witness,MUST NOT 直接包装为 Public Snapshot。 + +投影依赖按以下最小矩阵解释;“缺失”只在列出的正常情形可接受,任何存在但无法解析、digest/identity 不可验证或与其它 witness 冲突的 source 都不是空值: + +| 投影类别 | source | 正常缺失 / 空 | 存在但损坏或预期缺失 | +|---|---|---|---| +| project/view identity | trusted project context + project manifest | 不允许缺失 | 不能证明 projectId 与 Project Supervisor identity 时返回 read/transport error,不得发布 Snapshot | +| Session identity | Project Supervisor Session catalog | bootstrap 时可无 active Session,映射 `needsBootstrap` | catalog 损坏、digest 不可复现或与 Runtime identity 冲突时 fail-closed | +| 基础 Supervisor state | runtime state | bootstrap/尚无 Runtime 时可缺失,映射 `needsBootstrap` | 非 bootstrap 时 fail-closed | +| task/progress | task journal | 无 active run 时可为空或缺失 | active run 应有却缺失,或损坏时 fail-closed | +| response stream | stream sidecar | optional,不能单独决定 completed | 损坏不得公开 stream 文本;若完成/交付证明依赖它则 fail-closed | +| event journal | event JSONL | 空可接受;V1 默认不作为 Public Conversation source | 已登记 Public event 所需 source 损坏/截断时 fail-closed;不得由 recent reader 跳过后继续公开 | +| collaboration | manifest + durable collaboration binding | 无协作时为空;无 Runtime binding 时仅允许规范 `manifestFallback` | binding、parent lineage、fallback replacement 或 source identity 冲突时 fail-closed | +| interaction | pending/action sidecar + Shell interaction binding | 没有 open interaction 时两者都可缺失 | 任一方声称 interaction 存在而另一 required source 缺失/损坏时 fail-closed | +| capability | session/run/interaction revision witness + policy result | 没有合法操作时为空 | Snapshot 状态要求某操作但 target/revision/policy witness 不闭合时 fail-closed,不得只保留展示状态 | +| projection continuity | Shell ledger + derived projection journal | 首次投影可无 journal,并从 revision 1、event sequence 0 建立 | derived journal 可从完整 ledger 重建;ledger 损坏或无法恢复唯一 revision/event chain 时返回 read error | + +当完整 witness 证明 source 可组合时,reader 发布 `valid` Snapshot。只有 trusted project context 与 manifest 已证明 `projectId + supervisorAgentId + view`,但其它 required source 缺失、损坏、冲突或有界重试后不能闭合时,reader 才能发布内容安全的 `failClosed` Snapshot。V1 failClosed 的完整字段向量固定为: + +```text +schemaVersion = 当前 V1 +projectId / supervisorAgentId = 已验证 manifest identity +sessionContext = needsReconciliation +sessionId = None +sessionCatalogVersion = None +status = needsReconciliation +stage = reconciling +waitingOn = reconciliation +nextStep = reconcile +outcome = None +progress / collaborators / interactions = empty +commandCapabilities = 全空 +error.code = PUBLIC_STATE_INVALID +error.message = "Public state could not be verified." +``` + +`RECONCILIATION_REQUIRED` 只用于 source observation 本身有效、且 Runtime facts 明确处于 reconciliation 的 valid Snapshot,不用于 projection 损坏。无法证明 project、Supervisor 或 view identity 时 MUST 返回 read/transport error。failClosed 不得复制未证明 Runtime 事实;它按 `IC-READ-001` 计算确定的 hash/revision,并在非首次 publication 时产生事件,不得让 Consumer 停在旧 valid Snapshot。 + +### IC-EVT-001:SnapshotChanged 语义 + +出向事件 MUST 只表示 Snapshot 可能变化: + +```rust +enum SnapshotEventKind { SnapshotChanged } + +struct OutboundEnvelope { + schema_version: String, + event_id: String, + sequence: u64, + from_revision: u64, + to_revision: u64, + event: SnapshotEventKind, +} +``` + +每个 `(projectId, view)` scope 的首次 Snapshot 直接以 revision 1 建立,MUST NOT 为它生成 event;此时 `currentSequence=0`。从第二次规范 Snapshot 变化开始,每个 event 的 `fromRevision` MUST 等于此前已发布 revision,`toRevision` MUST 等于发布后可重读的 revision 且大于前者,首个 event sequence 为 1。一次 publication 可以合并多个 source 变化,但 source 变化而规范 Snapshot 不变时不得发 event。`sequence` 是该 scope 的独立单调事件序列,不要求与 revision 一一对应;projection journal 重建后必须从 durable projection/event record 恢复。事件 MUST NOT 携带完整 Runtime 状态或成为最终事实。Consumer 遇到重复、乱序、缺口、重连或 Snapshot revision 低于 `toRevision` 时 MUST 重新读取完整 Snapshot;重读后仍不能得到 `toRevision` 或更高 revision 时,必须显示 stale/read failure,不得用 event 自行合并状态。 + +### IC-EVT-002:事件保留 + +`SnapshotChanged` event log MAY 有界保留,但 V1 subscription 不承诺历史 event 补读;断线重连总是重新取得完整 Snapshot 和新的 `currentSequence` watermark。该语义 MUST NOT 套用于 Public Conversation 的 session-lifetime cursor。 + +### IC-EVT-003:订阅路由与建立 + +Public 与 Developer MUST 使用独立 read endpoint、权限能力和事件流;任何 event stream 的 scope 固定为 `(projectId, view)`,其中 `view` 是 `public` 或 `developer`。`SnapshotSubscribeRequest` 的 endpoint 已隐含 `view`,不得由 Consumer 在 Public endpoint 中切换为 Developer。订阅建立请求必须经 trusted project context 与该 view capability 核验,不得接受 Consumer 传入路径或以 eventId 推导 scope: + +```rust +enum SnapshotView { Public, Developer } + +struct SnapshotSubscribeRequest { + schema_version: String, + project_id: String, +} + +struct PublicSnapshotSubscriptionStarted { + schema_version: String, + project_id: String, + current_snapshot: PublicSnapshot, + current_sequence: u64, +} + +struct DeveloperSnapshotSubscriptionStarted { + schema_version: String, + project_id: String, + current_snapshot: DeveloperSnapshot, + current_sequence: u64, +} +``` + +建立订阅 MUST 原子地返回一个已验证的完整当前 Snapshot 和其 `currentSequence` watermark,然后仅投递该 scope 中 `sequence > currentSequence` 的实时 event;跨 scope event MUST NOT 投递。V1 不接受 `afterSequence`,不在握手前后发送 backlog;断线、缺口、权限变化或重连均重新建立订阅并读取完整 Snapshot。Public stream 与 Developer stream 的 watermark 不得混用。 + +### IC-CAP-001:Capability 是唯一写入口来源 + +Consumer MUST 只提交当前可信 Snapshot 中服务端生成的 capability 允许的 command/target/option。Consumer MUST NOT 从 `waitingOn`、`nextStep`、status 或错误文案自行构造命令。 + +Shell MUST 在写锁内重读 facts 并复核 capability target;capability 过期返回 `TARGET_STALE` 或 `TARGET_BUSY`,不能因为 Consumer 曾看到能力就继续执行。 + +### IC-CAP-002:Stale display + +Public read 暂时失败且尚未收到新的 Snapshot 时,Consumer MAY 显示上一份已验证 Snapshot 并明确标记 stale。stale 不是新的 Snapshot,也不等于 `failClosed` Snapshot。收到 `failClosed` Snapshot 后,Consumer MUST 用它替换旧视图并禁用全部写入口;不得保留旧 interaction 或 capability。仅在仍展示上一份 valid Snapshot 的短暂 stale 状态,上一份精确 cancel capability MAY 被提交;Shell 仍必须在 owner/project lock 内重新授权和复核,且若期间已发布 `failClosed` 或 target 漂移则零副作用拒绝。 + +### IC-CAP-003:Capability DTO + +```rust +struct CommandCapabilities { + submit_intent: Option, + interaction_responses: Vec, + cancel: Option, + resume: Vec, +} + +struct InteractionResponseCapability { + interaction_id: String, + interaction_revision: u64, + session_id: String, + expected_session_catalog_version: String, + action: InteractionAction, // answer | approve + allowed_decisions: Vec, // answer 时为空 +} + +enum InteractionAction { Answer, Approve } + +struct SubmitIntentCapability { + session_id: String, + expected_session_catalog_version: String, + conversation_options: Vec, + builtin_command: Option, +} + +struct SubmitIntentOption { + intent_kind: IntentKind, + entry_binding_kind: Option, + run_profile: RunProfile, + input_policy: InputPolicy, + allowed_attachment_media_kinds: Vec, +} + +struct BuiltinCommandCapability { + expected_parser_version: String, + allowed_command_names: Vec, +} + +struct CancelCapability { + session_id: String, + expected_session_catalog_version: String, + run_id: String, + expected_run_revision: u64, +} + +enum PublicResumeCapability { + ContinueRun { + session_id: String, + expected_session_catalog_version: String, + run_id: String, + expected_run_revision: u64, + }, + RetryTerminalRun { + session_id: String, + expected_session_catalog_version: String, + target: RetryTarget, + }, +} + +struct DeveloperReconcileCapability { + session_id: Option, + run_id: String, + expected_run_revision: u64, + reconciliation_id: String, +} +``` + +Capability 必须按下表签发;“必须签发”表示全部 required witness 闭合且 policy 至少产生一个合法选项时,投影不得任意省略;任一禁止条件成立时必须撤销: + +| capability | 必须签发条件 | 必须不签发 / 失效条件 | audience | +|---|---|---|---| +| `submitIntent` | valid Snapshot、`sessionContext=ready`、Session catalog witness 有效,且 policy matrix 至少产生一个 conversation/builtin option | bootstrap、failClosed/reconciliation、Session/catalog 漂移、无合法 option | Public | +| `builtinCommand` | `submitIntent` 已签发、parser version 可验证且至少一个公开 command name 合法 | parser/version 未知、命令集合为空或只剩 Local/Developer management | Public | +| `interactionResponses` | User audience Open interaction、private binding/revision/target witness 闭合;每个 allowed action 有精确 capability | Resolving/非 User、binding 漂移、target/policy 不闭合、failClosed | Public | +| `cancel` | valid Snapshot 中存在同 Session 的可取消 live Project Supervisor run,run identity 与 catalog witness 闭合 | 无 live run、已 terminal/cancelling、identity/revision 漂移、failClosed | Public | +| `ContinueRun` | valid Snapshot 的同一 Project Supervisor run 明确为 `pausedByUser`,run/catalog revision 闭合 | 非 pausedByUser、target 漂移、failClosed | Public | +| `RetryTerminalRun` | valid Snapshot 明确给出 `nextStep=retryTerminalRun`,predecessor terminal 与 retry policy witness 闭合 | 非允许终态、已有 successor、lineage/terminal revision 漂移、failClosed | Public | +| `reconcile` | trusted Developer endpoint,存在 durable reconciliation binding,run/reconciliation revision 闭合 | Public view、binding 不完整、结果可能被当作自动副作用重放 | Developer/Runner | + +所有 capability 在其引用的 Session catalog、Run、interaction、policy 或 reconciliation witness 变化时立即失效。Developer `reconcile` 仍通过 `resume` command、同一 Shell handler、owner 与幂等门禁执行,不是第六个命令。 + +`conversationOptions` MUST 按合法组合输出,Consumer MUST NOT 从多个独立白名单字段做笛卡尔积。每个 Open Public interaction 的 `allowedActions` 必须与 `interactionResponses` 中同 interactionId/revision 的一个或多个 capability 精确一致;Resolving interaction 与 `failClosed` Snapshot 的该数组必须为空。Consumer 只能由该 capability 构造 `InteractionResponseMeta` 与答案/decision,`allowedActions` 本身只是展示字段。对 Collaborator interaction,capability 的 `sessionId` 始终是 Project Supervisor Session 的 stale/auth guard,不得解释为 child Session;实际 child target 只能由 interaction 的 private durable binding 在 owner 锁内恢复和复核。Resume capability MUST 使用与 `ResumeIntent` 同构的 tagged target,并携带所需 revision/correlation;Consumer 只能原样提交 capability 中的 target。 + +--- + +## 5. 五个公开写命令 + +### IC-CMD-001:命令集合 + +V1 正式写命令仅为: + +```text +submit_intent +answer +approve +cancel +resume +``` + +start、steer、confirm、reject、retry、schedule、wake、compact、goal management、session management 和 local path management MUST NOT 作为新的平行 Public Runtime 命令。 + +### IC-CMD-002:统一响应 + +成功响应 MUST 包含: + +```rust +struct CommandResponse { + schema_version: String, + request_id: String, + request_fingerprint: String, + replayed: bool, + observed_snapshot_revision: u64, + result: T, +} +``` + +`accepted` 只表示请求已可靠受理或映射,不表示 Runtime 已终结。 + +V1 ack union 冻结为: + +```rust +enum IntentDisposition { DirectReply, Start, Steer, SteerDeferred, Reject, InteractionRequired } +enum ResumeMode { ContinueRun, RetryTerminalRun, ReconcileRun } + +enum CommandAck { + IntentAccepted { + disposition: IntentDisposition, + accepted_run_id: Option, + steer_id: Option, + response_message_id: Option, + runtime_status_message_id: Option, + }, + InteractionAccepted { + interaction_id: String, + decision: Option, + follow_up_interaction_id: Option, + }, + CancelAccepted { + run_id: String, + cancel_operation_id: String, + }, + ResumeAccepted { + mode: ResumeMode, + predecessor_run_id: String, + accepted_run_id: Option, + affected_run_count: u32, + }, + InteractionRequired { + interaction_id: String, + interaction_revision: u64, + }, +} +``` + +只有 DirectReply MAY 在 ack 中返回 `responseMessageId`;Start/Steer 的最终 assistant 属于 RuntimeFinalReply,MUST NOT 在受理 ack 中伪造。`CancelAccepted` 不代表 Run 已 terminal;RetryTerminalRun 才在 ResumeAccepted 中返回新的 `acceptedRunId`。 + +### IC-CMD-003:SubmitIntent 请求 + +```rust +struct SubmitIntentCommand { + meta: CommandMeta, + payload: SubmitIntentPayload, +} + +enum SubmitIntentPayload { + Conversation { + session_id: String, + expected_session_catalog_version: String, + message: String, + attachments: Vec, + intent_kind: IntentKind, + entry_binding: Option, + run_profile: RunProfile, + }, + BuiltinCommand { + session_id: String, + expected_session_catalog_version: String, + command_line: String, + expected_parser_version: String, + }, +} + +enum IntentKind { + CreateFromPrompt, + ContinueProject, + CreateFromTemplate, + ImportExistingDesign, +} + +struct AttachmentBinding { + attachment_namespace: String, + attachment_id: String, + attachment_revision: u64, + digest_algorithm: String, + content_digest: String, + media_kind: PublicMediaKind, +} + +enum EntryBinding { + Template { + template_id: String, + template_revision: u64, + digest_algorithm: String, + content_digest: String, + }, + ExistingDesign { + design_id: String, + design_revision: u64, + digest_algorithm: String, + content_digest: String, + }, +} + +struct RunProfile { + name: String, + version: String, +} + +enum InputPolicy { TextOnly, TextWithOptionalAttachments } +enum EntryBindingKind { Template, ExistingDesign } +enum PublicMediaKind { Image, Audio, Video, Document, Code, ProjectVersion, Other } +``` + +Conversation message MUST 非空,attachment-only MUST 在 ledger 前 `INVALID_REQUEST`。`intentKind` MUST 由 Consumer 明确表达入口意图,但不是权限主张;Shell MUST 按 capability 和 policy matrix 复核。MUST NOT 从 message 推断 intent,也不得把 intent 塞入 runProfile。 + +`entryBinding` 仅与对应 intentKind 组合;附件和 entry 必须来自现有资源/模板 lineage,包含 immutable revision 和 sha256 digest,不接收文件字节、绝对路径、`file://`、临时 URL 或上传 token。 + +BuiltinCommand MUST 通过同一版本 Shell parser。slash route MUST NOT 携带附件、entry binding 或 Runtime profile;项目路径、导入、配置等 Local Management MUST 走独立受信任管理面。 + +### IC-CMD-004:SubmitIntent 路由 + +Shell MUST 在 project lock 内按冻结 option/policy matrix 将 Conversation 判为: + +```text +directReply +start +steer +steerDeferred +reject +interactionRequired +``` + +active root Run 已绑定冻结 Goal Contract 时,普通 execute intent MUST NOT 被偷偷转换为 replacement steer;必须返回 `TARGET_BUSY`,或要求显式 Goal management operation。Consumer MUST NOT选择 disposition。 + +### IC-CMD-005:Input envelope + +Conversation 在执行 Runtime/Provider 前 MUST 先建立同 requestId 绑定的 input envelope,并按以下顺序: + +```text +reserve stable conversationUserMessageId +→ 幂等写入并回读 user conversation message +→ 标记 input committed +→ 调用 Runtime start/steer 或生成 direct reply +``` + +无法证明 user message commit 时 MUST 进入 outcome-unknown/reconciliation;MUST NOT 用新 messageId 补写第二条用户消息。 + +### IC-CMD-006:Answer 请求 + +```rust +struct AnswerCommand { + meta: CommandMeta, + interaction: InteractionResponseMeta, + answers: Vec, +} +``` + +```rust +enum Answer { + Option { question_id: String, option_id: String }, + Freeform { question_id: String, text: String }, +} +``` + +Answer 仅适用于 UserInput。答案 MUST 恰好覆盖当前全部 questionId,每题只能选择当前 optionId 或提交 freeform;缺失、重复、越界或旧 revision MUST 零副作用失败。 + +### IC-CMD-007:Approve 请求 + +```rust +struct ApproveCommand { + meta: CommandMeta, + interaction: InteractionResponseMeta, + decision: ApprovalDecision, + feedback: Option, +} + +enum ApprovalDecision { Approve, Reject, RequestChanges } +``` + +- ToolApproval:仅 approve/reject; +- Project scope PolicyApproval:仅 approve/reject,并绑定冻结 target set; +- `requestChanges`:只允许 Run/Action scope、User audience、单一 immutable targetArtifact 的 PolicyApproval;必须携带 feedback; +- approve/reject MUST 省略 feedback。 + +`requestChanges` MUST 在 interaction resolving 时预分配唯一 `reworkOperationId`,并在 durable rework prepared/enqueued 后才把旧 interaction 标记 Resolved。unknown result MUST NOT 创建第二返工。 + +### IC-CMD-008:Cancel 请求 + +```rust +struct CancelCommand { + meta: CommandMeta, + session_id: String, + expected_session_catalog_version: String, + run_id: String, + expected_run_revision: u64, +} +``` + +Cancel MUST 精确绑定当前 Project Supervisor Session/Run/revision;`expectedRunRevision` 不匹配时零副作用 `TARGET_STALE`。它只接受取消 operation,不得伪造 Runtime terminal。重复 cancel MUST 复用同一 cancel operation。目标终结、Session 变化或归属冲突 MUST 返回 durable `TARGET_STALE`/业务拒绝。 + +### IC-CMD-009:Resume 请求 + +```rust +enum ResumeIntent { + ContinueRun { + session_id: String, + expected_session_catalog_version: String, + run_id: String, + expected_run_revision: u64, + }, + RetryTerminalRun { + session_id: String, + expected_session_catalog_version: String, + target: RetryTarget, + }, + ReconcileRun { + session_id: Option, + run_id: String, + expected_run_revision: u64, + reconciliation_id: String, + }, +} + +enum RetryTarget { + Supervisor { + run_id: String, + expected_terminal_revision: u64, + expected_retry_policy_digest: String, + }, + Collaborator { + collaboration_id: String, + expected_terminal_revision: u64, + expected_retry_policy_digest: String, + }, +} +``` + +- ContinueRun 只继续 capability 明确声明的 pausedByUser 同一 durable Run; +- RetryTerminalRun MUST 为失败终态预分配唯一 successor runId,保存 predecessor/successor lineage,MUST NOT 复用旧 runId;`expectedRetryPolicyDigest` 是 V1 sha256 canonical policy digest,policy 变化时旧 capability 必须 stale; +- ReconcileRun 只允许受信任 Developer/Runner capability,MUST NOT 自动重放未知副作用; +- timer、lane、ownerRecovered、interactionResolved 和 schedule-ready 是内部 recovery intent,不进入 Public Resume; +- 自然语言“继续”MUST NOT 静默路由成 Resume。 + +### IC-CMD-010:旧入口并存 + +P3–P5 期间,任何能操作同一 Supervisor Run 的旧 Tauri/CLI/`swarm_cli`/helper MUST 满足其一: + +1. 启用 External Runner 时转发同一 Runner Shell endpoint; +2. 未启用 Runner 时调用同一 Shell handler 并持等价 owner; +3. 只读或明确的内部/Developer-local 能力; +4. 在新协议启用时显式禁用。 + +“调用一个 Adapter 函数”不足以证明收口;必须证明真实写入发生在 owner 进程。既有 Runner `runtime.resume/steer/cancel/pause/compact` RPC 只能作为 Shell 内部实现或委托 Shell,MUST NOT 保留为并行公开控制协议。 + +--- + +## 6. InteractionRequired + +### IC-INT-001:Interaction identity + +Interaction MUST 使用独立 identity: + +```rust +struct InteractionResponseMeta { + interaction_id: String, + response_id: String, + expected_interaction_revision: u64, + session_id: String, // 始终是 Project Supervisor Session + expected_session_catalog_version: String, +} +``` + +若 interaction context 是 Collaborator,创建时 MUST durable 保存该 interactionId 到完整 collaboration binding、target identity、policy 与 source witness 的 private binding;manifestFallback 不得承载 Public interaction。处理 response 时,Shell MUST 在 owner lock 内重读 binding 和 target lineage;retry/successor、parent lineage、source 或 group 发生漂移时旧 interaction MUST `INTERACTION_STALE`,不得路由到 successor 或同组其它 child。缺失/冲突 binding 必须进入 reconciliation 或从 Public 列表移除。 + +`interactionRevision` 从 1 开始,只在该 interaction 可回答内容、约束、binding 或状态变化时递增。无关 Snapshot 更新 MUST NOT 使 interaction stale。 + +同 `responseId` + 同 fingerprint MUST 重放;同 responseId 不同内容 MUST `IDEMPOTENCY_KEY_REUSED`。 + +### IC-INT-002:Interaction 类型与状态 + +```text +kind: UserInput | ToolApproval | PolicyApproval +scope: Project | Run | Action +audience: User | Developer +status: Open | Resolving | Resolved | Superseded | Cancelled +``` + +Public DTO 冻结为: + +```rust +struct PublicQuestion { + id: String, + header: String, + question: String, + options: Vec, +} + +struct PublicQuestionOption { + id: String, + label: String, + description: String, +} + +struct PublicInteractionView { + interaction_id: String, + interaction_revision: u64, + kind: InteractionKind, + scope: InteractionScope, + actionable: bool, + allowed_actions: Vec, // answer | approve + status: InteractionPublicStatus, // open | resolving + context: PublicInteractionContext, + presentation: PublicInteractionPresentation, +} + +enum PublicInteractionContext { + Supervisor, + Collaborator { + collaboration_id: String, + group: CollaboratorGroup, + }, +} + +enum PublicInteractionPresentation { + UserInput { + questions: Vec, + allow_freeform: bool, + }, + PolicyApproval { + title: String, + summary: String, + allowed_decisions: Vec, + }, + ToolApproval { + title: String, + summary: String, + risk_level: PublicRiskLevel, + allowed_decisions: Vec, + }, +} + +enum InteractionKind { UserInput, ToolApproval, PolicyApproval } +enum InteractionScope { Project, Run, Action } +enum InteractionPublicStatus { Open, Resolving } +enum PublicRiskLevel { Low, Medium, High } +``` + +Public Snapshot 只投影 User audience 的 Open/Resolving。Open 可回答;Resolving 只显示处理中并禁用再次提交;其它状态从公开列表移除。`kind` 与 presentation variant MUST 严格一致;不匹配按 `PUBLIC_STATE_INVALID` 失败关闭。`actionable/allowedActions` 只是展示能力,Shell 仍必须锁内授权。 + +### IC-INT-003:Bound fingerprint + +每个 interaction MUST 保存 private request fingerprint 和 `boundStateFingerprint`,精确绑定其 durable target、policy 和父子身份。Shell MUST 在解决前锁内重读并复核。目标漂移返回 `INTERACTION_STALE`,零副作用。 + +### IC-INT-004:Artifact binding + +产物审批 MUST 绑定: + +```rust +struct ArtifactBinding { + artifact_namespace: String, + artifact_id: String, + artifact_revision: u64, + digest_algorithm: String, + content_digest: String, +} +``` + +只有现有 lineage 可按 immutable revision 重读并计算 sha256 时才能开放 requestChanges;否则返回 `ARTIFACT_BINDING_UNAVAILABLE`。MUST NOT 新建平行 artifact identity。 + +### IC-INT-005:UserInput presentation + +UserInput MUST 有 1–3 个问题;questionId 为稳定唯一 snake_case;每题 2–3 个 option;optionId MUST 在 interaction 创建时按规范 questionId 和 ordinal 生成,不得从 label 推导。freeform 始终允许。label/description/顺序变化必须递增 revision。 + +私有问题原文必须经过内容安全过滤。不能安全公开时 MUST 转为 Developer interaction 或 needs-reconciliation,不得泄漏到 Public。 + +### IC-INT-006:Approval target set + +Project scope PolicyApproval MUST 固化精确 `agentId + parentRunId + runId + actionId + actionFingerprint` target set;MUST NOT 把“当前所有 Run”作为隐含目标。target set 变化必须创建新 interaction。 + +### IC-INT-007:解决顺序 + +Answer/Approve MUST: + +```text +request ledger replay check +→ owner/project/interaction lock +→ 重读 interaction 与 target +→ 校验 revision、status、binding、principal 和 policy +→ CAS Open → Resolving +→ 执行或准备唯一 operation +→ durable evidence 后 Resolved +``` + +崩溃恢复 MUST 复用 interaction/response/rework identity;外部结果未知时保持 Resolving 或进入 reconciliation,MUST NOT 伪造 Resolved。 + +--- + +## 7. Public Conversation 与交付 + +### IC-CONV-001:独立展示通道 + +Conversation/response stream 与 Runtime Snapshot 是独立通道。Conversation message MUST NOT 改变 Runtime 生命周期结论;Runtime completed MUST NOT 反向伪造 assistant message。 + +### IC-CONV-002:不复制正文 + +Public Conversation MUST 是既有 source record 之上的只读索引/分页层。index 只保存: + +```text +sourceKind +deliveryKind +messageId +sourceRecordRef +sourceDigest +sequence +cursor +previousCursor +``` + +MUST NOT 保存第二份正文。读取时必须按 sourceKind 回读原 source 并验证 digest。 + +### IC-CONV-003:Delivery kinds + +```rust +enum DeliveryKind { + User, + DirectReply, + RuntimeFinalReply, + RuntimeStatus, + PublicEvent, +} + +enum ConversationRole { User, Assistant, System } + +struct ConversationProvenance { + run_id: Option, + source_agent_id: Option, + source_run_id: Option, +} + +struct PublicConversationMessage { + schema_version: String, + project_id: String, + session_id: String, + delivery_kind: DeliveryKind, + message_id: String, + sequence: u64, + cursor: String, + role: ConversationRole, + public_text: String, + event_id: Option, + provenance: Option, +} +``` + +`eventId` 只有 PublicEvent 为 Some,且 MUST 逐字节等于 messageId;其它 delivery MUST 为 None。provenance 只允许公开 run/source lineage,MUST NOT 包含 finalizationId、Provider、locator、path、tool/action 或 private ledger identity。 + +Public 去重键固定为: + +```text +(projectId, sessionId, deliveryKind, messageId) +``` + +### IC-CONV-004:User 与 DirectReply + +User 和 DirectReply MUST 从对应 Session conversation message 回读。DirectReply 在 command prepared 时 MUST 预分配稳定 user/assistant messageId,先提交并回读 user message,再写 assistant response,最后关闭 command result。 + +未知 slash command 的直接回复也走该顺序;路径或 Local Management 结果不得伪装成 assistant conversation。 + +### IC-CONV-005:RuntimeFinalReply + +RuntimeFinalReply MUST 复用现有 finalization/conversation identity,不生成第二身份: + +- `messageId` 标识 Agent/Session/Run conversation assistant; +- `finalizationId` 还绑定 response fingerprint/revision/request slot/steer cursor/plan/Goal; +- response stream 由 requestSlot/responseRevision/appliedSteerCursor 标识。 + +三者 MUST 分开保存和核验,不能称为同一 identity。成功后 finalization/recovery sidecar 可能清理,因此长期正文 source 是带稳定 messageId 的 conversation assistant。response stream 单独不足以证明 final reply committed。 + +### IC-CONV-006:RuntimeStatus 准入 + +RuntimeStatus MUST 按具体类型准入,不得假设每个 Run 都有同构 source: + +- 根 Project Supervisor accepted start/terminal:可能来自 project conversation; +- 专业 Agent terminal:可能来自其 Agent Session conversation; +- 带 parent 的 Supervisor receipt/isolated join:当前没有 Session status message,默认不进入 Public Conversation。 + +只有具体 status 同时具有稳定 messageId、明确 conversation scope、task/run correlation 和可回读 source 时才可进入 committed chain。project conversation source MUST 通过 correlation 显式绑定 Session,不能从文件位置猜测。 + +### IC-CONV-007:PublicEvent 默认拒绝 + +现有普通 Runtime event 默认 MUST NOT 进入永久 Public Conversation。只有显式登记的 event type 同时满足以下条件才 MAY 进入: + +1. 写入时已有可恢复、可幂等复用的规范 eventId; +2. `messageId == eventId`; +3. role 固定为 system; +4. 正文仅为 allowlisted `publicText`; +5. 存在可按 eventId 定位、报告损坏/截断并校验 publicText digest 的 source reader; +6. source agent/session/run scope 已证明; +7. 重放复用同一 eventId/sequence/cursor。 + +现有 pid/时间/进程内计数 eventId 和只返回最近 20 条、跳过损坏行的 recent-events reader 不满足条件。GUI MUST NOT 事后生成 `game-chat-runtime-event:*` identity。child event 被旧 GUI 写入 project transcript 时,在 source scope 未证明前必须历史隔离。 + +### IC-CONV-008:Sequence 与 cursor + +每个 `(projectId, sessionId)` MUST 使用 conversation-global sequence。sequence 预留失败 MAY 形成永久空洞;空洞不触发无限重读。response-stream sequence 与 conversation sequence 无关。 + +committed cursor chain 只连接已能从原 source 回读的消息。Public read 请求: + +```rust +struct ConversationReadRequest { + schema_version: String, + project_id: String, + session_id: String, + after_cursor: Option, + limit: u32, +} +``` + +响应 DTO 冻结为: + +```rust +enum ConversationHistoryState { + Complete, + LegacyEntriesIsolated, +} + +struct PublicConversationReadPage { + schema_version: String, + project_id: String, + session_id: String, + messages: Vec, + next_cursor: Option, + has_more: bool, + history_state: ConversationHistoryState, +} +``` + +`afterCursor=None` 表示从该 Session Public origin 分页。cursor 在 Session 生命周期内 MUST 持续有效;Public Conversation read MUST NOT 返回 `CURSOR_EXPIRED`。`legacyEntriesIsolated` 只表示 V1 committed chain 完整、但存在无法证明 identity 的 pre-V1 私有隔离项;不得把截断的 V1 history 标成该状态。 + +### IC-CONV-009:历史失败关闭 + +以下任一情况 MUST 返回无 partial page 的 `CONVERSATION_HISTORY_INCOMPLETE`: + +- committed index 损坏; +- source 缺失、不可定位或 digest 漂移; +- sourceKind/scope/correlation 不匹配; +- 同一 source 对应多个 identity; +- cutover 后出现未登记 writer、双写或无稳定 messageId; +- committed cursor chain 无法从 origin 补读。 + +非法 cursor 返回 `CURSOR_INVALID`,不得夹带 partial page。Consumer MUST 停止合并并显示恢复状态,不能用私有 path、ledger 或 recent window 掩盖截断。 + +### IC-CONV-010:Writer cutover + +P3 启用 Public Conversation read adapter 前,迁移矩阵 MUST 列出所有正式 writer,包括: + +- project conversation; +- Agent Session conversation; +- Runtime finalization/status/event; +- CLI/`swarm_cli` direct reply; +- GUI ordinary Agent chat; +- Developer Agent panel; +- project pending-message autosave; +- GUI response-stream/final-reply 和 Runtime-event 派生 autosave; +- 所有 `append_local_conversation_message` 调用方。 + +每个 writer MUST 明确三选一: + +1. 由 Shell source binding 接管; +2. 保留为 Developer/local history 且永不进入 Public; +3. 在启用 Public Conversation read adapter 前禁用。 + +P5 只删除已经停止写入后的 Consumer 展示/调用分支,不承担正式 writer cutover。cutover watermark 后 Public scope MUST 不存在缺少稳定 messageId 的正式 append;同一 source identity 在同一时刻只能有一个 writer。 + +--- + +## 8. Error contract + +### IC-ERR-001:Command、Snapshot 与 read error 分离 + +Snapshot error、Snapshot read error 和 Command error MUST 使用不同 DTO,不能把 requestId、request fingerprint、replayed 或 command result 嵌入 Snapshot/read error。无法安全发布完整 Snapshot 时返回: + +```rust +enum SnapshotReadErrorCode { + ProjectIdentityUnavailable, + ProjectionContinuityUnavailable, + DeveloperStateInvalid, + DeveloperSnapshotLimitExceeded, + PublicSnapshotLimitExceeded, + PermissionDenied, + TransientUnavailable, +} + +struct SnapshotReadError { + schema_version: String, + code: SnapshotReadErrorCode, + retryable: bool, + message: String, +} +``` + +`ProjectIdentityUnavailable`、`ProjectionContinuityUnavailable`、`DeveloperStateInvalid`、`DeveloperSnapshotLimitExceeded` 和 `PublicSnapshotLimitExceeded` 默认不可重试;`TransientUnavailable` 可用同一 read/subscribe request 重试。read error message 必须为稳定脱敏文案,不能包含 path、source 原文或内部 identity。 + +### IC-ERR-002:稳定 Command error code + +Command error DTO 冻结为: + +```rust +enum CommandErrorCode { + InvalidRequest, + ProtocolVersionUnsupported, + PermissionDenied, + ProjectNotFound, + ProjectIdMismatch, + RequestNotFound, + IdempotencyKeyReused, + CommandInProgress, + CommandResultUnknown, + NeedsReconciliation, + TargetStale, + TargetBusy, + CancelAlreadyTerminal, + InteractionStale, + InteractionAlreadyResolved, + ArtifactBindingUnavailable, + OwnerUnavailable, + TransientUnavailable, + Internal, +} + +struct CommandError { + schema_version: String, + code: CommandErrorCode, + retryable: bool, + message: String, + request_id: Option, + request_fingerprint: Option, + replayed: bool, + observed_snapshot_revision: Option, + interaction_required: bool, +} +``` + +V1 `CommandErrorCode` 冻结为下表中的 code;Rust 枚举名按 `IC-WIRE-001` 序列化为 SCREAMING_SNAKE_CASE。`retryable` 必须等于表中默认值,只有 `TARGET_BUSY` 可以由签发 capability 明确覆盖: + +| Code | 语义 | 默认 retryable | +|---|---|---| +| `INVALID_REQUEST` | schema/字段/类型/上限错误 | false | +| `PROTOCOL_VERSION_UNSUPPORTED` | major/version 不支持 | false | +| `PERMISSION_DENIED` | 调用来源或项目权限拒绝 | false | +| `PROJECT_NOT_FOUND` | locator 无法解析项目 | false | +| `PROJECT_ID_MISMATCH` | locator 与 projectId 不一致 | false | +| `REQUEST_NOT_FOUND` | 从未进入 ledger 或无权读取 | false | +| `IDEMPOTENCY_KEY_REUSED` | 同 requestId 不同 fingerprint | false | +| `COMMAND_IN_PROGRESS` | 同 request 正在处理 | true | +| `COMMAND_RESULT_UNKNOWN` | 结果无法安全证明 | false | +| `NEEDS_RECONCILIATION` | durable facts 冲突或未知结果需修复 | false | +| `TARGET_STALE` | Session/Run/revision 已变化 | false | +| `TARGET_BUSY` | 当前状态禁止该操作 | true/按 capability | +| `CANCEL_ALREADY_TERMINAL` | cancel 目标已终结 | false | +| `INTERACTION_STALE` | interaction revision/binding 已变化 | false | +| `INTERACTION_ALREADY_RESOLVED` | 已由其它 response 解决 | false | +| `ARTIFACT_BINDING_UNAVAILABLE` | 目标不可按 immutable revision/digest 绑定 | false | +| `OWNER_UNAVAILABLE` | 无有效 execution owner | true | +| `TRANSIENT_UNAVAILABLE` | transport/Runner 暂不可用 | true | +| `INTERNAL` | 已脱敏内部错误 | false | + +`retryable=true` 只允许使用同 requestId 重试 transport/读回,MUST NOT 表示可换 requestId 重做真实副作用。 + +Snapshot error code 冻结为: + +```rust +enum SnapshotErrorCode { + ConfigurationRequired, + AuthenticationFailed, + RateLimited, + TransportFailed, + ProviderFailed, + VerificationFailed, + BudgetExhausted, + SandboxDenied, + ArtifactUnavailable, + ReconciliationRequired, + PublicStateInvalid, + Internal, +} +``` + +上述枚举按 `IC-WIRE-001` 序列化为 SCREAMING_SNAKE_CASE。 + +Snapshot error 只描述当前可公开状态,不包含 requestId、requestFingerprint、replayed 或 command result。`interactionRequired` 只能与同 revision 的 Public interaction/capability 互相校验,不能单独驱动按钮。 + +### IC-ERR-003:Public content safety + +Public message、interaction、status、event 和 error 文案 MUST 经过长度、路径、Provider、密钥、Token、工具参数和 observation 过滤。过滤失败 MUST 拒绝公开或进入 Developer/reconciliation,MUST NOT 原样降级输出。 + +--- + +## 9. Migration 与兼容 + +### IC-MIG-001:Shadow 只读 + +Shadow mode 仅可比较 Public projection、schema 和脱敏结果,MUST NOT 调用真实 Runtime/Provider/工具或写 conversation。 + +### IC-MIG-002:Fallback 边界 + +迁移 fallback 只允许 transport 明确返回“unknown command”,且能证明新 handler 未执行。新 handler 已收到请求后的 structured error、timeout、disconnect 或 unknown outcome MUST NOT fallback 到旧写入口。 + +### IC-MIG-003:旧公开面删除 + +P6 MUST 通过调用图证明正式 transport、Consumer 和 Public DTO 已无旧控制协议。Runtime 内部 primitive、恢复测试和明确的管理面 MAY 保留;MUST NOT 使用“全仓旧名称为零”误删内部能力。 + +### IC-MIG-004:产品决策隔离 + +`--swarm-chat` 的普通 Public CLI / 受信任 Developer CLI 定位由 `FD-001` 决定。该决策只能选择 read capability 和命令入口呈现,MUST NOT 改变五命令、owner、source authority 或单 writer 规则。它只阻塞 P5 的 CLI 产品绑定与呈现验收,不阻塞 Interaction Contract 核心冻结,也不阻塞 P0–P4 的 shared Shell、owner、record、Snapshot 或 write ingress 实施。 + +### IC-MIG-005:P3 写入口收口与 P5 Consumer 迁移 + +P3 的完成条件是:任何仍能操作同一 Project Supervisor Run 的 legacy Tauri command、普通 CLI、`swarm_cli` 或 helper 都已成为同一 Shell handler 的纯 transport(External Runner 时在 owner Runner 内执行;进程内时先持等价 owner),或者在新协议启用时明确禁用。它不得先在 Consumer 进程调用 Runtime primitive 再通知 Runner。 + +P5 只负责把 GUI/CLI 的读取、渲染、capability 驱动交互和旧 UX 分支迁为 Public/Developer Consumer;P5 不得被用来延后 P3 的真实 writer 收口。 + +### IC-MIG-006:P4 Runner 重启发现 + +P4 若承诺已受理 operation 不依赖 GUI/CLI 请求推进,Runner MUST 有跨重启的、受信任宿主拥有的 project discovery registry。registry 只保存已验证 manifest 的 private locator / project binding,且只提供候选项目发现;它不是 Public DTO、Runtime fact、owner lease 或接管依据。Runner 对每个候选项目仍 MUST 重新解析 manifest、取得 execution owner,并依 durable command/source evidence 决定 wake、reconciliation 或不动作。 + +--- + +## 10. 冻结条件 + +本文转为正式冻结前 MUST 满足: + +1. 所有 `IC-*` 有唯一含义,无重复或相互冲突定义; +2. 迁移矩阵为每个正式 ingress/read/writer 建立 `MX-* → IC-*` 映射; +3. 冻结前 `EG-*` 明确证明或隔离现有行为; +4. `FD-001` 已被标记为 P5 专属的 read/呈现决策;它必须在 P5 开始前决定,但不阻塞 P0–P4; +5. Rust/TypeScript schema fixture 的规范、golden/negative 向量和预期结果已经定义;P1 负责实现并运行生成链路; +6. request、interaction、conversation 和 owner 的 crash/read-back 语义可以形成可执行 fixture; +7. P1 的 trusted project resolver、owner-guard 内 Shell lock、RFC 8785 canonicalization 与专用 Shell record namespace 已有单一实现边界; +8. P2 的 projection observation/witness 可以形成可执行 fixture; +9. PR #168 重新评审本文,而不是继续依赖旧评论或旧大文档段落。 diff --git a/docs/technical/【技术方案】AI游戏创作Agent Runtime交互边界重构实施计划-2026-08-12.md b/docs/technical/【技术方案】AI游戏创作Agent Runtime交互边界重构实施计划-2026-08-12.md index 0feeeaef9..6f17bb5b6 100644 --- a/docs/technical/【技术方案】AI游戏创作Agent Runtime交互边界重构实施计划-2026-08-12.md +++ b/docs/technical/【技术方案】AI游戏创作Agent Runtime交互边界重构实施计划-2026-08-12.md @@ -1,2357 +1,345 @@ -# AI 游戏创作 Agent Runtime 交互边界重构实施计划 +# AI 游戏创作 Agent Runtime 交互边界重构 -更新时间:`2026-08-15` -状态:评审中(已完成 #168、持续边界复核及冻结前补充审计;第 10 节证据门禁闭合前禁止进入生产实现编码,允许 P0 只读 fixture/test harness 用于验证本方案) +> 文档角色:总览与分阶段实施计划 +> 状态:评审中;尚未允许进入 P1–P6 生产实现 +> 更新日期:`2026-08-17` -## 0. 目标与范围 +## 0. 阅读入口与权威顺序 -统一 Consumer(GUI / CLI / 自动化测试)与 Runtime 之间的公开交互边界,达到: +本文只解释重构目的、系统边界和 P0–P6 实施顺序,不定义协议字段与状态机。四份配套文档的职责和权威顺序如下: -- **Consumer 对 Runtime 生命周期只做两件事**:`render(snapshot)` 与 `dispatch(command)`,不保留跨轮业务真相或生命周期决策;conversation/response stream 仍是独立展示通道,但不得反向推导 Runtime 状态。 -- **交互 Loop 收归后端 Supervisor Shell**:Consumer 不再根据 Runtime 状态自行选择 start / steer / confirm / retry / resume。 -- **GUI、CLI、测试夹具是同一套协议的平等 Consumer**,GUI 没有任何特权通道。 -- **Runner 在 owner/lifecycle 门禁有效期间自驱**:工作发现、确定性唤醒和安全恢复不依赖 Consumer 轮询或主动触发;本轮仍保留 GUI-owner/Runner 生命周期门禁,不宣称无 GUI 常驻或无限制 headless 自驱。 +1. 本文:第一次理解方案和实施阶段的入口。 +2. [`【技术协议】AI游戏创作Agent Runtime交互合同V1-2026-08-17.md`](./【技术协议】AI游戏创作Agent%20Runtime交互合同V1-2026-08-17.md):唯一规范性协议;所有 `IC-*` 要求以它为准。 +3. [`【迁移方案】AI游戏创作Agent Runtime交互边界迁移矩阵-2026-08-17.md`](./【迁移方案】AI游戏创作Agent%20Runtime交互边界迁移矩阵-2026-08-17.md):把 `IC-*` 映射到当前代码、入口、阶段和验收证据。 +4. [`【设计依据】AI游戏创作Agent Runtime交互边界证据与决策附录-2026-08-17.md`](./【设计依据】AI游戏创作Agent%20Runtime交互边界证据与决策附录-2026-08-17.md):保存 `EV-*` 代码事实、`DR-*` 设计决策和 `EG-*` 证据门禁。 -本重构**不重新设计 Runtime 内部执行模型**(Part D 保持黑盒),只补充 Shell 需要的边界能力。 +如四份文档发生冲突: -### 不在本轮 - -- Agent 执行状态机(main_loop / task_queue / recovery)内部重构。 -- Runner 进程生命周期策略(开机自启 / 无 GUI 常驻)——自驱只限于"Runner 存活期间",保留 GUI 启动 + GUI-owner watchdog。 -- LLM / Provider / 提示词体系改动。 -- 现有项目资源上传/登记、`preview.start`/`preview.validate`、本地预览 Registry 和 Session 管理面不在本协议内重定义;它们继续使用各自现行的项目权限、immutable revision、preview authorization 和 exactly-once 合同,不得因删除 Consumer 的 Runtime 生命周期分支而被误删或由前端从 Snapshot 自行推导。 +- 当前代码和仓库最新架构文档决定“系统现在是什么”; +- Interaction Contract 决定“本次重构必须实现什么”; +- 迁移矩阵和证据附录不得改变 Contract,只能暴露当前差距; +- 实现发现 Contract 不可行时,先修改 Contract 并重新评审,不得在 Consumer 或 Adapter 中自行发明兼容语义。 --- -## 1. 交互 Loop 协议(Interaction Contract) +## 1. 为什么要重构 -这是 Consumer 与 Supervisor Shell 之间唯一的公开 Runtime 控制协议。协议不绑定 transport(Tauri 命令、Runner 协议和进程内测试均复用同一语义),但 transport 必须把调用来源传给 Shell,不能信任 Consumer 自报权限。 +当前 GUI、CLI、`swarm_cli`、Tauri wrapper 和测试路径分别承担了一部分 Runtime 生命周期判断: -权威执行拓扑固定为:Tauri/CLI 只是 transport adapter;启用 External Runner 时,五个写命令和 Public projector 的权威 Shell handler 必须在已经取得 project execution owner 的 Runner 内执行并写项目 ledger,GUI 不得先行写一份平行 ledger。Public read/订阅通过 Runner 返回已修复投影;Runner 不可达时 transport 返回 `TRANSIENT_UNAVAILABLE`,但不能把可能陈旧的 Snapshot 伪装成成功响应。非 owner 进程不得修复 dirty journal。未启用 Runner 的进程内模式和测试使用同一 handler,并先取得等价 project owner。Developer read 在 Tauri/受信任开发 CLI 内只读内部状态并经过宿主来源 capability,不承担 Public 投影修复。 +- 是否启动新 Run; +- 是否 steer 当前 Run; +- 是否直接回复; +- 如何处理用户输入、批准、拒绝、重试、恢复和取消; +- 如何合并 Runtime state、event、response stream 和 conversation; +- 如何把 Runtime 输出再次保存为聊天消息。 -V1 初始 `schemaVersion`(Rust 字段 `schema_version`)固定为 `game-creator-agent-interaction.v1`。Snapshot、事件 envelope、命令和公开错误必须携带该值,或由同一 transport 在调用前明确协商到该值;不支持的 major 返回 `PROTOCOL_VERSION_UNSUPPORTED`,写命令失败关闭。Rust 字段按 camelCase 序列化;公开业务枚举使用本文冻结的 lowerCamelCase wire value,结构化 `error.code` 使用本文冻结的 SCREAMING_SNAKE code;所有公开 ID 均为不透明字符串,Consumer 不得从 ID 推导路径、run 或时序。所有带数据的公开 tagged union 统一使用扁平的内部 tag 形式:对象必须包含 `kind` 字段,variant 使用 lowerCamelCase wire value,其余 variant 字段与 `kind` 同级;不使用 serde 默认的外部 variant 包装,也不允许同一 union 在不同 transport 使用不同 tag。无数据的枚举仍序列化为 lowerCamelCase 字符串。V1 写命令使用严格字段合同:缺少必填字段、未知字段、重复字段、错误类型或超出长度/数量上限均返回 `INVALID_REQUEST`,不执行任何副作用;不通过“忽略未知字段”实现协议兼容,后续字段只能通过新 schemaVersion 引入。请求处理顺序固定为:先完成 schema/version/结构/类型/大小校验,再完成 locator、项目身份与来源权限校验,最后执行 session/rotation phase gate 和 ledger 查询;因此 rotation 期间 malformed request 仍返回 `INVALID_REQUEST`,只有结构合法且未命中既有 requestId 的新请求才返回 `TARGET_BUSY`。 +这导致同一个用户操作可能因 Consumer 不同而走不同控制流,也使前端刷新、CLI 无头运行、Runner 恢复和测试夹具难以共享同一行为边界。 -带数据 variant 的 canonical JSON 形态固定为 `{ "kind": "", ...variantFields }`。例如 `SubmitIntentPayload::Conversation` 使用 `{"kind":"conversation","sessionId":"...","expectedSessionRevision":1,"message":"...","attachments":[],"intentKind":"createFromPrompt","entryBinding":null,"runProfile":{"name":"standard","version":"v1"}}`,`BuiltinCommand` 使用 `{"kind":"builtinCommand","commandLine":"/status","expectedParserVersion":"..."}`;`AgentRuntimePublicInteractionPresentation`、`AgentRuntimeIntentEntryBinding`、`AgentRuntimeResumeIntent`、`AgentRuntimeCommandAck` 等其它带数据 union 也必须遵守同一形态。`kind` 是必填 discriminator,variant 不得再携带同名字段;未知 kind、缺失 kind、variant 字段多带/缺失、错误类型和重复字段均按 `INVALID_REQUEST` 处理,并纳入 Rust→TypeScript schema 生成与 golden fixture。 +本重构解决的不是“代码散落”本身,而是控制权归属不清: -V1 统一边界常量必须由 Rust 单一来源生成 TypeScript schema、fixture 和 transport validator,不能由各 Consumer 各自复制: +> Consumer 现在既展示状态,又在部分路径中决定下一步并写入 Runtime;重构后 Consumer 只展示后端状态并表达用户意图,Supervisor Shell 统一作出交互决策。 -| 项目 | V1 上限与校验 | 超限行为 | -|---|---|---| -| 通用 Public 不透明 ID(适用于所有 Public DTO/command/event 中的 `*Id` 字段与 `cursor`,包括 `projectId/agentId/sessionId/runId/requestId/interactionId/responseMessageId/conversationUserMessageId/runtimeStatusMessageId/steerId/cancelOperationId/eventId/collaborationId`;下列 UserInput ID 与已有 resource/artifact binding identity 是显式例外) | `1–128` 个 UTF-8 字节;禁止控制字符、空白首尾和路径分隔符;新增 Public `*Id` 默认继承该规则,不能靠名称枚举遗漏校验 | ledger 前 `INVALID_REQUEST` | -| UserInput `responseId` | 沿用现有 answer 合同,最多 `160` 个 Unicode scalar、`640` 个 UTF-8 字节;禁止控制字符,trim 后不能为空;该 ID 仍是不透明值,不得用于路径或时序推导 | ledger 前 `INVALID_REQUEST`;若未来收紧上限必须提升 `schemaVersion` | -| 单个 command JSON | `64 KiB`(UTF-8,包含 `schemaVersion/projectId/requestId`) | ledger 前 `INVALID_REQUEST` | -| `submit_intent.Conversation.message` | 最多 `4,000` 个 Unicode scalar,且最多 `16 KiB` UTF-8 字节 | ledger 前 `INVALID_REQUEST` | -| `submit_intent.BuiltinCommand.commandLine` | 最多 `512` 个 Unicode scalar,且最多 `4 KiB` UTF-8 字节;规范化后必须仍以 `/` 开头 | ledger 前 `INVALID_REQUEST`;未知命令只允许固定长度 direct reply | -| same-run steer | 每个 run 最多 `16` 条追加指令、累计最多 `16 KiB`;单条最多 `4 KiB` UTF-8(沿用现有 steer ledger 合同),不得因 Conversation message 上限为 `16 KiB` 而放宽单条上限 | ledger 前或 steer ledger 受理前 `INVALID_REQUEST` / `TARGET_BUSY` | -| 输入附件 | V1 仅允许随非空 message 提交,最多 `8` 个既有项目资源 binding;不得携带原始字节、绝对路径或临时上传 token | attachment-only 或超限在 ledger 前 `INVALID_REQUEST`;binding 漂移为 `ARTIFACT_BINDING_UNAVAILABLE` | -| `requestChanges.feedback` | 最多 `2,000` 个 Unicode scalar,且最多 `8 KiB` UTF-8 字节;过滤、Unicode 规范化后重新计数 | ledger 前 `INVALID_REQUEST`;不保存原文 | -| UserInput | 最多 `3` 个问题;单个答案最多 `4,000` 个字符;全部答案最多 `8,000` 个字符、`32 KiB` UTF-8 字节 | ledger 前 `INVALID_REQUEST` | -| Public presentation 文本 | `title/summary/currentStepSummary/currentTaskSummary/checkSummary/latestReworkSummary` 各最多 `512` 个 Unicode scalar、`2 KiB` UTF-8;单个 interaction presentation 最多 `8 KiB` | ledger 前 `INVALID_REQUEST`;投影超限为 `PUBLIC_STATE_INVALID` | -| Public UserInput presentation | `question.id` 沿用现有唯一 snake_case 合同,最多 `64` 个 ASCII/UTF-8 字节;`header` 最多 `12` 个 Unicode scalar、`48` 字节;`question` 最多 `400` 个 Unicode scalar、`1,600` 字节;每个 option 的 `id` 最多 `64` 个 UTF-8 字节,`label` 最多 `60` 个 Unicode scalar、`240` 字节,`description` 最多 `240` 个 Unicode scalar、`960` 字节;均沿用现有单行、控制字符和 trim 后非空合同;问题数为 `1–3`,每题 option 数为 `2–3` | ledger 前 `INVALID_REQUEST`;投影超限、不安全或结构不完整为 `PUBLIC_STATE_INVALID` | -| Public conversation `directReply.publicText` | 最多 `4,000` 个 Unicode scalar、`16 KiB` UTF-8 字节;复用现有有界安全文本常量,不另建平行正文机制 | conversation append/commit 前确定性拒绝:现有 response delivery 进入 `rejected`,command 返回有界 `INTERNAL` 安全摘要;不得提交 message、sequence 或 cursor,也不得留下无法分页补读的 committed 消息 | -| Runtime status / public event message | 脱敏 status/publicText 各最多 `512` 个 Unicode scalar、`2 KiB` UTF-8;status 使用固定模板,publicText 只来自 Rust allowlist projector | 超限或不安全内容不投递;status 硬门失败/unknown 阻止 Run 推进,event message 以带原因的 `discarded` tombstone 丢弃并保留私有审计 | -| Local transport reply / local error message | reply 最多 `4,000` 个 Unicode scalar、`16 KiB` UTF-8;error message 最多 `512` 个 Unicode scalar、`2 KiB` UTF-8;均拒绝控制字符并只允许安全摘要/受信任 resolver 展示值 | ledger/response 前 `INVALID_REQUEST`;已持久化记录损坏为 `CORRUPT_RECORD`;不得截断后当作完整路径、错误或结果提交 | -| Public interactions / 专业组 / progress / intent options | 同时最多 `16` 个 User audience interaction;专业组固定最多 `6` 个公开 slot;progress `activeGroups` 最多 `6`、`latestChecks` 固定最多 `4`;submit intent option 最多 `16` 个,动态 child 只计数不列身份 | 超出表示投影不变量破坏,进入 `PUBLIC_STATE_INVALID`,不得静默截断 | -| Project approval target set | 最多 `16` 个精确 action target;按 `(agentId,parentRunId,runId,actionId,actionFingerprint)` 去重并固化 digest | 超限或集合漂移为业务拒绝 / `INTERACTION_STALE`,不得扩展为“当前全部任务” | -| Public Snapshot JSON | 最多 `256 KiB` UTF-8;数组按稳定 identity 排序 | 超限进入 `PUBLIC_STATE_INVALID`,不得返回部分 Snapshot | -| Public event 补读 | 单次最多 `256` 条 envelope;`afterCursor` 最多 `128` 字节 | `CURSOR_INVALID` / `CURSOR_EXPIRED`,不自动扩大批次 | -| Public conversation 补读 | 单页最多 `256` 条 committed message、最多 `1 MiB` JSON;`limit` 为 `1–256`,`afterCursor` 最多 `128` 字节;committed message/cursor 在 session 生命周期内逻辑保留 | `CURSOR_INVALID` 或 `CONVERSATION_HISTORY_INCOMPLETE` 时不返回部分页;该接口不返回 `CURSOR_EXPIRED`,不能扩大批次、静默截断历史或改读私有 ledger | -| response stream | 沿用现有 `AGENT_RUNTIME_RESPONSE_STREAM_MAX_CHARS=32,000`;sidecar 最多 `256 KiB`(`AGENT_RUNTIME_RESPONSE_STREAM_SIDECAR_MAX_BYTES`) | 进入 response operation 的确定性失败或 `outcome-unknown`,不得截断后提交 | +--- -长度均按 UTF-8 字节和 Unicode scalar **同时**检查;组合 JSON 大小优先于单字段上限。上限改变属于 breaking contract,必须提升 `schemaVersion`,不能在实现 PR 中悄悄放宽。 +## 2. 目标边界 -### 1.1 项目身份、事实源与双 Snapshot +### 2.1 核心目标 -- 项目 manifest 的稳定 `projectId` 是公开协议身份;`projectPath` 只作为本地 transport locator。Shell 每次调用都先 canonicalize locator、验证项目已在现有本地项目授权/目录簿中、取得并重读 manifest,再验证 `projectId` 一致。仅持有任意路径字符串不构成授权;符号链接、替换目录和 TOCTOU 按现有安全 path resolver/目录句柄约束处理。路径不进入公开 DTO、错误、事件、指纹或报告。 -- Runtime durable state 及其同事务/同锁持久投影是业务事实;`SupervisorPublicSnapshot` 是 Consumer 唯一可见的完整状态事实。事件、命令 ack、错误和 response stream 都不能被合并成另一份 Runtime 状态。 -- V1 每个项目只有一个 Public Snapshot、一个 `snapshotRevision` 和一个项目级事件流。Snapshot 的控制主体仍只有当前 Project Supervisor,但必须同时投影当前 run 的 Runtime-owned progress 与六个静态专业组的有界只读状态;动态 child 仍只折叠为数量,不公开 instance 身份。仅给出 `collaboratorCount` 无法满足现有工作台底栏、专业 Agent 确认和失败重试合同,因此不再作为完整 Public read model。 +1. GUI、CLI 和公开协议测试成为同一协议的平等 Consumer。 +2. Consumer 只执行 `render(snapshot)` 与 `dispatch(command)`,不推进 Runtime 状态机。 +3. Supervisor Shell 统一处理正式用户交互决策、命令校验、幂等受理和结果读回。 +4. 现有 Runtime task、state、pending、provider、steer、finalization、conversation 和 event records 继续作为执行事实源。 +5. External Runner 存在时,由持有现有 OS project execution-owner lock 的 Runner 执行正式写入。 +6. Public 与 Developer read model 分离,正式用户协议不泄漏路径、Provider、工具参数、内部 action 或 recovery 细节。 +7. 通过 Adapter 和明确 writer cutover 分阶段迁移,不制造第二套 Runtime authority 或第二份 conversation 正文。 -```rust -struct SupervisorPublicSnapshot { - schema_version: String, - snapshot_revision: u64, - event_cursor: String, - project_id: String, - session_context: AgentRuntimePublicSessionContext, - supervisor: Option, - collaborators: Vec, - interactions: Vec, - command_capabilities: AgentRuntimeCommandCapabilities, - updated_at: u64, -} +### 2.2 不在本轮 -enum AgentRuntimePublicSessionContext { - Ready { session_id: String, session_revision: u64 }, - NeedsBootstrap, - HandoffInProgress, - NeedsReconciliation, -} +- Runtime `main_loop`、task queue、Provider retry、delegation/all-join 或 Agent 执行状态机内部重构; +- LLM、Provider、提示词或工具体系调整; +- Runner 开机自启或无人值守常驻; +- 新增 live Session rotation、Session handoff、第二 active-session index 或 session control lease; +- 新增全局 owner generation/lease CAS; +- 重新设计资源上传、项目资源 lineage、Preview Registry 或 Session 管理面; +- 把管理命令、路径操作或 Developer 调试能力伪装成五个 Public Runtime 命令。 -enum AgentRuntimeCollaboratorProjectionSource { - Runtime, - ManifestFallback, -} +--- -enum AgentRuntimePublicStatus { - Idle, - Preparing, // 已受理但 Public status message 尚未提交,不可 dequeue - Queued, - Running, - Waiting, - Paused, - Cancelling, - Completed, - Failed, - Cancelled, - TerminalPending, // 终态摘要尚未完成唯一 Runtime status message - NeedsReconciliation, -} - -enum AgentRuntimePublicStage { - Idle, - Preparing, - PublicStatusPending, - Planning, - Executing, - Coordinating, - WaitingForUserInput, - WaitingForUserApproval, - WaitingForPolicyApproval, - WaitingForDeveloperApproval, - WaitingForTimer, - WaitingForRunner, - PausedByUser, - Cancelling, - Finalizing, - Reconciling, - Completed, - Failed, - Cancelled, - TerminalPending, -} - -enum AgentRuntimePublicOutcome { - None, - Success, - Failure, - Cancelled, - Unknown, -} - -struct SupervisorRuntimeSummary { - agent_id: String, - session_id: String, - run_id: String, - status: AgentRuntimePublicStatus, - stage: AgentRuntimePublicStage, - completed_step_count: u32, - total_step_count: u32, - current_step_summary: Option, - progress: Option, - waiting_on: AgentRuntimePublicWaitingOn, - next_step: AgentRuntimePublicNextStep, - collaborator_count: u32, // 当前 Supervisor run 中已登记且未终结的协作单元;不含 manifestFallback、历史 child 或静态占位组 - outcome: AgentRuntimePublicOutcome, - error: Option, - updated_at: u64, -} - -struct AgentRuntimeSupervisorProgressView { - run_id: String, - loop_iteration: u32, - task_progress: AgentRuntimePublicProgressCount, - plan_progress: AgentRuntimePublicProgressCount, - active_groups: Vec, - latest_checks: Vec, - latest_rework_summary: Option, - updated_at: u64, -} - -struct AgentRuntimePublicProgressCount { - completed: u32, - total: u32, -} - -enum AgentRuntimePublicCheckKind { - Playtest, - StaticVerification, - CodeMutation, - ScreenshotVerification, -} - -enum AgentRuntimePublicCheckOutcome { - Pending, - Passed, - Failed, - Unknown, -} - -struct AgentRuntimePublicCheckSummary { - kind: AgentRuntimePublicCheckKind, - outcome: AgentRuntimePublicCheckOutcome, - summary: Option, - evidence_count: u32, -} - -struct AgentRuntimeCollaboratorSummary { - collaboration_id: String, // 公开不透明身份,不能解析为内部 agentId - group: AgentRuntimePublicCollaboratorGroup, - source: AgentRuntimeCollaboratorProjectionSource, // runtime | manifestFallback - parent_run_id: Option, - run_id: Option, - status: AgentRuntimePublicStatus, - stage: AgentRuntimePublicStage, - completed_step_count: u32, - total_step_count: u32, - current_task_summary: Option, - outcome: AgentRuntimePublicOutcome, - error: Option, - recovery: Option, - updated_at: u64, -} - -enum AgentRuntimePublicCollaboratorGroup { - Design, Art, Code, Balance, Audio, Publishing -} - -enum AgentRuntimeCollaboratorRecoveryView { - RetryAvailable, - RepairApprovalPending { - interaction_id: String, - interaction_revision: u64, - }, -} - -// RepairApprovalPending 不是新的写 capability;它只引用同一份 Public -// PolicyApproval interaction。Consumer 必须从 interactions 原样构造 approve, -// 不得从 recovery view 生成第二个 repair/approval 命令。 - -struct AgentRuntimeCollaboratorRunTarget { - collaboration_id: String, - parent_run_id: String, - run_id: String, - expected_terminal_revision: u64, -} - -struct AgentRuntimeCommandCapabilities { - submit_intent: Option, - cancel: Option, - resume: Vec, -} - -struct AgentRuntimeSubmitIntentCapability { - session_id: String, - expected_session_revision: u64, - conversation_options: Vec, - builtin_command: Option, -} - -struct AgentRuntimeBuiltinCommandCapability { - parser_version: String, - supported_commands: Vec, -} - -struct AgentRuntimeBuiltinCommandSummary { - name: String, - route: AgentRuntimeBuiltinCommandRoute, - accepts_attachments: bool, // V1 永远为 false;显式字段用于拒绝漂移 -} - -enum AgentRuntimeBuiltinCommandRoute { - DirectReply, - Interaction, - ManagementAction, - RuntimeCommand, -} - -enum AgentRuntimeSlashParseResult { - RuntimeBuiltinCommand, - LocalManagementRoute, - TransportOnly, - UnknownCommand, -} - -// 本地/全局 route(尤其是无 projectId/sessionId 的命令)不复用 Public -// Snapshot 中的 submit_intent capability;它们通过受信任 local transport -// 单独取得 parser capability。宿主来源由 transport 绑定,Consumer 不能自报 localScope。 -struct AgentRuntimeLocalManagementCapability { - schema_version: String, - capability_id: String, - parser_version: String, - scope: AgentRuntimeLocalCapabilityScope, - supported_commands: Vec, -} - -enum AgentRuntimeLocalCapabilityScope { - Global, - Project { - project_id: String, - session_id: Option, - expected_session_revision: Option, - }, -} - -// `Project` 是 capability 的上界,不代表每个命令都可以省略 session 或 -// target。Shell 在锁内按命令再次校验 route-specific invariant:Goal mutation -// (`/goal <目标>`、`edit`、`pause`、`resume`、`clear`) 必须同时携带 -// `session_id + expected_session_revision`,并在 command target 中携带 Goal ID -// 与 Goal revision;Goal read/status 可以只使用 project read capability。 -// `/resume` 必须绑定 session、run 以及 recovery target revision,不能只靠 -// project scope;`/project` 的初始解析和 `/config` 使用 Global scope,二者 -// 不得携带 project/session target;其它 project-scoped 命令若要求 active session -// 也必须显式声明并验证这两个字段。字段缺失返回 `SCOPE_MISMATCH`,不能以 -// `None` 放宽 mutation 权限或从当前窗口猜目标。 - -struct AgentRuntimeLocalCommandSummary { - name: String, - route: AgentRuntimeLocalCommandRoute, - // 服务端私有绑定的精确目标;Goal/Runtime/legacy trace 等 revision 变化时 - // 整个 capability 失效,Consumer 不解析 targetRef。 - target_ref: Option, -} - -enum AgentRuntimeLocalCommandRoute { - DirectReply, - ManagementAction, - TransportOnly, -} - -enum AgentRuntimeLocalManagementOperationStatus { - Prepared, - Executing, - Succeeded, - Rejected, - OutcomeUnknown, - NeedsReconciliation, -} - -// Local operation 与项目 request ledger 使用同一未知结果闭合规则: -// `prepared -> executing | rejected | outcome-unknown`,其中无法证明是否已 -// 开始执行时必须走 `outcome-unknown`;`executing -> succeeded | rejected | -// outcome-unknown`,`outcome-unknown -> needs-reconciliation`;只有受信任 -// reconciliation 流程能把 `needs-reconciliation` 推进为确定的 succeeded/rejected。 -// unknown/reconciliation 期间同一 (localScopeId, requestId) 只能读回或核对, -// 不能换 handle/requestId 重做;Local readback 对前者返回 COMMAND_RESULT_UNKNOWN, -// 对后者返回 NEEDS_RECONCILIATION,并始终保留原 operation identity。 - -// Local command 的 commandLine 只在受信任 transport → parser → resolver 的 -// 短链路中出现;含 host locator 时原始路径由 resolver 拆出并绑定临时 handle, -// command durable record 和 response 只引用 locatorHandleRef/digest,不保存原文。 -struct AgentRuntimeLocalManagementCommand { - schema_version: String, - capability_id: String, - request_id: String, - command_line: String, - expected_parser_version: String, -} - -struct AgentRuntimeLocalTransportReply { - text: String, -} - -struct AgentRuntimeLocalManagementResponseMeta { - schema_version: String, - // malformed/missing requestId 的 ledger 前错误使用 None;transport 自身 - // 仍按调用上下文关联该响应,不能伪造业务 requestId。 - request_id: Option, - request_fingerprint: Option, - replayed: bool, -} - -enum AgentRuntimeLocalManagementResponse { - Succeeded { - meta: AgentRuntimeLocalManagementResponseMeta, - result: AgentRuntimeLocalManagementResult, - // Succeeded 的 meta.request_fingerprint 必须为 Some;TransportClosed - // 也使用规范化 command fingerprint,不创建项目 ledger。 - }, - Failed { - meta: AgentRuntimeLocalManagementResponseMeta, - error: AgentRuntimeLocalManagementError, - }, -} - -enum AgentRuntimeLocalManagementResult { - Reply { reply: AgentRuntimeLocalTransportReply }, - ManagementAccepted { - operation_id: String, - status: AgentRuntimeLocalManagementOperationStatus, - resolved_project_id: Option, - project_operation_ref: Option, - }, - TransportClosed, -} - -// 本地 route 的错误是独立合同;Consumer/GUI/CLI 只能按 code/retryable 和 -// readback 处理,不能解析中文 message。错误响应不伪造 projectId,也不携带 -// observedSnapshotRevision 或 interactionRequired;前者只在成功结果的 -// `resolved_project_id` 有真实解析结果时出现,后者永远不是 local route 的字段。 -// ResponseMeta 是唯一的 requestId/fingerprint/replayed 来源;Succeeded/Failed -// 不得再复制这些字段。缺少/非法 requestId 的 ledger 前错误使用 -// `meta.request_id = None`,不因为错误本身无法构造而退回 transport 文本。 -struct AgentRuntimeLocalManagementError { - code: AgentRuntimeLocalManagementErrorCode, - retryable: bool, - // 只允许安全、有限的摘要;Consumer 不解析该字段。 - message: String, -} - -enum AgentRuntimeLocalManagementErrorCode { - CapabilityNotFound, - ProtocolVersionUnsupported, - InvalidRequest, - TargetStale, - ScopeMismatch, - PermissionDenied, - LocatorUnavailable, - CommandInProgress, - CommandResultUnknown, - NeedsReconciliation, - IdempotencyKeyReused, - CorruptRecord, - RequestNotFound, - Internal, -} - -// AgentRuntimeLocalTransportReply 可在受信任本地 UI/CLI 展示本地路径;它没有 -// observedSnapshotRevision,也不能进入 Public conversation/response stream。 -// `Succeeded` 与 `Failed` 是严格互斥的 tagged union;不存在 result/error -// 同时为空或同时存在的第三种形态。`requestFingerprint` 仅在完成规范化且 -// 未含 host locator 原文时返回;错误 message 只允许安全摘要,并遵守上方 -// Local transport reply / local error message 的字符、字节和控制字符上限。 - -// 不使用 intent/profile/binding 三个独立数组,避免 Consumer 误把它们做 -// 笛卡尔积。每个 option 是一个已经由 Shell 注册并校验过的组合;实际 -// Template/ExistingDesign 资源仍由资源管理面返回 immutable binding, -// capability 只声明所需 binding kind,不把资源目录复制进 Snapshot。 -struct AgentRuntimeSubmitIntentOption { - intent_kind: AgentRuntimeIntentKind, - run_profile: AgentRuntimeRunProfile, - entry_binding_kind: AgentRuntimeIntentEntryBindingKind, - input_policy: AgentRuntimeSubmitInputPolicy, - allowed_attachment_media_kinds: Vec, -} - -enum AgentRuntimeSubmitInputPolicy { - TextOnly, // textOnly - TextWithOptionalAttachments, // textWithOptionalAttachments;仍要求 message 非空 -} - -enum AgentRuntimeIntentEntryBindingKind { - None, - Template, - ExistingDesign, -} - -struct AgentRuntimeCancelCapability { - session_id: String, - expected_session_revision: u64, - run_id: String, -} - -enum AgentRuntimeResumeCapability { - ContinueSupervisorRun { - session_id: String, - expected_session_revision: u64, - run_id: String, - expected_run_revision: u64, - }, - RetrySupervisorRun { - session_id: String, - expected_session_revision: u64, - run_id: String, - expected_terminal_revision: u64, - }, - RetryCollaboratorRun { - session_id: String, - expected_session_revision: u64, - target: AgentRuntimeCollaboratorRunTarget, - }, -} - -enum AgentRuntimePublicWaitingOn { - None, - PublicStatusMessage, - UserInput, - UserApproval, - PolicyApproval, - DeveloperApproval, - Timer, - Runner, - Reconciliation, -} - -enum AgentRuntimePublicNextStep { - None, - WaitForPublicStatus, - SubmitIntent, - AnswerInteraction, - ApproveInteraction, - CancelRun, - ResumeRun, - RetryTerminalRun, - WaitForRunner, - Reconcile, -} - -enum AgentRuntimeCommandErrorCode { - ProtocolVersionUnsupported, // PROTOCOL_VERSION_UNSUPPORTED - InvalidRequest, // INVALID_REQUEST - PermissionDenied, // PERMISSION_DENIED - TargetStale, // TARGET_STALE - TargetBusy, // TARGET_BUSY - InteractionStale, // INTERACTION_STALE - InteractionAlreadyResolved, // INTERACTION_ALREADY_RESOLVED - CancelAlreadyTerminal, // CANCEL_ALREADY_TERMINAL - ArtifactBindingUnavailable, // ARTIFACT_BINDING_UNAVAILABLE - IdempotencyKeyReused, // IDEMPOTENCY_KEY_REUSED - CommandInProgress, // COMMAND_IN_PROGRESS - CommandResultUnknown, // COMMAND_RESULT_UNKNOWN - NeedsReconciliation, // NEEDS_RECONCILIATION - OwnerUnavailable, // OWNER_UNAVAILABLE - TransientUnavailable, // TRANSIENT_UNAVAILABLE - OwnerFenced, // OWNER_FENCED - RequestNotFound, // REQUEST_NOT_FOUND - CursorInvalid, // CURSOR_INVALID - CursorExpired, // CURSOR_EXPIRED - Internal, // INTERNAL -} - -enum AgentRuntimeCommandErrorKind { - Protocol, Validation, Authorization, Target, Interaction, - Idempotency, InProgress, UnknownOutcome, Ownership, Cursor, Internal, -} - -enum AgentRuntimeSnapshotErrorCode { - ConfigurationRequired, // CONFIGURATION_REQUIRED - AuthenticationFailed, // AUTHENTICATION_FAILED - RateLimited, // RATE_LIMITED - TransportFailed, // TRANSPORT_FAILED - ProviderFailed, // PROVIDER_FAILED - VerificationFailed, // VERIFICATION_FAILED - BudgetExhausted, // BUDGET_EXHAUSTED - SandboxDenied, // SANDBOX_DENIED - ArtifactUnavailable, // ARTIFACT_UNAVAILABLE - ReconciliationRequired, // RECONCILIATION_REQUIRED - PublicStateInvalid, // PUBLIC_STATE_INVALID - Internal, // INTERNAL -} - -enum AgentRuntimeSnapshotErrorKind { - Configuration, Authentication, RateLimit, Transport, Provider, - Verification, Budget, Sandbox, Artifact, Reconciliation, PublicState, Internal, -} - -struct AgentRuntimeSnapshotError { - code: AgentRuntimeSnapshotErrorCode, - kind: AgentRuntimeSnapshotErrorKind, - retryable: bool, - interaction_required: bool, -} - -struct DeveloperRuntimeSnapshot { - // 独立开发 DTO,不嵌入 Public 类型或 event cursor: - schema_version: String, - project_id: String, - source_snapshot_revision: u64, - project_path: String, - selected_agent_id: String, - selected_session_id: String, - selected_run_id: String, - current_task: String, - current_action: String, - plan_revision: u64, - plan_steps: Vec, - active_plan_step_index: Option, - recent_tool_calls: Vec, - interaction_records: Vec, -} - -struct AgentRuntimeToolCallDebugView { - tool_call_id: String, - tool_name: String, - status: AgentRuntimeDebugStatus, - request_summary: Option, - response_summary: Option, - started_at: u64, - finished_at: Option, -} - -struct AgentRuntimeInteractionDebugView { - interaction_id: String, - interaction_revision: u64, - kind: AgentRuntimeInteractionKind, - status: AgentRuntimeInteractionStatus, - presentation: AgentRuntimeInteractionPrivatePresentation, -} - -struct AgentRuntimeInteractionPrivatePresentation { - title: String, - summary: String, - questions: Vec, - allowed_decisions: Vec, - private_context_summary: Option, -} - -struct AgentRuntimePrivateQuestion { - id: String, - header: String, - question: String, - options: Vec, - allow_freeform: bool, -} - -struct AgentRuntimePrivateQuestionOption { - id: String, - label: String, - description: String, -} - -enum AgentRuntimeDebugStatus { - Started, // started - Succeeded, // succeeded - Failed, // failed - OutcomeUnknown, // outcomeUnknown -} -``` - -Developer-only DTO 也必须沿用 V1 的字符串、数量、字节和内容安全上限;`request_summary`、`response_summary`、`private_context_summary` 只允许有界脱敏摘要,禁止 Provider 原文、凭据、完整工具参数、绝对路径和未经过滤的 observation。Developer capability 只允许读取这些 DTO,不能把它们作为 Public interaction 或命令 target 发送回 Shell。 - -Public Snapshot 白名单固定为:稳定项目与当前 Project Supervisor 身份、紧凑阶段、完成数/总数、当前步骤摘要、稳定等待枚举、稳定下一步枚举、六个静态专业组的有界只读状态、动态 child 数量、正式用户可回答的最小交互、可由服务端生成的 command capabilities、终态摘要和不含命令私有字段的 Snapshot error。命令错误(包括 requestId、requestFingerprint、replayed 和 observedSnapshotRevision)只出现在命令响应/结果读回,不得嵌入 Snapshot。它不得包含项目绝对路径、完整任务/action/plan、动态 child 身份、原始 observation、工具名称/参数/计划、Provider 原文、`recentToolCalls` 或内部 interaction fingerprint。`collaborationId`、`runId` 和 capability target 都是不透明句柄,不能由 Consumer 推导 agent/task/path;`tool_request` 不进入正式公开 Snapshot 或事件。 - -`sessionContext=NeedsBootstrap` 只表示现有项目确实没有可绑定的 Project Supervisor Session。若 active Session 索引缺失但项目内存在身份完整的当前 `project-supervisor` durable Runtime,projector 必须先按 1.1.1.b 的 project/session/run/lineage 证据恢复 Session record/handoff 并继续展示该 Runtime;证据冲突则 `NeedsReconciliation`,不能误报 NeedsBootstrap、隐藏已落盘失败事实或创建第二 Session。只有确无 Runtime/session 证据时,Consumer 才调用既有 Session 管理面创建/恢复 active session 后重新读取 Snapshot;不能向五命令伪造 sessionId 或把首次启动隐式塞入 submit_intent。 - -Session rotation 的 `Prepared` 只表示唯一 operation 已持久化、active index 尚未 fenced;它单独存在时继续按旧 active session 投影 `sessionContext=Ready`,不得投影 `HandoffInProgress`,也不得创建 successor session、handoff、target/continuation set、manifest 或其它 rotation 副作用。Prepared 后若旧 session revision、run creation epoch 或 active index 已变化,该 operation 必须以 `Rejected` tombstone 结束,不能用旧预期强行提交 fence。只有 operation 的 `phase=FenceCommitted`、`rotationFenceCommitMarker` 与 active index 的 `rotationFenceOperationId + rotationFenceCommitMarker` 在同一 journal 线性化提交并可一致回读后,才开始投影 `sessionContext=HandoffInProgress`。`FenceCommitted`、`HandoffManifestCommitted`、`SuccessorSessionCommitted` 和 `HandoffsCommitted` 均只允许读取 Snapshot、已有 command result/conversation 和 reconciliation 证据,`commandCapabilities` 为空,不能提交 submit/answer/approve/cancel/resume/retry,旧 session 的 interaction 也不能在切换期间写入;同时所有 interaction view 的 `actionable=false` 且 `allowedActions=[]`,Consumer 不得仅因 interaction 仍出现在 Snapshot 就渲染或派发 answer/approve。只有 `sessionContext=Ready`、interaction 为 Open 且对应 action 通过 capability/锁内复核时,Shell 才返回 `actionable=true`。rotation fence 生效后,新 requestId 的五命令统一在 ledger 前返回 `TARGET_BUSY`;已经存在的 requestId 仍可只读回原结果,但不能借重放推进新的 session-bound 副作用。只有 operation 为 `ActiveSessionCommitted`、`activeSessionCommitMarker` 可读,且同一线性化点满足 `activeIndex.committedRotationOperationId == operation.operationId`、`activeIndex.activeSessionId == operation.successorSessionId == successorSession.sessionId`、`activeIndex.activeSessionRevision == successorSession.sessionRevision`、manifest 的 operation/predecessor/successor/session revision 均与 operation 相等、`manifest.runCreationEpoch == operation.expectedRunCreationEpoch`、`activeIndex.runCreationEpoch == operation.expectedRunCreationEpoch + 1`,并且完整 handoff manifest、Run target set 与 continuation set 均可读时,才投影 `Ready { successorSessionId, successorSessionRevision }` 并生成新 capability;任一关系不成立统一 `NeedsReconciliation`,不得扫描其它 operation 猜测已提交者。Open/Resolving interaction、未终结 command/input envelope、DirectReply/RuntimeFinalReply、Runtime status/public-event delivery 的原 `sessionId` 保持创建时 lineage,不改写为 successor;successor 只能凭 manifest continuation item 继续授权、投影或恢复同一 record identity。未被 continuation set 覆盖的旧 session record 必须失败关闭。`Rejected` 在旧 active session 未改变时恢复为旧 `Ready`;`ReconciliationRequired` 或 index/manifest/target/continuation digest 不一致统一投影 `NeedsReconciliation`,清空全部写 capability。`HandoffInProgress` 期间只读,`NeedsReconciliation` 不产生写 capability。`source=manifestFallback` 只表示“没有可匹配 Runtime 的历史 manifest 摘要”,不得伪造 running、进度百分比、完成或失败结论;该状态的 command capability 必须为空,直到真实 Runtime Snapshot 建立。Transport 暂时读失败时,Consumer 可以在本地短暂保留最后可信 Snapshot 作为 stale display,但必须标记本地 stale、禁用除上一份精确 cancel capability 外的其它写按钮并继续重试;stale cancel 只是用户止损尝试,Shell 仍重新授权和锁内复核,且当前 rotation/drain/reconciliation phase gate 优先于 stale 止损例外。不得把 stale display 当作新的 Runtime 事实,也不得清空或倒退已有专业 Agent 状态。Shell 不接受 Consumer 传来的 stale 标记。 - -专业组投影必须按 `parentRunId == 当前 Project Supervisor runId` 精确筛选,并按固定六组顺序输出;旧父 run、其它项目、动态 child、没有父绑定的 Runtime 记录不能冒充当前协作。只存在 manifest 任务的静态组可以输出 `source=manifestFallback + parentRunId=None + runId=None + idle/idle/none` 的占位 slot,但不得携带 Runtime error、进度、当前任务或 recovery,也不能计作活动 collaborator;存在当前父 run 的真实 Runtime 记录后必须由 `source=runtime` 唯一替换。每个公开 `collaborationId` 都由 Shell 绑定真实 `agentId + parentRunId + runId`,失败重试和 ToolApproval 重新读回时必须同时核对该三元身份。`recovery=RetryAvailable` 只是展示“当前 commandCapabilities.resume 中存在同 collaborationId 的 RetryCollaboratorRun”;`RepairApprovalPending` 只引用 Public `interactions` 中同一 interactionId/revision 的审批,不是第二个写 capability。两处任一缺失或不一致都按 `PUBLIC_STATE_INVALID` 失败关闭。`expectedArtifacts`、artifact SHA、verification gate 和文本回执只以安全摘要/数量/结果类别投影,详情仍通过既有项目资源/文档 read model 读取,不能把专业 Agent 内部对话或私有 observation 塞入 Snapshot。 - -Developer Snapshot 使用独立命令和 Rust DTO,不嵌入 Public Snapshot,避免开发调用方误订阅正式事件后把两种投影合并。仅前端 `devMode`、query/hash 或调用者提供的布尔值不构成授权;Tauri 端只允许 debug 构建中受信任的 `developer` 窗口标签,受信任开发 CLI 使用显式本地 capability,进程内测试使用 test capability;release/client/supervisor-chat 和 Runner 普通 Consumer 一律返回 `PERMISSION_DENIED`。后续若开放其它开发调用方,必须新增等价的服务端 capability,不得复用 Public read 权限。 - -`snapshotRevision` 仅在 Public 白名单字段的规范化值真实变化并成功持久化时递增;Developer-only 变化不推进。数组按稳定 identity 排序、枚举和缺省值统一规范化后再比较,不能因文件遍历顺序产生新 revision。`updatedAt` 是该 Public 投影最后真实变化的持久时间,不直接复制底层 Runtime 每次内部写入的时间,也不参与顺序判断。投影修复若重建出同一规范化 Public Snapshot,不递增 revision、不更新时间,也不创建新逻辑事件。 - - -Projection-dirty 的提交顺序冻结为以下四步,所有会改变 Public 白名单的 Runtime 写入口必须复用,不得各自发明顺序: - -1. 在 project lock 内写入并同步 dirty journal,记录 `projectId`、operation identity、变更前 durable digest、预期写入口和 `journalVersion`。 -2. 调用现有 Runtime durable writer 原子提交业务事实;业务写入失败则将 journal 标记为可关闭的 no-op,不产生 Public revision。 -3. 从已提交的 durable state 生成规范化 Public Snapshot;若 hash 变化,按事件规则一次性持久化新 Snapshot、revision、event record 和 cursor;若 hash 未变化则只关闭 dirty journal。 -4. 同步 projection ledger 后关闭 journal,再进行 best-effort event delivery。任何中间崩溃都由 owner 恢复或 Public read 按 operation identity 幂等重跑第 3/4 步,不重复第 2 步业务副作用。 - -因此,“Runtime durable state 已提交但 projection 未刷新”是可自动补投影状态;“Runtime durable state 是否提交无法证明”不是可补投影状态,必须进入 `needs-reconciliation`。 - -Public status/stage 是稳定枚举,不直接透传内部 phase。映射必须穷尽已知内部状态:Start 在 user message 已提交但 Runtime-owned status message 尚未提交时为 `Preparing/PublicStatusPending`,`waitingOn=publicStatusMessage`、`nextStep=waitForPublicStatus`,绝不显示 `queued`;status commit marker 成功后才为 `Queued`。planning/LLM 为 `Running/Planning`;action/observation/协作为 `Running/Executing|Coordinating`;user input、user/tool/policy approval、确定性 retry/lane/timer 分别为 `Waiting` 下的明确 stage;Runner/owner 暂不可用或等待 Runner 恢复时为 `Waiting/WaitingForRunner`,`waitingOn=runner`、`nextStep=waitForRunner`;paused、cancelling 分别为 `Paused/PausedByUser`、`Cancelling/Cancelling`;根终态事实已确定但唯一 terminal failure status 尚未提交时为 `TerminalPending/TerminalPending`,`waitingOn=publicStatusMessage`,`outcome=Unknown`,不得提前显示 `Failed` 或 `Failure`;只有 status commit marker 可读回后才投影 `Failed/Failure`。completed、failed/budget-exhausted、cancelled 和 needs-reconciliation 分别映射稳定终态/核对态。遇到未知或互相矛盾的内部 status/phase 时不得猜成 Running,而要投影 `NeedsReconciliation` 和脱敏 `PUBLIC_STATE_INVALID`。Runtime 失败原因使用独立 `AgentRuntimeSnapshotErrorCode`,不能复用命令协议错误码;配置缺失、鉴权、限流、Provider/验证/sandbox/预算失败都必须映射固定 code,不把 transport/Provider 原文带到 Public。具体映射表与 DTO 同模块维护并做穷举契约测试。 - -进度只从当前 Supervisor run 的可信结构化事实计算:顶层 `totalStepCount=planSteps.len()`,`completedStepCount` 只计 completed,当前摘要只取唯一 active step 的脱敏标题;没有结构化计划时为 `0/0`,不得按 tool/action 数猜进度。现有工作台要求的 Runtime-owned 进度卡不能继续由客户端跨 manifest/plan/event 自行拼装,因此 `progress` 由 Shell 投影同 run 的 `loopIteration`、任务/计划完成数、当前活跃专业组,以及最多四类最新校验(试玩、静态检查、代码 mutation、截图检查)的稳定 outcome/安全摘要/证据数量;返工只提供有界安全摘要。没有可信 receipt/verification evidence 就投影 `unknown` 或省略,绝不根据日志文字猜通过。该 progress 只更新 Snapshot 同一 run 的卡片,不写 conversation;run 切换时由新 Snapshot 原子替换,Consumer 不能合并旧 run 证据。`collaboratorCount` 只计当前 Supervisor run 的 durable、尚未终结专业协作单元并去重,不包含历史 child 或 manifestFallback slot。`waitingOn/nextStep/outcome/error` 是有界、脱敏、仅展示的 Shell 字段,Consumer 不得解析它们路由命令;可执行能力只由 interaction view 与 command capabilities 决定。 - -进度字段关系冻结如下:`SupervisorRuntimeSummary.completed_step_count/total_step_count` 在 `progress` 存在且 `plan_progress` 可用时必须逐字等于 `plan_progress.completed/total`;无结构化计划、plan receipt 缺失或 task/plan receipt 不一致时,Shell 省略 `progress` 并将顶层计数固定为 `0/0`,不得用 task 计数替代或把数字伪装成 unknown。`task_progress` 只来自当前 Supervisor run 的 Runtime task journal,`plan_progress` 只来自同一 run 的 immutable plan journal,二者不互相推导、不得跨 run 合并;同一 revision 内若两者 receipt 不一致,进入 reconciliation,而不是选择较新或较大数字;单个 check 缺少 receipt 时仍可保留该 check,但 outcome 必须为 `Unknown` 且 evidenceCount 为 `0`。`active_groups` 只列当前 run 的 active group,`latest_checks` 每种 check 最多一条且必须带同一 run 的 evidence receipt;任何上述字段变化都推进同一 Snapshot revision。 - -所有可能改变 Public 白名单的 Runtime 写入都必须经过统一 projection-dirty 协议:先在同一 project lock 下写 durable dirty journal,再提交原 Runtime 变更,随后重建 Public Snapshot/事件并关闭 journal。变更前崩溃可重建为无变化,变更后崩溃可由 Public read、订阅启动、Runner 启动或项目 wake 幂等补投影。P2 必须枚举并接入现有 state、task、interaction、终态和恢复写入口;不允许依赖 Consumer 轮询偶然发现漏掉的内部变更。 - - -### 1.1.1 身份来源与生命周期 - -公开协议中的三类身份不是同一个概念,来源和生命周期固定如下: - -| 身份 | 权威来源 | 生命周期与约束 | -|---|---|---| -| `projectId` | 项目 manifest 的持久字段 | 创建项目时生成一次;迁移时只允许从已验证的旧 manifest 显式导入;写入后不可变。缺失、重复或 manifest 校验失败时项目进入 `needs-reconciliation`,不得按路径或名称猜测身份。 | -| `sessionId` | 现有项目会话管理记录 | 由会话管理面创建并持久化,带项目归属、角色和 `sessionRevision`;Project Supervisor 只能绑定一个当前有效 session。结束或切换 session 后旧 session 不可作为新命令 target。Runtime 命令不隐式创建或切换 session。 | -| `runId` | Runtime durable run 记录 | Shell 在产生 Runtime 副作用前预分配并持久化;一个 runId 只对应一次 run,终态后不可复用。`acceptedRunId` 只是该同一 runId 的公开回显,不是第二套身份。 | - -`actionId`、child instanceId、executor generation 和下游 provider request identity 只属于内部 durable 记录;它们可以参与内部恢复和幂等,但不进入正式 Public Snapshot、公开事件、公开错误或 Consumer 路由。`projectPath` 只在 transport 到 Shell 的第一步作为 locator 使用,完成 canonicalize、目录簿授权、目录句柄绑定和 manifest 复核后丢弃;后续日志和协议只使用 `projectId`。 - -所有身份校验都在取得 project execution owner 后、写入 request ledger 前完成。locator canonicalize 与 manifest 复核必须针对同一已打开目录句柄完成;复核失败返回结构化错误并不产生 ledger 记录。会话或 run 的 revision 只由其权威持久化记录递增,不能使用 Consumer 看到的时间戳或事件 sequence 代替。 - -### 1.1.1.a Durable record envelope - -所有项目级 Shell durable record 先嵌入同一个 envelope;尚未解析 `projectId` 的本地 locator/窗口操作使用独立 local envelope,不能把 `projectId` 改成空字符串或可空字段来复用项目 envelope: - -```rust -// 项目级 durable record 使用;envelope.projectId 必须与 record body 中重复出现的 -// projectId 一致,不一致即损坏,不能选择其一继续。 -struct AgentRuntimeDurableEnvelope { - schema_version: String, - record_id: String, - // 记录自身的 CAS/recovery revision;每次该 record 的 durable 状态变化递增, - // continuation item 的 record_revision 固定取这里,不能用时间或文件版本猜测。 - record_revision: u64, - // 项目 ledger 的追加顺序;它不是 record_revision,不可单独证明某条记录未变化。 - ledger_version: u64, - project_id: String, - checksum: String, - owner_boot_id: String, - owner_generation: u64, - created_at: u64, - updated_at: u64, -} - -// 仅用于尚未解析 projectId 的受信任本地管理面。localScopeId 由宿主的 -// app profile/control lease 派生,不接受 Consumer 自报,也不包含路径。 -struct AgentRuntimeLocalDurableEnvelope { - schema_version: String, - record_id: String, - // local record 自身的 CAS/recovery revision;与项目 envelope 使用同一递增规则。 - record_revision: u64, - ledger_version: u64, - local_scope_id: String, - checksum: String, - owner_boot_id: String, - owner_generation: u64, - created_at: u64, - updated_at: u64, -} -``` - -两类 envelope 的 `recordId` 都与业务 identity 一一对应;`recordRevision` 从 `1` 开始并在同一 record 的每次状态变化时单调递增,`ledgerVersion` 只表示项目 ledger 追加顺序;`checksum` 均按“去掉 checksum 字段后的完整 record”计算 RFC 8785 canonical JSON SHA-256,owner boot/generation 参与 fencing。local envelope 不能进入 Public Snapshot、项目事件或 Runtime conversation;一旦解析出项目身份,后续项目级副作用必须在 project owner/lock 下建立或关联项目级 operation record,并把稳定引用写入 local record 的 `projectOperationRef`;两边状态无法唯一对应时进入 outcome-unknown/reconciliation,不能继续只靠 local record 执行或重复副作用。 - -### 1.1.1.b Session handoff 映射 - -现有 Runtime/Process Session 的 `conversation_session_id` 只能作为候选 `sessionId`,不能直接当作 Supervisor session lineage;它没有证明“新窗口可以控制旧 Run”的关系。P1 必须新增与既有会话管理记录一对一关联的 durable handoff 记录(不复制 conversation 正文): - -```rust -enum ActiveSessionStatus { - Pending, // pending;rotation 尚未提交 active-session marker - Active, // active - Superseded, // superseded - Closed, // closed -} - -enum SessionHandoffReason { - Rotation, // rotation - OwnerRecovery, // ownerRecovery - Migration, // migration -} - -struct ProjectSupervisorSessionRecord { - envelope: AgentRuntimeDurableEnvelope, - project_id: String, - supervisor_lineage_id: String, // 首次创建后不可变 - session_id: String, // 每次 rotation 新建,永不复用 - session_revision: u64, - predecessor_session_id: Option, - status: ActiveSessionStatus, // pending | active | superseded | closed - issued_by_control_lease_id: String, - created_at: u64, - closed_at: Option, -} - -struct ProjectSupervisorRunHandoff { - envelope: AgentRuntimeDurableEnvelope, - project_id: String, - supervisor_lineage_id: String, - run_id: String, - rotation_operation_id: String, - handoff_manifest_id: String, - predecessor_session_id: String, - successor_session_id: String, - target_run_set_digest: String, - handoff_revision: u64, - handoff_reason: SessionHandoffReason, -} - -// rotation 开始时固化的非终态 Run 集合。目标集合本身也是 durable record,不能 -// 只把 target_run_set_ref 当作未定义的 sidecar;恢复不得重新扫描当前 Run 集合。 -struct ProjectSupervisorRunHandoffTargetSetRecord { - envelope: AgentRuntimeDurableEnvelope, - target_set_id: String, - rotation_operation_id: String, - manifest_id: String, - project_id: String, - supervisor_lineage_id: String, - predecessor_session_id: String, - successor_session_id: String, - // 按 runId 排序的不可变 payload;每个 item 同时固化捕获时的 session/run revision。 - sorted_targets_ref: String, - target_run_count: u32, - target_run_set_digest: String, - commit_marker: Option, - status: HandoffTargetSetStatus, -} - -struct ProjectSupervisorRunHandoffTargetPayload { - envelope: AgentRuntimeDurableEnvelope, - target_set_id: String, - chunk_index: u32, - items: Vec, // 每 chunk 最多 256 条 - checksum: String, -} - -struct ProjectSupervisorRunHandoffTargetItem { - run_id: String, - predecessor_session_id: String, - captured_run_revision: u64, -} - -enum HandoffTargetSetStatus { - Reserved, - Committed, - Corrupt, -} - -// target set payload 使用同一 owner 下有界、带 checksum 的私有 record/chunk;V1 -// 每个 chunk 最多 256 条、整个 target set 最多 4096 条,超过上限拒绝 rotation -// 并保留原 active session。digest 固定为 sha256(RFC 8785 canonical JSON(sorted -// target items)),不允许按文件名或最新 updatedAt 选择。target set/chunk 的保留与 -// 隔离规则和其它 durable record 一并冻结;payload 缺失、checksum 不一致或 commit -// marker 不可读直接 reconciliation。 -struct ProjectSupervisorRunHandoffManifest { - envelope: AgentRuntimeDurableEnvelope, - manifest_id: String, - rotation_operation_id: String, - project_id: String, - supervisor_lineage_id: String, - predecessor_session_id: String, - successor_session_id: String, - source_session_revision: u64, - target_run_set_ref: String, - target_run_count: u32, - target_run_set_digest: String, - run_creation_epoch: u64, - continuation_set_ref: String, - continuation_count: u32, - continuation_set_digest: String, -} - -// 除 Run 本身外,rotation 还必须固化所有会跨 session 继续的交互/投递记录。 -// 这些记录的 session_id 是创建时的 lineage/provenance,不在 handoff 中改写; -// successor 只能凭该 continuation set 取得一次性的当前 session 授权,不能 -// 通过“当前 session + 最近记录”重新猜测要继续哪条消息或 interaction。 -struct ProjectSupervisorSessionHandoffContinuationSetRecord { - envelope: AgentRuntimeDurableEnvelope, - continuation_set_id: String, - rotation_operation_id: String, - manifest_id: String, - project_id: String, - supervisor_lineage_id: String, - predecessor_session_id: String, - successor_session_id: String, - sorted_continuations_ref: String, - continuation_count: u32, - continuation_set_digest: String, - commit_marker: Option, - status: HandoffContinuationSetStatus, -} - -struct ProjectSupervisorSessionHandoffContinuationItem { - record_kind: HandoffContinuationRecordKind, - record_id: String, - // 必须等于被引用 record 的 envelope.record_revision;不得使用 - // interactionRevision、ledgerVersion、时间戳或文件版本替代。 - record_revision: u64, - session_id: String, - run_id: Option, -} - -struct ProjectSupervisorSessionHandoffContinuationPayload { - envelope: AgentRuntimeDurableEnvelope, - continuation_set_id: String, - chunk_index: u32, - items: Vec, // 每 chunk 最多 256 条 - checksum: String, -} - -enum HandoffContinuationRecordKind { - Interaction, - CommandOperation, - InputEnvelope, - DirectReplyDelivery, - RuntimeFinalReply, - RuntimeStatusMessage, - PublicEventMessage, -} - -enum HandoffContinuationSetStatus { - Reserved, - Committed, - Corrupt, -} - -// continuation payload 同样使用有界、带 checksum 的 chunk;V1 最多 8192 条, -// digest 固定为 sha256(RFC 8785 canonical JSON(sorted continuation items))。 -// 缺失记录、revision/record_kind/session/run 不匹配、checksum 或 commit marker -// 不一致,均直接进入 reconciliation,不按 ledger 当前“最新状态”补猜集合。 - -// Session rotation 跨多个 session/handoff record,不能靠“最后写入的文件”判断 -// 成功。rotation operation 是唯一恢复事实;active-session commit marker 之前 -// 旧 active session 仍然有效,新的 session/handoff 只能作为待提交事实存在。 -struct ProjectSupervisorSessionRotationRecord { - envelope: AgentRuntimeDurableEnvelope, - operation_id: String, - project_id: String, - supervisor_lineage_id: String, - predecessor_session_id: String, - successor_session_id: String, - expected_session_revision: u64, - // Prepared 时尚未建立 manifest,必须为 None;只有 target/continuation set 与 - // manifest 均提交并回读成功后,才在 HandoffManifestCommitted 中补为 Some。 - handoff_manifest_ref: Option, - expected_run_creation_epoch: u64, - phase: SessionRotationPhase, - // 与 active index 的 rotation_fence_commit_marker 一致;Prepared 时必须为 None。 - // fence 与该 marker/phase 必须在同一 journal 线性化提交。 - rotation_fence_commit_marker: Option, - active_session_commit_marker: Option, -} - -enum SessionRotationPhase { - Prepared, // operation 已持久化,active index 尚未 fenced - FenceCommitted, // operation phase/marker 与 active-index fence 已原子提交 - HandoffManifestCommitted, - SuccessorSessionCommitted, - HandoffsCommitted, - ActiveSessionCommitted, - ReconciliationRequired, - Rejected, -} - -// 当前 active session 只能从该项目唯一 index 读取;不能扫描 session 文件 -// 或按 updatedAt/文件名选择“最新”记录。 -struct ProjectSupervisorActiveSessionIndex { - envelope: AgentRuntimeDurableEnvelope, - project_id: String, - supervisor_lineage_id: String, - active_session_id: String, - active_session_revision: u64, - run_creation_epoch: u64, - // 非空表示 rotation fence 已提交;所有五命令、新 Run/child/delegation、 - // interaction 和 conversation delivery identity 的分配都必须在同一 project - // lock 内拒绝或延后,不能继续落到 predecessor session。 - rotation_fence_operation_id: Option, - // 与 rotation operation 的同名 marker 一致;operationId/marker 必须同时为空或 - // 同时存在,禁止出现 active-index fence 找不到唯一 operation recovery fact。 - rotation_fence_commit_marker: Option, - // 最近一次成功切换 active session 的 operationId。最终 active-session journal - // 必须把它更新为当前 operationId;任何 Rejected 都保留历史成功值,不得清空或覆盖。 - committed_rotation_operation_id: Option, -} -``` - -Session、handoff、target set 和 lineage 身份记录同样嵌入统一 durable envelope;`status`、`closedAt`、target-set commit marker 和 lineage 状态迁移必须通过 checksum 与 owner-generation fencing。`Prepared` recovery 只允许校验唯一 operation、predecessor/successor 预分配身份、expected session revision/epoch 和“active index 尚未引用该 operation”;它不能创建 successor session、manifest、handoff 或外部入队。若预期仍成立,可以在重新取得 owner/project lock 后继续同一 operation 的 fence commit;预期已漂移则写 `Rejected` tombstone。若 active index 已引用该 operation,但 operation 仍为 `Prepared`、两边 marker 缺失或不一致,则属于不可达的 torn commit,必须 `ReconciliationRequired`,不能把 Prepared 当作 fence 已成功。 - -session rotation 必须在项目锁内先校验当前 active index 没有其它 fence,分配唯一 `rotation_operation_id`、`successor_session_id` 并固化 `expected_session_revision + expected_run_creation_epoch`,首先持久化 `ProjectSupervisorSessionRotationRecord(Prepared)`;此时 `handoff_manifest_ref=None`、两个 commit marker 均为 `None`,active index 不变,旧 session 继续 `Ready`。随后重新读取并校验 active index、session revision、run creation epoch 和 owner generation,在同一 projection/rotation journal 线性化提交中同时写入 operation 的 `phase=FenceCommitted + rotation_fence_commit_marker` 与 active index 的 `rotation_fence_operation_id + rotation_fence_commit_marker`;任一侧缺失、operationId/marker 不一致或只能读到半边时进入 `ReconciliationRequired`,不得自动清除 fence。只有该 journal 可一致回读后 fence 才生效并投影 `HandoffInProgress`。fence 同时阻止新 requestId 的五命令、新 Run/retry successor/child/delegation/Runtime-owned schedule run、Interaction 和 conversation delivery identity 分配;不得先分配 identity 后再补 handoff。fence 前已进入 prepared/executing 的 answer/approve/cancel/resume/submit 必须停止创建新的下游 identity,并收束到 handoff-safe durable boundary:能证明权威结果则闭合原 command result;尚未产生未知外部结果但需要跨 session 继续的 operation 与其已分配 response/status/interaction identity 一并写入 continuation set;结果已未知则 rotation 进入 reconciliation。已分配 identity 的 response/status/public-event delivery 可补到稳定 commit marker、handoff-safe operation boundary 或稳定 `outcome-unknown`,但不得创建替代 message identity。所有目标 Run 到达不再产生未登记 session-bound identity 的 durable handoff barrier 后,才在同一锁序内按当前 active session 和 supervisor lineage 固化、排序并 digest 非终态 Run 集合,以及全部 Open/Resolving interaction、未终结 command/input envelope、DirectReply/RuntimeFinalReply、Runtime status/public-event delivery,分别写入 committed target set 与 continuation set,并提交 manifest;manifest 可读且 refs/count/digest 全部匹配后,才把同一 operation 的 `handoff_manifest_ref` 从 `None` 补为 `Some(manifestId)` 并推进到 `HandoffManifestCommitted`。`Prepared` 或 `FenceCommitted` 不得保存一个声称已提交但尚不可回读的 manifest ref。无法在有界期限内到达 barrier 时,按同一 operation 写 `Rejected` tombstone;若 fence 已提交则通过 journal 隔离 pending records、清除 active-index fence/marker 并递增 epoch,恢复旧 active session,不能不带 fence 强切。 - -manifest 的 `target_run_set_ref + target_run_count + target_run_set_digest + run_creation_epoch` 和 `continuation_set_ref + continuation_count + continuation_set_digest` 共同构成本次 handoff 的不可变目标边界,rotation fence 生效后没有新 session-bound identity 可以落到 predecessor;若检测到 epoch 漂移、barrier 后 predecessor record revision 变化或集合摘要漂移,rotation 必须失效并进入 reconciliation,不能静默排除新 Run、interaction 或 delivery。所有 `ProjectSupervisorRunHandoff` 必须一一匹配 manifest/target set 的 operation、manifest、predecessor/successor session、run、lineage、captured revision 和 digest;所有跨 session 继续的 command/input/interaction/delivery 必须一一匹配 continuation item 的 record kind/id/revision/session/run,不能恢复时重新扫描当前 ledger 集合。`ActiveSessionCommitted` marker 之前,旧 session 保持 `active`,不得先落 `superseded`;`Prepared` 单独存在时仍可按旧 session 受理写入,但提交 fence 前必须重新校验 revision/epoch,`FenceCommitted` 之后不得再受理新写命令或 session-bound identity。阶段按 `Prepared → FenceCommitted → HandoffManifestCommitted → SuccessorSessionCommitted → HandoffsCommitted` 推进;只有新 session、manifest、全部 Run handoff 与 continuation record 均可读且身份/lease/revision 校验通过后,才在同一 active-session journal commit 中切换 active index、将旧 session 标记 `superseded`、把 `activeIndex.committedRotationOperationId` 同步写为当前 `operation.operationId`、清除当前 operation 的 rotation fence/marker、把 `activeIndex.runCreationEpoch` 从 `operation.expectedRunCreationEpoch` 精确递增为 `expected + 1`,并提交 operation 的 `active_session_commit_marker`,再进入 `ActiveSessionCommitted`。该 journal 恢复和 `Ready` projector 必须同时校验 committed operationId、predecessor/successor session、source/successor session revision、manifest operation/session/epoch、active index epoch 和 active-session marker;不得只凭 activeSessionId 或最新文件判定成功。任一步崩溃或写失败都由 rotation operation 恢复,并严格区分两类 Rejected:`Prepared-before-fence Rejected` 仅在 active index 没有引用该 operation 时成立;恢复只幂等写 operation 的 `Rejected` tombstone,不清理任何 fence、不递增 active-index epoch、不创建或隔离本来就不允许存在的 successor/manifest/handoff/set,并原样保留 active index 既有 `committed_rotation_operation_id`,然后继续使用旧 active session。`FenceCommitted-after-fence Rejected` 只在 operation/index 的 operationId 与 fence marker 一致、active-session marker 尚未提交且没有未知外部结果时成立;恢复先隔离该 operation 的 pending successor/handoff/target-set/continuation,再在同一 journal 中清除且只清除该 operation 的 active-index fence/marker,把 epoch 从捕获值按规则递增一次,保留历史 `committed_rotation_operation_id` 不变,并继续使用旧 active session。operation/index marker 不一致、active-session marker 已提交或结果未知时不得走 Rejected,统一 `ReconciliationRequired`。active-session marker 之后发现 committed operationId、session/manifest/epoch 关系、任一集合/manifest、digest、handoff/continuation 缺失或不一致,同样进入 `ReconciliationRequired`,保留 fence/commit 证据,禁止两个 session 控制或继续同一 record,不能按“最新文件”猜成功。新 session 只有在同一 `supervisorLineageId`、项目归属、control lease、单调 revision 和对应 handoff/continuation proof 全部成立时,才可以控制旧 session 遗留 Run 或继续旧 session record;旧 session 永远不能重新变为 active。没有 proof、lineage 不匹配、revision 回退或双窗口竞争时返回 `TARGET_STALE`,不能根据 `conversation_session_id`、路径或时间猜测归属。迁移前已有 session 必须先生成显式 migration handoff/continuation fixture,无法证明 lineage 的旧 Run、command/input envelope、interaction 或 delivery 暂停在 `needs-reconciliation`,不自动开放 answer/approve/cancel/resume。 - -### 1.1.2 Public 摘要枚举与投影提交合同 - -`status`、`stage`、`waitingOn`、`nextStep` 和 `outcome` 是公开稳定枚举,不向 Consumer 透传内部 phase,也不要求 Consumer 解析自然语言。V1 至少冻结以下值: - -| 字段 | 稳定值 | -|---|---| -| `status` | `idle`、`preparing`、`queued`、`running`、`waiting`、`paused`、`cancelling`、`completed`、`failed`、`cancelled`、`terminalPending`、`needsReconciliation` | -| `stage` | `idle`、`preparing`、`publicStatusPending`、`planning`、`executing`、`coordinating`、`waitingForUserInput`、`waitingForUserApproval`、`waitingForPolicyApproval`、`waitingForDeveloperApproval`、`waitingForTimer`、`waitingForRunner`、`pausedByUser`、`cancelling`、`finalizing`、`reconciling`、`completed`、`failed`、`cancelled`、`terminalPending` | -| `waitingOn` | `none`、`publicStatusMessage`、`userInput`、`userApproval`、`policyApproval`、`developerApproval`、`timer`、`runner`、`reconciliation` | -| `nextStep` | `none`、`waitForPublicStatus`、`submitIntent`、`answerInteraction`、`approveInteraction`、`cancelRun`、`resumeRun`、`retryTerminalRun`、`waitForRunner`、`reconcile` | -| `outcome` | `none`、`success`、`failure`、`cancelled`、`unknown` | - -`waitingOn` 和 `nextStep` 只用于展示提示,任何可执行按钮必须来自当前 Public `interactions` 或 `commandCapabilities`;专业组 `recovery` 只是与这两处互相校验的展示索引,不能独立构造命令。Consumer 不得根据状态、错误文案或这两个字段自行拼装命令。capability 是 Snapshot 在同一 revision 上投影的精确命令目标:无 capability 就禁用入口,有 capability 才原样提交其中的 session/run/revision/interaction target;Shell 仍在写锁内重读事实,过期 capability 返回 `TARGET_STALE`。`submit_intent.conversationOptions` 按可用组合而不是多个独立白名单字段输出,避免 Consumer 误组合 intentKind、runProfile 和 entryBinding;resource identity 仍由既有资源管理 read model 提供。该 capability 只声明 Conversation 请求形态可提交,不预先承诺 interaction kernel 的 `execute` 一定可转为 start/steer:active Project Supervisor 根 Run 已绑定冻结 Goal Contract 时仍可保留合法 Conversation option 以承接 direct reply,但 Shell 不投影、也不接受任何从该 capability 推导出的 replacement-steer 能力;若同一请求在锁内被判定为 `execute`,稳定返回 `TARGET_BUSY` 并指向显式 Goal management mutation。短暂 Public read 失败时,Consumer 可以继续显示上一份已验证 Snapshot 并明确标记 stale;除上一份精确 `cancel` capability 外所有写入口禁用。为避免失去用户止损能力,stale cancel 可继续提交原 session/sessionRevision/run target,但它只在最后可信 Snapshot 的 `sessionContext=Ready` 且本地没有观测到 `HandoffInProgress`/draining/reconciliation 时作为例外;Shell 必须先在 project lock 内检查当前 phase/fence,再重新授权和锁内复核,phase gate 优先时返回 `TARGET_BUSY`/`TARGET_STALE`,目标变化返回 `TARGET_STALE`,transport/owner 不可用则返回 `TRANSIENT_UNAVAILABLE/OWNER_UNAVAILABLE`。已有 cancel request 的结果读回仍可进行,但不得借 stale capability 创建第二个 cancel operation。这解决“nextStep 不可路由、但 UI 又需要启停 submit/cancel/resume/retry”以及“读短暂失败时仍要能尝试取消”的矛盾。文案由 Shell 根据稳定值本地化并做长度、路径、Provider 原文和敏感信息过滤;Snapshot hash 必须比较除 `snapshotRevision/eventCursor/updatedAt` 这些提交元数据外的所有实际序列化 Public 业务字段,包括规范化展示文案,不能出现业务响应字节变化但 `snapshotRevision` 不变。只有不进入 DTO 的本地化资源或渲染变化不推进 revision。 - -Public projector 的一次提交以 `projectId` 为边界,在 `project execution owner → supervisor project lock → projection journal` 的锁序内完成: - -1. 读取并规范化 durable Runtime state、当前 Supervisor session/run 和 User audience interaction;校验身份、枚举和白名单。 -2. 对规范化 Public DTO 计算 `publicSnapshotHash`。与已持久化 hash 相同则关闭 dirty journal,不增加 revision、sequence、cursor 或 `updatedAt`。 -3. 有真实 Public 变化时分配 `snapshotRevision = previous + 1`、`sequence = previousSequence + 1`,并在同一 journal 中预分配 `eventId` 与新 opaque cursor。`eventId = sha256(RFC 8785 canonical JSON([projectId, snapshotRevision, publicSnapshotHash]))` 的编码结果只作为不透明 ID 返回;Consumer 不得解析其构成。 -4. 持久化 Snapshot、事件记录和 journal 状态。底层存储不能提供单文件事务时,使用 journal 恢复保证“旧 Snapshot/无事件”或“新 Snapshot/有唯一事件记录”两种可重建结果,不允许出现新 Snapshot 配旧 cursor 或同 revision 多事件。 -5. 只有 Snapshot 与事件记录均可读后才允许投递;投递失败不回滚事实,后续订阅或 wake 按同一 eventId 补投。 - -`event_cursor` 的流起点为项目专属的不透明 `origin` 游标;每个事件的 cursor 由 Shell 生成并持久化,禁止按时间、路径或可猜测的数字直接编码。读 Snapshot 与建立订阅必须共享一次 project projection lock 的线性化边界:Consumer 先取得 Snapshot 返回的 cursor,再以该 cursor 作为 `afterCursor` 建立订阅;订阅端先补发 cursor 之后已持久化的记录,再接收新投递。这样读取与订阅之间发生的事件不会丢失。 - - -### 1.1.3 Project owner、GUI-owner 与并发栅栏 - -项目执行所有权、Runner 存活所有权和 GUI 存活所有权是三层独立门禁,不得用一个布尔值互相替代: - -| 所有权 | 持有者 | 持久/运行时记录 | 失效行为 | -|---|---|---|---| -| project execution owner | 当前实际执行 Runtime 写入和调度的 Runner/进程 | 项目私有 owner record:`ownerInstanceId`、单调 `ownerGeneration`、lease 到期点 | 取得前不扫描、不写入、不修复;lease 失效后旧 owner 被 fencing,不能继续提交。 | -| Runner owner | 当前 Runner 进程 | Runner boot identity、drain 状态和 heartbeat | Runner drain 或进程失活时停止新调度;恢复只能由新 boot 在重新取得 project owner 后执行。 | -| GUI-owner | 当前授权 Runner 存活的 GUI 会话 | 现有 GUI-owner lease/heartbeat 与 release 协议 | heartbeat 到期或收到 release 后停止新调度;不撤销已持久 Runtime 事实,不把 GUI 断线当作任务取消。 | - -owner record 的取得、续租和释放使用同一项目锁内的 CAS;generation 每次成功换主递增,旧 generation 的写入返回 `OWNER_FENCED`,不得覆盖新 owner 的 ledger、Runtime state 或 projection。lease 到期不能只凭本地时钟判定可接管:新进程必须先取得 owner,再在锁内复核 Runner drain、GUI-owner 和 manifest projectId。时钟只用于 lease 超时提示,CAS/generation 才是权威。 - -调用 capability 与生命周期 owner 是两套轴: - -| 调用方 | 可读 | 可写/自动动作 | -|---|---|---| -| Public User GUI/CLI | Public Snapshot、Public event、自己的 command result/conversation | `submit_intent/answer`、用户 audience 的 `approve`、cancel、ContinueRun/RetryTerminalRun;必须通过项目权限和当前 active session 校验 | -| Developer capability | Public + Developer Snapshot | Developer audience ToolApproval、显式 ReconcileRun 和受信任管理面;仍受 project owner/fencing 约束 | -| Runner internal capability | Public projector、durable runnable/recovery records | 只执行已持久 wake/recovery intent;不能伪装 Public Consumer 生成用户 answer/approve/requestChanges | - -GUI-owner 是 GUI 启动 Runner 的生命周期 lease,不是 GUI 的业务特权。V1 明确**不提供无 GUI CLI 写入的 headless control lease**:CLI 可以读取 Public Snapshot、事件和自己的 command result;CLI 写入只有在已存在且有效的 GUI-owner/Runner 上转发时才可执行,否则在写入 `prepared` 前返回 `OWNER_UNAVAILABLE`。不得为让 CLI “可用”而绕过 GUI-owner,也不得把本轮协议平等表述成无 GUI 常驻 Runner;后续若实现 headless lease,必须提升/协商生命周期能力合同并新增 fencing fixture。 - -调度 worker 的顺序固定为:取得/续租 project owner → 检查 Runner drain 与 GUI-owner → 从 durable wake/runnable 索引取一项 → 在 dequeue 前再次校验 owner generation 和项目状态 → 以同一 operation identity 调用 Runtime。任何检查失败都不得先 dequeue 后补救;旧 worker 在失去 generation 后的结果一律按 fencing 处理并进入既有 reconciliation 路径。 - -### 1.2 出向事件、有序性与重连 - -```rust -struct AgentRuntimeOutboundEnvelope { - schema_version: String, - event_id: String, - sequence: u64, - cursor: String, - snapshot_revision: u64, - project_id: String, - // 项目级事件可为空;有值时只作为当前 Supervisor 的刷新定位提示。 - agent_id: Option, - session_id: Option, - run_id: Option, - event: AgentRuntimeOutboundEvent, -} - -enum AgentRuntimeOutboundEvent { - SnapshotChanged, -} -``` - -V1 将公开事件刻意收窄为项目级失效通知。`agentId/sessionId/runId` 只在事件生成点存在当前 Project Supervisor 且能从同一投影线性化点确定时填充;项目级恢复、空 Supervisor 或多 Run 影响事件保持为空。它们不是状态载荷,也不能替代 Snapshot 中的当前身份。事件不携带 interaction、artifact、terminal 或 error 状态载荷;这些内容全部从完整 Public Snapshot 读取,避免事件类型演变成第二个 read model。规则如下: - -1. `eventId` 标识一次已提交的 Public Snapshot revision;同一项目同一 revision 的重建/重投沿用同一确定性 ID。投递至少一次,Consumer 按 ID 去重。 -2. `sequence` 在 `projectId` 事件流内从 1 严格递增,且一次有效 Public revision 最多对应一个 sequence。初始空投影的 revision 为 0、cursor 为流起点且不产生事件;第一次真实 Public 变化提交 revision/sequence 1。`cursor` 是绑定同一项目和 sequence 的不透明日志位置,不能跨项目使用或解析。 -3. Projection journal 的业务事实与投影提交顺序按 1.1.2 冻结;事件日志追加只允许在 Snapshot 可完整读取后进行。若崩溃在两步之间,恢复只补同一 revision 的唯一事件;不生成第二 revision 或第二 eventId。 -4. 一个 envelope 引用的 revision 发布时必须已经可读。Consumer 收到后读取完整 Snapshot,只接受 `snapshotRevision >= envelope.snapshotRevision`;本地已有更高 revision 时忽略提示。短暂读不到目标 revision 时有界重读,仍失败则显示结构化暂态错误,绝不合并事件载荷。 -5. 首次读取 Snapshot 得到与该读取线性化点一致的 `eventCursor`,随后从 `afterCursor` 订阅;读取与订阅间发生的更新会出现在补读结果中。断线后沿用最后确认处理的 cursor 补读。sequence 回退或缺口只触发补读与全量刷新,不猜测状态。 -6. 每个项目至少保留最近 256 条 envelope。cursor 未知、属于其它项目或已过窗口时返回 `CURSOR_INVALID` / `CURSOR_EXPIRED`;Consumer 重新读取完整 Snapshot,并从新 `eventCursor` 继续订阅。 -7. Snapshot 才是状态事实;事件日志只承担通知、缺口检测和审计定位,不能独立还原 Runtime 状态。 - -### 1.3 五个写命令、结果合同与幂等状态机 - -| 命令 | 吸收的旧命令 | V1 业务输入 | -|---|---|---| -| `submit_intent` | CLI `Reply/Execute` 与 `start_*` / `steer_*` | `Conversation` payload 为用户消息、目标 Project Supervisor sessionId、入口 `intentKind` 与公开 runProfile;`BuiltinCommand` payload 为 command line 与 expected parser version;Shell 按 parser/intent policy matrix 判定 direct reply/start/steer/reject,source 由 transport 派生 | -| `answer` | `answer_*_user_input` | `interactionId + responseId + answers` | -| `approve` | `confirm_*` / `reject_*` | `interactionId + responseId + decision(approve/reject/requestChanges)`;`requestChanges` 可携带有界意见 | -| `cancel` | `cancel_*` | 当前公开 Project Supervisor `runId` | -| `resume` | 旧 `resume_*` / `retry_*` 生命周期入口 | 明确 tagged intent:继续同一 durable run、针对终态失败创建 successor run,或由受信任 capability 进入人工 reconciliation;timer/lane/schedule 属于 Runner 内部 wake,不是公开 resume | - -所有写命令都包含: - -```rust -struct AgentRuntimeCommandMeta { - schema_version: String, - project_id: String, - request_id: String, -} - -struct InteractionResponseMeta { - interaction_id: String, - response_id: String, - expected_interaction_revision: u64, - session_id: String, - expected_session_revision: u64, -} - -enum AgentRuntimeIntentKind { - CreateFromPrompt, // createFromPrompt - ContinueProject, // continueProject - CreateFromTemplate, // createFromTemplate - ImportExistingDesign, // importExistingDesign -} - -// Shell 私有路由审计;不要求 Consumer 解析 command name。 -enum AgentRuntimeIntentRoute { - BuiltinCommand { - parser_version: String, - command_name: String, - }, - Conversation, -} - -enum ApprovalDecision { - Approve, // approve - Reject, // reject,终止当前审批链 - RequestChanges, // requestChanges,带意见退回同一工作链重做 -} - -struct AgentRuntimeCommandResponse { - schema_version: String, - request_id: String, - request_fingerprint: String, - replayed: bool, - observed_snapshot_revision: u64, - result: T, -} - -enum IntentDisposition { - DirectReply, // directReply - Start, // start - Steer, // steer:可在当前安全 Provider 边界中接管 - SteerDeferred, // steerDeferred:保留当前 action/approval,receipt/barrier 后消费 -} - -enum AgentRuntimeCommandAck { - IntentAccepted { - disposition: IntentDisposition, // directReply | start | steer | steerDeferred - accepted_run_id: Option, - steer_id: Option, // Shell prepared 后为 Steer 映射的现有 steer ledger 身份 - response_message_id: Option, - runtime_status_message_id: Option, // Start 才有;Runtime-owned status message - }, - InteractionAccepted { - interaction_id: String, - decision: Option, - follow_up_interaction_id: Option, - }, - CancelAccepted { run_id: String, cancel_operation_id: String }, - ResumeAccepted { - mode: AgentRuntimeResumeMode, // continueRun | retryTerminalRun | reconcileRun - predecessor_run_id: String, - accepted_run_id: Option, // RetryTerminalRun 才分配 successor runId - affected_run_count: u32, - }, - InteractionRequired { interaction_id: String, interaction_revision: u64 }, -} - -struct AgentRuntimeCommandError { - schema_version: String, - code: AgentRuntimeCommandErrorCode, - kind: AgentRuntimeCommandErrorKind, - retryable: bool, - message: String, - request_id: Option, - request_fingerprint: Option, - replayed: bool, - observed_snapshot_revision: Option, - interaction_required: bool, -} -``` - -`submit_intent` 仍必须保留现有项目的 `/` 内置命令语义,但解析权不能留在 GUI/CLI。Shell 在 interaction kernel 和 intent policy matrix 之前,使用 capability 声明版本的同一 Rust parser 对规范化 message 做一次确定性路由:普通文本只能进入 `Conversation`;不含 absolute host locator、`file://` 或本地句柄的项目级 slash 才能进入 `SubmitIntentPayload::BuiltinCommand`。经项目相对路径安全校验的 resource path 不属于 host locator,但仍必须拒绝绝对路径、`..` 和 symlink escape。路径、窗口和全局配置等本地管理命令必须在同一 parser 中先产出 `LocalManagementRoute`,先进入 `AgentRuntimeLocalManagementOperationRecord`,不能伪装成 `submit_intent` 或用空 `projectId/sessionId` 进入 Runtime ledger;若该 local route 在解析后确实产生项目级领域副作用,则必须在同一 project lock 下建立关联的 `AgentRuntimeBuiltinManagementOperationRecord`,把 `projectOperationRef` 双向固化,但仍不得伪造 conversation user message 或进入 Public conversation。BuiltinCommand 只提交 `command_line + expected_parser_version`,不再伪装成 `continueProject + runProfile`,也不携带 attachments、entry binding 或 Runtime intent/profile;项目级 BuiltinCommand 才进入 `submit_intent` 的 command ledger。命令需要的 project/session scope 由 Shell 在锁内从当前 capability/管理面取得。expected parser version 必须是调用方从当前 capability 原样读回的版本:格式/版本不支持返回 `PROTOCOL_VERSION_UNSUPPORTED`,与当前 capability 不一致返回 `TARGET_STALE`;一旦写入 prepared,parser version、规范化 command line 和 route 固化在 ledger,恢复/重放不得重新按新 parser 解释同一文本。未知命令仍可产生有界、持久 direct reply,不落成 Runtime task,但只有在 host-locator/path safety 检查通过且不含绝对路径、`file://` 或无法分类的本地句柄时才允许保存原 command line;未知命令携带这类输入必须在 ledger 前 `INVALID_REQUEST`,不能以 direct reply 回显或持久化原文。只读/说明类内置命令可走 `DirectReplyDelivery`;需要预览、文件、工具或其它副作用的内置命令必须创建既有类型的稳定 Interaction,由 `answer/approve` 或受信任管理面继续,不能在 parser 内直接绕过确认;若最终需要 start/steer/resume/cancel,必须调用同一 Shell 内部能力与 project lock/ledger,不得回退旧公开命令。存在 Open/Resolving interaction 时,内置只读命令是否可回复、会产生新 interaction/副作用的命令是否 `TARGET_BUSY`,由逐命令 fixture 冻结;V1 禁止嵌套第二个 User interaction。这样 `/preview` 仍只生成 `preview.start` 确认而不会成为自主构建任务,GUI/CLI 也不再各自维护 slash 分支。 - -现役 slash catalog 按当前 `apps/ai-game-creator-shell/src/App.tsx` 与 `projectSummaryConstants.ts` 冻结为以下路由;新增命令必须先更新 Rust parser、capability catalog、GUI/CLI golden fixture 和本表,不能落入未知命令的偶然行为: - -| 路由类别 | 现役命令(完整命令名;带参数的参数形态保持现有帮助文案) | 协议行为 | -|---|---|---| -| 只读 direct reply | `/help`、`/capabilities`、`/能力`、`/audit`、`/审计`、`/status`、`/llm-status`、`/llm-routes`、`/brief`、`/goal`、`/progress`、`/spec`、`/mvp`、`/pitch`、`/demo`、`/rules`、`/tutorial`、`/mobile`、`/compatibility`、`/accessibility`、`/localization`、`/performance`、`/polish`、`/risks`、`/blockers`、`/ready`、`/evidence`、`/deps`、`/revise`、`/privacy`、`/audience`、`/invite`、`/bug-report`、`/survey`、`/cover`、`/screenshots`、`/trailer`、`/faq`、`/post`、`/store`、`/media-kit`、`/release-notes`、`/known-issues`、`/criteria`、`/groups`、`/balance`、`/budget`、`/qa`、`/changes`、`/review`、`/context`、`/timeline`、`/handoff`、`/next`、`/guide`、`/plan`、`/todo`、`/publish`、`/listing`、`/playtest`、`/test-plan`、`/feedback`、`/retention`、`/share`、`/tasks`、`/agents`、`/agent-conversations`、`/agent-memories`、`/trace`、`/loop`、`/agent-status`、`/history`、`/files`、`/assets`、`/credits`、`/art`、`/audio`、`/artifacts`、`/run-artifacts`、`/passes`、`/runs`、`/run-files`、`/internals`、`/logs`、`/checkpoints`、`/diff `、`/policy`、`/read `、`/exports`、`/preview-status`、`/memory [scope]`、`/commands`、`/limited-commands` | 按 parser route 选择交付:`RuntimeBuiltinCommand` 的 direct-reply 分支先持久化一次用户 command line,再提交 `DirectReplyDelivery`;`LocalManagementRoute` 改用 `AgentRuntimeLocalManagementResponse::Succeeded { meta, result: Reply }`,不写 Public conversation/response stream;两者均不创建 Runtime run。文件/Runtime 摘要按各自 read capability 脱敏;有 Open/Resolving interaction 时不得偷偷推进副作用。 | -| 交互或管理动作 | `/project <绝对路径>`、`/generate `、`/draft `、`/index`、`/checkpoint`、`/smoke`、`/restore `、`/policy-deny `、`/policy-allow `、`/policy-confirm `、`/policy-auto `、`/agent-policy-deny `、`/agent-policy-allow `、`/agent-policy-confirm `、`/agent-policy-auto `、`/asset-register [kind] [mediaType]`、`/run`、`/export`、`/open-project`、`/show-project`、`/switch-project`、`/open-preview`、`/preview-stop`、`/remember [scope] `、`/memory-set [scope] `、`/forget-memory [scope]`、`/canvas `、`/sync-canvas-project `、`/generate-art `、`/import-canvas-asset `、`/import-canvas-export ` | `/project`、`/open-project`、`/switch-project`、`/config` 是本地管理面;其中 `/project <绝对路径>`、`/import-canvas-export ...` 等 host-locator 参数只能进入 trusted local locator resolver,不能进入 Public conversation、Snapshot、事件、错误或 request fingerprint 原文;`/asset-register`、`/import-canvas-asset`、`/read` 的 path 必须先证明是项目内安全相对路径,不能接受绝对路径、`..` 或符号链接逃逸。其它命令按现有管理面权限/CAS/确认合同执行。资源登记、文件导入、记忆写入和 External Editor 调用都必须有稳定 operation identity,失败/重放不得生成第二副作用;不得把这些管理动作伪装成 Runtime task。`/diff`、`/policy`、`/read` 已列入只读 direct reply,不创建 Interaction。 | -| CLI-only 管理/观察别名 | `/resume`、`/goal <目标>`、`/goal status`、`/goal edit <目标>`、`/goal pause`、`/goal resume`、`/goal clear`、`/compact`、`/mcp`、`/quit`、`/exit` | 这些命令来自 `swarm_cli/input.rs`,必须纳入同一 Rust parser 的显式 catalog,不能依赖 GUI 未知命令 fallback。精确 `/goal` 已在上一行作为 direct read 列出;`/goal status` 同样是 direct read。`/resume` 是既有 Runtime recovery scan + observe 入口,不等于模糊的 `ResumeCommand`,只能按当前 session/run capability 恢复并观察;`/goal <目标>`、`/goal edit <目标>`、`/goal resume` 是带 Goal ID+revision CAS 的管理 mutation,其中现有 CLI 会在 mutation 成功后继续等待/启动该 Goal 对应 Runtime turn,迁移后必须通过同一 Shell start/steer/lineage handler,不能旁路创建第二个 Supervisor run;`/goal pause`/`/goal clear` 只改变 Goal 状态,不自动取消或终止 Runtime;`/compact` 只调用既有 context-compaction 管理合同,不创建 task/provider;`/mcp` 是 direct reply;`/quit`/`/exit` 只关闭 CLI 观察 transport,不写 conversation/Runtime ledger。 | -| 本地配置/窗口动作 | `/config` | 只打开现有独立配置面板,不写 Runtime ledger、不持久化为 Runtime task;配置保存继续走现有 config CAS/secret boundary。 | -| Agent run control 管理动作 | `/agent-kill`、`/agent-retry`、`/agent-resume [说明]` | 这些 slash 在当前 `App.tsx` 中调用既有 `control_agent_run`:`agent.kill` 只更新 legacy run trace/activity/output/context,`agent.retry` 和 `agent.resume` 从最近 trace 的 goal 启动新的本地生成 run;它们不能伪装成五命令的 `cancel`、`RetryTerminalRun` 或 same-run `ContinueRun`。parser 只生成带精确 legacy trace target、action 和有界 detail 的 management operation;如果迁移后确实要控制正式 Runtime,必须另有 capability 显式选择五命令,并写 `RunLineageRecord`,否则只返回管理动作结果。`/agent-resume` 的说明作为 prompt detail 保存,不能从文字猜 Runtime mode。 | -| Preview sibling contract | `/preview` | 只进入既有 `preview.start` authorization/confirmation 链;不得进入 `submit_intent` Conversation、自动创建 Supervisor run 或从 `nextStep` 猜授权。`/run` 若同时请求自检和预览,先执行既有静态检查,预览部分仍复用同一 `preview.start` 链;“准备启动预览”的 UI 提示只能来自该 Interaction/管理面状态,不再额外持久化一条 assistant ack。 | - -关键命令必须同时满足以下“单一协议落点”矩阵;表外别名只能先进入同一 parser catalog,不能由 GUI/CLI 增加第二种解释: - -| 命令族 | parser route | scope | durable 事实 | delivery / recovery | -|---|---|---|---|---| -| `/help`、`/llm-status`、`/mcp` | `LocalManagementRoute` | 无 project/session | 纯 direct reply;`/mcp` 不落 ledger | `AgentRuntimeLocalManagementResponse::Succeeded { meta, result: Reply }`;不读写 Public Snapshot | -| `/config` | `LocalManagementRoute` | 无 project/session | 既有 config CAS/secret boundary | 独立配置面板结果;不进入 Runtime ledger 或 conversation | -| `/quit`、`/exit` | `TransportOnly` | 无 project/session | 无 durable record | `TransportClosed`;不写 conversation/Runtime ledger | -| `/goal`、`/goal status` | `LocalManagementRoute` | 当前 project/session(缺失时只返回 scope 错误) | Goal read model;不创建 Runtime operation | local/CLI readback;不触发 Runtime turn | -| `/goal <目标>`、`/goal edit/pause/resume/clear` | `LocalManagementRoute` | project + active session + Goal ID/revision | 先写 `AgentRuntimeLocalManagementOperationRecord`(无 conversation user message),需要项目副作用时再以唯一 `projectOperationRef` 关联 `AgentRuntimeBuiltinManagementOperationRecord`;会产生 Runtime turn 时关联 `lineageRef`;现有冻结 Goal Contract 根 Run replacement primitive 仅允许由该管理 route 调用 | operation readback;同 requestId 重放,未知结果不重做;Runtime admission/replacement 必须走同一 project lock、operation identity 和 lineage,普通 `submit_intent` 不得复用 | -| `/resume` | `LocalManagementRoute` | project + session/run recovery target | recovery-observe operation;不创建 Public `ResumeCommand` | 按精确 session/run/revision 扫描并观察;同一 operation identity 恢复,不能借自然语言或最近 run 猜 target | -| `/agent-kill`、`/agent-retry`、`/agent-resume` | `LocalManagementRoute` | project + 精确 legacy trace target | legacy management operation;retry/resume 只有取得正式 lineage 后才关联 successor | operation readback;不得映射为 cancel/ContinueRun/RetryTerminalRun | -| `/project `、`/import-canvas-export ` | `LocalManagementRoute` | 初始无 project;解析后绑定 project | `AgentRuntimeLocalManagementOperationRecord`,解析后关联 project operation | local resolver/OS handle 结果;路径原文不入 Public/Runtime delivery,local→project link 必须唯一 | -| `/asset-register`、`/import-canvas-asset` | `RuntimeBuiltinCommand` 的 `ManagementAction` 分支 | project + safe project-relative path | resource/External Editor 既有 CAS + project management operation | project operation readback;相对路径不等于 host locator,仍须拒绝绝对路径、`..` 和 symlink escape | -| `/read `、`/diff`、`/policy` | `RuntimeBuiltinCommand` 的 direct-reply 分支 | project read capability | command ledger + 一次 user message;不创建 Runtime run | `DirectReplyDelivery`;超时按同 requestId 读回,不把读取结果写入 Snapshot | - -该矩阵明确:没有 project/session 的命令不伪造五命令 `meta.projectId`,有 project scope 的管理命令也不伪装成 Runtime run;resource path 只有在项目相对路径安全校验通过后才可属于 RuntimeBuiltinCommand,不得把 absolute host locator、`file://` 或本地句柄混入该 route;任何命令若无法唯一落到 route、scope、durable record 和 delivery/recovery 四项,必须在协议冻结前补充 fixture,而不是由 Consumer fallback。local/global command 在调用前必须从受信任 transport 取得 `AgentRuntimeLocalManagementCapability`,并原样提交 `capabilityId + expectedParserVersion`;Shell 重新校验 capability scope、project/session/target revision 和 principal。capability 不存在返回 `CAPABILITY_NOT_FOUND`,targetRef 漂移返回 `TARGET_STALE`,parser version 不支持返回 `PROTOCOL_VERSION_UNSUPPORTED`,不得从当前窗口、最近 Goal 或最近 run 猜目标。local operation record 必须保存 originatingCapabilityId(仅审计)与 authorizationPrincipalRef/authorizationScopeFingerprint;读回时使用当前 capability 重新授权,不要求 capabilityId 跨 owner boot 稳定;不同权限调用方不能借相同 requestId 读取本地路径或管理结果。 - -项目级 management route 在自身领域 CAS 之前先写 `AgentRuntimeBuiltinManagementOperationRecord`;Goal mutation 若会产生后续 Runtime turn,operation result 必须同时保存 `goalId + goalRevision + resultingRunId/lineageRef`,并由同一 project lock 串起 Goal CAS 与 Runtime admission。现有 `goal_contract_root_steer`/replacement primitive 只作为该 Goal management operation 的内部实现:必须复用同一 `projectOperationRef`、预分配的 resulting run identity 和 lineage,不能从 `submit_intent` 的 `Steer` disposition 进入,也不能把 replacement 回显成 same-run steer;同 requestId/同 argument fingerprint 回放已保存结果,异指纹返回 `IDEMPOTENCY_KEY_REUSED`,`prepared/executing` 返回 `COMMAND_IN_PROGRESS`,`outcome-unknown` 禁止重新执行;核对流程只能沿同一 `projectOperationRef` 推进 `needsReconciliation`,并写入 `reconciliationOperationId` 后闭合为确定的 `succeeded/rejected`。领域 writer 成功但 operation result 未闭合时由 owner 按 operation identity 补结果;不能以现有 UI pending state 代替 durable operation。 - -Agent run control management action 的 prepared payload 必须至少保存 `legacyTraceIdentity`(项目、trace/run 标识及其 revision/digest)、`action=kill|retry|resume`、`detail`(仅 resume,按同一 message 上限过滤)和新 generation operation identity。`kill` 的成功只表示 legacy trace 已写入 killed,不得向 Public Runtime Snapshot 投影 `cancelled`;`retry/resume` 只有新 generation 已取得正式 Runtime lineage 并完成唯一 predecessor/successor 记录后,才能向 Runtime read model 暴露 successor,否则只作为管理面结果。旧 trace 缺失、被替换或 revision/digest 漂移时返回 `TARGET_STALE`,禁止对“最近 run”重新猜 target。 - -所有进入项目/对话协议的 slash route 都拒绝 attachments、entry binding 和自然语言补偿;参数缺失/重复/未知字段在对应 ledger 前 `INVALID_REQUEST`,未知命令则仅生成固定长度 direct reply。进入 `submit_intent`/DirectReplyDelivery 的 command line,其用户消息只持久化一次,后续 delivery/operation 均引用该 `conversationUserMessageId`;`LocalManagementRoute`/`LocalTransportReply` 不写 Public conversation,原始 path 只留在 resolver/OS handle 边界内;`/quit`、`/exit` 这类 transport-only 命令是显式例外,不写 conversation/Runtime ledger。GUI 和 CLI 必须使用同一 Rust parser 和相同规范化/错误合同,公共命令得到相同 route、interaction kind、operation identity 规则和副作用计数;CLI-only 别名只能通过上表显式映射到不同 management/observation route,不能在 GUI/CLI 各自增加隐含解析分支。 - -`submit_intent` 的 `Conversation` payload 要求 Consumer 先经现有会话管理面取得明确的 Project Supervisor `sessionId + expectedSessionRevision`;Shell 锁内验证该 session/revision 属于本项目/当前 Supervisor,active session 已变化则返回 `TARGET_STALE`,本轮不把会话 CRUD 隐式塞入 Runtime 命令。BuiltinCommand 的项目级命令由 Shell 根据当前 capability/管理面 scope 取得同一上下文;无项目上下文的 `/help`、`/config`、`/llm-status` 等本地/全局命令不得伪造 sessionId。请求中的 `intentKind` 是 Consumer 对用户实际入口的业务陈述,不是权限或 source 主张;V1 固定为 `createFromPrompt`、`continueProject`、`createFromTemplate`、`importExistingDesign`,并与执行方式正交:`runProfile` 只表示怎么执行,`intentKind` 表示从哪个业务入口开始。Shell 在锁内按项目 manifest、当前 session 和策略校验 intent,可接受、拒绝或将其归一到受支持的内部分支;Consumer 不能自报 source。受信任 transport 使用 `(transport, intentKind) -> source` 的后端映射,映射和否决权始终在 Shell。这样同一 GUI 的不同入口即使 `message` 与 `runProfile` 相同,请求也不会逐字节相同。Shell 复用现有 interaction kernel 只决定 direct reply/execute,再由冻结 intent policy matrix 对 execute 决定 start/steer/reject;direct reply 仍只写 conversation/response stream,不伪造 Runtime Snapshot 变化,其稳定 responseMessageId 预先写入 request ledger。显式 `resume` 命令服务按钮/自动化的结构化生命周期意图;自然语言“继续”只作为普通 message 参与 reply/steer,不得触发 ContinueRun、RetryTerminalRun 或 reconciliation。现有 `agent/interaction.rs` 的 `runtime_resume` tool / `AgentInteractionAction::Resume` 属于迁移前内部分支:P5 必须禁止模型输出直接升级为 Public ResumeCommand;若保留该内部 action,只能在同一 Shell 内归一为普通 steer/reply 或显式 recovery-observe,并保留原 request/message identity,不能绕过新 capability、ledger 和 target revision。 - -V1 的并发不变量是:一个 projectId 同一时刻最多只有一个非终态 Project Supervisor run;专业 Agent/child run 由该 Supervisor 管理,不计作第二个公开 Supervisor。`BuiltinCommand` 先按上一段冻结规则收束,不进入下面的 Runtime matrix;所有 `Conversation` 路由的 `submit_intent` 在 project lock 内串行读取当前 run,再按已注册、带版本的 intent policy matrix 决定 `directReply | start | steer | reject`。对于已由 interaction kernel 判定为 `directReply` 的消息,Shell 先执行下表的“开放交互门禁”:存在 Open/Resolving interaction 时统一 `TARGET_BUSY`,否则不产生 Runtime run,直接走第 1.5 节交付合同;只有 `execute` 分支进入下表。这样 direct reply 不是 Consumer 自己绕过 matrix 的第二条路径。 - -V1 execute 分支的 intent policy matrix 冻结如下;表内每格只有一个 disposition,`steer` 表示在同一 Project Supervisor run 内调整当前执行,必须保持原 runId,不得创建 replacement 或第二个并行 Supervisor run: - -| `intentKind` | 无 run | 活动 run(`running`) | 等待交互(`waiting`) | 已有终态(`completed/failed/cancelled`) | `needs-reconciliation` | -|---|---|---|---|---|---| -| `createFromPrompt` | `start` | `steer` | `TARGET_BUSY` | `start` | `NEEDS_RECONCILIATION` | -| `continueProject` | `start` | `steer` | `TARGET_BUSY` | `start` | `NEEDS_RECONCILIATION` | -| `createFromTemplate` | `start` | `TARGET_BUSY` | `TARGET_BUSY` | `start` | `NEEDS_RECONCILIATION` | -| `importExistingDesign` | `start` | `TARGET_BUSY` | `TARGET_BUSY` | `start` | `NEEDS_RECONCILIATION` | - -`createFromTemplate` 必须携带且锁内复核 `entry_binding=Template`;`importExistingDesign` 必须携带且锁内复核 `entry_binding=ExistingDesign`;另外两种必须省略 `entry_binding`。绑定的 revision/digest 不存在、已失效或不能证明来源时返回 `ARTIFACT_BINDING_UNAVAILABLE`,而不是从 `message` 猜测模板/设计。所有 `start` 的 `runId` 只在 request ledger 的 `prepared` 阶段分配;matrix、绑定校验和并发竞争均纳入 P0 每格 fixture。 - -上表的 `活动 run/等待交互` 还必须按现有 V1.13/V1.23 barrier 细分,不能把所有 `waiting` 当成同一种状态: - -| durable barrier | `createFromPrompt/continueProject` | 其它新入口 | 规则 | -|---|---|---|---| -| active root Run 已绑定冻结 Goal Contract | `TARGET_BUSY` | `TARGET_BUSY` | 该行优先于其它 barrier;普通 `submit_intent` 不调用现有 replacement steer,返回稳定错误并引导使用 `/goal edit` 等显式 Goal management mutation;interaction kernel 判为 direct reply 时仍按开放交互门禁交付 | -| planning/final-reply Provider await | `steer` | `TARGET_BUSY` | 只中断可中断的纯 Provider await;不 abort worker,不重放 action | -| pending confirmation / approved / executing action | `steerDeferred` | `TARGET_BUSY` | 保留原 action fingerprint、确认和 process session;terminal receipt 后再消费 steer | -| `waiting-for-user-input` | `TARGET_BUSY` | `TARGET_BUSY` | 必须精确 `answer`,不能用普通自然语言绕过问题 | -| `waiting-for-isolated-join` | `steerDeferred` | `TARGET_BUSY` | 保留 join barrier,不创建 joinRun/旁路 Provider | -| timer/lane/schedule wait | `steerDeferred` | `TARGET_BUSY` | 只写 steer ledger,唤醒后按 cursor 消费,不把 timer 当作用户 resume | -| `pausedByUser` / `cancelling` / `finalizing` | `TARGET_BUSY` | `TARGET_BUSY` | 必须分别使用 ContinueRun、cancel 读回或等既有 finalization 收束 | - -当 matrix 选择 `steer` 时,Shell 必须先在 project lock 内证明 active root Run 未绑定冻结 Goal Contract;命中冻结合同一律以 `TARGET_BUSY` 持久化业务拒绝,不能进入现有会取消旧树并创建 replacement Run 的特殊 steer primitive。通过该门禁后,Shell 才在同一 `prepared` 记录中由 `sha256(RFC 8785 canonical JSON(["runtime-steer", projectId, requestId, targetRunId]))` 确定性派生并保存 `steerId`,同时保存并复核 V1.13 要求的 `agentId/taskId/sessionId/runId/source` 完整身份;该 seed 只负责稳定生成公开 steerId,不能替代 V1.13 的 source/audience 校验。再通过现有 V1.13 `steer` ledger 的 `prepared → conversation-persisted → queued → applied → closed` 合同提交;重试永远复用这个派生身份。`requestId` 负责公开命令幂等,`steerId` 负责同一 run 的追加指令身份,二者不能互相替代。Steer 的容量、正文脱敏、`appliedSteerCursor`、Provider 中断和 finalization 竞态沿用 V1.13;已有 action/confirmation/process session/side effect 不因普通 steer 被暗中取消,旧计划只能在安全边界失效。 - -`source` 只用于受信任归因、权限和审计,不能替代 intentKind 或改变 start/steer/retry 业务路由。source 不进入 requestFingerprint,但 command ledger 必须保存内部 `authorizationScopeFingerprint`;每次重试/读回仍先重新授权,只有当前 principal 对同一项目、同一 audience 拥有等价或更高有效 capability 时才允许回放,否则返回 `PERMISSION_DENIED`。这样 GUI 与 CLI 可以用同一 requestId 安全恢复,但低权限调用方不能借已存在 ledger 结果越权读回。 - -`cancel` 是持久化取消意图,不等同于调用返回时 Run 已经进入终态;`CancelAccepted` 只表示取消受理并返回同一 runId 与稳定 `cancelOperationId`,Consumer 必须继续读 Snapshot/事件观察最终收束。`cancelling` 是独立公开状态,不能让 UI 把仍在执行的 Run 显示成 `cancelled`,也不能在取消期间再次发起普通 submit/retry。V1 取消矩阵冻结如下: - -| 目标状态 | Shell 行为 | 结果边界 | -|---|---|---| -| `queued/pending` 且尚未开始执行 | 写 cancel tombstone,阻止 dequeue,收束为 cancelled | 不产生 Runtime/Provider 副作用 | -| `running`、等待 child、等待 lane 或等待 timer | 写同一 operation identity 的 cancel intent,锁内固化当前 parent/child/action/process-session target set,向非终态 child 写 cancel tombstone,通知 Driver 中断并等待 durable finalization | 取消请求可先成功受理,不能提前伪造终态;child 未收束不能把 parent 标成 cancelled | -| `waitingForUserInput`、`waitingForPolicyApproval`、`waitingForDeveloperApproval` | cancel interaction 为 superseded/cancelled,再收束 Run | 不自动提交 answer/approve/reject | -| pending action、Provider/工具执行中或 `finalizing` | 只允许走现有 interruption/finalization 合同;无法证明副作用结果时进入 `needs-reconciliation` | 不因 cancel 创建第二 action、第二 Provider 请求或虚假 cancelled 终态 | -| `cancelling` 且已有 cancel operation | 读取并返回已有 `cancelOperationId`;同 requestId 回放原 ack,不创建第二 tombstone | 新旧 requestId 都指向同一取消操作,不重复中断或写第二终态 | -| `needs-reconciliation` / 外部结果未知 | 记录取消请求但不自动宣称已取消;由 reconciliation owner 判定 parent、child、action 和 process session 是否可安全收束 | 任一 target 外部结果未知时返回 `COMMAND_RESULT_UNKNOWN` 或等价结构化状态,禁止只收束 parent 掩盖 child 未知 | -| 已经是 `completed/failed/cancelled` | 同 requestId 回放原结果;不同 requestId 返回 `CANCEL_ALREADY_TERMINAL` | 零副作用、不可重新取消或隐式 retry | - -`CancelCommand.sessionId` 表示当前调用方的 active project session,不要求等于 Run 创建时的旧 session;只要 session handoff 已由会话管理面持久化并证明属于同一 Project Supervisor lineage,新的 active session 可以取消旧 session 遗留的 Run。session 切换不会复用旧 sessionId,也不会自动解绑仍在运行的 Run;没有有效 handoff/lineage 时返回 `TARGET_STALE`。没有 active session 的 owner recovery 只能走受信任 Runner/Developer reconciliation capability,不能由 Public Consumer 猜测恢复身份。 - -`submit_intent/cancel/resume` 不使用项目级 `snapshotRevision` 作为业务 CAS:无关的进度刷新不能让用户命令无效。它们在锁内以 payload 中的精确目标身份和当前 durable state 校验可执行性;`cancel` 的 run 已切换时返回 `TARGET_STALE`。`answer/approve` 使用 interaction 自身的 `expectedInteractionRevision`,而不是全项目 Snapshot revision;Public Snapshot 中的 interaction view 同时投影该值。命令返回值只是最小 ack 和操作完成时观察到的 revision,不携带 Runtime state;Consumer 成功或 `interactionRequired=true` 后都重读完整 Snapshot。 command ack 的 `responseMessageId` 只用于在既有 conversation/response-stream 管道定位 direct reply;对话正文仍通过原有 durable conversation read/stream 获取,不进入 Snapshot、事件或 command response。`observedSnapshotRevision` 是操作完成时已闭合的 Public revision,不表示命令结果本身是一份状态。 - -命令错误使用稳定 `code/kind/retryable/message/interactionRequired`,命中 ledger 的错误还返回原 `requestFingerprint` 和 `replayed`;SnapshotError 只投影稳定 code/kind/retryable/interactionRequired,不携带命令身份或正文。Consumer 不解析中文 message。最小错误矩阵如下;未知错误码按不可重试失败关闭: - -| code | 语义 | retryable / Consumer 动作 | -|---|---|---| -| `PROTOCOL_VERSION_UNSUPPORTED` / `INVALID_REQUEST` / `PERMISSION_DENIED` | ledger 前的版本、格式或权限拒绝 | false;修正客户端/权限,不能原请求盲重试 | -| `TARGET_STALE` / `TARGET_BUSY` / `INTERACTION_STALE` / `INTERACTION_ALREADY_RESOLVED` / `CANCEL_ALREADY_TERMINAL` | 精确目标、并发槽位或 interaction 已变化 | false;重读 Snapshot,若仍需操作则新 requestId | -| `ARTIFACT_BINDING_UNAVAILABLE` | entry binding、输入附件或 requestChanges 的目标产物没有可复核的 immutable revision + `sha256` digest | false;只能等待既有资源/artifact lineage 补齐或改用不需要该 binding 的合法入口,不得猜测当前最新版本或静默使用最新资源 | -| `IDEMPOTENCY_KEY_REUSED` | 同 requestId/responseId 被不同内容复用 | false;视为调用方错误 | -| `COMMAND_IN_PROGRESS` | 同请求已有 live executor 或同 response 正在 Resolving | true;同 payload/requestId 读回或重试,不启动第二 executor | -| `COMMAND_RESULT_UNKNOWN` / `NEEDS_RECONCILIATION` | 已受理操作的外部结果无法证明 | false;重读并进入人工核对,禁止换 ID 自动重放 | -| `OWNER_UNAVAILABLE` / `TRANSIENT_UNAVAILABLE` | 尚未写入 prepared,当前没有合法 owner/transport | true;完全相同 payload/requestId 可重试 | -| `OWNER_FENCED` | 已受理 executor 失去 owner generation | false;Consumer 先读回同 requestId,由新 owner 按 ledger 恢复;若副作用无法证明则转 `COMMAND_RESULT_UNKNOWN`,旧 owner 不得继续写入 | -| `REQUEST_NOT_FOUND` | 当前 project ledger 没有该 requestId 的受理记录 | false;不泄漏项目存在性;调用方根据原 transport 结果决定是否用原 requestId 重试 | -| `CURSOR_INVALID` / `CURSOR_EXPIRED` | 事件补读起点非法或过期 | 不适用于写重试;全量读取 Snapshot 后换新 cursor | -| `INTERNAL` | 已脱敏的未分类内部失败 | false,除非未来细分为明确暂态 code | - -`retryable=true` 仅表示可用同一 requestId 重试同一请求;需要基于新 Snapshot 改变 payload 时必须使用新 requestId。 - - -### 1.3.1 请求结果读回与崩溃语义 - -`read_game_creator_agent_command_result(projectId, requestId)` 是只读恢复接口,不是第六个 Runtime 写命令。它返回项目内 request ledger 的以下稳定投影: - -```rust -struct AgentRuntimeCommandResultView { - schema_version: String, - project_id: String, - request_id: String, - request_fingerprint: String, - status: CommandLedgerStatus, - replayable: bool, - result: Option, - error: Option, - observed_snapshot_revision: Option, -} - -enum CommandLedgerStatus { - Prepared, // prepared - Executing, // executing - Succeeded, // succeeded - Rejected, // rejected - OutcomeUnknown, // outcomeUnknown;外部结果无法证明 - NeedsReconciliation, // needsReconciliation;仅由核对流程推进,不自动重试 -} -``` - -`result/error` 是随 `status` 绑定的严格 tagged-union 投影,不能出现未定义组合:`succeeded` 必须是 `result=Some、error=None`;`rejected` 必须是 `result=None、error=Some` 且 error 为已持久化的确定性业务拒绝;`prepared/executing` 必须是 `result=None、error=None`;`outcomeUnknown` 必须是 `result=None、error.code=COMMAND_RESULT_UNKNOWN`;`needsReconciliation` 必须是 `result=None、error.code=NEEDS_RECONCILIATION`。`observedSnapshotRevision` 只有在对应 Public projection 已闭合并可读回时才为 `Some`。任一组合不满足该不变量都按 corrupt record 隔离并进入 `outcome-unknown/needs-reconciliation`,不得让 Consumer 猜测结果。 - -`CommandLedgerStatus` 的 wire value 固定为注释中的 lowerCamelCase。`prepared`/`executing` 的 `replayable=true`,表示只能使用同一 requestId 继续或读回;`succeeded`/`rejected`/`outcomeUnknown`/`needsReconciliation` 为不可自动重做的结果,`replayable=false`。`outcomeUnknown` 不是可执行状态:它必须由受信任 reconciliation 流程转为 `needsReconciliation`;`needsReconciliation` 在核对完成前只能读回并返回 `NEEDS_RECONCILIATION`,核对流程确认权威结果后才可推进为确定的 `succeeded/rejected`。在 `outcomeUnknown` 或 `needsReconciliation` 下,任何同 requestId 调用都不得执行命令或创建新的副作用;所有转移都必须保留原 operation identity,并记录 reconciliation operation identity。 - -读回先做 locator/projectId/调用来源复核,再在 project command lock 内查询;不能跨项目按 requestId 搜索。`prepared`/`executing` 返回 `COMMAND_IN_PROGRESS` 或等价的 `status`,Consumer 继续用同一 requestId 读回;`succeeded`/`rejected` 永久返回已保存结果;`outcome-unknown` 返回 `COMMAND_RESULT_UNKNOWN` 并标记项目 `needs-reconciliation`;已进入核对终态的记录返回 `NEEDS_RECONCILIATION`。如果 requestId 从未被受理,返回不泄漏项目存在性的 `REQUEST_NOT_FOUND`;该错误只表示“本次调用没有留下受理记录”,调用方仍需根据原 transport 响应决定是否使用同一 requestId 重试,不能据此生成新 requestId 重放未知副作用。已进入 ledger 的确定性业务拒绝必须通过 `rejected` 结果读回,而不是依赖错误文字重新判断。 - -request ledger 的状态转移冻结为: +## 3. 新系统的一句话结构 ```text -prepared -> executing | rejected (后者仅限可证明尚未产生副作用) -prepared -> outcome-unknown (无法证明是否已开始执行) -executing -> succeeded | rejected (有权威 durable 证据) -executing -> outcome-unknown (外部结果无法证明) -outcome-unknown -> needs-reconciliation(仅受信任核对流程) -needs-reconciliation -> succeeded | rejected(仅核对确认权威结果) +GUI / CLI / Tests + 读取 Public Snapshot 与 Public Conversation + 提交五个公开写命令 + ↓ +Supervisor Shell + 校验身份、权限、版本、目标和幂等性 + 决定 direct reply / start / steer / reject / interaction required + 通过 Adapter 调用现有 Runtime 能力 + ↓ +Existing Runtime + 继续维护 task / state / action / provider / finalization / conversation / event 事实 ``` -每条记录保存 `recordRevision`、`ledgerVersion`、创建/更新时间、请求指纹、操作身份、执行者 generation、状态和完整结果引用;写入使用临时文件/同步/原子替换,恢复时按 record revision 与 ledger version 双重校验,损坏记录保留原始证据并阻止同 requestId 再执行。`replayed=true` 仅表示返回已持久化的同一结果,不代表再次执行。任何“Shell 已写入 prepared 但 transport 未收到响应”的情况都必须先读回;不能以 unknown-command fallback 或新 requestId 规避 ledger。 +四个角色的责任如下: -所有 durable 记录统一使用 `DurabilityCapability`,不把“rename 成功”当成跨平台持久化证明;V1 至少覆盖 command ledger、Builtin management operation、interaction、rework、projection、input envelope、Runtime status message、public event message、response delivery、RunLineage、Session/Handoff/ActiveSessionIndex/SessionRotation/HandoffManifest 和 local management operation: +| 角色 | 负责 | 禁止 | +|---|---|---| +| Consumer | 显示 Snapshot、提交 command、读取 Conversation | 根据 phase/文案自行选择 start、steer、retry、resume;直接解释私有 Runtime records | +| Supervisor Shell | 统一交互判断、命令受理、幂等、能力投影和错误映射 | 复制 Runtime 生命周期真相;把投影完成当成执行成功 | +| Runtime | 实际执行、恢复、finalization 和事实持久化 | 依赖 GUI 轮询推进状态 | +| Runner | 持 owner lock 时执行正式 Shell 写入、wake 和恢复 | 失去 owner 后继续写;依赖诊断 JSON 或本地时间接管 | -1. Unix:同目录创建临时普通文件 → 写完整 envelope → `fsync(file)` → 原子 rename/replace → `fsync(parent directory)`;临时文件、目标文件或目录是 symlink、类型错误或 schema/ledgerVersion 不完整时隔离为 `.corrupt.`,不覆盖旧证据。 -2. Windows:同目录临时文件 → `FlushFileBuffers` → 使用平台原子 replace(保留目标备份/ACL)→ 对目标句柄再次 `FlushFileBuffers`;若平台 API 或文件系统不能证明 replace 后持久化,能力报告为 `unsupported`,禁止 prepared→executing。 -3. 恢复只接受完整 envelope、单调 `recordRevision`/`ledgerVersion`、匹配 request/operation identity 和完整 checksum;发现新旧两个版本都存在时按 journal 状态选择唯一合法前缀,无法唯一选择就进入 `outcome-unknown/needs-reconciliation`,不“取最新文件”。 +--- -能力报告必须在 Runner boot 时持久化并绑定 `ownerBootId`;运行期间能力降级会停止新受理,但不回滚已提交事实。上述规则是安全门禁,不以“目标平台通常支持”替代 Unix/Windows crash-point 实测。 +## 4. 一次用户输入如何流动 -request ledger 是项目级私有 durable 记录,状态为 `prepared / executing / succeeded / rejected / outcome-unknown / needs-reconciliation`,并保存规范请求指纹、预分配的内部 operation identity、executor boot/generation 及完整权威成功或错误结果。恢复所需的用户消息/answers 只保存有界私有 payload 或指向既有 durable conversation/interaction record 的稳定引用,沿用现有内容安全、权限和脱敏规则;绝不复制到 Public Snapshot、事件或错误。requestId 提供幂等受理与结果读回边界,不承诺无法判定的外部副作用 exactly-once;这种窗口必须显式 outcome-unknown,核对完成后才可进入 needs-reconciliation 或确定结果。处理顺序固定: +1. Consumer 读取 `PublicSnapshot`。 +2. Snapshot 返回当前状态以及服务端生成的 command capability。 +3. 用户提交消息时,Consumer 调用 `submit_intent`,不调用 start/steer primitive。 +4. Shell 校验 schema、项目、Session、权限、requestId 和 capability target。 +5. Shell 持久化可读回的 request acceptance record。 +6. Shell 根据冻结 policy matrix 决定: + - DirectReply; + - Start; + - Steer; + - Reject; + - InteractionRequired。 +7. 需要 Runtime 执行时,Shell 通过 Adapter 绑定现有 run/steer/action/finalization identity;不复制这些对象的生命周期。 +8. Runtime 更新 durable facts。 +9. Public projector 从 facts 重建 Snapshot;`SnapshotChanged` 只提示 Consumer 重新读取 Snapshot。 +10. 文本交付通过 Public Conversation Adapter 按 source 回读;Snapshot 和 conversation 都不能互相推导对方的权威结论。 -1. transport 先 canonicalize root、验证本地项目授权、manifest `projectId` 与调用权限;版本/身份/权限失败发生在 ledger 之前,保证攻击者不能向任意项目写记录。 -2. Shell 在 ledger 前完成严格结构/数量/长度/UTF-8 字节上限和内容安全校验;失败返回 `INVALID_REQUEST` 且不落原始正文。通过后,项目级 Runtime command 用 RFC 8785 canonical JSON 规范化“schema version + command kind + projectId + 完整业务 payload(含 Conversation/BuiltinCommand variant、规范化 commandLine、expectedParserVersion、intentKind、entryBinding、immutable attachments、runProfile、resume tagged intent、interaction/response identity、decision、feedback、answers 和任何精确 target)”,计算小写 SHA-256。LocalManagementRoute 不进入项目 request ledger;它只对去掉 locator 的参数计算 `argumentFingerprint`,并把不含原文的 `locatorDigest` 单独固化。任何 fingerprint 都不得保存或回显原始路径;`requestId`、时间戳及 transport/source 字段不进项目业务指纹;当前有效 capability 另存内部 authorizationScopeFingerprint。Local command 的幂等键为 `(localScopeId, requestId)`;同 scope/同 requestId 必须保持 parser version、route、commandName、targetRef、locatorDigest 和 path-free arguments 这组**历史操作 identity**完全相同,才可回放;不同则 `IDEMPOTENCY_KEY_REUSED`。首次受理的 capabilityId 只作为 `originatingCapabilityId` 审计记录,不进入历史操作 identity;重试/读回可以使用 capability rotation 后的新 capabilityId,但当前 capability 必须重新证明同一 localScope、principal、route/target 的授权范围未被撤销且覆盖该操作,否则返回 `PERMISSION_DENIED`/`TARGET_STALE`。这些 parser/route/target 字段必须原样写入 `AgentRuntimeLocalManagementOperationRecord`,成为 prepared 后的历史 identity;当前 capability 只能用于重新授权,不能替代或重新解释已保存的 parser/route/target。恢复时任一历史字段不一致返回 `TARGET_STALE`,不得仅按新的 capability 或 commandLine 继续执行。local command 结果通过 `read_local_management_result(localScopeId, requestId)` 读回并重新授权;`prepared/executing/outcome-unknown/needs-reconciliation` 的恢复语义与项目 operation 相同,但绝不按 commandLine 原文或当前窗口重新解析路径。 -3. 在 project command lock 内先按 `requestId` 查 ledger,再做任何当前业务状态校验;但每次重放仍必须通过 project identity、principal 和 authorizationScopeFingerprint 兼容性复核。相同 ID/相同指纹且授权等价的 `succeeded` 或 `rejected` 直接返回原结果;不同指纹返回 `IDEMPOTENCY_KEY_REUSED`;`prepared/executing` 返回 `COMMAND_IN_PROGRESS`(可同 ID重试/读回);`outcome-unknown` 返回原 `COMMAND_RESULT_UNKNOWN`,不再执行;`needs-reconciliation` 返回 `NEEDS_RECONCILIATION`,只能读回或进入受信任核对流程,不得执行命令。 -4. 只有 ledger 未命中时才验证 target/interaction 当前状态,并在副作用前原子写 `prepared`;`submit_intent` 的 inputEnvelopeId/acceptedRunId/steerId、RetryTerminalRun 的 successorRunId、requestChanges 的 reworkOperationId、direct reply 的 responseMessageId 以及下游 Runner/Provider request identity 必须在 prepared 中预分配并在恢复时复用,不能在重试中生成第二身份。业务拒绝也原子落为 `rejected`,使同一请求重放得到相同结果。进入内部执行前转为 `executing` 并绑定 executor;完成内部状态写后必须先闭合对应 Public projection,再写入并回读 `succeeded/rejected` 权威结果和 `observedSnapshotRevision`。 -5. 崩溃恢复只能依据 ledger、executor 生命状态、Runtime journal 和既有 durable identity 前向闭合;仍有 live executor 时不得由第二执行者接管。能证明未产生副作用可用同一 operation identity 继续;能证明结果则幂等补投影/结果;外部结果不明则原子转为 `outcome-unknown` 并使项目进入 `needs-reconciliation`,禁止自动换 requestId 或重复入队。 -6. Consumer 对 transport 超时、`COMMAND_IN_PROGRESS` 或明确暂态错误只可重发完全相同 payload 和同一 requestId,或调用 `read_game_creator_agent_command_result(projectId, requestId)`;读回接口同样先完成 locator/project identity/权限复核,禁止跨项目扫描。 -7. V1 ledger 跟随项目 Runtime durable archive 生命周期保存,不按时间或条数隐式淘汰。若将来压缩,必须先设计持久 tombstone,使已淘汰 requestId 仍能失败关闭。 +### 4.1 五个 Public 写命令 -`RetryTerminalRun` 的 successor lineage 不再直接复用现有 `acceptedRunId` 或调用方传入的 `nextRunId`。当前实现中的 `agent.runtime.background_task.retry` 记录、`retryRunId` 和 `accepted_run_id` 只能作为迁移 adapter 的输入:adapter 必须在 project lock 内验证 `(projectId, agentId, taskId, predecessorRunId, retryRunId, sessionId)` 唯一且 predecessor 确为终态,然后写入新的 `RunLineageRecord`;`acceptedRunId` 仍只是响应中的实际 successor 回显,不是 lineage 事实。V1 `RetryTerminalRun` 不接受 `nextRunId`,successor runId 由 prepared 阶段生成并写入: +| 命令 | 用户含义 | Shell 负责决定的内部动作 | +|---|---|---| +| `submit_intent` | 提交消息或受支持的内置命令 | direct reply / start / steer / reject / interaction required | +| `answer` | 回答 UserInput | 校验 interaction revision、答案约束和 durable target | +| `approve` | 批准、拒绝或带意见返工 | 校验 audience、policy、artifact binding 和 rework identity | +| `cancel` | 取消精确 Supervisor Run | 校验 Session、Run 和取消矩阵;不伪造终态 | +| `resume` | 继续、重试或受信任 reconcile | 明确区分 ContinueRun、RetryTerminalRun、ReconcileRun | -```rust -struct RunLineageRecord { - envelope: AgentRuntimeDurableEnvelope, - project_id: String, - supervisor_lineage_id: String, - agent_id: String, - session_id: String, - task_id: String, - parent_run_id: Option, // Supervisor=None;专业 Agent 必须为当前父 run - delegation_id: Option, // 专业 Agent 必填并锁内复核 - predecessor_run_id: String, - successor_run_id: String, - predecessor_terminal_revision: u64, - retry_operation_id: String, - source_record_ref: Option, // 仅迁移旧 retry sidecar/DB record - status: SuccessorLineageStatus, // prepared | enqueued | active | terminal | rejected | outcomeUnknown | needsReconciliation -} +### 4.2 两个 read model -enum SuccessorLineageStatus { - Prepared, - Enqueued, - Active, - Terminal, - Rejected, - OutcomeUnknown, - NeedsReconciliation, -} +- Public Snapshot:普通 GUI、普通 CLI 和公开测试的唯一完整 Runtime 视图。 +- Developer Snapshot:受信任开发入口的独立 DTO;扩大 read,不扩大正式 Supervisor 写权限。 + +### 4.3 Conversation 是独立展示通道 + +Public Conversation 不保存第二份正文,只建立 source-to-Public 索引并从原 source 回读。V1 区分: + +- User; +- DirectReply; +- RuntimeFinalReply; +- 满足准入条件的 RuntimeStatus; +- 满足准入条件的 PublicEvent。 + +不能稳定定位、校验或归属的 source 默认隔离,不因 GUI 当前能展示就进入永久 Public history。 + +--- + +## 5. 关键不变量 + +1. **单一执行事实源**:Runtime durable records 决定执行事实;Shell ledger 只记录 request 协调和 source binding。 +2. **单一正式 writer**:同一真实副作用和同一 conversation source identity 在任一时刻只有一个正式 writer。 +3. **服务端能力驱动**:没有 capability 就不能构造命令;有 capability 仍需 Shell 在锁内重读事实并复核。 +4. **结果未知不重放**:无法证明真实副作用是否发生时进入 outcome-unknown/reconciliation,不换 requestId 重做。 +5. **Session 不重归属**:历史 command、interaction 和 delivery 依赖已落盘 `agentId + sessionId + runId`,不依赖当前 active Session 猜测。 +6. **事件不是状态**:事件只提示重新读取 Snapshot;事件丢失、重复或乱序不能改变最终状态。 +7. **Conversation 不复制正文**:Public index 不成为正文 authority;source 不可回读时失败关闭。 +8. **Public 与 Developer 隔离**:Developer read capability 不能成为绕过同一 Shell write ingress 的通道。 + +完整规范见 Contract 中的 `IC-*` 要求。 + +--- + +## 6. 分阶段实施计划 + +### P0:行为基线与协议验证框架 + +**人话目标**:在改变系统前,建立可以重复观察当前行为、发现重构破坏的测试入口;不是决定协议,也不实现生产协议。 + +允许: + +- 只读 fixture、golden trace、negative fixture; +- crash-point harness; +- 调用图和 writer inventory; +- 对当前 identity、Session、owner、conversation 和 event 行为的代码/运行证据记录。 + +禁止: + +- 新生产 handler; +- Consumer fallback; +- 改变 Runtime 生产行为; +- 用空 DTO、stub 或 ignored test 假装协议已实现。 + +完成条件: + +- 迁移矩阵中的 P0 inventory 均有证据; +- 每条 `EG-*` 能区分“现状满足”“现状必须隔离”“待后续阶段实现”; +- 当前 master 行为基线可重复通过。 + +### P1:最小持久协议底座 + +**人话目标**:实现 Shell 以后需要的请求受理、结果读回和 source binding 基础,但不开放五个 Public 写命令。 + +实现: + +- 统一 Contract/schema 单一来源; +- 以专用 append-only Shell ledger 为 authority 的 command acceptance/read-back、interaction/rework mapping 与 source-binding record; +- 可从 ledger 重建的 projection journal/index; +- RFC 8785 canonical fingerprint/checksum、连续 ledgerVersion、内容安全过滤和损坏隔离; +- 专用 `.agent/runtime/supervisor-shell/` record namespace(不混入 `agent.db` 或 Runtime journal); +- `TrustedProjectContext` resolver、execution-owner guard 内的 Shell lock 与调用来源基础设施。 + +P1 的实现边界:Public DTO 始终无路径,但 Shell 只能接收宿主已解析、已核验 manifest 的 trusted project context;现有 `.agent/project.lock` 具有 PID/时间回收语义,不能作为 Shell protocol lock。`.agent/runtime/supervisor-shell/ledger.jsonl` 是唯一追加顺序 authority,sidecar/index 只是可重建缓存。P1 交付的是持久协调底座和 read-back,不把当前 Runner 内存 request cache 当作幂等证据,也不开放 Public 写入口。 + +不实现: + +- 新 Public 写入口; +- Consumer 迁移; +- 第二份 task/finalization/provider/steer 生命周期。 + +### P2:Public / Developer Snapshot 与事件流 + +**人话目标**:先让 Consumer 能通过一个稳定接口看懂系统,而不改变旧写行为。 + +实现: + +- Public Snapshot; +- Developer Snapshot; +- Snapshot revision/hash; +- Public/Developer 隔离的 `(projectId, view)` Snapshot 订阅、`SnapshotChanged` 有界事件和重连; +- read-only/shadow projection; +- User/Developer interaction view 的只读物化; +- 带 source identity/revision/digest witness 与正常缺失/损坏矩阵的有界 projection observation:能确定 project/view scope 而观察无法闭合时,发布无 capability、无未证实 Runtime 事实的 fail-closed invalid/reconciliation Snapshot;完全不能确定安全 outcome 时返回 read error,而不挑一份跨文件旧读结果继续。 +- 为每项协作执行持久化 opaque `collaborationId` binding;同组多 child、retry successor 与 manifest fallback replacement 不依赖动态 child identity;无 parent run 的静态 fallback 绑定 project/session/manifest digest/group,不能承载可操作 interaction。 + +旧 GUI/CLI 仍保留写路径;shadow 只能比较投影,不能执行真实副作用。P2 不能把当前跨 `runtime.json`、JSONL、stream 和 sidecar 的聚合读取结果直接序列化为 Public Snapshot。 + +### P3:五命令与统一 Interaction Loop + +**人话目标**:让后端具备完整、真实可用的统一写协议,并将新旧正式入口收进同一 Shell ingress。 + +实现: + +- 五个 Public 命令及 strict schema; +- requestId 幂等、业务拒绝读回、unknown outcome; +- submit intent policy matrix; +- answer/approve/cancel/resume 状态机; +- Public Conversation read adapter; +- legacy ingress 的单 writer 收口。 + +P3 不提前迁移 GUI/CLI 的读模型和界面体验,但必须先收口真实 writer:任何仍能操作同一 Supervisor Run 的旧 Tauri/CLI/`swarm_cli` wrapper 都要转发同一 Shell handler(或在新协议启用时明确禁用),不得先在 Consumer 进程写 Runtime 再通知 Runner。P5 只迁移 Consumer 的读与交互体验。 + +### P4:Runner 自驱与安全恢复 + +**人话目标**:已受理操作不依赖 GUI 轮询推进;Runner 在现有 owner/lifecycle 门禁内完成 wake、恢复和 reconciliation。 + +实现: + +- 已接受 operation 的 durable wake/discovery; +- dirty projection 修复; +- Runner 重启后的安全恢复与跨重启 project discovery registry; +- drain、owner 冲突和 GUI-owner/CLI 启动路径的区分; +- outcome-unknown 零自动真实副作用重放; +- watchdog 强退视为 crash 边界,而非已完成的 drain。 + +本阶段不新增 headless lease,也不承诺无人值守常驻。 + +### P5:迁移 CLI、Tests、GUI + +**人话目标**:只切换 Consumer,不新增协议语义。 + +顺序: + +1. 普通 Supervisor CLI; +2. 面向 Public Contract 的测试; +3. GUI; +4. Developer UI/CLI 的独立 read 边界。 + +迁移后: + +- Consumer 只读 Snapshot/Conversation,只提交五命令; +- GUI 不再解释 Runtime phase、合成启动决策或 autosave Runtime output; +- CLI 不再直接调用 start/steer/resume primitive; +- 内部 Runtime 单测和恢复测试仍可直接测试内部能力。 + +P5 前产品决策 `FD-001`:`--swarm-chat` 必须明确选择为只读 Public view 的普通 Supervisor CLI,或显式受信任的 Developer CLI;无论选择哪种,其正式写操作均不得绕过 Shell。 + +### P6:删除旧公开面并最终收口 + +**人话目标**:删除已经没有正式 Consumer 的旧公开控制协议,同时保留 Runtime 内部能力和必要回归测试。 + +删除: + +- 正式 transport 的旧 start/steer/confirm/reject/answer/cancel/retry/resume/schedule/read 注册; +- Consumer 旧调用点和生命周期分支; +- GUI Runtime output 派生 autosave; +- migration fallback; +- Public scope 内缺少稳定 messageId 的 conversation append。 + +保留: + +- Runtime 内部 start/steer/resume/recovery primitive; +- 验证内部能力的单元和恢复测试; +- 独立管理面 goal/compact/session/config; +- 明确隔离的 Developer/local history。 + +--- + +## 7. 里程碑 + +| 里程碑 | 对应阶段 | 产出 | 允许进入下一阶段的条件 | +|---|---|---|---| +| M0 | P0 | 基线、调用图、证据门禁 | 现状与隔离边界可证明 | +| M1 | P1 | 最小持久底座 | 原子性、损坏、幂等基础测试通过 | +| M2 | P2 | 双 Snapshot 与事件 | source 缺失/损坏、fail-closed 发布、协作 lineage、订阅重连/缺口、权限和字段隔离通过 | +| M3 | P3 | 五命令、Interaction、Conversation read | crash/read-back、单 writer、跨 transport fixture 通过 | +| M4 | P4 | Runner wake/recovery | owner、drain、重启、unknown outcome 通过 | +| M5 | P5 | CLI/Tests/GUI 迁移 | 三类 Consumer 行为等价且无私有字段依赖 | +| M6 | P6 | 旧公开面删除 | 静态调用图和最终协议验收通过 | + +阶段完成条件必须引用 Contract `IC-*`、迁移矩阵 `MX-*` 和证据门禁 `EG-*`;本文不重复字段级验收。 + +--- + +## 8. 冻结与开发准入 + +当前允许: + +- 继续评审和收束四份文档; +- 编写不改变生产行为的 P0 基线与 fixture 骨架; +- 解决 `FD-001` 产品决策。 + +当前不允许: + +- 开始 P1–P6 生产实现; +- 因实现方便而修改 Contract 语义; +- 根据旧评论恢复 owner generation、Session rotation、handoff 或 Consumer fallback; +- 把 P0 证据任务解释为“以后再决定协议规则”。 + +允许进入 P1 的前提: + +1. P0/M0 已完成:当前 master 行为基线可重复通过,正式 ingress/read/writer inventory 已形成,且每条后续 `EG-*` 已标记现状、隔离边界和责任阶段; +2. Contract 中没有未标注的候选字段、重复定义或互相冲突的 `IC-*`; +3. 迁移矩阵覆盖所有正式 ingress、read、conversation writer 和删除面; +4. 附录中的冻结前 `EG-*` 有明确预期结果; +5. P1 的 trusted project resolver、owner-guard Shell lock、RFC 8785 canonicalization、专用 Shell record namespace 已明确为单一实现边界; +6. `FD-001` 可在 P5 开始前决定,不阻塞 Contract 核心冻结或 P0–P4; +7. PR #168 完成针对四份文档职责和 Contract 可施工性的重新评审。 + +--- + +## 9. 最小心智模型 + +```text +Consumer 只表达意图、读取状态; +Shell 统一交互决策和正式写入口; +Runtime 继续保存和推进执行事实; +Runner 只在持有现有 owner 时写; +Snapshot 是状态视图,事件是刷新提示; +Conversation 从原 source 回读,不复制正文; +不知道副作用结果时停止并 reconciliation,不重复执行。 ``` - -唯一约束使用 `(projectId, agentId, predecessorRunId)`,同一 predecessor 在其整个 lineage 生命周期内最多有一个 successor(不论 successor 当前是 prepared、active 还是 terminal);不能假设不同 Agent 的 runId 全局唯一。并发 retry 通过 predecessor terminal revision + lineage 唯一约束收敛为同一 successor。successor 已终态后不得再次从旧 predecessor 分叉;后续 retry 必须针对该 successor 的最新终态记录创建下一条 lineage,重复提交同一 retryOperationId 才能回放原 successor。Supervisor lineage 的 `parentRunId/delegationId` 必须为空;专业 Agent lineage 必须同时保存并复核当前 `parentRunId + delegationId + taskId + sessionId`,且父 Supervisor 仍是允许该 recovery 的当前 run。旧 `nextRunId` 与新生成 ID 冲突、旧 retry 记录缺字段、父 Supervisor 已终态或任一 lineage 证据不完整时,业务拒绝并进入 `TARGET_STALE`/`NEEDS_RECONCILIATION`,不能另造 successor。`ContinueRun` 不写 `RunLineageRecord` 且始终保持原 runId。 - -现有 Runner 内存 request cache、`acceptedRunId` 和 Goal CAS 只作为内部附加护栏,不替代公开 ledger。goal CRUD、`compact`、会话管理、资源上传/登记、`preview.start`/`preview.validate` 和配置读写属于管理面,不进入这五个 Runtime Loop 命令;它们若是公开写操作,继续遵守各自现行 CAS/权限合同,不能借本次重构降级。 - - -### 1.3.2 五命令请求体冻结 - -五个公开写命令使用严格 tagged union;除 `schemaVersion/projectId/requestId` 外,业务字段如下。未列出的字段不属于 V1,transport 不得透传额外字段参与执行;未知字段、重复字段和错误字段类型统一返回 `INVALID_REQUEST`,不得通过忽略未知字段实现兼容。后续新增字段必须提升 schemaVersion 或经过显式兼容协议协商,并同步更新请求指纹规则。 - -```rust -struct SubmitIntentCommand { - meta: AgentRuntimeCommandMeta, - payload: SubmitIntentPayload, -} - -enum SubmitIntentPayload { - Conversation { - session_id: String, - expected_session_revision: u64, - message: String, - attachments: Vec, - intent_kind: AgentRuntimeIntentKind, - // 只有 createFromTemplate/importExistingDesign 使用;不能把模板或设计身份 - // 藏在自然语言 message 中。 - entry_binding: Option, - run_profile: AgentRuntimeRunProfile, - }, - BuiltinCommand { - command_line: String, - expected_parser_version: String, - // slash 不允许 attachments、entry binding 或 Runtime intent/profile; - // project/session scope 由 Shell 从 capability/管理面取得。 - }, -} - -// 项目级 slash management action 的私有幂等记录;不把 management action 伪装成 Runtime run。 -struct AgentRuntimeBuiltinManagementOperationRecord { - envelope: AgentRuntimeDurableEnvelope, - command_request_id: String, - // RuntimeBuiltinCommand 的项目级 slash action 必须引用已经幂等提交的 - // conversation user message;LocalManagementRoute(例如 CLI /goal)不写 - // Public conversation,此字段必须为 None,不能伪造 message identity。 - conversation_user_message_id: Option, - command_name: String, - target_identity_fingerprint: String, - argument_fingerprint: String, - private_argument_ref: Option, - // 领域副作用一旦被受理,必须绑定同一 project operation;核对期间保留 - // 原 operation identity,不能通过新 requestId 或新的领域 operation 重做。 - project_operation_ref: Option, - reconciliation_operation_id: Option, - status: AgentRuntimeBuiltinManagementOperationStatus, - result_ref: Option, - error_ref: Option, -} - -enum AgentRuntimeBuiltinManagementOperationStatus { - Prepared, - Executing, - Succeeded, - Rejected, - OutcomeUnknown, - NeedsReconciliation, -} - -// 状态迁移:prepared -> executing | rejected;prepared -> outcomeUnknown; -// executing -> succeeded | rejected | outcomeUnknown; -// outcomeUnknown -> needsReconciliation; -// needsReconciliation -> succeeded | rejected。最后两条只能由受信任核对流程 -// 执行,并必须写入 reconciliation_operation_id 与确定的 result/error ref。 -// 只有需要项目身份/副作用的 Builtin management route 建该记录;/mcp、/quit、/exit -// 等纯 CLI transport 动作不进入项目 ledger。其 target/argument fingerprint 在 -// prepared 后固定,重试未知结果只能读回或核对,不能换 requestId 再做副作用。 - -// LocalManagementRoute 的 path-free 参数恢复规则:`private_argument_ref` 指向 -// 同一 localScopeId 下有界、脱敏、带 checksum 的私有 payload。恢复只能读取该 -// ref,并同时校验 payload checksum 与 `argument_fingerprint`;缺失、篡改或二者 -// 不一致进入 `CORRUPT_RECORD`,不得重新解析 commandLine。locator 原文永不入 -// payload、fingerprint、result/error 或 Public delivery。 - -// LocalManagementResponse 的错误闭合规则:capability 不存在返回 -// `CAPABILITY_NOT_FOUND`;parser 版本不支持返回 `PROTOCOL_VERSION_UNSUPPORTED`; -// targetRef 漂移返回 `TARGET_STALE`,scope 字段缺失/多带或 session revision -// 不匹配返回 `SCOPE_MISMATCH`,权限不足返回 `PERMISSION_DENIED`,locator -// 不可用/过期/撤销返回 `LOCATOR_UNAVAILABLE`,活跃执行返回 -// `COMMAND_IN_PROGRESS`,结果无法证明返回 `COMMAND_RESULT_UNKNOWN`;已进入 -// reconciliation 的记录返回 `NEEDS_RECONCILIATION`,同一 `(localScopeId, -// requestId)` 换指纹返回 `IDEMPOTENCY_KEY_REUSED`,损坏记录 -// 返回 `CORRUPT_RECORD`。确定性校验失败不得依赖中文 message;已进入 local -// record 的拒绝和未知结果必须可由 `read_local_management_result(localScopeId, -// requestId)` 读回。Local response 的 `requestFingerprint` 在成功时必有,错误 -// 仅在已完成无路径原文的规范化时有值;`projectId` 只通过成功结果的 -// `resolved_project_id` 返回,`observedSnapshotRevision` 和 -// `interactionRequired` 对所有 local response 均不存在。 - -enum AgentRuntimeLocalLocatorHandleState { - NotAcquired, - Acquired, - Released, - ReconciliationHeld, -} - -// locator_handle_ref 的生命周期与 projectOperationRef 绑定:resolver 先验证 -// locator 类型、digest、权限和 symlink/reparse 安全,再取得绑定 -// `(localScopeId, requestId, ownerGeneration)` 的临时 handle。local-only route -// 在 local record 已进入 terminal 且 `project_operation_ref = None` 时释放; -// project-linked route 必须等 local record 与关联 project operation 都到达 -// terminal 且结果已 durable commit 后释放。`prepared -> executing` 前 handle -// 过期/撤销返回 `LOCATOR_UNAVAILABLE`;executing 或 project operation 已受理 -// 后失去 handle 必须进入 `outcome-unknown/reconciliation`,状态固定为 -// `ReconciliationHeld`,不能换 handle 或 requestId 重做。owner 重启只能用同一 -// 稳定 resolver identity 重新绑定并复核 locator digest、target revision 和 -// operation identity,不能从原 commandLine 重新解析。`Released` 只表示 handle -// 已撤销,record 可保留不透明 ref 供审计但不得再次使用。至少覆盖 -// `prepared → handle acquired → crash`、`executing → handle expired`、 -// `outcome-unknown → retry/readback`、无 project operation 的 resolver rejection -// 和“project resolve 成功但 project operation 失败”的 fixture;所有分支最终 -// 都必须释放或明确保留待 reconciliation 的 handle。 - -// 尚未解析出 projectId 的本地 locator / path-bearing action 使用独立私有记录, -// 不强行伪造 projectId,也不把绝对路径写入 Public/Runtime ledger。 -struct AgentRuntimeLocalManagementOperationRecord { - envelope: AgentRuntimeLocalDurableEnvelope, - command_request_id: String, - originating_capability_id: String, // 仅审计;不作为重放的历史业务 identity - authorization_principal_ref: String, - authorization_scope_fingerprint: String, - expected_parser_version: String, - route: AgentRuntimeLocalCommandRoute, - target_ref: Option, - command_name: String, - resolved_project_id: Option, - project_operation_ref: Option, - locator_digest: Option, - // 仅引用受信任 resolver/OS bookmark/handle store;不能包含原始路径。 - locator_handle_ref: Option, - locator_handle_state: AgentRuntimeLocalLocatorHandleState, - // 只覆盖去掉 locator 后的规范化参数;路径身份单独由 locatorDigest 约束。 - argument_fingerprint: String, - // `/goal <目标>`、`/goal edit` 和 `/agent-resume [说明]` 等 path-free - // 参数保存为有界私有 payload;host path 原文禁止进入该 payload。 - private_argument_ref: Option, - status: AgentRuntimeLocalManagementOperationStatus, - result_ref: Option, - error_ref: Option, -} - -struct AgentRuntimeInputAttachmentBinding { - attachment_namespace: String, - attachment_id: String, - attachment_revision: u64, - digest_algorithm: String, // V1 固定 sha256 - content_digest: String, - media_kind: AgentRuntimePublicMediaKind, -} - -// Shell 私有 durable payload;不是 Public Snapshot 或 conversation 正文。 -struct AgentRuntimeInputEnvelopeRecord { - envelope: AgentRuntimeDurableEnvelope, - input_envelope_id: String, - status: AgentRuntimeInputEnvelopeStatus, // reserved | committed | outcome-unknown | corrupt - command_request_id: String, - project_id: String, - session_id: String, - session_revision: u64, - conversation_user_message_id: String, - conversation_commit_marker: Option, - message: String, - attachments: Vec, - input_fingerprint: String, -} - -enum AgentRuntimeInputEnvelopeStatus { - Reserved, - Committed, - OutcomeUnknown, - Corrupt, -} - -`Committed` 必须同时具有可回读的 `conversation_commit_marker`;`OutcomeUnknown`/`Corrupt` 禁止进入 Provider/Runtime context,恢复只能保留同一 envelope identity 并进入 reconciliation。 - -enum AgentRuntimeIntentEntryBinding { - Template { - template_id: String, - template_revision: u64, - digest_algorithm: String, // V1 固定 sha256 - content_digest: String, - }, - ExistingDesign { - design_id: String, - design_revision: u64, - digest_algorithm: String, // V1 固定 sha256 - content_digest: String, - }, -} - -struct AgentRuntimeRunProfile { - name: String, - version: String, -} - -struct AnswerCommand { - meta: AgentRuntimeCommandMeta, - interaction: InteractionResponseMeta, - answers: Vec, -} - -struct ApproveCommand { - meta: AgentRuntimeCommandMeta, - interaction: InteractionResponseMeta, - decision: ApprovalDecision, // approve | reject | requestChanges,必填 - feedback: Option, // 仅 requestChanges;有界、脱敏,其他 decision 必须省略 -} - -struct CancelCommand { - meta: AgentRuntimeCommandMeta, - session_id: String, - expected_session_revision: u64, - run_id: String, -} - -enum AgentRuntimeResumeMode { - ContinueRun, // continueRun - RetryTerminalRun, // retryTerminalRun - ReconcileRun, // reconcileRun -} - -enum AgentRuntimeResumeIntent { - ContinueRun { - session_id: String, - expected_session_revision: u64, - run_id: String, - expected_run_revision: u64, - }, - RetryTerminalRun { - session_id: String, - expected_session_revision: u64, - target: AgentRuntimeRetryTarget, - }, - ReconcileRun { - // 没有 active session 时允许受信任 reconciliation capability 置空; - // Public User/CLI 不能使用该分支。 - session_id: Option, - run_id: String, - expected_run_revision: u64, - reconciliation_id: String, - }, -} - -enum AgentRuntimeRetryTarget { - Supervisor { - run_id: String, - expected_terminal_revision: u64, - }, - Collaborator { - target: AgentRuntimeCollaboratorRunTarget, - }, -} - -struct ResumeCommand { - meta: AgentRuntimeCommandMeta, - intent: AgentRuntimeResumeIntent, -} -``` - -V1 `submit_intent` 的 `Conversation.message` 必须是非空、非纯空白文本并按第 1 节统一常量校验;attachment-only 在 ledger 前返回 `INVALID_REQUEST`。slash route 携带 attachment 或 entryBinding 同样在 ledger 前返回 `INVALID_REQUEST`,不得先执行 parser 再忽略多余输入。这是对现有 Runtime `task` 非空合同的显式继承,Consumer 不得为了绕过门禁自动合成“请处理附件”等自然语言。attachment 只能在 capability 对应 option 的 `inputPolicy=textWithOptionalAttachments` 时随 message 提交,`mediaKind` 必须命中同一 option 的 `allowedAttachmentMediaKinds` 且由资源 registry 的真实类型复核;`Other` 不属于 V1 可输入媒体。attachment 必须已经通过现有上传/项目资源管理面进入 manifest/resource lineage。Runtime 命令不接收文件字节、绝对路径、`file://`、浏览器临时 URL 或上传 token;Shell 在锁内重读 revision 和 `sha256`,任一漂移整条请求失败关闭。attachment 顺序、完整 binding 和 mediaKind 均进入 request fingerprint,不能只散列文件名。V1 所有 `digestAlgorithm` 必须等于 `sha256`,`contentDigest` 必须是 64 位小写十六进制;namespace/id、revision 和实际重读内容均在 project lock 内复核,缺失/格式错误在 ledger 前 `INVALID_REQUEST`,内容不一致为 `ARTIFACT_BINDING_UNAVAILABLE`。 - -附件不能只做“校验后丢弃”。对于 `Conversation` payload,Shell 在 command `prepared` 时将规范化 message 与有序 immutable bindings 写入同一私有 `AgentRuntimeInputEnvelopeRecord`,direct reply/start/steer 三条路径都引用同一 envelope identity;`BuiltinCommand` 不创建 Runtime input envelope,而是将规范化 command line 固化在 command ledger,并只引用一次 `conversationUserMessageId`;现有 Runtime 继续以原 message 作为非空 task/steer 文本,Provider/context adapter 另以结构化 attachment context 注入 resource identity、revision、digest 和 mediaKind,工具读取仍只经既有 manifest/resource resolver。不得把资源路径、签名 URL 或自动生成的自然语言拼进 task;恢复时必须重读同一 envelope 和 binding,资源已替换/删除则在 Provider/Runtime 副作用前失败关闭。conversation user message 可以显示用户原文和安全附件摘要,但摘要不是第二份资源事实。对 `Start`,同一 prepared 记录必须同时预分配 `conversationUserMessageId + runId + runtimeStatusMessageId`;`runtimeStatusMessageId = runtime-public-status- + lowerHex(sha256(RFC 8785 canonical JSON(["runtime-status", projectId, runBoundSessionId, runId, statusKind])))`;其中 `runBoundSessionId` 是 prepared/Runtime run 固化的 session 身份,不随后续 active session handoff 改写,只在该 prepared identity 下生成一次:先幂等提交用户原文/安全附件摘要的 conversation message 并回读 commit marker,再以 `runtime-public-status-*` 前缀将脱敏“任务已接收,正在启动处理”作为 Runtime-owned status message 原子写入 conversation;status message 的 commit marker 可读回后才允许把 Run 从 `preparing/public-status-pending` 提升为 `queued/pending` 并让 Runner dequeue。崩溃在 user message committed 与 status committed 之间时,恢复只用同一三组 identity 补 status;conversation 已提交而辅助 audit 尚未提交时,以 conversation commit marker 为公开真相继续补 audit/入队,不得留下“已接收但永不执行”或写第二条 user/status message。status 写入失败则持久化 `failed/public-status-write-failed`,不得执行或继续重试 Provider;写入结果未知则 Run 保持不可执行并进入 reconciliation,恢复只能复用同一 message/run identity。Steer、DirectReply 和 RuntimeFinalReply 不重复写该 Start status message。根 Supervisor 的正式失败、预算耗尽或硬期限 reconciliation 也必须先用唯一 Runtime-owned failure status message 写入用户可理解摘要,再提交其它 task/event/state 终态;status 写入失败/结果未知时不把 Public outcome 提前投影为 `failed`,而是保持 terminal-pending/reconciliation,直到同一 status identity 可读回或由 owner 明确核对;status message 必须被 prompt builder 按固定前缀排除,前端按其来源顺序展示且不得二次持久化。现有需要进入聊天的安全 Runtime 事件另走 `AgentRuntimePublicEventMessageRecord`:Rust 只在同一父 run 下生成稳定 `eventId + publicText`(`eventId = runtime-public-event- + lowerHex(sha256(RFC 8785 canonical JSON(["runtime-public-event", projectId, parentRunId, sourceRunId, durableEventId, eventKind, normalizedPublicPayload])))`,其指纹输入包含 project/parent/source run、底层 durable event identity、allowlisted event kind 和规范化 public payload),前端按该二元身份去重和展示;无 eventId、空 publicText、legacy/raw tool/provider/runner payload 一律丢弃。它不进入 `SnapshotChanged` envelope、Public Snapshot 或 Runtime status message,也不改变 Runtime 状态事实;只允许现有 allowlist 的专业 Agent/公开 child 事件进入该 delivery;根 Supervisor 的 `turn.started/turn.failed/turn.budget_exhausted` 不再生成第二条 event message,终态失败/预算摘要只走上面的唯一 Runtime-owned status message。当前工作台对 Supervisor/主 Agent/直接 child 的最近 4/最多 20 条聚合只是该 conversation read model 的展示限制,不能在 Consumer 自行从 raw event 重算。 - -`Conversation.intentKind` 必填且只能取 V1 稳定枚举值,不能从 `message` 启发式推断,也不能塞入 `runProfile`。`entry_binding` 与 `intentKind` 必须是严格一一对应的 tagged union,且 `(intentKind, runProfile, entryBindingKind, inputPolicy, attachmentMediaKind)` 必须命中当前 `conversationOptions` 的同一个 option;禁止从独立列表做笛卡尔积、通过未知字段或自然语言补充身份。实际 entry binding 由既有模板/资源管理面提供,不把所有模板或设计 identity 复制到 Snapshot。Shell 必须在锁内校验该入口对当前项目、session 和状态是否可用;校验失败为持久化业务拒绝,不产生副作用。`sessionId + expectedSessionRevision` 必须命中当前 Project Supervisor active session。`cancel` 必须同时提交当前 `sessionId + expectedSessionRevision + runId`,防止旧 capability 被新会话误用;session handoff 已明确绑定同一 lineage 时只接受新 active session 的 capability,run 已终结、session 已切换或绑定关系不一致均为 `TARGET_STALE`。V1 `resume` 不再使用模糊的 project scope/reason。`ContinueRun` 只允许 Snapshot capability 明确声明的 `pausedByUser` 同一 durable Supervisor run,必须携带 `expectedRunRevision`;running、waiting interaction、pending/executing action、timer/lane、finalizing 和 needs-reconciliation 均不得用 ContinueRun 越过各自门禁。`RetryTerminalRun` 必须精确绑定 capability 给出的失败终态 run 和 terminal revision,在 prepared 阶段预分配新的 successor runId,并持久化 predecessor/successor lineage,绝不复用旧 runId;completed 不可 retry,cancelled 只有产品策略明确给出 retry capability 时才可 retry。专业 Agent retry 使用 `AgentRuntimeCollaboratorRunTarget`,同时复核 collaborationId、parentRunId、runId 和 delegation/repair 状态;若 Supervisor 已准备唯一 contract repair,Snapshot 只给 `recovery=RepairApprovalPending` 并指向同一 Public PolicyApproval interaction;在该 interaction 解决前不得创建平行 successor 或开放 `RetryCollaboratorRun`。`ReconcileRun` 只允许受信任 Developer/Runner reconciliation capability,且允许没有 active session,但必须由 capability 通过 Project Supervisor lineage 精确定位目标,不能以空 session 放宽权限,也不能自动重放未知 Provider/工具副作用。timer、lane release、ownerRecovered、interactionResolved 和 schedule-ready 都是 Shell/Runner 内部 recovery intent,不进入 Public ResumeCommand。自然语言“继续”不得路由为任何 resume tagged intent,更不能静默触发 RetryTerminalRun;Consumer 必须显式发送结构化 ResumeCommand。`runProfile` 的 wire 形态为 Snapshot capability 中已注册的公开 profile 名称和版本,不能携带 Provider、模型、工具、提示词或路径配置。 - -`AnswerCommand` 的答案结构只允许 Public Snapshot 当前 interaction view 中声明的 question/option/自由回答约束;缺失、重复、越界或不符合约束返回 `INVALID_REQUEST`,不产生部分写入。`ApproveCommand` 的 decision 不允许由 UI button、命令名或缺省值推断;`requestChanges` 只在当前 interaction 的 `allowed_decisions` 声明该值时可用,V1 公开支持的场景仅为 `scope=Run | Action`、绑定单一不可变 targetArtifact 的用户 `PolicyApproval`;Project scope PolicyApproval 与 ToolApproval 仍只允许 `approve/reject`。选择 `requestChanges` 时必须携带 `feedback`,长度限制为最多 2,000 个 Unicode 字符;选择 `approve/reject` 时必须省略该字段。Shell 对 feedback 复用公开内容安全过滤,拒绝绝对路径、密钥/Token、Provider 原文和超长内容;过滤失败返回 `INVALID_REQUEST`,不产生部分写入。`requestFingerprint` 覆盖上述规范化业务请求体以及 `schemaVersion/commandKind/projectId`,不覆盖 transport source、locator、时间戳、重试次数或响应展示文案。通过身份、权限和版本校验的请求,即使因 target stale、interaction stale、当前状态不允许或策略拒绝而没有 Runtime 副作用,也必须在 ledger 中以 `rejected` 持久化;只有格式、版本、身份或权限失败且请求尚未进入项目 ledger 的情况才返回 `REQUEST_NOT_FOUND`。 - -### 1.4 InteractionRequired 身份、版本和解决状态机 - -```rust -struct AgentRuntimeArtifactBinding { - artifact_namespace: String, - artifact_id: String, - artifact_revision: u64, - digest_algorithm: String, // V1 固定 sha256 - content_digest: String, -} - -// Shell 内部 durable record;不直接作为正式公开 DTO。 -struct AgentRuntimeInteractionRecord { - envelope: AgentRuntimeDurableEnvelope, - interaction_id: String, - interaction_revision: u64, - // continuation item 的 record_revision 取 envelope.record_revision;interaction_revision - // 只表示交互业务版本,不能替代 durable record revision。 - kind: AgentRuntimeInteractionKind, - scope: AgentRuntimeInteractionScope, - audience: AgentRuntimeInteractionAudience, - project_id: String, - agent_id: Option, - session_id: Option, - run_id: Option, - action_id: Option, - action_fingerprint: Option, - policy_snapshot_fingerprint: Option, - approval_target_set: Option, - // 私有绑定:保证审批意见针对创建交互时看到的确切不可变产物版本。 - target_artifact: Option, - request_fingerprint: String, - bound_state_fingerprint: String, - status: AgentRuntimeInteractionStatus, - resolution: Option, - private_presentation: AgentRuntimeInteractionPrivatePresentation, -} - -// Shell 内部 durable resolution;UserInput 与 Approval 使用严格 tagged union。 -enum AgentRuntimeInteractionResolution { - UserInput { - response_id: String, - response_fingerprint: String, - answers: Vec, - }, - Approval { - response_id: String, - response_fingerprint: String, - decision: ApprovalDecision, - // 只保存经过公开内容安全过滤的副本。 - feedback: Option, - target_artifact: Option, - rework_operation_id: Option, - follow_up_interaction_id: Option, - }, -} - -struct AgentRuntimeApprovalTargetSet { - targets: Vec, - target_set_fingerprint: String, -} - -struct AgentRuntimeApprovalTarget { - agent_id: String, - parent_run_id: Option, // Supervisor=None;专业 Agent 必须为当前父 run - run_id: String, - action_id: String, - action_fingerprint: String, -} - -`parent_run_id=None` 仅允许 Supervisor action;专业 Agent/child action 必须为当前 Public Supervisor run 的 Some(parentRunId),并在 target-set fingerprint 与 approve 锁内复核。 - -struct AgentRuntimeInteractionView { - interaction_id: String, - interaction_revision: u64, - kind: AgentRuntimeInteractionKind, - scope: AgentRuntimeInteractionScope, - // Consumer 只能在 actionable=true 且 allowed_actions 非空时渲染 answer/approve; - // Shell 仍须在锁内重新校验,字段不是绕过服务端权限的凭据。 - actionable: bool, - allowed_actions: Vec, - status: AgentRuntimeInteractionPublicStatus, - context: AgentRuntimePublicInteractionContext, - presentation: AgentRuntimePublicInteractionPresentation, -} - -enum AgentRuntimePublicInteractionContext { - Supervisor, - Collaborator { - collaboration_id: String, - group: AgentRuntimePublicCollaboratorGroup, - }, -} - -struct AgentRuntimePublicQuestion { - id: String, // 沿用现有唯一 snake_case,最多 64 个 ASCII/UTF-8 字节 - header: String, - question: String, - options: Vec, -} - -struct AgentRuntimePublicQuestionOption { - id: String, // Shell 在 interaction 创建时分配并持久化的不透明 option identity - label: String, - description: String, -} - -enum AgentRuntimeAnswer { - Option { question_id: String, option_id: String }, - Freeform { question_id: String, text: String }, -} - -enum AgentRuntimePublicMediaKind { Image, Audio, Video, Document, Code, ProjectVersion, Other } - -enum AgentRuntimePublicInteractionPresentation { - UserInput { - questions: Vec, - // V1 固定为 true;false 是 PUBLIC_STATE_INVALID,不是关闭自由回答的能力。 - allow_freeform: bool, - }, - PolicyApproval { - title: String, - summary: String, - allowed_decisions: Vec, - }, - ToolApproval { - title: String, - summary: String, - risk_level: AgentRuntimePublicRiskLevel, - allowed_decisions: Vec, // V1 只能 approve/reject - }, -} - -enum AgentRuntimePublicRiskLevel { Low, Medium, High } - -enum AgentRuntimeInteractionKind { UserInput, ToolApproval, PolicyApproval } -enum AgentRuntimeInteractionScope { Project, Run, Action } -enum AgentRuntimeInteractionAudience { User, Developer } -enum AgentRuntimeInteractionStatus { Open, Resolving, Resolved, Superseded, Cancelled } -enum AgentRuntimeInteractionPublicStatus { Open, Resolving } -``` - -`interactionId` 在项目内稳定唯一,`interactionRevision` 从 1 开始并只在该 interaction 的可回答内容、约束或状态变化时递增;`responseId` 在单个 interaction 内唯一。UserInput 的 Public presentation 必须保持现有合同:question id 为唯一 snake_case 且最多 64 个 ASCII/UTF-8 字节,问题数 1–3,每题有 2–3 个 option,所有题都必须回答且自由输入始终可选,模型不能关闭“其他”。option identity 不从 UI label 推导:Shell 在 interaction 创建时按规范化 question id 与 option ordinal 分配 `optionId = "opt_" + lowerHex(sha256(canonicalJson({interactionId, questionId, ordinal})))[:32]`,将该 ID 与 option 顺序同时写入 private presentation、interaction request fingerprint、continuation item 和 resolution;同一 interaction 内 option 顺序不可变,label/description 或顺序变化必须递增 interactionRevision 并使旧 response stale。答案必须恰好覆盖当前全部 question id,每个 question 只能是一个 option 或一个 freeform;`Option` 必须提交 Public view 中的稳定 option id,不能提交 UI label。`boundStateFingerprint` 绑定创建交互的 durable 对象与策略前提,不能用全项目 revision 替代;若交互针对产物审批,`targetArtifact.artifactNamespace + artifactId + artifactRevision + digestAlgorithm + contentDigest` 必须同时绑定并在锁内复核,不能只靠用户意见正文或当前最新产物猜测目标版本。V1 只为已有稳定 immutable revision 且能在同一锁内计算 `sha256` 的 manifest/resource/artifact lineage 生成该 binding;当前只有 `resourceId`、可变路径、无 revision 或无法重读内容计算 digest 的对象不得开放 `requestChanges`,返回 `ARTIFACT_BINDING_UNAVAILABLE`,不新建一套平行 artifact identity。项目级 PolicyApproval 使用 `scope=Project`,必须把锁内重新枚举的精确 `agentId + parentRunId + runId + actionId + actionFingerprint` 集合固化为 `approval_target_set`,可一次覆盖多个 run 但只允许 `approve/reject`,不能把“当前所有 run”当作隐含目标;目标集合变更必须创建新 interaction。允许 `requestChanges` 的 PolicyApproval 必须使用 Run/Action scope 并绑定单一不可变 targetArtifact;User audience ToolApproval 使用 Action scope 并绑定精确 action,只允许 `approve/reject`;Developer audience ToolApproval 继续走 Developer Snapshot。UserInput 按真实落点使用 Run 或 Action scope。 - -Public Snapshot 只投影 `audience=User` 且状态为 Open/Resolving 的最小 view;Open 可回答,Resolving 只显示处理中并禁用再次提交,Resolved/Superseded 从公开列表移除。正式写命令仍在执行时重新校验当前调用来源与项目策略,不公开 audience、request fingerprint、bound fingerprint、内部 actionId、策略字符串、工具名称/参数或动态 child 身份;`context=Collaborator` 只使用公开不透明 `collaborationId` 和普通用户组名,Shell 内部仍必须复核真实 `agentId + runId + parentRunId + actionId`。`audience=User` 的 ToolApproval 必须进入 Public Snapshot,提供脱敏行为摘要和风险等级,满足现有工作台“确认”入口;`audience=Developer` 的 ToolApproval 才只进入 Developer Snapshot 的 debug interaction view。内部 private presentation 与 Public presentation 使用不同 DTO;Public presentation 采用严格 tagged union 和长度上限:UserInput 从本地私有 user-input sidecar 经过敏感信息/路径过滤后,复用现行最多 3 题、每题 2–3 选项与自由回答约束,`allow_freeform` 在 V1 必须为 `true`,不能由模型或 sidecar 关闭;若问题或选项不能安全公开则转为 `audience=Developer`/needs-reconciliation,不把原文带入 Public。用户 PolicyApproval 只含有界、脱敏的行为影响摘要和允许 decision。Public view 允许 `kind=UserInput` 搭配 `UserInput`、`kind=ToolApproval` 搭配 `ToolApproval`、`kind=PolicyApproval` 搭配 `PolicyApproval` 三种组合,未知或不匹配的 variant 按不支持协议失败关闭;presentation 不是可执行 payload。Developer ToolApproval 使用独立 debug DTO。 - -`answer` 仅接受 UserInput,`approve` 仅接受 User audience ToolApproval/PolicyApproval 或 Developer capability 的 Developer ToolApproval;命令与 kind 不匹配返回 `INVALID_REQUEST` 且零副作用。`answer/approve` 的处理顺序为:先走 request ledger 重放检查,再取得 project execution owner 与 interaction lock,重读 record,校验 `interactionId + expectedInteractionRevision`、状态为 Open、bound fingerprint 仍与 durable 对象一致,并复核 `targetArtifact` 的 identity、revision 和 content digest 仍指向创建交互时的不可变产物版本,然后重新校验当前 principal capability、不可放宽的 hard-deny/sandbox/安全策略和交互绑定的 `policy_snapshot_fingerprint`。业务策略快照不因普通 live policy 文案变化而重新解释;若安全 hard-deny 收紧、目标 action/actionFingerprint 漂移或 policy snapshot 不可读,则零副作用返回 `INTERACTION_STALE` 或进入 `NEEDS_RECONCILIATION`,不能把旧 approve 施加到新 action。通过后以 responseId 和 response fingerprint 原子转为 Resolving,调用既有内部 answer/confirm/reject 实现;UserInput/approve-reject 可在权威结果闭合后写 Resolved,requestChanges 只有在 rework operation 已 durable prepared/enqueued 后才能写 Resolved,否则保留 Resolving/unknown;response fingerprint 覆盖 interactionId、interactionRevision、responseId、decision、feedback 和 answers 的完整规范化响应,不能只散列自由文本。`approve` 必须显式携带 `decision=approve|reject|requestChanges`;禁止从按钮、命令名或缺省值猜测。 - -`answer/approve` 的 `InteractionResponseMeta` 还必须提交当前 `sessionId + expectedSessionRevision`。interaction record 的 `session_id` 保留创建时的 lineage/provenance;正常情况下二者必须相同,rotation 成功后只有 continuation set 中精确匹配 `interactionId + interactionRevision + recordRevision` 的 item 才允许 successor session 重新授权同一 interaction。该 proof 只授予继续原 interaction operation 的权限,不改变 `interactionRevision`、`boundStateFingerprint`、target artifact 或 response identity;Open interaction 可在 successor Snapshot 中继续展示并回答,Resolving interaction 只能恢复/读回原 response,不能接受第二 response。proof 缺失、interaction 未被捕获、session/revision 不一致或 rotation phase 不是 `Ready`,统一零副作用返回 `TARGET_BUSY`/`TARGET_STALE`/`NEEDS_RECONCILIATION`,不能按当前项目最新 Open interaction 猜目标。 - -`requestChanges` 与 `reject` 不是同一语义:`reject` 终止当前审批链并收束 run;`requestChanges` 是带意见的非终止工作流转移。V1 只允许 `scope=Run | Action`、恰好绑定一个不可变 targetArtifact 的用户 PolicyApproval 声明 `requestChanges`;覆盖多个 run 的 Project scope PolicyApproval 仍只允许 `approve/reject`,避免一段 feedback 模糊作用于多个目标。 - -处理 `requestChanges` 时,Shell 必须在 interaction lock 内先把 interaction 原子认领为 Resolving,同时预分配并保存 `reworkOperationId`、response fingerprint、过滤后的 feedback 和 target artifact binding;随后通过同一 project operation journal 创建/查找唯一 rework operation。只有 rework operation 已 durable `prepared/enqueued` 后才能把旧 interaction 写为 Resolved;崩溃恢复按 `reworkOperationId` 幂等补入队,不能重新解释 feedback 或创建第二操作。外部执行结果未知时旧 interaction 保持 Resolving 或进入 needs-reconciliation,不得伪造 Resolved。旧产物版本保持不可变;新版本生成并通过 lineage 校验后才创建下一次 PolicyApproval interaction,并写回 followUpInteractionId。若 rework 尚未物化,`InteractionAccepted` 返回 `follow_up_interaction_id=None`,Consumer 只读取 Snapshot/事件等待后续交互,不得重复提交;同一 requestId 或 responseId 重放按 ledger 当前状态返回成功、in-progress 或 unknown,不能把 unknown 伪装成原 resolution 成功。 - -即使 Consumer 更换 command requestId,同一 `responseId`、相同 response fingerprint 也不能重复消费:interaction 已 Resolved 时新 command ledger 返回原 resolution;仍为 Resolving 时返回 `COMMAND_IN_PROGRESS`;对应 operation 已 outcome-unknown 时返回 `COMMAND_RESULT_UNKNOWN`。同一 responseId 不同内容失败为 `IDEMPOTENCY_KEY_REUSED`。已由其它 response 解决返回 `INTERACTION_ALREADY_RESOLVED` 并携带 `interactionRequired=false`;interaction revision、绑定对象或策略前提漂移返回 `INTERACTION_STALE`,零副作用。项目其它无关 Runtime/Snapshot 更新不使 interaction stale。禁止只凭持久化的 `"agent.resume"` 字符串或旧 policy 文案直接恢复。 - - -### 1.5 Conversation / response stream 交付合同 - -conversation/response stream 仍是独立展示通道,但现有“Runtime final-reply stream”和新协议 `submit_intent` 的 direct reply 不是同一种交付,且 Start/根终态的 Runtime-owned status message 也不是 Provider response;不能共用一个没有来源标签的 ledger。V1 冻结两条 response 分支、一条 status-message 分支和一条不进入 Public conversation 的 LocalTransport 分支: - -1. `DirectReplyDelivery`:interaction kernel 判定为 direct reply 时,在 request ledger `prepared` 阶段预分配稳定 `responseMessageId`,绑定 projectId、sessionId、requestId 和 response operation identity;消息记录至少具有 `reserved | streaming | committed | outcome-unknown` 状态。只有该分支的 `IntentAccepted.responseMessageId` 可直接定位 command reply;Start/Steer 的最终 assistant 属于下面的 RuntimeFinalReply,不在受理 ack 中假装已经存在。 -2. `RuntimeFinalReply`:已有 durable Runtime run 的最终回复继续由现有 finalization v4、`responseRequestSlot`、Agent/Session/run 和 response fingerprint 约束;Shell 不另造一套 Runtime message identity,必须读取/复用 finalization journal 已生成的 `finalizationId + messageId`(当前实现由 `game_creator_agent_runtime_finalization_id` 与 `game_creator_agent_runtime_finalization_message_id` 生成),并在 adapter 中校验二者与 run/session 完全一致。Runtime state、conversation assistant 和 response stream 三者以同一 finalization identity 闭合。Runtime final reply 的流中间态不能被 submit_intent command 结果代替,最终 assistant 也不能反向改变 Snapshot 的生命周期结论。 - -3. `LocalTransportReply / LocalManagement`:现有 CLI `project_location`、GUI/CLI 的 `/project`、`/open-project`、`/switch-project`、绝对导出包路径和本地配置操作只在受信任本地 transport/独立面板显示;它们不是 Public conversation message,不进入 Runtime prompt、Snapshot、事件或 Runtime response stream。所有 absolute host-locator operation(包括 `/project`、`/import-canvas-export`、本地文件选择器结果及 CLI `project_location`)只保存 `locatorDigest + locatorHandleRef + path-free argumentFingerprint + projectId(若已解析)`,原始路径留在受信任 path resolver/OS handle 边界内;路径需要展示时只能作为本地 UI/CLI transport result 返回,不能由 assistant message 或公共错误承载。`project_location` 现有 `ProjectLocation` action 若继续保留,P5 必须改为该分支,禁止把绝对路径写入 `conversation`。 - -三条会话写入顺序也冻结:DirectReply(包括未知 slash command)必须使用 `AgentRuntimeConversationUserMessageBinding::Present`,先用其中的 `conversationUserMessageId` 幂等提交原始用户文本(slash 保留规范化 command line),回读 user commit marker 后再写 assistant response,最后才关闭 command ledger;Steer 必须先完成同一 `conversationUserMessageId` 的 `conversation-persisted`,再推进既有 steer ledger 的 `queued`,不得在 steer ledger 与 input envelope 各写一条用户消息;若恢复发现 user marker 已存在,只能复用它,若正文/来源不一致则 `IDEMPOTENCY_KEY_REUSED`/`NEEDS_RECONCILIATION`。Start 也必须使用 `Present`,沿用 user message → Runtime status message → queued/dequeue。RuntimeFinalReply 或 terminal failure status 若属于没有原始用户轮次的后台恢复,才可使用 `NotApplicable`;该分支仍必须绑定 `source_record_ref`,不得借此补写第二条用户消息。 - -Public conversation 使用独立于 Snapshot event、Provider response stream 和各私有 delivery ledger 的**单一会话全局序列**。每个 `(projectId, sessionId)` 在 conversation append lock 内为待提交的规范 message identity 预留严格递增且永不复用的 `sequence` 与不透明 `cursor`;只有提交并回读成功后才把该 cursor 接入私有 committed cursor chain 并对 Public 可见,预留后失败可以留下永久 sequence 空洞,但失败 reservation 不进入 committed cursor chain,同一 `messageId` 不得获得第二个 sequence/cursor。cursor chain 与 `afterCursor/nextCursor` 分页是 committed message 补读完整性的唯一权威依据;`sequence` 只用于同一会话内稳定排序和诊断,数值不连续是合法终态,不表示漏读,也不得触发 Consumer 全量重读或无限恢复。只有 committed cursor chain 断链、重复链接或漏掉已 committed message 才表示历史不完整。response stream 的 `sequence` 只表示同一 Provider/final-reply stream 的 chunk/source revision,**绝不等于 conversation sequence**,也不能作为 Public conversation `afterCursor`。Runtime status message 不进入 response stream,但以独立 commit marker、唯一 `runtimeStatusMessageId` 和 conversation read-back 恢复。Consumer 只消费下面冻结的 Public conversation read DTO,按 `(projectId, sessionId, deliveryKind, messageId)` 去重,并按 `sequence` 稳定展示;不得读取私有 status/event/response ledger、解析不透明 ID 前缀或把 chunk 合并成 Runtime Snapshot。只有对应分支的私有 commit marker 已提交、conversation message 以同一 message key/正文/作用域回读成功且 delivery/input record 已进入 `committed` 后,Public read 才能返回该消息;`reserved/streaming/failed/rejected/discarded/outcome-unknown` 均不返回。commit marker、finalizationId、Provider request/slot/chunk、locator/path 和内部 observation 永远不进入 Public DTO。任一 Provider 已调用但正文提交无法证明时,仅将该分支置为 `outcome-unknown` 并进入 reconciliation,禁止换 requestId 或新 finalization identity 生成第二条回答。公开正文沿用同一权限、长度和内容安全过滤;其中 user 复用 `Conversation.message` 上限,runtimeFinalReply 复用 response stream 上限,runtimeStatus/publicEvent 复用 Runtime status/public event message 上限,directReply 统一复用上表 `4,000` Unicode scalar/`16 KiB` UTF-8 有界安全文本常量。Shell 必须在 conversation append lock 内、提交 Public message/sequence/cursor 前完成 directReply 正文规范化、内容安全和双上限校验;超限或不安全正文只能使既有 response delivery 确定性进入 `rejected` 并返回有界 `INTERNAL` 安全摘要,不得截断或提交正文,也不得留下无法分页补读的 committed 消息。原始 Provider chunk、凭据、绝对路径和内部 observation 不进入 Public Snapshot、Public conversation、公开错误或审计摘要。 - -下面的 Public conversation request/message/page/provenance/error/enums 与五命令、Public Snapshot 使用同一 Rust 权威 wire 模块:字段统一 camelCase,无数据枚举统一 lowerCamelCase 字符串。Rust request decoder 必须拒绝缺失字段、未知字段、重复字段、错误类型、非法 `schemaVersion`、越界 `limit/afterCursor` 和超限 request JSON,不能依赖 serde 默认忽略未知字段;结构或类型错误在任何 transport 查询 conversation 前统一返回 `INVALID_REQUEST`,不支持的版本统一返回 `PROTOCOL_VERSION_UNSUPPORTED`。GUI、CLI 和测试不得自行放宽、补默认值或回退读取私有 ledger。`AgentRuntimePublicConversationReadRequest`、`AgentRuntimePublicConversationMessage`、`AgentRuntimePublicConversationReadPage`、`AgentRuntimePublicConversationProvenance`、`AgentRuntimePublicConversationReadError` 及其全部枚举必须从该 Rust 单一来源生成 TypeScript schema/decoder、transport validator、golden fixture 和 negative fixture;Public response 的 success page 与 error envelope 必须分别进入同一生成和验证链路,decoder 对缺失/未知/重复字段、错误类型、未知枚举/code 或错误 schemaVersion 必须确定性报协议错误,不能静默丢字段后继续渲染。新增字段或改变必填性、wire value、文本/ID/cursor/数组/JSON 上限均须提升 `schemaVersion`,不能借 Public read 的只读性质绕过版本规则。 - -Session rotation 不会重写上述 delivery/status/event record 的 `session_id`、`responseMessageId`、`finalizationId`、`runtimeStatusMessageId`、`eventId` 或 `conversationMessageKey`。rotation barrier 捕获的 `DirectReplyDelivery`、`RuntimeFinalReply`、Start/terminal `RuntimeStatusMessage` 和 `PublicEventMessage` 必须由 continuation item 精确授权给 successor 继续同一 operation:`reserved/streaming` 只能复用原 message identity 继续提交,`outcome-unknown` 只能读回或 reconciliation,Start status 未提交前不得 dequeue,terminal failure status 未提交前不得投影 `failed/failure`。若 record 已在 barrier 后发生 revision 变化、缺少 continuation proof 或 provider call evidence 不明,successor 不得重建 response/finalization/status/event identity;统一保留证据并进入 reconciliation。 - -```rust -enum AgentRuntimePublicConversationDeliveryKind { - User, // user - DirectReply, // directReply - RuntimeFinalReply, // runtimeFinalReply - RuntimeStatus, // runtimeStatus - PublicEvent, // publicEvent -} - -enum AgentRuntimePublicConversationRole { - User, // user - Assistant, // assistant - System, // system -} - -// 仅允许公开、安全且渲染所需的 lineage;不包含 finalizationId、Provider、 -// locator、path、tool/action 或私有 ledger identity。 -struct AgentRuntimePublicConversationProvenance { - run_id: Option, - source_agent_id: Option, - source_run_id: Option, -} - -struct AgentRuntimePublicConversationMessage { - schema_version: String, - project_id: String, - session_id: String, - delivery_kind: AgentRuntimePublicConversationDeliveryKind, - message_id: String, - // 同一 project/session 的 conversation-global 顺序;不是 response-stream sequence。 - sequence: u64, - cursor: String, - role: AgentRuntimePublicConversationRole, - public_text: String, - // 仅 publicEvent 为 Some,且必须逐字节等于 message_id;其它类型必须为 None。 - event_id: Option, - provenance: Option, -} - -struct AgentRuntimePublicConversationReadRequest { - schema_version: String, - project_id: String, - session_id: String, - // None 表示从 session Public conversation 的 committed origin 开始全量分页。 - after_cursor: Option, - limit: u32, -} - -enum AgentRuntimePublicConversationHistoryState { - // 已覆盖 session origin 以来全部 retained committed message。 - Complete, // complete - // V1 committed history 完整,但存在无法证明 identity 的 pre-V1 私有隔离项。 - LegacyEntriesIsolated, // legacyEntriesIsolated -} - -enum AgentRuntimePublicConversationReadErrorCode { - CursorInvalid, // CURSOR_INVALID - HistoryIncomplete, // CONVERSATION_HISTORY_INCOMPLETE - Internal, // INTERNAL -} - -struct AgentRuntimePublicConversationReadError { - schema_version: String, - code: AgentRuntimePublicConversationReadErrorCode, - retryable: bool, -} - -struct AgentRuntimePublicConversationReadPage { - schema_version: String, - project_id: String, - session_id: String, - messages: Vec, - // 本页最后一条 committed 消息的 cursor;该 cursor 在 session 生命周期内持续有效。 - // 空页沿用 afterCursor,首次空历史为 None。 - next_cursor: Option, - has_more: bool, - history_state: AgentRuntimePublicConversationHistoryState, -} - -// 不属于 Provider response stream;它是 Runtime-owned conversation status message。 -enum AgentRuntimeInteractionAction { - Answer, // answer - Approve, // approve -} - -// 用户消息可能来自 submit_intent/input envelope;后台恢复的既有 Runtime final reply -// 可以没有可证明的用户轮次,但不得伪造一个 user message identity。 -enum AgentRuntimeConversationUserMessageBinding { - Present { - conversation_user_message_id: String, - // reserved 阶段可以为 None;用户消息 commit 成功并回读后必须补齐, - // delivery committed 前不得仍为 None。 - conversation_commit_marker: Option, - }, - NotApplicable { - reason: AgentRuntimeConversationUserMessageAbsenceReason, - source_record_ref: String, - }, -} - -enum AgentRuntimeConversationUserMessageAbsenceReason { - BackgroundRuntimeRecovery, // 仅 RuntimeFinalReply/terminal failure status 可用 -} - -struct AgentRuntimeStatusMessageRecord { - envelope: AgentRuntimeDurableEnvelope, - runtime_status_message_id: String, - project_id: String, - session_id: String, - run_id: String, - command_request_id: Option, - conversation_user_message: AgentRuntimeConversationUserMessageBinding, - status_kind: RuntimeStatusMessageKind, // startAccepted | terminalFailure; 固定 runtime-public-status- 前缀 - conversation_message_key: String, - status: RuntimeStatusMessageStatus, // reserved | committed | failed | outcome-unknown - failure_code: Option, - commit_marker: Option, - message_sequence: Option, - conversation_cursor: Option, -} - -enum RuntimeStatusMessageKind { - StartAccepted, // startAccepted - TerminalFailure, // terminalFailure -} - -enum RuntimeStatusMessageStatus { - Reserved, - Committed, - Failed, - OutcomeUnknown, -} - -// 现有“进入聊天的安全 Runtime 事件”也不是 SnapshotChanged envelope。 -// 由 Rust event projector 生成,前端只消费 eventId + publicText。 -struct AgentRuntimePublicEventMessageRecord { - envelope: AgentRuntimeDurableEnvelope, - // Shell 私有 delivery record;正式 conversation delivery 只投影 eventId + publicText。 - event_id: String, - project_id: String, - session_id: String, // 创建该 event 的 session provenance;rotation 后保持不变 - parent_run_id: String, - source_agent_id: String, - source_run_id: String, - durable_event_id: String, - event_kind: String, - public_text: String, - public_payload_digest: String, - conversation_message_key: String, - commit_marker: Option, - message_sequence: Option, - conversation_cursor: Option, - discard_reason: Option, - status: PublicEventMessageStatus, // reserved | committed | discarded | outcome-unknown -} - -enum PublicEventMessageStatus { - Reserved, // reserved - Committed, // committed - Discarded, // discarded;安全/大小策略确定拒绝,不再重试 - OutcomeUnknown, // outcomeUnknown -} - -enum AgentRuntimePublicEventDiscardReason { - PayloadTooLarge, - UnsafePayload, - NotAllowlisted, -} - -struct AgentRuntimeResponseDeliveryRecord { - envelope: AgentRuntimeDurableEnvelope, - delivery_kind: ResponseDeliveryKind, // directReply | runtimeFinalReply - response_message_id: String, // DirectReply 预分配;RuntimeFinalReply 复用 finalization.messageId - command_request_id: Option, - conversation_user_message: AgentRuntimeConversationUserMessageBinding, - finalization_id: Option, - response_operation_id: String, - project_id: String, - session_id: String, - run_id: Option, - response_request_slot: Option, - // RuntimeFinalReply legacy adapter 必须保存捕获时的完整 source identity; - // 不能只保存可重新指向当前 sidecar 的 key。 - legacy_source_binding: Option, - conversation_message_key: String, - commit_marker: Option, - status: ResponseDeliveryStatus, // reserved | streaming | committed | rejected | outcome-unknown - message_sequence: Option, - conversation_cursor: Option, - provider_call_evidence: ProviderCallEvidence, -} - -struct AgentRuntimeLegacyResponseStreamBinding { - legacy_stream_key: String, - task_id: String, - session_id: String, - run_id: String, - request_kind: String, - request_slot: String, - applied_steer_cursor: u64, - response_revision: u64, - source_sequence: u64, - source_status: AgentRuntimeLegacyResponseStreamStatus, - // 按捕获时完整 legacy source record(含正文/finish reason)计算; - // 恢复时 sidecar key、全部 identity、status、sequence 或 digest 任一漂移 - // 都不得继续 finalization,必须进入 reconciliation。 - source_record_digest: String, -} - -enum AgentRuntimeLegacyResponseStreamStatus { - Streaming, - Ready, - Committed, - Discarded, - Failed, -} - -enum ResponseDeliveryKind { - DirectReply, - RuntimeFinalReply, -} - -enum ResponseDeliveryStatus { - Reserved, - Streaming, - Committed, - Rejected, - OutcomeUnknown, -} - -struct ProviderCallEvidence { - request_slot: Option, - provider_request_id: Option, - call_status: ProviderCallStatus, -} - -enum ProviderCallStatus { - NotCalled, - Started, - Completed, - OutcomeUnknown, -} -``` - -五类 Public conversation message 的映射固定如下,adapter 不得按正文、文件位置、时间戳或 ID 前缀猜类型: - -| `deliveryKind` | `role` | Public `messageId` 来源 | `eventId` | Public provenance | -|---|---|---|---|---| -| `user` | `user` | `conversationUserMessageId` | 必须为 `None` | 默认 `None`;不得暴露 command/input envelope 私有 identity | -| `directReply` | `assistant` | prepared 时预分配的 `responseMessageId` | 必须为 `None` | 默认 `None`;request/operation identity 保持私有 | -| `runtimeFinalReply` | `assistant` | 现有 finalization journal 的 `messageId` | 必须为 `None` | `runId` 必填;`sourceAgentId/sourceRunId` 仅在已有公开 lineage 可证明时填写 | -| `runtimeStatus` | `system` | `runtimeStatusMessageId` | 必须为 `None` | `runId` 必填;不公开 failure 内部堆栈或 status ledger identity | -| `publicEvent` | `system` | 与 `eventId` **同一不透明 identity、同一 wire value** | 必须为 `Some(messageId)` | `runId/sourceAgentId/sourceRunId` 按 allowlist event projector 的已验证公开 lineage 填写 | - -Public 去重键固定为 `(projectId, sessionId, deliveryKind, messageId)`;同键正文、role、eventId、provenance 或 sequence/cursor 任一不一致都表示 durable identity 冲突,必须阻断该 session 的增量投影并进入 reconciliation,不能选择“最新”副本。`sequence` 只用于 conversation-global 排序和诊断,不参与业务 identity或分页完整性判断;同一去重键的 at-least-once 重放必须返回完全相同的 message。不同 deliveryKind 即使偶然得到同一 messageId 也不是同一消息,但 Public event 的 eventId/messageId 等值规则是显式例外,不得再生成第二 conversation message identity。 - -`read_public_conversation` 返回 `Result`。合法 `afterCursor` 必须属于同一 project/session 且指向 committed cursor chain 中已确认的 Public conversation 位置,返回链上其后的 committed message;单页同时受 `limit<=256` 和 `1 MiB` JSON 上限约束,下一条完整消息会使任一上限超出时在该消息前结束本页,只有链上仍存在未返回的 committed message 时 `hasMore=true`,`nextCursor` 指向本页最后一条 committed message。任一单消息仍必须先满足自身 Public 正文上限,不允许为满足页大小而截断正文。`afterCursor=None` 表示从该 session 的 committed origin 开始全量分页,不表示“只读最新一页”;全量读取期间新 committed message 只追加到后续 cursor 页,不得改写已经返回的 sequence/cursor。 - -V1 选择与现有本地 append-only/durable conversation 一致的**逻辑永久保留**方案:一条消息首次对 Public 可见后,其 committed message、cursor、私有 predecessor/successor chain link 以及 session conversation origin/tail marker 必须保留到该 session 被显式删除;物理压缩、checkpoint 或文件合并只有在仍能按原 cursor 读出完全相同的逻辑链时才允许执行。因此 `nextCursor` 在 session 生命周期内持续有效,Public conversation read **不得返回 `CURSOR_EXPIRED`**;`CURSOR_EXPIRED` 只保留给有界 Public Snapshot event 日志。格式非法、属于其它 project/session 或从未由该 session 签发的 cursor 返回 `CURSOR_INVALID`,不夹带部分 page;Consumer 可以在确认本地 cursor 损坏或丢失后以 `afterCursor=None` 重新全量分页,但不能把服务端曾签发 cursor 的消失当作普通 invalid 后静默重置。 - -若任一已签发 cursor、committed message、chain link 或 origin/tail marker 丢失、物理截断,或者 chain 漏掉已 committed message,服务端必须返回不可重试的 `CONVERSATION_HISTORY_INCOMPLETE`,不返回 page、`nextCursor` 或 `historyState`,并进入 reconciliation;不得从“当前最早文件”继续并伪装成 `complete`。`CURSOR_INVALID` 对同一 cursor 不可重试;`INTERNAL` 在 V1 也不可重试,后续只有通过新错误码才能声明明确暂态语义。成功 page 的 `historyState=complete` 表示从 session origin 起的 V1 committed history 完整,`legacyEntriesIsolated` 只表示完整 V1 history 之外另有无法证明 identity 的 pre-V1 隔离项,绝不能表示 retention truncation。Consumer 遇到 `CONVERSATION_HISTORY_INCOMPLETE` 只能停止增量/全量合并并显示有界恢复状态,不能回退读取私有 ledger、`LocalConversationResult` 或用 `afterCursor=None` 掩盖服务端截断。 - -现有 `LocalConversationResult` 是受信任本地/Developer 管理 DTO,其 `path`、本地 session catalog 和磁盘顺序不能复用为 Public read 合同。Public adapter 必须从已 read-back 的 conversation commit 生成上述 path-free message/page,绝不返回 `path/absolutePath/finalizationId/commitMarker/providerRequestId/requestSlot/chunk/tool observation`。commit marker 只保存在 Shell 私有 delivery/input record 中,用于证明 Public message 已 committed;Consumer 既不能读取也不能提交它。 - -legacy conversation message 若没有 `messageId`,只有在现有 finalization/audit/input/steer 证据能唯一证明 project/session、五类 deliveryKind、规范 message identity、role、正文和 lineage 时,migration adapter 才能以该规范 identity 分配一次 conversation-global sequence/cursor,并保存不可变 migration binding;不得按数组位置、正文 hash、mtime 或相邻消息猜 ID。无法唯一证明的 legacy record 必须移入只读私有隔离索引,保留原证据且不进入 Public incremental/full read,page 返回 `historyState=legacyEntriesIsolated`;它只能在受信任 Developer/local history 中查看,不能阻塞新 committed message 继续使用新的 sequence/cursor,也不能在以后重新投影成另一个 Public identity。 - -`Present.conversation_commit_marker=None` 只允许存在于尚未提交用户消息的 reserved/准备阶段;`conversation_commit_marker=Some(...)` 必须经过 conversation read-back 校验。DirectReply、Start 和有用户轮次的 RuntimeFinalReply 在 delivery/status committed 前必须使用 `Present(Some(marker))`;只有有明确 durable 证据证明不存在原始用户轮次的后台 RuntimeFinalReply 或 terminal failure status 才允许使用 `NotApplicable`。 - -所有 delivery record 的 `committed` 状态都必须同时具有可读回的 `commit_marker + conversation_message_key + message_sequence + conversation_cursor`,并与 Public conversation message 的 project/session/deliveryKind/messageId/正文逐项校验;`reserved/streaming` 不得被 Consumer 当作消息已交付,`outcome-unknown` 不得通过新 message key 或新 sequence 补写第二条消息。Public event 的 `discarded` 状态必须具有 `discard_reason`、私有审计证据且不可重新投递,且不得带有已提交的 `commit_marker/message_sequence/conversation_cursor`;其它状态的 `discard_reason` 必须为空。Public event 的 `public_payload_digest` 必须等于 eventId 派生时使用的规范化 public payload digest,不能只依赖可变 `publicText`。 - -所有上述 record 的 `AgentRuntimeInputEnvelopeRecord`、`AgentRuntimeInteractionRecord`、`AgentRuntimeBuiltinManagementOperationRecord`、`RunLineageRecord`、Session/Handoff/ActiveSessionIndex/SessionRotation/HandoffManifest、status、public-event、response 以及 LocalManagement record 的 `envelope.record_id` 必须与业务 identity 一一对应;每条 record 的 `envelope.record_revision` 是该 record 的唯一 CAS revision,`checksum` 是去掉 checksum 字段后的 RFC 8785 canonical JSON SHA-256,`ledger_version` 单调递增,`owner_boot_id + owner_generation` 用于 fencing,`created_at/updated_at` 只作审计不能决定恢复胜负。校验失败统一隔离为 corrupt record 并保留原证据,不能用“最新文件”覆盖或静默降级;状态枚举之外的值、缺失的 commit marker、身份不匹配和重复 message key 都按 `outcome-unknown/needs-reconciliation` 处理。 - -现有 `response-streams/{agentIdHash}/{runIdHash}.json` 实际属于 `RuntimeFinalReply`:它按 `agentId/runId` 定位,记录还包含 `taskId/sessionId/requestKind/requestSlot/appliedSteerCursor/responseRevision/sequence` 等 lineage 与流身份字段;它没有 direct reply 的 `responseMessageId` 或 conversation message cursor。adapter 在 prepared 阶段必须把捕获时的这些字段、完整 source status/sequence 和含正文/finish reason 的 `source_record_digest` 写入 `legacy_source_binding`;恢复时只能校验同一 binding,不能按 key 重新读取当前 sidecar 猜测 lineage。状态是 `streaming/ready/committed/discarded/failed`,legacy adapter 必须对五种状态穷举映射:`streaming` 只能映射为 `streaming`;`ready` 只能映射为“待 finalization commit”,不能直接映射为 `committed`;`committed` 只有在 conversation layer 以同一 finalization journal 的 `finalizationId + messageId` 原子写入并可读回最终消息后才可提交;`discarded`/`failed` 只有在 durable evidence 证明没有 conversation commit、没有未决 Provider/进程副作用且可确定拒绝原因时才映射为 `rejected` 并保留不可重试 tombstone,否则一律映射为 `outcome-unknown` 并进入 reconciliation。legacy `committed` 若找不到对应 conversation commit marker、`AgentRuntimeConversationUserMessageBinding`,或其 messageId 与现有 finalization 不一致,也必须转 `outcome-unknown`,不得凭 sidecar 正文补写第二条消息。只有存在明确 durable 证据证明历史 finalization 没有原始用户轮次时,legacy adapter 才能生成 `NotApplicable { source_record_ref }` 的后台恢复记录;用户消息缺失、存在多个候选、正文/来源冲突或 lineage 无法唯一解析时,必须进入 `outcome-unknown/reconciliation`,不得使用 NotApplicable 掩盖歧义。DirectReplyDelivery 必须新增独立 ledger/stream adapter,不能把现有 final-reply sidecar 路径或 `ready` 状态冒充 direct reply 身份。 - -P0/P3 fixture 必须分别覆盖 direct reply 和 Runtime final reply 在预分配 messageId/finalizationId 后崩溃、stream 中断、正文 committed 但 command/finalization result 未写、同 requestId 重放、跨 transport 读回、steer/取消使旧 finalization 失效和 content-filter 拒绝;另覆盖 Start status message committed 前禁止 queued/dequeue、status write failure/unknown、根终态 failure status 先于 task/event/state 终态、prompt 排除固定前缀、public event 的 eventId/publicText 去重和恢复不重复 status message;并覆盖五类 Public message mapping、eventId/messageId 等值、conversation-global sequence/cursor 预留崩溃形成永久合法空洞且 Consumer 不重启补读、同 identity 重放、committed cursor chain 不漏消息、跨页并发追加、`afterCursor=None` 从 session origin 全量分页、已签发 cursor 经物理压缩后仍有效、格式/跨 scope cursor 返回 `CURSOR_INVALID`、message/cursor/chain/origin/tail 人为截断返回无 partial page 的 `CONVERSATION_HISTORY_INCOMPLETE`、conversation read 永不返回 `CURSOR_EXPIRED`、legacy message 唯一迁移或隔离、LocalConversationResult.path 与 finalization/Provider 私有字段零泄漏,以及 response-stream sequence 不能冒充 conversation sequence。验收以每条 delivery 分支最终 durable 消息唯一、cursor chain 补读覆盖全部 retained committed message、合法 sequence 空洞不造成恢复循环、截断历史不伪装完整和 Provider 调用计数不重复为准。 - ---- - -## 2. 现状盘点与复用清单 - -### 2.1 可直接复用的资产(近 1 个月内形成,活跃演进期) - -| 资产 | 位置 | 复用方式 | -|---|---|---| -| CLI 交互决策状态机 | `swarm_cli/turn_dispatch.rs:245` `decide_interaction_action`、`:48` `handle_swarm_user_turn`(steer/start、Reply/Execute/Resume、goal 门禁) | **整体上提**到后端 Shell(纯 Rust、无 UI 纠缠) | -| Runner 自驱续跑定时器 | `runtime_driver/provider_recovery.rs` 的 `schedule_waiting_provider_retry_wake_after_lane_release` 等 | 已存在,P4 直接复用 | -| 确定性 e2e | `scripts/agent-runtime-deterministic-playable-e2e.mjs` + `deterministic-lane-defense-provider.mjs`(`expectedProviderStats`/`expectedChildReport` 断言) | P0 基线扩展(协议级 trace) | -| 版本协商 fallback | 前端 `model.ts:1186-1226` `isMissing*CommandError` | 迁移期新旧并存的标准模式 | -| 内部幂等护栏 | `accepted_run_id`(`runtime_state.rs:1548`)、runner requestId 缓存(`runner/protocol.rs:353`)、goal CAS | 继续复用;P1 建 request ledger 底座,P3 接入五个公开写命令 | -| 进程内集成测试 | `src-tauri/tests/`(`command_runtime.rs`、`runtime_actions/`、`collaboration/`、`goal.rs`) | P1-P6 每步回归的护栏 | - -### 2.2 需要收敛/改造的点 - -| 点 | 位置 | 动作 | -|---|---|---| -| 前端 phase→文案映射 | `model.ts:612/644/974/1498/1554` | Shell 填充 `waiting_on/next_step` 后删除 | -| 前端 steer/start 决策 | `model.ts:849` `submitProjectSupervisorRuntimeTask`、`App.tsx:5791` | 迁入 Shell(`submit_intent`) | -| 前端 confirm/retry/resume 门禁 | `model.ts:1131-1154`、`panels.tsx:381-497` | 由 `InteractionRequired` + `approve/resume` 取代 | -| 前端跨轮状态修补 | `model.ts:244` `normalizeAgentRuntimeState`、`:385` `mergeAgentRuntimeStateIntoMap` | 依赖 P2 Snapshot 稳定后删除 | -| CLI 独立状态机 | `swarm_cli/turn_dispatch.rs` | 上提 Shell,CLI 只留终端交互(stdin/stdout/observer) | -| 重复触发恢复 | `App.tsx:2799/10341`、`useDeveloperAgentPanel.ts:684`(启动时 resume)、`App.tsx:10550`(devMode schedule 按钮) | P4 后删除,由 Runner 自驱 | - ---- - -## 3. 分阶段实施计划 - -> 依赖顺序:**先建安全网 → 建持久协议底座 → 建唯一读模型 → 一次性开放完整写协议并收回决策 → Runner 自驱 → 迁移 Consumer → 删除旧公开面**。后续阶段不得反向依赖尚未落地的公开 DTO。 - -### P0 行为基线与协议验证框架 - -**目标**:在改动前建立可判断迁移语义和新协议安全性的验证入口,不冻结旧 DTO。P0 允许新增只读 fixture、golden input/output、crash-point harness 和测试辅助代码;禁止新增生产 handler、Consumer fallback 或改变现有 Runtime 生产行为。 - -- 复用确定性 Provider 与现有进程内 Runtime 测试,记录当前 master 的用户可见语义:提交模式、目标 run、等待/终态、批准/拒绝、取消、恢复及重复副作用计数;统一归一化随机 ID 和时间戳。 -- 为第 1 节建立尚未启用的契约 fixture:项目/session handoff 身份、不可变 Run target set/continuation set/manifest/rotation phase/active-session index 与 Public `sessionContext` 映射、`Prepared` operation 先于 fence 持久化、`FenceCommitted` operation/index marker 原子提交、barrier 后 Open/Resolving interaction、command/input envelope 和 direct/final/status/event delivery 的 successor 重新授权、LocalManagement parser/route/target 历史 identity 恢复、Snapshot/Command error 类型隔离、六个专业组父 run 投影与 retry/repair、Runtime-owned progress view、command capabilities、attachment binding、UserInput/User ToolApproval/PolicyApproval、slash built-in parser/交互门禁、intent/steer policy matrix、cancel matrix、ContinueRun/RetryTerminalRun/ReconcileRun、revision/cursor、request/response/status-message/public-event ledger、Interaction/rework 状态机、结构化错误和崩溃点。 -- 基线比较只要求业务语义和副作用一致,不要求旧 DTO、旧事件名字或旧命令调用序列与新协议相同。 -- P0 不修改 Runtime 生产行为,也不以空 handler、ignored 断言或永真 stub 让新契约提前通过。 - -**完成门禁**:当前 master 基线可重复通过;每个协议不变量都有明确测试入口和预期失败原因,能区分“尚未实现”与“错误通过”。 - -### P1 持久协议底座 - -**目标**:先落不依赖公开命令和 Snapshot 的共享基础设施,避免 P1 命令反向依赖 P2/P3。P1 不注册五个新公开命令。 - -- 新增 `agent/supervisor_shell/` 内部模块,集中处理 canonical root、manifest project identity、调用来源 capability、结构化公开错误映射和 project lock 顺序;`schemaVersion`、五命令、错误码及 Snapshot/Interaction DTO 放入同一共享契约模块,由 Rust 与 TypeScript 绑定共同生成/校验,避免两端手抄漂移。 -- 实现带 schema 的 command ledger、response message ledger、interaction/rework ledger、projection journal 基础读写:私有目录、原子写/回读、损坏隔离、锁、状态转移校验、同 ID 指纹冲突、authorization scope replay guard 和 archive/tombstone 生命周期。 -- 将 RFC 8785 + SHA-256 规范化、request/response fingerprint、公开文本脱敏和稳定 ID 生成收敛为单一实现;禁止各命令自行拼接字符串做指纹。 -- 明确锁顺序为 `project execution owner → supervisor project lock → command/interaction/projection 子记录`;不得持有文件锁等待 Consumer,也不得绕过现有 Runtime 的 run/action 锁顺序。长时间内部执行使用 ledger ownership 标记而非长期占用 transport 线程锁。 -- 现有公开命令和 Runner 内存 request cache 行为不变;P1 只通过存储/状态机单测和 crash-point 测试验证底座。 - -**完成门禁**:ledger 状态转移、相同/不同指纹、torn write、损坏记录、权限拒绝和锁竞争均失败关闭;尚无新公开调用面,旧行为基线不变。 - -### P2 Public/Developer Snapshot 与项目级事件流 - -**目标**:先建立稳定、完整、可重连的唯一公开读模型,继续保留旧 read 接口供迁移。 - -- 从现有 Runtime state、task/event、response stream 元数据和 pending sidecar 生成第 1.1 节双投影;Public 包含当前 Project Supervisor、同 run 的 Runtime-owned progress view、父 run 精确匹配的六个静态专业组、interaction 和 command/recovery capabilities,waitingOn/nextStep 使用稳定枚举,Snapshot error 与 Command error 使用不同 code/DTO;Developer 通过独立受信任 capability 读取选定内部 run。 -- 在 shadow/read-only 语义下把既有 user-input、User/Developer pending tool confirmation 和可识别的 policy confirm 物化为稳定 Interaction record/view:首次投影在 owner/lock 内持久化 identity、audience、context、action/policy fingerprint 和 target set,后续按 bound fingerprint 复用;旧 sidecar 缺少可信绑定时投影 needs-reconciliation,不能每次读取生成新 interactionId。P2 只建立/刷新 record,不改变旧命令的交互行为。 -- project projection ledger 原子维护当前规范化 Public Snapshot、revision、event cursor、最近 256 条 envelope 及恢复 journal;每次 Public read 先在 projection lock 内修复未闭合 journal,再返回同一线性化点的 Snapshot/cursor。 -- 提供 `read_game_creator_agent_runtime_snapshot`、Public 事件订阅和 `afterCursor` 有界补读;Developer read 使用独立命令/DTO,不与 Public 返回 union。 -- Public revision 只由白名单变化推进;事件只发布 `SnapshotChanged`,同 revision 的恢复沿用同 eventId。测试不得依赖 Tauri best-effort event 自身保存补读历史。 -- P2 不删除 Consumer normalize/merge,也不注册五个写命令;只允许测试或 shadow observer 对照旧 read 与新 Snapshot。stale display 可以保留上一份 Snapshot 供展示,但只有上一份精确 cancel capability 可作为止损入口;其它旧 capability 必须禁用,且 Snapshot 恢复后不得把旧 capability 当作当前事实。 - -**完成门禁**:重复、乱序、缺口、读订阅竞态、cursor 非法/过期、投影崩溃窗口和本地高 revision 均通过完整 Snapshot 收敛;progress 不跨 run 合并且校验证据缺失不猜通过,六个专业组按当前 parentRunId 稳定排序,manifest fallback 不伪造 Runtime 状态,stale display 只允许上一份精确 cancel capability 且服务端重新校验;User ToolApproval/Needs input 可定位到 Supervisor 或专业组且不泄漏内部 action;正式 Public 零路径、完整任务/action/plan、动态 child、原始工具计划和 Provider 正文;Developer capability 服务端拒绝未授权来源。 - -### P3 完整写协议与 Interaction Loop 收归 - -**目标**:在 P1 底座和 P2 唯一读模型都可用后,一次性注册真实可用的五命令;不发布“DTO 已存在但仍要求 Consumer 选择旧分支”的半成品协议。 - -- `submit_intent` 先将 GUI/CLI 现有 slash parser 收到 Shell:`/` 输入只走同一版本 parser,只读命令走 direct reply,副作用命令生成 Interaction,禁止成为自主 Runtime task;随后才将 CLI 的 Reply/Execute interaction kernel 和 GUI 的 start/steer 判定上提到 Shell:在 project lock 内按冻结 intent policy matrix 决定 direct reply/start/steer/reject;active root Run 已绑定冻结 Goal Contract 且 kernel 判定为 execute 时必须在旧 steer wrapper 前稳定拒绝为 `TARGET_BUSY`,direct reply 仍按原门禁交付;现有 replacement primitive 仅由显式 Goal management mutation 经其 operation identity/lineage 调用;`(intentKind, entryBindingKind, runProfile, inputPolicy)` 必须命中当前 capability 的同一 option,实际资源 binding 来自既有资源管理面,message 必须非空,附件进入同一 input envelope 且不能被丢弃。steer 在 prepared 阶段绑定现有 V1.13 steerId/cursor,source 只做受信任归因/权限/审计,不能改变业务路由。Continue/Retry/Reconcile 只能走结构化 `resume` tagged intent。 -- 接管 P2 已物化的 user-input/User/Developer tool-confirm Interaction record,并为项目 resume/retry policy confirm 创建稳定 record;`answer/approve` 只按 interaction response meta 路由,approve 显式处理 `approve | reject | requestChanges` 及其 feedback、policy snapshot 和 target set 合同。 -- `cancel` 按第 1.3 节取消矩阵锁内校验精确 Supervisor run;`resume` 只按 tagged intent 区分 ContinueRun、RetryTerminalRun、受信任 ReconcileRun。pending/timer/lane/ready task 的 Runner wake 与项目级自动恢复留在内部 recovery,不由 Public ResumeCommand 模糊触发;需要用户决定时创建项目级 PolicyApproval,而不是执行。 -- 五命令全部先走 request ledger,再进行 target/interaction 校验和内部调用;成功、业务拒绝、并发 in-progress、崩溃可证明结果及 outcome unknown 都按第 1.3 节闭合。 -- 在同一阶段实现第 1.5 节独立 Public conversation append/read adapter:五类 committed message 共用 project/session conversation-global sequence/cursor,committed cursor chain 是分页完整性的权威,sequence 永久空洞合法且只用于排序/诊断;`read_public_conversation(afterCursor, limit)` 只返回 path-free Public DTO,并将 committed message/cursor/origin/tail 逻辑保留到 session 显式删除。DirectReply、RuntimeFinalReply、RuntimeStatus、PublicEvent 与 user input 的私有 commit marker/read-back 必须先闭合,不能把现有 `LocalConversationResult.path`、response-stream sequence 或私有 delivery ledger 暴露给新 Consumer。legacy 无 messageId 记录只按唯一 durable evidence 迁移,否则进入只读隔离并返回 `legacyEntriesIsolated`;物理截断或 cursor chain 损坏必须返回 `CONVERSATION_HISTORY_INCOMPLETE`,不得返回 partial page、`CURSOR_EXPIRED` 或伪造 `complete`。 -- Shell 负责生成 Public `stage/waitingOn/nextStep`、六个专业组状态、command/recovery capabilities 和安全 Interaction presentation;CLI 的 Reply/Execute/Resume、Consumer 的 start/steer/confirm/retry/resume 判断在此阶段只作为旧公开路径的兼容实现存在,不作为新命令输入。 -- 新旧公开命令并存,但新命令从注册之日起即具备完整生产语义。所有旧写 wrapper 同时改为经过同一 Shell project lock 和 projection-dirty/Interaction 同步 adapter:旧接口可以没有新 requestId 保证,但不能绕过 Interaction 状态、Public 投影或与新命令并发写出矛盾事实。P3 通过进程内调用和专用协议 harness 验证,不提前迁移正式 CLI/GUI 调用点。 - -**完成门禁**:五命令的同 requestId 重放、同键异内容、并发重复、业务拒绝重放、Runner 强杀读回和 unknown outcome 全部闭合;slash 已知/未知/带附件/开放 interaction 路由与现有语义等价且绝不创建自主任务;入口 intentKind/attachment 的同 requestId 异值命中 `IDEMPOTENCY_KEY_REUSED`,attachment 漂移零副作用,并发 intent 不创建第二个 Supervisor run;same-run steer 保持 runId/steer cursor 且不重复 conversation,冻结 Goal Contract 的 execute 请求返回 `TARGET_BUSY`、不会调用 replacement primitive;Goal management replacement 使用独立 operation identity/lineage,并覆盖旧树已取消但 replacement 未入队的 crash point;cancel matrix 逐行验证、CancelAccepted 不伪造终态且重复 cancel 共用 operation;ContinueRun 保持 runId、Supervisor/专业 RetryTerminalRun 生成唯一 successor lineage、prepared repair 只生成/引用唯一 Public PolicyApproval interaction 且在其解决前不开放 retry、ReconcileRun 不重放未知副作用;UserInput/User ToolApproval/Developer ToolApproval audience 隔离,无关 Snapshot 更新不使 interaction 失效,action/policy/target set 漂移失败关闭;`approve` 的 `requestChanges` 反馈过滤、单一不可变产物绑定、唯一 reworkOperationId 与后续审批交互可读回且不可重复;direct reply 的 durable responseMessageId/stream、Start/terminal Runtime status message 先后顺序与恢复闭合;五类 Public conversation message 的 mapping、全局 sequence/cursor、去重、cursor-chain 分页、合法 sequence 空洞、session-lifetime retention、`CURSOR_INVALID` 与 `CONVERSATION_HISTORY_INCOMPLETE` 失败关闭、legacy 隔离及零 path/私有字段跨 transport fixture 全部通过,且 conversation read 不产生 `CURSOR_EXPIRED`;新旧路径用户可见终态与副作用计数等价。 - -### P4 Runner 自驱与安全恢复 - -**目标**:Runner 合法存活期间,工作发现、确定性唤醒和安全恢复不依赖 Consumer 调用,同时保持 owner、drain 和未知外部结果边界。 - -**项目目录簿**: - -- 将进程内 `known_roots` 扩展为 AppData 私有 `game-creator-known-roots.v1`。记录稳定 project identity、canonical root、首次/末次登记时间和有效状态,不保存用户输入、Provider 内容或凭据。 -- Unix 父目录/文件权限分别为 `0700/0600`;Windows 使用仅当前用户可访问的等价 ACL。写入使用同目录临时文件、文件同步、原子替换及目录同步(平台支持时);读取校验 schema、普通文件/非链接、owner/ACL、重复 identity 和重复 canonical path。 -- root 每次使用前重新 canonicalize 并重读 manifest identity。目录消失只标记失效;搬迁仅在新的、已授权 locator 注册并能唯一证明同一 projectId 时更新,不主动遍历磁盘寻找项目。identity/path 冲突或目录簿损坏保留证据并进入 reconciliation。 -- root 注册权属于现有项目创建/打开管理面,不属于五个 Runtime 命令。项目首次创建或授权打开时,必须在发送任何 Runner wake 前写入 manifest projectId 并原子登记 canonical root;登记失败则项目不能进入自动调度。项目关闭只撤销 active control lease,不删除 ledger;归档/删除必须先 drain 并保留 request/interaction tombstone。复制目录若沿用同一 projectId 视为 identity 冲突,必须显式执行 clone-as-new-project 生成新 projectId,不能靠路径先到先得。 - -**wake、扫描与 owner**: - -- Shell 成功提交工作、解决 interaction、写入确定性 timer、lane 释放、manifest ready 或 owner 状态变化后发送按 projectId 去重的有界进程内 wake;队列已满时只合并同 project wake,不阻塞持久提交。durable runnable state 本身是丢 wake 后的恢复依据,wake 不是事实源。 -- Runner 启动时及兜底轮询前,逐 root 完成 canonicalize/identity 校验并取得既有 project execution owner;未取得 owner 时不得扫描该项目 durable task、修改 Runtime 或标记活跃。每个 wake/扫描批次都有时间和工作量预算,同一热项目完成一批后重新排队,不能饿死其它 root。 -- 现有 25ms socket accept loop 继续只处理连接与 heartbeat,不在该线程中扫描目录或执行 Runtime 工作。另建阻塞式调度 worker(或等价专用 Runtime task)消费进程内 wake/队列信号;兜底扫描每个 Runner 最快 30 秒一轮,带抖动、轮转且每轮最多 8 个 root;启动恢复也使用同一有界批次并持续轮转,不能启动瞬间全量扫盘。 -- draining、GUI-owner 丢失或 project owner 释放开始后立即停止新 Runtime 调度;不得把已经受理的 cancel、读回、状态查询和 reconciliation 操作误判为普通新调度。除取消/收束类命令外,尚未写入 prepared 的会触发 Runtime 执行的新命令返回 `OWNER_UNAVAILABLE`;已开始的内部工作按现行 interruption/handoff/reconciliation 合同收束。 - -**恢复矩阵**: - -| durable 状态 / 条件 | 自动动作 | 禁止动作 | -|---|---|---| -| command `prepared` 且可证明未产生副作用 | 取得 owner 后继续同一 request | 不创建替代 requestId | -| session rotation `Prepared` 且 active index 未 fenced | 继续按旧 active session 投影 `Ready`;校验 operation 的 predecessor/successor、expected revision/epoch 和 owner generation,预期仍成立时继续同一 operation 的 fence commit,否则写 `Rejected` tombstone | 不投影 `HandoffInProgress`,不创建 successor session/manifest/handoff/集合或其它 rotation 副作用;不得在 index 已引用 operation 时把 Prepared 当作有效 fence | -| session rotation `FenceCommitted` / `HandoffManifestCommitted` / `SuccessorSessionCommitted` / `HandoffsCommitted` | 校验 operation/index 的 operationId 与 fence marker、不可变 target set、continuation set 与 manifest;只读显示旧 interaction/已提交 conversation,阻止新 requestId 和新 delivery identity;仅在所有 handoff、continuation 与 active-session marker 可证明后一次性切换 | 不使用 pending successor/handoff/continuation 控制 Run 或旧 record,不按当前扫描集合补目标,不开放写 capability;stale cancel 例外服从 rotation phase gate | -| session rotation `ActiveSessionCommitted` | 校验 `activeIndex.committedRotationOperationId == operation.operationId`、active-session marker、predecessor/successor session、source/successor revision、manifest operation/session/epoch、`activeIndex.runCreationEpoch == expected + 1` 和全部 handoff/continuation,幂等补 Public `Ready` 投影 | 不按 activeSessionId/最新文件猜已提交 operation,不同时接受旧/新 session,不重复生成 capability | -| session rotation `Prepared-before-fence Rejected` | 校验 active index 从未引用该 operation;只写 operation tombstone,不清 fence、不递增 epoch、不隔离或创建 successor/manifest/handoff/set,保留历史 `committedRotationOperationId`,幂等维持旧 `Ready` | 不修改 active index,不恢复 successor、不发送 Runtime wake、不复用被拒绝的 operation/successor identity | -| session rotation `FenceCommitted-after-fence Rejected` | 校验 operation/index 的 operationId 与 fence marker 一致、active-session marker 未提交且无未知结果;隔离该 operation 的 pending records,在同一 journal 只清除该 operation 的 fence/marker并按规则递增 epoch,保留历史 `committedRotationOperationId`,幂等恢复旧 `Ready` | marker/commit 状态不一致时不清 fence;不覆盖历史 committed id,不恢复 successor、不发送 Runtime wake、不把冻结 interaction 当新交互 | -| session rotation `ReconciliationRequired` 或 manifest/index/continuation 冲突 | 清空写 capability,保留所有 target/continuation、旧 interaction 和 delivery 证据并进入 reconciliation | 不选择最新文件、不自动开放 answer/approve/cancel/resume,不恢复旧/新双写窗口 | -| command `executing` 且结果有可信 durable 证据 | 幂等补 command result / Public 投影 | 不重复内部或外部副作用 | -| command `executing` 且外部结果未知 | `outcome-unknown` + `needs-reconciliation` | 不自动重放 | -| Runtime `pending` 且身份可信、owner 已取得 | 可调度一次 | 不跨 owner 重复调度 | -| 等待确定性 timer / lane release | 到期或 wake 后继续 | 未到期不轮询重放 | -| Open user input / tool approval / policy approval | 保持 InteractionRequired,只刷新投影 | 不自动批准或把权限拒绝当恢复失败 | -| interaction `resolving` 且结果可证明 | 幂等补 Resolved 和 command result | 不重新消费 response | -| `requestChanges` rework `prepared/enqueued` 但旧 interaction 未闭合 | 按 operation identity 补齐唯一 rework link,再关闭旧 interaction | 不重解释 feedback、不创建第二 rework | -| interaction `resolving` 且外部结果未知 | 保留 response/target 证据并进入 reconciliation;旧 interaction 不伪造 Resolved | 不以第二 response 自动解决 | -| Runtime `executing` 且 Provider/工具/进程结果未知 | `needs-reconciliation` | 不自动重放 request/action slot | -| journal、ledger、interaction 或 run 身份损坏/冲突 | `needs-reconciliation` | 不猜测、不覆盖证据 | -| 业务已完成、revision 尚未分配 | 由 dirty journal 生成唯一下一 revision/eventId | 不回滚业务事实、不跳号 | -| revision 已持久化但事件未闭合 | 幂等补同 revision/eventId | 不生成第二事件或第二终态 | -| project owner 未取得 | 不扫描 durable task、不执行 | 不越权读取后调度 | -| Runner draining / GUI-owner 丢失 | 停止新调度并收束进行中工作;允许只读、结果读回、取消和 reconciliation | 不接受新的自动恢复或新的 Runtime dispatch | -| root 不存在、搬迁或 identity 冲突 | 可证明失效则标记;其余 reconciliation | 不按旧路径执行 | - -**边界**:Runner 仍由 GUI 启动,保留 GUI-owner watchdog 与 `game_chat_release` 退出协议;本轮不实现开机自启或无 GUI 常驻。 - - -自动调度只允许处理以下状态:身份可信、已取得 project execution owner、项目不处于 draining、任务为 `pending` 且其依赖已满足,或确定性 timer/lane 到期且可证明尚未执行;这些都是内部 recovery intent,不得伪装为 Public ResumeCommand。投影补偿只允许重复生成已确定的 Snapshot/event 结果。`waiting for user input`、`waiting for policy approval`、`waiting for developer approval` 永不自动推进;`executing`、Provider/工具/进程结果未知、状态身份冲突和 `needs-reconciliation` 永不自动重放。Runner 的调度 worker 在 owner lease、GUI-owner lease 或 drain 状态任一失效时先停止 dequeue,再决定进行中工作如何收束;不得先取出任务后再补做 owner 检查。 - -所有自动动作都必须记录可恢复的 wake reason 和 operation identity。wake 只负责唤醒,不能证明任务仍可执行;worker 每次 dequeue 前重新读取 durable state、owner generation 和 project drain 状态,校验通过后才创建或继续同一 operation。兜底扫描发现不满足上述条件的 root 时只记录跳过原因,不改变 Runtime 状态;扫描过程不得为了“发现新项目”而遍历未登记目录。 - -**完成门禁**:目录簿安全合同和恢复矩阵逐行验证;25ms socket loop 零持久目录扫描/Runtime 执行;丢 wake 可由有界兜底恢复;未知结果、owner 冲突、drain 和 GUI-owner 丢失均零自动重放/新调度。 - -### P5 迁移 CLI、Tests、GUI - -**目标**:只迁移 Consumer,不在本阶段新增协议语义。顺序固定为 CLI → 面向公开协议的 Tests → GUI。 - -- 面向用户/自动化的 Supervisor CLI 改为 Public Snapshot + 五命令 + event cursor;现有 `--swarm-chat` 若继续展示完整专业 Agent 状态,必须明确归类为受信任开发 CLI 并走 Developer capability,不能一边读取内部字段一边宣称是正式 Public Consumer。 -- 公开 e2e/fixture 迁移到同一协议;直接调用内部 start/steer/resume 的单元和恢复回归继续保留,不把内部能力误算为 Consumer。 -- 面向用户的 CLI 与 GUI 会话列表统一改读 `AgentRuntimePublicConversationReadPage`:使用 `(projectId,sessionId,deliveryKind,messageId)` 去重、conversation-global sequence 排序、committed cursor chain 与 `afterCursor` 分页;首次读取或本地 cursor 确认丢失时以 `afterCursor=None` 从 session origin 全量补读。sequence 数值空洞是合法诊断信息,不得触发重读;服务端签发的 conversation cursor 在 session 生命周期内必须持续有效,该 read 不处理 `CURSOR_EXPIRED`。`CURSOR_INVALID` 不夹带 partial page;`CONVERSATION_HISTORY_INCOMPLETE` 必须停止合并并显示恢复状态,不能以 `afterCursor=None`、`LocalConversationResult.path` 或私有 ledger 掩盖截断。不得按 response-stream sequence 排序、从 eventId/ID 前缀猜 deliveryKind 或直接访问私有 delivery ledger。受信任 Developer/local history 可以继续使用独立本地 DTO,但必须在类型和调用面上与 Public read 隔离。 -- GUI 正式 Supervisor、Runtime-owned 进度卡、六组底栏状态、用户确认/Needs input 和专业失败重试只使用 Public Snapshot 的 collaborator/interaction/capability;上传先经既有项目资源管理面取得 immutable attachment binding,再以非空 message 调用 submit_intent。attachment-only 在 V1 明确拒绝,不能由 GUI 合成自然语言;附件必须在 input envelope 和 Provider/resource context 中可恢复。`preview.start`/`preview.validate` 继续只消费既有 preview authorization 与 `PreviewRegistry`,不从 Runtime Snapshot 的 `nextStep` 猜测,不被 P6 旧 Runtime 命令删除误伤。删除 normalize/merge、phase 文案、前端 slash 解析、steer/start、confirm/retry/resume 和启动 schedule-ready 决策。开发窗口显式走 Developer read capability;开发者对当前 Project Supervisor 的写操作仍走同一五命令,专业/child Agent 的直接调试控制属于既有受信任管理面,不伪装成正式 Supervisor 协议。 -- Consumer 只按结构化 code/kind/retryable/interactionRequired 分流;此处的事件去重、缺口和 cursor 过期仅指有界 `SnapshotChanged` event 日志,并只触发完整 Snapshot 读取,不得套用到逻辑永久保留的 Public conversation cursor。 -- 迁移期 fallback 仅处理 transport 明确的 unknown command:该错误证明新 handler 未执行,才可调用旧命令。任何已到达新 handler 的结构化错误或超时都不得 fallback;超时只可同 requestId 重试/读回。 - -**完成门禁**:三类正式 Consumer 的 Runtime 调用面和 Public conversation read 合同一致,差异只剩输入输出形态;Public/Developer 类型无交叉,正式 Consumer 返回体不存在 `path/absolutePath/finalizationId/commitMarker/Provider` 私有字段;迁移前后用户语义、消息顺序和副作用计数等价。 - -### P6 删除旧公开面并最终收口 - -**目标**:Interaction Contract 成为唯一稳定公开 Runtime 控制边界。 - -- 从 Tauri invoke handler 和其它正式 transport 删除旧 start/steer/confirm/reject/answer/cancel/retry/resume/schedule/read 注册、旧公开 DTO、旧事件和 migration fallback。 -- 删除 Consumer 旧调用点、生命周期分支和把 `LocalConversationResult`/response stream 当作正式会话补读的路径;正式 Public conversation 只保留第 1.5 节 message/read DTO、五类 mapping、committed cursor chain 和 session-lifetime logical retention。任何 cleanup/compact 旧路径都不得删除仍存续 session 的 Public committed message/cursor 或把截断历史重新标记为 `complete`。管理面 goal/compact/session/config 及受信任 Developer/local history 按第 1.3/1.5 节边界保留,但不得重新导出为 Public DTO。 -- Shell 内部 start/steer/resume/recovery 函数、Runner 内部方法及验证这些能力的回归测试允许保留或重命名,不设置全仓旧名称为零的伪门禁。 -- 同步 Runtime V1.1、智能体 App 实施计划、文档索引和长期架构记忆,确保本计划不成为与权威 Runtime 并行的冲突事实源。 - -**完成门禁**:定向静态检查证明 invoke handler、正式 transport、Consumer 调用点和公开 DTO 不再引用旧协议,也不引用 `LocalConversationResult.path` 或私有 delivery/finalization/Provider 字段;协议契约、conversation cursor chain/永久空洞/session-lifetime retention/截断失败关闭、进程内、确定性、独立进程恢复、真实 Runner 和前端验证覆盖最终调用面。 - ---- - -## 4. 迁移与兼容原则 - -1. **依赖先行,不发布半协议**:P1 只建内部底座,P2 先稳定 read,P3 才同时注册完整五命令;公开 handler 出现时必须可真实执行、读回和恢复。 -2. **新旧并存只发生在 P3–P5**:旧公开命令服务尚未迁移的 Consumer,新协议服务已迁移 Consumer;两者调用同一内部 Runtime 能力并共享 Shell project lock、Interaction 和 projection 同步,只有新协议承诺 request ledger 的安全重试/读回合同。 -3. **fallback 不处理不确定结果**:只有 transport 的 unknown command 可走旧命令;超时、崩溃、结构化错误和结果未知必须沿新 request ledger 收敛。 -4. **等价比较看语义,不冻结旧结构**:比较用户可见状态、目标 run、interaction 结果、终态和副作用唯一性;不要求命令名、事件名、DTO 或中间调用序列一致。 -5. **旧接口删除前先完成调用图证明**:区分正式 Consumer、内部实现、恢复工具和回归测试,P6 只删除公开注册及调用,不误伤 Runtime 内部能力。 - ---- - -## 5. 里程碑与验收门禁 - -| 里程碑 | 交付 | 必须证明 | -|---|---|---| -| M0 | P0 行为基线与协议 fixture | 旧语义可复验;每个新不变量有非伪造测试入口 | -| M1 | P1 持久协议底座 | ledger/锁/权限/崩溃状态机闭合,尚无半成品公开命令 | -| M2 | P2 双 Snapshot 与事件流 | Public 唯一事实、Developer 服务端授权、revision/cursor 可恢复 | -| M3 | P3 五命令与 Interaction Loop | 幂等/冲突/读回/Interaction/策略复核闭合,新旧语义等价 | -| M4 | P4 Runner 自驱 | 目录簿与恢复矩阵闭合,owner/drain/未知结果失败关闭 | -| M5 | P5 Consumer 迁移 | CLI/Tests/GUI 只经统一协议,正式/开发读模型隔离 | -| M6 | P6 旧公开面删除 | 正式注册/调用/DTO 无旧协议,内部能力与回归测试保留 | - -任一阶段只能依赖已完成的前序里程碑;不能以“后续阶段会补”为理由放行当前公开合同缺口。 - ---- - -## 6. 关键风险与强制约束 - -| 风险 | 强制约束 | -|---|---| -| Snapshot 与 durable state 在文件崩溃窗口不同步 | projection journal + 单一锁序;Public read 先修复,事件永远只作提示 | -| 进度卡由 Consumer 跨 manifest/plan/event 猜结论 | Shell 投影当前 run 的结构化 progress/check outcome;无可信 receipt/evidence 就 unknown/省略,run 切换原子替换 | -| 全项目 revision 导致无关更新误杀交互 | 回答 CAS 使用 interactionRevision;精确 target 命令锁内重读 durable identity | -| 重试先做当前状态校验而失去首次结果 | 固定先查 request ledger,再做状态/策略校验;成功和业务拒绝都持久化 | -| responseId 换 requestId 造成二次消费 | interaction resolution 同时保存 responseId/fingerprint,独立于 command requestId 去重 | -| command/interaction executing 的外部结果未知 | outcome-unknown / reconciliation;禁止自动重放或换 ID | -| Tauri 前端 devMode 被当作权限 | Developer read 只认服务端构建、窗口标签或显式受信任 capability | -| Public interaction 正文可能泄漏私有问题、工具计划或路径 | interaction audience + kind 白名单 + 公共内容安全过滤;不安全内容降为 Developer/reconciliation,原始信息仅私有 sidecar/开发 capability | -| P5 误删预览/资源管理面或从 Runtime 状态猜授权 | preview/resource/session 管理面保持 sibling contract;只消费现行 authorization/immutable revision/PreviewRegistry,不纳入五命令 | -| 目录簿泄漏本地路径或扫描失控 | 私有权限、仅 AppData 持久化、公开零路径、wake 优先和有界低频轮转 | -| P3/P5 重复迁移导致阶段不可独立审查 | P3 只完成后端协议与 harness;P5 只切换 Consumer 和删 Consumer 决策 | -| 旧名称静态检查误删内部能力 | P6 只检查正式 transport、Consumer 和公开 DTO | -| `resume` 模糊吸收 retry/schedule/reconcile | Public ResumeCommand 使用 tagged intent;ContinueRun 不换 runId,RetryTerminalRun 必须生成 successor lineage,自动 wake 不进入公开命令 | -| slash 命令被当作普通 prompt 或由 Consumer 分叉解析 | submit_intent 在 matrix 前统一调用带版本 Rust parser;只读命令 direct reply,副作用命令生成 Interaction,`/preview` 等绝不进入自主 Runtime task | -| CancelAccepted 被误当成 cancelled 终态 | 冻结取消矩阵;pending action/finalizing/unknown 不能虚假完成,Consumer 继续读 Snapshot | -| Command error 私有字段进入 Snapshot | SnapshotError 与 CommandError 分型,Public projection 零 requestFingerprint/requestId/replayed | -| requestChanges 在 resolution 与 rework 入队之间崩溃 | interaction Resolving 先预分配 reworkOperationId,operation journal 幂等补链,unknown 不伪造 Resolved | -| direct reply stream 超时后生成重复回答 | responseMessageId/response operation identity 在 prepared 预分配,durable message ledger 读回,unknown 禁止换 requestId | -| Runtime status message 与 Run 受理顺序错乱 | Start 先提交唯一 `runtimeStatusMessageId` 的 commit marker,再允许 queued/dequeue;写失败/unknown 不执行,根终态 failure status 先于其它终态投影 | -| 安全 Runtime 事件被前端拼成重复/泄漏消息 | event projector 只交付 Rust 生成的 `eventId + publicText`,不把 raw event 放进 SnapshotChanged;无身份、空正文、legacy/raw payload 丢弃 | -| “Runner 自驱”与 GUI-owner 门禁表述冲突 | 自驱仅指 owner/lifecycle lease 有效时不依赖 Consumer 轮询;无 GUI headless lease 未落地前返回 OWNER_UNAVAILABLE | - ---- - -## 7. 建议实施顺序(一句话) - -P0 建语义基线与契约 fixture → P1 建身份/权限/ledger/锁底座 → P2 建 Public/Developer Snapshot 与项目事件流 → P3 一次性开放完整五命令并收归 Interaction Loop → P4 按恢复矩阵实现 Runner 自驱 → P5 按 CLI、Tests、GUI 迁移 → P6 定向删除旧公开面。 - ---- - -## 8. 协议候选冻结输出 - -本方案提交后,以下内容视为 V1 编码前的候选合同;只有第 10 节证据门禁闭合并重新评审后,才转为正式冻结合同。候选合同期间,Consumer 或 transport 不得自行解释或扩展: - -- 身份:manifest `projectId` 不可变;`sessionId` 来自会话管理面;`runId` 由 Shell 在 prepared 阶段预分配;所有 locator、owner generation 和内部 operation identity 不进入 Public 协议。 -- 所有权:project execution owner、Runner owner、GUI-owner 分层;owner generation 是 fencing 权威;未取得 owner、draining 或 GUI-owner 失效时不扫描、不执行、不接受自动恢复。 -- 投影:先 durable 业务事实,再按 dirty journal 补 Public projection;已提交事实但投影未刷新可幂等补偿,事实提交结果未知必须 reconciliation。 -- 事件:项目级 at-least-once `SnapshotChanged`;`snapshotRevision`、`sequence`、`eventId`、opaque `cursor` 的持久关系不可改变;事件只通知,Consumer 只能重读完整 Snapshot。 -- 写入:五命令统一 request ledger;相同 requestId/指纹且 authorization scope 等价才回放原结果,异指纹冲突;结果未知不换 ID 重放;结果读回只按 `(projectId, requestId)` 查询。 -- 生命周期:CancelAccepted 只表示取消受理;ContinueRun 保持原 runId,RetryTerminalRun 生成唯一 successor runId,ReconcileRun 只允许受信任 capability;timer/lane/schedule/recovery wake 不属于 Public ResumeCommand。 -- 入口与边界:slash 输入先由 Shell 同一版本 parser 路由,严格使用 `BuiltinCommand(commandLine, expectedParserVersion)` tagged payload,不能作为普通 prompt/start/steer;普通 `Conversation` 与四种 `intentKind` 使用完整 policy matrix;模板/导入必须提交 typed `entryBinding` 及 immutable revision/digest;V1 ID、JSON、文本、答案和补读上限由 Rust 单一常量源生成,超限在 ledger 前失败。 -- 本地命令:`AgentRuntimeLocalManagementCapability`、`LocalManagementCommand/Response` 与 project-scoped targetRef 是独立候选合同;无 project/session 的命令不伪造五命令 meta,local path 仅在 resolver/OS handle 边界展示;同一 parser route 不能同时落入 Public conversation 和 LocalTransportReply。 -- 会话与 lineage:`conversation_session_id` 不自动等价 Supervisor session;session handoff 依赖持久 `supervisorLineageId + sessionRevision`,Retry 的 `acceptedRunId` 只是回显,唯一 predecessor/successor 由 `RunLineageRecord` 证明;Run target set 之外,Open/Resolving interaction、未终结 command/input envelope 与 response/status/event 还必须由不可变 continuation set 逐条证明,successor 不改写原 record identity。 -- 交互:`interactionId + interactionRevision + responseId` 独立于全项目 revision;UserInput、User ToolApproval 和 PolicyApproval 均有明确 audience/context/presentation 合同;Shell 在锁内重读 action、父子身份、policy snapshot 和 artifact binding。ApprovalDecision 冻结为 `approve | reject | requestChanges`,其中 `requestChanges` 只允许单一不可变产物绑定的 Run/Action PolicyApproval,必须携带有界、脱敏 feedback 和唯一 reworkOperationId;项目级多 Run PolicyApproval 只能针对固化的精确 target set 执行 approve/reject。 -- 交付:direct reply 的 responseMessageId 和 operation identity 在 command prepared 预分配;Runtime final reply 复用现有 finalization journal 的 finalizationId/messageId,不生成第二身份;Start/terminal Runtime status message 使用独立 `runtimeStatusMessageId + commit marker`,先后顺序和 prompt 排除规则固定;安全 Runtime event message 的 Public `messageId` 与 Rust `eventId` 使用同一 wire identity 且独立于 SnapshotChanged。五类 committed conversation message 统一投影为 path-free Public message/read DTO,去重键固定为 `(projectId,sessionId,deliveryKind,messageId)`;committed cursor chain/分页是补读完整性的权威,conversation-global sequence 只排序/诊断且允许永久空洞,response-stream sequence 不是 conversation sequence。committed message/cursor/origin/tail 在 session 生命周期内逻辑保留,conversation read 不返回 `CURSOR_EXPIRED`;物理截断返回无 partial page 的 `CONVERSATION_HISTORY_INCOMPLETE`,不能伪装 `historyState=complete`。commitMarker/finalization/Provider/locator 保持私有,legacy 无 messageId 只能按唯一证据迁移或隔离。conversation/response stream 独立持久、可补读且不参与 Runtime Snapshot,结果未知时不生成第二回答。 -- 兼容与生命周期:现有 response sidecar 必须经 delivery ledger adapter 穷举映射 `streaming/ready/committed/discarded/failed`,并持久化捕获时完整 source identity/digest;legacy `ready/committed` 不能直接当作消息 committed,`discarded/failed` 需有证据才能 rejected 否则 reconciliation;V1 不提供无 GUI CLI headless control lease,无有效 GUI-owner 时写入返回 `OWNER_UNAVAILABLE`。所有 durable journal 受跨平台 `DurabilityCapability` 门禁,无法证明持久化时禁止进入 executing。 -- 公开面:Public 字段白名单和稳定枚举是唯一正式 Supervisor/专业组展示合同;同 run progress、六组 collaborator、interaction 和 command capabilities 都必须受父 run/权限/数量边界约束;waitingOn/nextStep 不使用任意字符串,SnapshotError 与 CommandError 分型;Developer Snapshot 必须经过服务端 capability;`tool_request` 不进入 Public。 - -任何实现若无法满足上述合同,必须先修改本技术方案并重新评审,不得通过新增 Consumer fallback、缓存或隐式状态字段绕过。 - ---- - -## 9. 本轮设计修订结论 - -1. Snapshot 是唯一公开 Runtime 状态事实;事件收窄为项目级 `SnapshotChanged`,不再公开可被误合并的 run/interaction/tool 状态载荷。 -2. 项目 manifest `projectId` 是协议身份,`projectPath` 只是每次都要 canonicalize 和复核的私有 locator。 -3. Public Snapshot 除当前 Project Supervisor 紧凑摘要外,还必须提供当前父 run 下六个静态专业组的有界只读状态、专业 retry capability 和指向既有 approval interaction 的 repair view、UserInput/ToolApproval/PolicyApproval 和五命令 capabilities;动态 child 仍只计数。Developer Snapshot 使用独立 DTO 和服务端 capability,前端 devMode 不算授权。 -4. 五命令不再统一滥用全项目 Snapshot revision:interaction 回答使用独立 interactionRevision;cancel 使用精确 run target、稳定 cancelOperationId 和 `cancelling` 中间态;resume 使用带 capability target 的 ContinueRun/RetryTerminalRun/ReconcileRun tagged intent;same-run steer 另外保留 V1.13 的 steerId/cursor 合同。冻结 Goal Contract 的 active root Run 不接受普通 execute→steer,稳定返回 `TARGET_BUSY`;replacement 只属于显式 Goal management operation。 -5. `submit_intent` 显式携带稳定 `intentKind`,与表示执行方式的 `runProfile` 正交;capability 按 `(intentKind, runProfile, entryBindingKind, inputPolicy, attachmentMediaKind)` 冻结完整组合,Conversation message 在 V1 必须非空,immutable attachment binding、entry binding 和 input envelope 进入 requestFingerprint,source 只做受信任归因/权限/审计,业务路由由冻结 policy matrix 决定。`approve` 显式携带 `approve | reject | requestChanges` decision;requestChanges 的 feedback 有界、脱敏,绑定单一不可变产物和唯一 reworkOperationId。requestId 负责命令幂等受理/结果读回,steerId 负责 same-run 追加指令,responseId 负责 interaction response 去重,三层身份不可互相替代,未知外部结果不虚假承诺 exactly-once。 -6. request ledger 明确“身份/权限 → 指纹 → 先查 ledger → 再校验状态 → 写 prepared → 执行 → 权威结果”的顺序,并持久化成功和业务拒绝;外部结果未知统一 reconciliation。 -7. P1 改为内部持久协议底座,P2 先提供唯一 read model,P3 才注册完整可用的五命令;P3 不迁移 Consumer,P5 不再重复设计后端 Loop。 -8. Runner 自驱补齐跨平台目录权限、丢 wake 恢复、启动有界轮转、interaction resolving 和 command executing 恢复矩阵。 -9. P6 只删除正式 transport、Consumer 和公开 DTO 的旧协议引用,内部 start/steer/resume/retry/recovery 能力和回归测试保留。 -10. direct reply 与 Runtime final-reply 是两条独立 durable response 分支,Start/terminal Runtime-owned status message 与安全 Runtime event message 另有独立 ledger;现有 response sidecar 只通过 finalization adapter 接入,不能把 Runtime `ready` 冒充 direct message;SnapshotError/CommandError、Public/Developer Snapshot 和 Runtime/response stream 均不得混型。 -11. 四次复核进一步补齐了现有项目所需的专业组状态与失败重试、用户确认、Needs input、slash 内置命令、上传附件、command capabilities、Runtime 错误分类、取消中间态、steer identity 和父 run policy snapshot;这些规则已有字段/状态机,但仍需 P0/P1 fixture 与 Unix/Windows 实测证明。 -12. 第五次冻结前审计移除了 slash 对 `continueProject + runProfile` 的伪装,改为严格 `BuiltinCommand` tagged payload,并把 parser version 放进请求、指纹和 prepared ledger;现役 `/preview`、`/agent-resume`、资源/记忆/画板导入和只读命令按当前代码逐项冻结路由,其中 `/agent-kill`/`/agent-retry`/`/agent-resume` 明确保持 legacy Agent run control 语义,不冒充正式五命令;CLI `/resume`、`/goal`、`/compact`、`/mcp`、`/quit`/`/exit` 也按其现役 management/observation 语义显式纳入 catalog。 -13. 新增 `preparing/publicStatusPending` 与 `terminalPending` 的 Public 状态映射,禁止 status message commit marker 之前 dequeue,禁止根失败 status message 之前公开 failed;同时冻结 direct reply、steer 的用户消息单次提交顺序和崩溃恢复。 -14. 补齐 durable input/status/event/response record 的统一 envelope、checksum、owner generation、ledgerVersion 和 corrupt 隔离,并将 status/event identity 改为 RFC 8785 canonical JSON 派生,消除直接字符串拼接的边界碰撞。 -15. 冻结 Supervisor 顶层 plan progress 与 progress view 的等式、task/plan journal 来源及同 run 证据边界;retry successor 改为 predecessor 全生命周期永久唯一,后续 retry 必须针对最新 successor;Supervisor approval target 的 `parentRunId` 允许且仅允许为 None。 -16. Session rotation 改为独立 `ProjectSupervisorSessionRotationRecord` 多记录恢复合同;active-session marker 提交前旧 session 保持 active,其中 operation/index 的 operationId 与 fence marker 一致、active-session marker 尚未提交且无未知外部结果时,incomplete handoff 可按 `FenceCommitted-after-fence Rejected` 合同隔离该 operation 的 pending records、同 journal 清除且只清除该 operation 的 fence/marker、按规则递增 epoch并保留历史 `committedRotationOperationId`,安全回退旧 active session;只有 operation/index marker 冲突、结果未知或 active-session marker 提交后校验失败时进入 reconciliation,不按最新文件猜成功。 -17. Local Management response 使用唯一 `ResponseMeta` 承载 requestId/fingerprint/replayed,ledger 前缺失 requestId 可用 `None` 表达;local reply/error message 增加字符、字节和控制字符边界;local operation 补齐 `outcomeUnknown -> needsReconciliation` 与 `NEEDS_RECONCILIATION` 读回。 -18. locator handle 增加 `NotAcquired/Acquired/Released/ReconciliationHeld` 状态;local-only terminal 与 project-linked terminal 分别定义释放条件,未知结果不得换 handle 或 requestId 重做。 -19. Session rotation 先持久化含 predecessor/successor/expected revision/epoch 的唯一 `ProjectSupervisorSessionRotationRecord(Prepared)`,再以同一 journal 原子提交 `FenceCommitted` operation marker 与 active-index fence marker;之后才固化不可变 `ProjectSupervisorRunHandoffTargetSetRecord + ProjectSupervisorRunHandoffManifest`。目标 payload/chunk 有 envelope、checksum、commit marker、固定 digest 和数量上限;fence 先于集合快照生效,任何新 Run/child/delegation 都拒绝或延后,不会落到 predecessor;manifest ref 在 Prepared/FenceCommitted 时为空,只在 manifest 可回读后补齐,manifest/index/phase 与 Public `sessionContext` 共享线性化边界。 -20. Session rotation 的 `Prepared` 单独存在时继续发布旧 active `Ready`;只有 `FenceCommitted` 及其后 pending phase 才映射为 `HandoffInProgress` 并清空写 capability。最终 active-session journal 必须同步写 `committedRotationOperationId=current operationId`,并以 operation/session/manifest/revision/epoch/active-session marker 全量相等关系作为 successor `Ready` 门禁。Rejected 拆为 Prepared-before-fence(只写 tombstone、不动 active index/epoch、保留历史 committed id)和 FenceCommitted-after-fence(校验 marker、隔离 pending、同 journal 清当前 fence并递增 epoch、保留历史 committed id);marker/commit torn state 进入 reconciliation、fail-closed。 -21. Local Management record 显式保存 `expectedParserVersion + route + targetRef + authorizationPrincipalRef`,`originatingCapabilityId` 仅作审计;恢复只按历史 operation identity 校验,当前 capability 可在 rotation 后重新授权,不得替代历史解析合同。 -22. Session rotation 还必须在 handoff barrier 固化 continuation set,覆盖 interaction、未终结 command/input envelope、DirectReply/RuntimeFinalReply、Runtime status 和 Public event;旧 `sessionId` 作为 provenance 保持不变,successor 只能凭 operation/manifest/record revision proof 继续原 identity,缺失或漂移统一 reconciliation;`HandoffInProgress`/stale cancel 的 phase gate 优先级已冻结。 - ---- - -## 10. 持续边界复核及冻结前补充审计:已修正的合同与编码前证据门禁 - -本轮复核只检查本方案直接承诺的公开交互协议、持久恢复、Runner owner、Consumer 迁移和旧公开面删除,不扩展到 Agent main loop、Provider 选型、提示词或无 GUI 常驻服务。以下不是未来优化,而是进入 P1/P2/P3 编码前必须闭合的合同: - -| 冻结项 | 当前不足 | 冻结证据 | -|---|---|---| -| intent policy matrix | 已冻结四种入口在五类状态下的唯一 disposition,并将 directReply 置于 interaction 门禁之后;模板/导入入口必须有 typed binding;冻结 Goal Contract 是优先于普通 active/waiting 状态的 execute barrier | P0 fixture 逐格断言唯一 disposition/error;冻结合同下 direct reply 正常、execute 稳定 `TARGET_BUSY` 且 replacement 调用计数为 0;并证明并发请求不创建第二 Supervisor run | -| slash built-in 路由 | 现有项目要求 `/preview`、`/agent-resume`、资源/记忆/画板导入和只读命令继续走现役语义;已改为严格 `BuiltinCommand(commandLine, expectedParserVersion)`,不再伪装成 Runtime intent/profile,并区分 legacy Agent run control 与正式五命令,且已按 App/帮助清单冻结完整 catalog | 当前全部 slash 命令逐项 golden fixture;GUI/CLI 同输入同 parser version、route/interaction/管理动作、副作用计数,带附件/entry binding/未知字段或嵌套 interaction 失败关闭,parser 升级与 prepared 恢复不漂移,`/preview` 零自主任务 | -| local management 参数恢复 | path-free Goal 文本、`/agent-resume` detail 等参数原先只有 fingerprint,无法在 owner 重启后恢复同一操作 | `privateArgumentRef` 有界私有 payload 与 fingerprint/checksum 一致性 fixture;host path 原文、secret 和内部 fingerprint 不进入 local/Public delivery;缺失或篡改统一隔离为 `CORRUPT_RECORD` | -| local management 错误响应 | 已改为 `Succeeded/Failed + ResponseMeta`,统一承载 requestId/fingerprint/replayed;缺失 requestId 可用 `None` 表示,并补齐 capability、scope、locator、幂等复用、执行中和 unknown-result 错误合同 | 每个错误 code、retryable、replayed、缺失 requestId 和 readback 行为 golden fixture;GUI/CLI 不解析中文 message;local response 不携带 `observedSnapshotRevision`/`interactionRequired`,projectId 只在成功解析结果中出现 | -| local response 输出边界 | `LocalTransportReply.text` 与 local error message 原先没有独立字符/字节/控制字符合同,路径或异常文本可能超出本地 transport/UI 边界 | reply `4,000` 字符/`16 KiB`、error `512` 字符/`2 KiB` 边界值、控制字符、长路径展示和错误摘要 fixture;不得截断后当作完整结果提交 | -| local durable command identity | 幂等合同要求 parserVersion/route/targetRef 固化,但不能把轮换后的 capabilityId 错当历史 operation identity | prepared/owner 重启/capability 轮换/targetRef 漂移 fixture;record 显式保存 parserVersion、route、targetRef、authorizationPrincipalRef,originatingCapabilityId 仅审计,等价新 capability 可恢复且权限收紧会失败 | -| local capability scope | `Project` scope 的 optional session 字段可能把 Goal mutation、resume 的 active-session 约束放宽,Global 与 project target 也可能混用 | Goal mutation 必须 session+sessionRevision+Goal target;Goal read 可 project-only;`/resume` 必须 session/run/recovery revision;`/project`、`/config` 不带 project/session target 的 scope fixture | -| locator handle 生命周期 | 已补 `NotAcquired/Acquired/Released/ReconciliationHeld`;local-only terminal 在无 project operation 时可释放,project-linked 必须等待双方 terminal,unknown 保留 reconciliation handle | `prepared→handle acquired→crash`、`executing→handle expired`、`outcome-unknown→retry/readback`、无 project operation 的 resolver rejection、project resolve 成功但 operation 失败;禁止换 handle/requestId 重做 | -| 数量与体积上限 | 已冻结 ID、command JSON、message、feedback、answers、补读和 response stream 的字符/字节/数量上限,且规定 Rust 单一来源 | 共享常量生成 TS schema/fixture;边界值、超限、组合爆炸在 ledger 前返回 `INVALID_REQUEST` | -| 现有项目专业 Agent 状态 | 仅有 `collaboratorCount` 无法满足当前工作台六个专业组状态栏、父 run 精确筛选、专业 Agent confirmation 和“当前项目重试”要求;已补 `collaborators`、opaque `collaborationId`、runtime/manifestFallback、retry/repair capability | `parentRunId` 过滤、六组固定顺序、旧父 run 隔离、manifest fallback 不伪造状态、专业 Agent retry/repair 竞争与唯一父子绑定 fixture | -| Runtime-owned 进度卡 | 现有工作台需要轮次、任务/计划、活跃 Agent、试玩/静态/代码/截图校验和返工摘要;若继续由客户端拼接会违反 Consumer 不承接业务真相 | 当前 run 结构化 progress fixture、四类 check receipt 映射、缺证据 unknown、run 切换不合并、路径/Provider/fingerprint 零泄漏 | -| preview/resource sibling contract | `preview.start`/`preview.validate` 与上传/登记不是 Runtime 生命周期命令,不能因统一 Shell 而由 Consumer 依据 nextStep 重造;本方案明确保留现有授权和 PreviewRegistry | preview authorization revision、一次启动/刷新、项目切换/Run 终止/写锁竞争和 P6 静态删除范围 fixture | -| Public command capabilities | 仅有 `nextStep` 会迫使 Consumer 自行推断可执行按钮;已补 `commandCapabilities`,提供按组合冻结的 intent/profile/binding/input target 与精确 session revision/run target,stale capability 失败关闭;冻结 Goal Contract 时 Conversation option 可保留 direct reply 入口,但不产生 replacement-steer capability | Snapshot revision 与 capability 同点、过期 cancel/resume/retry、冻结合同 capability + direct/execute 分流、无 capability 不显示可执行入口、读失败保留 stale display 但仅允许上一份精确 cancel capability 作为止损入口 | -| 用户确认与 Needs input | 原方案把 ToolApproval 全部放到 Developer,且未冻结 question/answer schema;已补 User audience ToolApproval、Collaborator context、1–3 题/2–3 option/自由输入/全量 answers 合同 | 普通工作台用户确认、Developer ToolApproval 隔离、问题重复/缺题/option label 冒充 id、action/parent/run 漂移和 interaction 恢复 fixture | -| 现有项目输入附件 | `submit_intent` 原来只有文本,无法承接工作台上传文件/既有资源引用;已补有界 immutable attachment binding、input envelope 和结构化 resource context,Runtime 不接收字节/path/token;V1 显式要求 message 非空 | 上传后 resource revision/digest 绑定、替换/删除/跨项目拒绝、附件顺序指纹、attachment-only 前置拒绝和恢复后附件不丢失 fixture | -| submit capability 组合完整性 | 独立 intent/profile/binding 数组会允许 Consumer 组合出未注册入口;已改为有限 option,每个 option 固定一个 runProfile、entryBindingKind、inputPolicy 和允许媒体类型 | option 上限、合法/非法笛卡尔组合、TextOnly 携带附件、Other 媒体、入口绑定类型错配均在 ledger 前失败 | -| input envelope 交付 | 现有 Runtime task 必须非空,附件不能校验后丢弃,也不能由前端伪造自然语言;已定义私有 input envelope 与结构化 resource context,direct/start/steer 共用同一身份 | prepared/强杀/恢复、attachment revision/digest 漂移、Provider 前失败、steer 与 direct reply 均能读回同一 envelope 且不泄漏路径/token | -| Runtime 失败与取消中间态 | 原来的公共错误 enum 混合 command error 与 Runtime failure,且没有 `cancelling/paused/finalizing` 的可展示状态;已拆分 SnapshotErrorCode/CommandErrorCode 并补状态 | 配置/鉴权/限流/Provider/验证/sandbox/预算错误脱敏映射;CancelAccepted→cancelling→cancelled/needsReconciliation,重复 cancel 共用 cancelOperationId | -| steer 与同 run 语义 | `submit_intent` 选择 steer 但 ack 未返回现有 steer 身份;已规定 prepared 阶段保存 `steerId`,复用 V1.13 cursor/容量/中断/finalization 合同;现有冻结 Goal Contract replacement 分支不得被普通 submit 调用 | same-run steer 不新建 task/run、steerId 幂等/冲突、冻结合同 execute=`TARGET_BUSY`/replacement 调用计数为 0、Goal management replacement 的 operation identity/lineage 与 cancel-old→start-new crash 恢复、confirmation/process action 不被暗中中断、steer/finalization 双向竞态 | -| approval policy snapshot | 只重新检查 live policy 会与 V1.38 的父 run 持久 policy snapshot 冲突;已补 `policy_snapshot_fingerprint`、精确 Project target set 和 hard-deny 例外 | 普通 policy 文案漂移不重解释;hard-deny 收紧、action fingerprint 漂移和 target set 变化 fail-closed | -| session handoff/lineage | 已定义 target-set payload/chunk、rotation fence、`ProjectSupervisorRunHandoff` 的 manifest/operation/digest identity、interaction/delivery continuation set 和 `ProjectSupervisorSessionRotationRecord`;先持久化 `Prepared` operation,再由同一 journal 提交 `FenceCommitted` operation/index marker;Prepared 的 manifest ref 允许为空且仍投影旧 active `Ready`,FenceCommitted 后才进入 `HandoffInProgress`/无写 capability;最终 journal 同步提交 `committedRotationOperationId=current operationId`,Ready/recovery 校验 operation/session/manifest/revision/epoch/active-session marker;Rejected 按 fence 是否提交拆为两个不混写 active index 的分支 | operation 写入前崩溃不留 fence;Prepared 后/fence 前崩溃继续旧 Ready;operation/index fence marker 半提交必须 reconciliation;Prepared-before-fence Rejected 断言不清 fence、不增 epoch、保留历史 committed id;FenceCommitted-after-fence Rejected 断言 marker 匹配、隔离 pending、只清当前 fence并精确增 epoch、保留历史 committed id;最终 commit 断言 committed operationId、predecessor/successor session、source/successor revision、manifest operation/session/epoch、active index `expected+1` 和 active-session marker 全量匹配;FenceCommitted 后 target/continuation set 缺失/损坏/超限、manifest ref 补写、barrier 后 revision 漂移、rotation fence 与新 Run/interaction/delivery 并发、旧 record successor 重新授权、各阶段崩溃、部分 handoff、Public sessionContext/capability 线性化、active index 缺失、重复/双窗口 rotation、旧 session 永不复活、无 proof 的 `TARGET_STALE` fixture | -| submit payload 与 parser version | 原 slash 方案要求无意义的 `continueProject + runProfile`,且 capability version 未进入请求;已改为 Conversation/BuiltinCommand strict tagged union,BuiltinCommand 显式提交 expected parser version,prepared 后固化 route/version | variant/未知字段/版本漂移、同文本 parser 升级后恢复、同 requestId 异 payload、GUI/CLI 复放只复用已固化结果 fixture | -| interaction kernel 隐式 resume | 当前 `agent/interaction.rs` 仍暴露 `runtime_resume` 工具,CLI active runtime 会把模型返回的 `AgentInteractionAction::Resume` 直接送入 recovery observe;这会让自然语言/模型分类绕过显式 ResumeCommand,且与“继续只走普通 message/steer”冲突 | `runtime_resume` tool 输出、自然语言“继续”、active/non-active Runtime、显式 `/resume` 的 golden fixture;迁移后模型 action 不得直接获得 Resume capability,所有 recovery/steer 都要带显式 target、ledger 和 revision | -| CLI slash catalog 漏项 | `swarm_cli/input.rs` 现役还有 `/resume`、`/goal ...`、`/compact`、`/mcp`、`/quit`/`/exit`,与 GUI 的 `/agent-resume` 和项目摘要命令语义不同;只写 GUI catalog 会造成 CLI fallback/语义漂移 | CLI-only 命令逐项 parser fixture;`/resume` recovery observe、Goal status/CAS mutation 及其后续 Runtime turn、`goal pause/clear` 不取消 Runtime、compact 无 provider、mcp direct reply、quit 无 ledger,GUI/CLI 同 parser 但 route 差异仅来自显式 catalog | -| slash path/classification boundary | 已把项目级 `BuiltinCommand` 与 `LocalManagementRoute` 分开;补齐 local capability、request/response、capabilityId/targetRef 和单一落点矩阵;absolute host locator 只能走 trusted resolver/local envelope,project-relative `/asset-register`、`/import-canvas-asset`、`/read` 先做相对路径/符号链接逃逸校验;未知 slash 携带 host locator 时不进入 direct reply;`/goal` exact 与 `/goal <目标>` 按 arity 分开 | known/unknown slash、绝对路径、`file://`、`..`、symlink escape、project-relative path、无 project/session、targetRef 漂移、local response 不进入 Public conversation、GUI/CLI 同 parser 和原文不进入 Public fingerprint fixture | -| legacy slash lifecycle 误映射 | 当前 `/agent-kill` 只是写 legacy trace,`/agent-retry`/`/agent-resume` 会从最近 goal 启动新 generation;若直接映射 cancel/retry/continue 会丢失旧 target、detail 和 lineage 语义 | legacy trace target revision/digest、kill 不产生 Runtime cancelled、retry/resume 新 generation 的 operation/lineage、旧 trace 替换和跨 transport replay fixture | -| public hard-gate status | `preparing/public-status-pending` 与 `terminal-pending` 原先只是内部文字;已新增稳定 Public status/stage/waitingOn/nextStep 枚举和 commit marker 映射 | user message/status commit/dequeue 崩溃点、status 写失败/unknown、根失败 status 先于 failed/task/event 终态、Consumer 不把 preparing/terminalPending 当 queued/failed fixture | -| conversation user-message 顺序 | Start 已有 status 门,但 DirectReply/Steer 的 user message 可能重复或越过 ledger;已冻结 direct reply 先 user message→assistant→ledger close,steer 复用 V1.13 conversation-persisted 和同一 message identity | direct/slash/steer 崩溃恢复、同正文/同身份回放、重复 user message、未知 commit marker、旧 action/confirmation 不被 steer 越过 fixture | -| 本地 path / project_location 泄漏 | 当前 CLI `ProjectLocation` 会把 `root.display()` 持久化到 conversation;`/project` 与 `/import-canvas-export` 也携带绝对路径,若沿用 DirectReply 会违反 Public 零路径合同 | LocalTransportReply/LocalManagement 分支 fixture;路径只在 trusted resolver/local UI 返回,Public conversation/Snapshot/event/error/prompt/fingerprint 无原文,projectId 未解析时不伪造 ledger target | -| Builtin management operation 幂等 | slash 管理动作原先只有 UI pending command,重试/恢复可能重复 checkpoint、导入、记忆、Agent control 或 External Editor 副作用;已补项目级 management operation record,global CLI action 明确不进 ledger | 每类 management action prepared/执行/结果未知/恢复、同 requestId 异 fingerprint、旧 target revision/digest、domain CAS 与 operation result 不一致 fixture | -| durable delivery envelope | 新增 input/status/event/response record 但需要统一恢复字段;已补 schemaVersion/recordId/ledgerVersion/checksum/owner boot+generation/timestamps 和 corrupt 隔离;interaction 与尚未解析项目身份的 local management 也分别绑定 project/local envelope,response delivery 具备可回读 commit marker | 每类 record 缺字段、checksum/身份/版本冲突、旧新文件并存、owner fencing、跨平台原子替换和结果未知 fixture;local→project operation link 丢失或重复副作用 fixture | -| Public conversation message/read envelope | 私有 delivery record 已有 message key/commit marker,但现有 `LocalConversationResult` 只有 role/content/messageId 并暴露 `path`,Consumer 无法按 deliveryKind 做跨 transport 去重、排序和 cursor 补读;已冻结五类 path-free Public message、eventId/messageId 等值规则、conversation-global sequence/cursor、committed cursor chain、session-lifetime logical retention、read page/historyState、read error envelope、directReply `4,000` scalar/`16 KiB` 双上限和私有字段边界;全部 Public conversation DTO 由 Rust 单一来源生成严格 TS schema/decoder | user/directReply/runtimeFinalReply/runtimeStatus/publicEvent 五类 golden mapping;directReply 分别恰好命中 scalar/UTF-8 边界、任一维度超限、多字节组合、同 requestId 拒绝读回和单消息不阻塞分页;request、success page 与 error envelope 分别覆盖缺失、未知、重复、类型错和版本错误的跨 transport negative fixture,error DTO 另覆盖未知 code;另覆盖同键重复/异正文冲突、乱序返回按 sequence 稳定展示、reservation 崩溃形成永久空洞但不触发重读、cursor chain 不漏 committed message、跨页新写入、空页、256 条/1 MiB 边界、`afterCursor=None` 从 origin 全量补读、物理压缩后旧 cursor 仍有效、格式/跨 scope cursor 为 `CURSOR_INVALID`、message/cursor/chain/origin/tail 截断为无 partial page 的 `CONVERSATION_HISTORY_INCOMPLETE` 且不得返回 `complete`、conversation `CURSOR_EXPIRED` 不可达、response-stream sequence 混淆拒绝、LocalConversationResult.path/finalization/commitMarker/Provider 字段零泄漏 | -| progress invariant | 顶层 step 计数与 progress 内 task/plan 计数可能重复且来源不明;已冻结顶层等于同 run planProgress、task/plan journal 分源、receipt 不一致 unknown/省略 | plan 缺失、task/plan 不一致、run 切换、旧 evidence 混入、check 重复和每种 check 多条 fixture | -| approval parent scope | Supervisor action 没有 parent run,但 ApprovalTarget 曾要求必填;已改为 `Option` 并冻结 Supervisor=None、专业 Agent=当前父 run | Supervisor/专业 Agent target-set 指纹、父 run 漂移、None/Some 错配和 approve/retry 权限 fixture | -| retry successor lineage | 已定义带 `agentId/taskId/sessionId/parentRunId/delegationId` 的 `RunLineageRecord`;`acceptedRunId` 仅为回显,V1 不接受 `nextRunId`,且 predecessor 全生命周期最多一个 successor;旧 retry record 只能经唯一性校验 adapter 导入 | Supervisor/专业 Agent 终态 retry、同 predecessor 并发 retry、崩溃后重放、父 Supervisor 已终态、delegation 漂移和 needs-reconciliation 拒绝测试 | -| response delivery 分支 | 已发现现有 `response-streams` 是 Runtime final-reply,不是 direct reply;已拆 `DirectReplyDelivery` 与 `RuntimeFinalReply`,Runtime 分支复用现有 finalization journal 的 messageId,legacy adapter 穷举 `streaming/ready/committed/discarded/failed` 并持久化 source identity/digest,不能把 `ready/committed` 伪造成 direct message,`discarded/failed` 无充分证据时进入 reconciliation;Start/terminal status 另走 status-message ledger,只有私有 commit marker/read-back 与 conversation-global sequence/cursor 同时闭合后才进入 Public read | user-message→status→queued crash points、两条 response 分支与 status-message 分支分别做 crash-point、Provider 调用计数、finalization/message/status commit marker 唯一性、conversation sequence/cursor 唯一性和跨 transport golden replay | -| stale display 与止损取消 | Public read 暂时失败时既不能把缓存当事实,也不能让用户失去取消入口;已规定只保留上一份精确 cancel capability,但 rotation/draining/reconciliation phase gate 优先,服务端重新授权/锁内校验,其他旧 capability 禁用 | 读失败/恢复、读失败期间开始 rotation、目标已变化、owner 不可用、重复 cancel 和 cancel operation 读回 fixture;不得从 stale Snapshot 推进 submit/resume/retry,不能以 stale cancel 绕过 HandoffInProgress | -| Runtime public event message | 现有项目要求安全 Runtime 事件进入聊天,但不能把 raw event 载荷并入 Snapshot;已定义 Rust 生成 `eventId + publicText` 的独立 conversation delivery,Public `messageId` 必须与 eventId 同值、deliveryKind 固定为 publicEvent、role 固定为 system | Supervisor/主 Agent/直接 child 父 run 过滤、eventId/messageId 不等、eventId/publicText 缺失或重复、同 event 重放保持 sequence/cursor、legacy/raw payload 泄漏和恢复去重 fixture | -| targetArtifact binding | 已扩展为 namespace/id/revision/algorithm/digest;仅允许可重读、不可变且可算 `sha256` 的现有 lineage,其他目标返回 `ARTIFACT_BINDING_UNAVAILABLE`,不造平行身份 | artifact/resource/manifest 适配表及 digest 漂移、删除、替换和旧版本审批 fixture | -| owner 与 headless 能力 | 已冻结 V1 不提供无 GUI CLI headless control lease;CLI 写入只能复用有效 GUI-owner,否则 prepared 前 `OWNER_UNAVAILABLE` | GUI-owner 断线、CLI 读写权限、owner fencing 和“无 GUI 不写入” fixture | -| 文件系统原子性 | 已补齐能力门禁:Unix 要求文件与目录同步;Windows 要求 `FlushFileBuffers` + 原子替换;任一平台无法证明持久性时禁止从 prepared 进入 executing,并隔离 torn record | Unix/Windows crash-point、损坏隔离、旧 owner fencing、恢复后副作用计数与证据保留实测 | - -本次已把上述问题从“泛化待定”修正为可编码的字段、状态机和失败闭合规则,但证据 fixture/跨平台实测尚未完成;在证据完成前,文档状态仍为“评审中”,不得把 P1 基础 DTO 或空 handler 当作协议已完成。停止继续扩展本轮审查的条件是:上述每项都有权威字段/状态机、可达失败路径和验收 fixture;cancel/resume/retry/requestChanges/direct-reply/slash-route/attachment/input-envelope/user-approval/collaborator-status/progress-view/stale-cancel 的崩溃矩阵无未分类窗口;冻结 Goal Contract 的普通 execute→steer 必须稳定 `TARGET_BUSY` 且 replacement 调用计数为零,显式 Goal management replacement 的 operation identity/lineage 与旧树取消后崩溃恢复必须闭合;BuiltinCommand parser version、命令 catalog(含 CLI-only aliases)和 management/runtime route 没有语义漂移;LocalManagement parser/route/target 历史 identity 不被新 capability 替代;SessionRotation Prepared operation、FenceCommitted operation/index marker、manifest ref、最终 committed operationId/session/manifest/revision/epoch/active-session marker、两类 Rejected、phase/continuation 与 Public sessionContext/capability 没有线性化漂移;旧 interaction/delivery 只能凭 continuation proof 继续;legacy Agent run control 和 interaction kernel resume 不伪造五命令状态或 successor;模型 action 不绕过显式 capability;preparing/terminalPending 不越过 status commit marker,顶层 plan progress 不混入旧 run;所有 durable record(含 local management、session rotation、target/continuation set payload/chunk 和 rotation fence)都能按 envelope/checksum/owner generation/commit marker 唯一恢复,且不存在有 active-index fence 而无唯一 rotation operation recovery fact 的崩溃窗口;path-bearing/local reply 不泄漏原文且遵守 local output 上限;predecessor 不产生第二 successor;Public Snapshot 不再暴露命令私有字段;capability 不存在可错误组合的独立白名单;Runtime final reply 不产生第二 message identity,Start status 不越过 commit marker 执行,public event 不重复/不泄漏;Public conversation 五类 mapping、去重键、committed cursor chain、合法 sequence 永久空洞、session-lifetime logical retention、afterCursor origin 全量补读、截断失败关闭、legacy 隔离和零 path/私有字段泄漏均有跨 transport 证据;且没有 P0/P1 级协议矛盾。其它 Runtime 内核技术债、性能优化和无 GUI 常驻能力记录为后续工程,不阻塞本次 Interaction Shell 重构。 diff --git a/docs/technical/【设计依据】AI游戏创作Agent Runtime交互边界证据与决策附录-2026-08-17.md b/docs/technical/【设计依据】AI游戏创作Agent Runtime交互边界证据与决策附录-2026-08-17.md new file mode 100644 index 000000000..77bd5f92a --- /dev/null +++ b/docs/technical/【设计依据】AI游戏创作Agent Runtime交互边界证据与决策附录-2026-08-17.md @@ -0,0 +1,432 @@ +# AI 游戏创作 Agent Runtime 交互边界证据与决策附录 + +> 文档角色:代码事实、设计决策、反例与证据门禁 +> 状态:持续维护;不能覆盖 Interaction Contract +> 规范来源:[`【技术协议】AI游戏创作Agent Runtime交互合同V1-2026-08-17.md`](./【技术协议】AI游戏创作Agent%20Runtime交互合同V1-2026-08-17.md) +> 迁移入口:[`【迁移方案】AI游戏创作Agent Runtime交互边界迁移矩阵-2026-08-17.md`](./【迁移方案】AI游戏创作Agent%20Runtime交互边界迁移矩阵-2026-08-17.md) + +## 0. 使用边界 + +本附录只保存三类内容: + +- `EV-*`:当前代码已经能直接证明的事实; +- `DR-*`:明确采用或拒绝的设计决策及其理由; +- `EG-*`:证明实现满足 `IC-*` 的测试/调用图门禁。 + +代码变化可能使 `EV-*` 过期;此时必须更新 evidence 和迁移矩阵。不得为了适应过期代码事实而静默放宽 Contract。 + +--- + +## 1. 当前代码事实 + +### 1.1 Session 与 conversation + +#### EV-SESSION-001:Session catalog 按 Agent 持久化 + +- 代码:`apps/ai-game-creator-shell/src-tauri/src/project/conversation.rs` +- 关键对象/函数:Agent Session catalog、`read_game_creator_agent_session_catalog_at` 一类 catalog 读写函数。 +- 事实:catalog 保存 `schemaVersion + agentId + activeSessionId + sessions`;不是项目级全 Agent catalog。 +- 事实:当前没有独立 `sessionRevision`;不能把候选 digest 写回或解释为第二 revision。 +- 约束:支持 `IC-ID-002`、`IC-ID-003`;对应 `MX-ID-002`、`MX-ID-003`。 + +#### EV-SESSION-002:Live task 阻止 Session mutation + +- 代码:`apps/ai-game-creator-shell/src-tauri/src/project/conversation.rs` +- 关键函数:`ensure_agent_session_has_no_live_tasks`、create/fork/set-active/archive Session 路径。 +- 事实:Agent 有未终结 Runtime task 时,create/fork/archive/set-active 会被拒绝。 +- 约束:V1 不能用新协议绕过该语义,也不能默认具备 live handoff。 + +#### EV-CONV-001:Conversation 支持有 identity 和无 identity append + +- 代码:`apps/ai-game-creator-shell/src-tauri/src/commands.rs` +- 关键函数:Tauri command `append_local_conversation_message`。 +- 事实:`messageId: Option`;有值走 idempotent append,无值走普通 append。 +- 风险:Public cutover 后无 identity append 会破坏去重、source mapping 和完整性证明。 +- 约束:支持 `IC-CONV-010`;对应 `MX-CONV-012`~`MX-CONV-014`。 + +#### EV-CONV-002:Conversation 正文已有持久 source + +- 代码:`apps/ai-game-creator-shell/src-tauri/src/project/conversation.rs` +- 关键函数:`append_local_conversation_message_for_session_at`、`append_local_conversation_message_for_session_idempotent_at`、带 finalization 的幂等 append。 +- 事实:已有 project conversation 与 Agent Session conversation;Public adapter 无需复制正文。 +- 约束:支持 `IC-CONV-002`。 + +### 1.2 Project owner、Runner 与 CLI + +#### EV-OWNER-001:OS lock 是当前 project execution owner + +- 代码:`apps/ai-game-creator-shell/src-tauri/src/runner/project_owner.rs` +- 事实:`.agent/runtime/execution-owner.lock` 通过平台 OS 文件锁实现排他;Windows/Unix 分别有安全打开与文件类型校验。 +- 约束:支持 `IC-OWNER-001`;拒绝新增平行 owner authority。 + +#### EV-OWNER-002:Owner JSON 和 bootId 是诊断信息 + +- 代码:`apps/ai-game-creator-shell/src-tauri/src/runner/project_owner.rs`、`runner/state.rs`、`runner/protocol.rs`。 +- 事实:诊断 record 描述 owner/boot,但真正写入排他来自 lock handle。 +- 风险:按 JSON、mtime、本地时钟或 bootId generation 接管会形成第二 authority。 + +#### EV-OWNER-003:现有 project 写锁不是 execution owner + +- 代码:`apps/ai-game-creator-shell/src-tauri/src/project/filesystem.rs`。 +- 事实:`.agent/project.lock` 用 create-new 文件和 PID/时间/mtime stale reclaim;它服务现役项目写操作,不由 Runner execution owner guard 定义。 +- 约束:不能把它直接解释为 `IC-OWNER-002` 的 supervisor project lock;Shell 必须在真实 owner 下另行串行。 + +#### EV-OWNER-004:进程内 Runtime 当前只持局部锁 + +- 代码:`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/recovery_scan.rs`、`runner/state.rs`。 +- 事实:production execution owner 的获取在 Runner state;未启用 Runner 的恢复主要依赖 Agent task/run lock。 +- 约束:P1/P3 进程内 Shell 必须在任何 record/projection/Runtime 写之前补同一 OS owner-lock 获取,不能把局部锁当等价 owner。 + +#### EV-RUNNER-001:Runner 已有内部 Runtime RPC + +- 代码:`apps/ai-game-creator-shell/src-tauri/src/runner/dispatch.rs` +- 当前方法包括:`runtime.resume`、`runtime.continue_action`、`runtime.steer`、`runtime.interrupt_for_steer_decision`、`runtime.pause`、`runtime.cancel`、`runtime.compact`、`runtime.wake_pending`。 +- 事实:这些是现有 Runner 内部控制能力,不能因新 Shell 再作为平行 Public 协议保留。 +- 约束:支持 `IC-CMD-001`、`IC-CMD-010`;对应 `MX-ING-006`。 + +#### EV-CLI-001:CLI 可无 GUI 启动/连接受限 Runner + +- 代码:`apps/ai-game-creator-shell/src-tauri/src/cli.rs` 与 Runner client/server 路径。 +- 事实:Runtime 写入要求显式项目外 `--config-dir` 并可启动 External Runner;普通 CLI Runner 路径不要求 GUI-owner。 +- 约束:V1 保留该终端会话能力,不新增 headless lease,也不承诺无人值守常驻。 + +#### EV-CLI-002:CLI/`swarm_cli` 当前仍直接调用内部能力 + +- 代码:`apps/ai-game-creator-shell/src-tauri/src/cli.rs`、`src-tauri/src/swarm_cli/turn_dispatch.rs`。 +- 事实:`AgentSteer` 路径调用 `steer_game_creator_agent_runtime_task_at`;`swarm_cli` 可 dispatch Runtime turn 并直接 append user/assistant conversation。 +- 约束:支持 `MX-ING-004`、`MX-ING-005`、`MX-CONV-002`。 + +#### EV-RUNNER-002:request 去重与已知项目均是进程内状态 + +- 代码:`apps/ai-game-creator-shell/src-tauri/src/runner/state.rs`、`runner/protocol.rs`、`runner/dispatch.rs`。 +- 事实:`write_request_cache` 和 `known_roots` 都在 Runner 内存;重启后 cache 无法提供 request result read-back,Runner 也不能仅凭自身发现此前项目。 +- 约束:P1 durable command record 不能复用该 cache;P4 的自驱恢复必须增加受信任的跨重启候选项目发现,且每个候选仍重取 owner、重读 durable evidence。 + +### 1.3 Runtime final reply 与 response stream + +#### EV-FINAL-001:messageId 与 finalizationId 不是同一 identity + +- 代码:`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/finalization.rs` +- 事实:final reply `messageId` 由 Agent/Session/Run 派生;`finalizationId` 还绑定 response fingerprint、revision、request slot、steer cursor、plan 和 Goal fingerprint。 +- 约束:支持 `IC-CONV-005`;Public 去重 key 不能反推 finalization。 + +#### EV-FINAL-002:Response stream 有独立 tuple + +- 代码:`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/response_stream.rs` +- 事实:stream 使用 taskId/sessionId/runId/requestSlot/responseRevision/appliedSteerCursor;`streaming → ready → committed` 会合法改变 status/sequence。 +- 约束:status/sequence 不能被放入“不可变 source identity digest”。 + +#### EV-FINAL-003:Response stream 不能独自证明 committed + +- 代码:`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/response_stream.rs` +- 事实:部分 streaming sidecar 写错误被忽略;publisher 可在 char 上限处截断;dirty 状态不能替代写后回读。 +- 约束:支持 `IC-IDEMP-004`、`IC-CONV-005`;必须交叉验证 finalization 和 conversation lifecycle。 + +#### EV-FINAL-004:成功后 recovery sidecar 会删除 + +- 代码:`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_state.rs` 与 `runtime_protocol/finalization.rs`。 +- 事实:成功路径完成 conversation assistant、Runtime completed、response committed 后会清理 finalization/provider/tool-plan handoff recovery sidecar。 +- 约束:历史正文必须从 conversation messageId 回读,不能假定 sidecar 永久存在。 + +#### EV-PROJECTION-001:现有 Runtime 聚合读取不是原子观察点 + +- 代码:`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/entrypoints.rs`、`agent/runtime_state.rs`。 +- 事实:读取会依次组合 runtime state、task/event JSONL、response stream 和 sidecar;写入跨文件,state rename 与 journal append 各有独立锁/时刻。 +- 约束:P2 不能直接把该聚合结果包装为 Public Snapshot;必须建立带 identity/revision/digest witness 的 observation,无法闭合即 fail closed。 + +### 1.4 RuntimeStatus + +#### EV-STATUS-001:根 Supervisor status 落在 project conversation + +- 代码:`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_state.rs` +- 关键函数:`append_game_creator_agent_runtime_public_status_message_at`。 +- 事实:messageId 由 agentId/sessionId/runId/status correlation 派生,但 append 时 `agent_id=None`、`session_id=None`。 +- 约束:进入 `(projectId, sessionId)` Public history 前必须显式保存 correlation mapping,不能从文件 scope 猜 Session。 + +#### EV-STATUS-002:专业 Agent terminal status 落在其 Session conversation + +- 代码:`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_state.rs` +- 关键函数:`append_game_creator_agent_runtime_terminal_public_message_at`。 +- 事实:非根 Supervisor 的 terminal status 使用其 `agentId/sessionId` 幂等 append。 +- 约束:历史消息保持原 agent/session/run,不重归属到 Supervisor 当前 Session。 + +#### EV-STATUS-003:部分 Supervisor continuation 不写 Session status + +- 代码:`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_state.rs`。 +- 事实:有 parent agent/run 的 Supervisor receipt 或 isolated-join continuation 为避免重复 formal project chat,terminal path直接返回,不写第二 Session message。 +- 约束:这类 status 默认不进入 Public Conversation;不能假设每个 Run 有同构 status source。 + +#### EV-STATUS-004:Accepted start status 只覆盖特定根 Supervisor + +- 代码:`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_driver/task_start.rs`。 +- 事实:`requires_public_start_status` 只对无 parent 的根 Project Supervisor 且非 receipt/join source 生效。 +- 约束:P0 必须按类型而非泛化 “RuntimeStatus” 建 fixture。 + +### 1.5 Runtime event + +#### EV-EVENT-001:普通 eventId 不可恢复 + +- 代码:`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_state.rs` +- 关键函数:`new_game_creator_agent_runtime_event_id`。 +- 事实:无 actionId 时使用 `pid + unixMillis + process-local sequence + eventType`;重启/重试没有稳定规范 key。 +- 约束:支持 `IC-CONV-007` 默认拒绝。 + +#### EV-EVENT-002:带 actionId 的 event 只覆盖部分路径 + +- 代码:`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_state.rs` +- 关键函数:`append_game_creator_agent_runtime_event_with_action`。 +- 事实:有 actionId 时可按 run/eventType/phase/actionId 形成较稳定 identity 和重复检查;普通 append 仍生成新 eventId。 +- 约束:即使 action event 较稳定,也必须同时满足 reader、digest、scope 和重放条件才能显式登记 Public。 + +#### EV-EVENT-003:现有 reader 吞坏行并截断最近 20 条 + +- 代码:`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_state.rs`、`src-tauri/src/main.rs`。 +- 关键函数/常量:`read_recent_game_creator_agent_runtime_events_for_session`、`AGENT_RUNTIME_RECENT_EVENT_LIMIT = 20`。 +- 事实:JSON 解析失败直接跳过;成功记录只返回最后 20 条。 +- 约束:该 reader 只能支持当前 GUI recent display,不能作为 `IC-CONV-007` 的 Public source reader。 + +#### EV-EVENT-004:Event record 自带公开正文 + +- 代码:`apps/ai-game-creator-shell/src-tauri/src/agent/runtime_state.rs`。 +- 事实:allowlist event 写入 `publicText`;正文不一定存在于 conversation message。 +- 约束:若未来接入,PublicEvent 是 source-record projection 例外,不能复制成普通 assistant conversation。 + +### 1.6 GUI writer 与跨 scope 落盘 + +#### EV-GUI-001:GUI 为 Runtime event 生成第二 identity + +- 代码:`apps/ai-game-creator-shell/src/features/project-workspace/SupervisorChatOnlyView.tsx`。 +- 事实:GUI 以 `game-chat-runtime-event:${eventId}` 构造聊天 message,并同时聚合 Supervisor 和直接 child events。 +- 约束:该 identity 不能进入规范 Public history。 + +#### EV-GUI-002:GUI 为 final reply 生成派生 identity + +- 代码:`apps/ai-game-creator-shell/src/features/project-workspace/SupervisorChatOnlyView.tsx`。 +- 事实:GUI 使用 `game-chat-final-reply:*` 构造 final 聊天 message。 +- 约束:应归一到现有 finalization conversation `messageId`。 + +#### EV-GUI-003:派生消息会 autosave 到 project conversation + +- 代码:`apps/ai-game-creator-shell/src/App.tsx`。 +- 事实:全局 messages autosave 可用 `agentId=null` 调用 `append_local_conversation_message`;child event 的 source Session 与实际 project transcript scope 不同。 +- 约束:支持 `MX-CONV-010`、`MX-CONV-011`;历史跨 scope 项默认隔离。 + +#### EV-GUI-004:普通 Agent chat 直接 append + +- 代码:`apps/ai-game-creator-shell/src/App.tsx`、`features/app-shell/useDeveloperAgentPanel.ts`。 +- 事实:普通 Agent user/assistant、错误回复和 Developer panel user message 存在直接 append 调用。 +- 约束:writer cutover 必须覆盖全部调用方,而不是只覆盖 Runtime output 双写。 + +--- + +## 2. 设计决策 + +### DR-001:采用统一 Supervisor Shell + +- 决定:正式 Consumer 只读状态、表达意图;Shell 统一交互决策。 +- 原因:GUI/CLI/测试当前存在重复且不一致的生命周期判断。 +- Contract:`IC-ARC-001`~`IC-ARC-004`。 + +### DR-002:拒绝平行 Runtime authority + +- 拒绝:让 request ledger、projection ledger 或 Public Snapshot 自己决定 task/finalization/provider 成功。 +- 原因:现有 Runtime facts 跨多个 record,当前没有可复用的全局事务;平行状态会漂移。 +- Contract:`IC-ARC-004`、`IC-IDEMP-003`、`IC-READ-004`。 + +### DR-003:拒绝 Session rotation 与 handoff + +- 拒绝:ActiveSessionIndex、live Session rotation、handoff manifest、continuation set、rotation fence、session control lease。 +- 原因:现有 catalog 明确禁止 live task 时切换;新增能力需要跨 task/conversation/finalization/interaction 的迁移和 rollback authority,超出本轮交互边界重构。 +- Contract:`IC-ID-002`、`IC-ID-003`。 + +### DR-004:拒绝全局 owner generation/lease + +- 拒绝:用 boot generation、诊断 JSON、lease expiry 或本地时间替代 OS lock。 +- 原因:会建立第二 owner authority,并在 pause/时钟漂移/文件残留时产生双 writer。 +- Contract:`IC-OWNER-001`。 + +### DR-005:采用 Snapshot + 有范围的事件提示 + +- 决定:Snapshot 是完整 Public read;event 只提示重新读取。Public 与 Developer 事件流按 `(projectId, view)` 隔离,订阅原子取得完整初始 Snapshot。 +- 原因:Consumer 本地合并不能可靠处理缺口、重连和跨 source 更新;全局或按路径过滤的 event 会泄露/混淆多项目状态。 +- Contract:`IC-READ-001`、`IC-READ-004`、`IC-EVT-001`~`IC-EVT-003`。 + +### DR-005A:fail-closed 也是可发布状态 + +- 决定:source observation 无法闭合但仍能确定 project/view scope 时,发布无 capability、无未证实 Runtime 事实的 `failClosed` Snapshot;完全不能确定安全 outcome 时返回 read error。 +- 原因:若 invalid 状态不推进 revision/hash/event,Consumer 会永久保留一份已失效的 valid Snapshot;保留旧 capability 会绕过失败关闭。 +- Contract:`IC-READ-001`、`IC-READ-004`、`IC-CAP-002`。 + +### DR-005B:协作实体有独立持久 identity + +- 决定:Public collaborator 使用 durable `collaborationId` binding,不从 group 或动态 child Agent identity 临时拼接;retry/successor 延续该 ID,manifest fallback 被 Runtime binding 替换。 +- 原因:同组多 child、重试与 isolated 执行都不能由单一 group/agentId 稳定代表,且 Public 不得泄露真实 child identity。 +- Contract:`IC-ID-003`、`IC-READ-001`、`IC-INT-002`。 + +### DR-006:采用五命令,不公开内部 primitive + +- 决定:`submit_intent/answer/approve/cancel/resume` 是唯一 Public 写集合。 +- 原因:Consumer 表达用户意图,不选择 Runtime primitive。 +- Contract:`IC-CMD-001`~`IC-CMD-010`。 + +### DR-007:Conversation 不复制正文 + +- 决定:Public 只建无正文 source index,从既有 conversation/event source 回读。 +- 原因:复制会建立第二正文 authority,并放大 GUI/CLI 双写。 +- Contract:`IC-CONV-002`。 + +### DR-008:PublicEvent 默认拒绝 + +- 决定:普通现有 event 不进入永久 Public history;只有显式登记且满足全部 identity/reader/digest/scope 条件的类型才可接入。 +- 原因:现有普通 eventId 不可恢复,reader 截断且吞坏行,GUI 还会再造 identity。 +- Contract:`IC-CONV-007`。 + +### DR-009:RuntimeStatus 按具体 source 准入 + +- 决定:不把 RuntimeStatus 泛化为所有 Run 的同构 conversation source。 +- 原因:根 Supervisor、专业 Agent 和 receipt/join 的实际落盘行为不同。 +- Contract:`IC-CONV-006`。 + +### DR-010:结果未知时停止而非重放 + +- 决定:Provider/工具/Runtime 副作用可能发生但不可证明时进入 outcome-unknown/reconciliation。 +- 原因:换 requestId 或 fallback 会产生重复真实副作用。 +- Contract:`IC-IDEMP-004`、`IC-MIG-002`。 + +### DR-011:保留 CLI 无 GUI 的受限 Runner 能力 + +- 决定:不以 GUI-owner 门禁删除现有 CLI 会话期间启动/连接 Runner 的能力。 +- 原因:普通 CLI 是协议平等 Consumer,headless 能力是前端逻辑是否泄漏的重要验收。 +- Contract:`IC-OWNER-001`。 + +### DR-012:`--swarm-chat` 产品定位仍需显式决定 + +- 决策 ID:`FD-001`。 +- 可选:普通 Public Supervisor CLI;或显式受信任 Developer CLI。 +- 不可选:无 capability 时静默读取私有字段;Developer write 绕过 Shell。 +- Contract 不变量:`IC-MIG-004`。 + +--- + +## 3. 冻结前证据门禁 + +同一个 `EG-*` 跨多个阶段时,状态按 `EG-ID@P阶段` 独立记录:某阶段 PR 只需关闭属于该阶段的子门禁,后续阶段的未实现证据不阻塞前一阶段完成;最终门禁只有在全部子门禁关闭后才整体完成。后续实现若推翻已关闭证据,必须重新打开对应子门禁。下文“阶段”按顺序对应各阶段必须提供的证据,不得以一个阶段的局部通过冒充整项关闭。 + +### EG-BASE-001:当前行为基线 + +- 对应:全部 P0。 +- 要求:确定性 Provider/进程内 Runtime 记录 submit、等待、批准、取消、恢复、终态与副作用计数;归一化随机 ID/时间。 +- 失败处理:阻塞迁移比较,不改变 Contract。 + +### EG-SCHEMA-001:Strict wire fixture + +- 对应:`IC-WIRE-001`、`IC-WIRE-002`。 +- 要求:`@P0` 定义全部 DTO 的 schema/golden/negative 向量和预期结果;`@P1` 实现 Rust→TypeScript 生成/校验,覆盖未知/重复/错误字段、tagged union、大小、Unicode scalar/UTF-8 byte、Public 零路径/私有字段。 +- 阶段:P0(规范向量)/P1(实现与通过);`@P1` 阻塞 P2/P3。 + +### EG-ID-001:身份与 Session + +- 对应:`IC-ID-001~005`。 +- 要求:projectId/path mismatch;opaque catalog digest;live-task create/fork/archive/set-active 拒绝;Supervisor 与 collaborator 交叉 Session mutation;历史 delivery 不重归属。 +- 阶段:P0/P2/P3。 + +### EG-OWNER-001:Owner 与执行位置 + +- 对应:`IC-OWNER-001~002`、`IC-CMD-010`。 +- 要求:External Runner 下所有正式 writer 实际在 owner Runner;进程内测试持等价 owner;双 Runner、drain、失锁、GUI-owner 丢失、CLI 启动回归。 +- 阶段:P0/P3/P4/P5。 + +### EG-STORE-001:Shell durable 底座 + +- 对应:`IC-ID-001`、`IC-OWNER-001~002`、`IC-DUR-001~003`、`IC-IDEMP-001~005`。 +- 要求:trusted project resolver 的缺失/manifest mismatch;进程内与 Runner 对同一 project owner 互斥;`.agent/project.lock` stale reclaim 不参与 Shell 互斥;RFC 8785 向量;专用 Shell ledger 的连续 ledgerVersion、tail repair/中间损坏隔离、atomic write、回读、checksum、派生 index 重建与 Runner crash 后 read-back。 +- 阶段:P1;阻塞 P2/P3。 + +### EG-PROJECTION-001:Projection observation + +- 对应:`IC-READ-001~004`、`IC-EVT-001~003`、`IC-CAP-001~003`。 +- 要求:读 manifest/catalog/state/task/event/stream/Shell binding/projection journal 期间并发变化;source witness 变化的有界 retry;验证 normal/required absence、损坏和 conflict;验证 failClosed 完整固定向量及 manifest identity 不可证明时 read error;逐类 capability issuance/撤销;首次 revision1/sequence0。 +- 阶段:P2;阻塞 P2 完成与 P3 capability 依赖。 + +### EG-CMD-001:Request 幂等与崩溃读回 + +- 对应:`IC-IDEMP-001~005`、`IC-CMD-001~010`。 +- 要求:同 request 同/异 fingerprint、并发重复、业务拒绝重放、prepared/executing/succeeded 各 crash point、Runner 强杀、unknown outcome 零重复副作用。 +- 阶段:P1/P3/P4。 + +### EG-INT-001:Interaction 状态机 + +- 对应:`IC-INT-001~007`。 +- 要求:identity/revision/response replay;Public interaction capability 与 allowedActions 精确一致;User/Developer audience;question/option/freeform;target set;artifact digest;requestChanges 唯一 rework;Resolving crash recovery;collaboration binding 的 restart 重建、retry successor/parent lineage/source/group 漂移旧 interaction stale、manifestFallback 不可操作。 +- 阶段:P2/P3。 + +### EG-READ-001:Snapshot 与事件 + +- 对应:`IC-READ-001~004`、`IC-EVT-001~003`、`IC-CAP-001~003`。 +- 要求:Public/Developer `(projectId, view)` 路由隔离、原子 initial Snapshot、snapshot-first/no-backlog、duplicate/out-of-order/gap/reconnect;Public failClosed 固定向量、Developer source failure 统一 read error;revision/hash/排序/size canonical vectors,Public/Developer 超限均返回固定 read error 且无 partial DTO;全部 capability 正反签发与 cancel run revision/builtin Session/retry policy guards;bootstrap collaborators 为空、parentRun multi-child、retry lineage、fallback replacement、stale cancel 复核。 +- 阶段:P2。 + +### EG-CONV-001:Final reply source + +- 对应:`IC-CONV-005`。 +- 要求:messageId/finalizationId/stream tuple 唯一性;streaming→ready→committed;publisher 写失败/截断;sidecar 清理后 conversation 回读;Provider 调用计数。 +- 阶段:P0/P3/P4。 + +### EG-CONV-002:Status source + +- 对应:`IC-CONV-006`。 +- 要求:根 Supervisor start/terminal、专业 Agent terminal、receipt/isolated join 无 source 三类分别验证;project status correlation 缺失/冲突失败关闭。 +- 阶段:P0/P3。 + +### EG-CONV-003:Event 默认隔离与准入 + +- 对应:`IC-CONV-007`。 +- 要求:枚举规范 action identity 与普通 pid/time identity call site;普通 event 未进入 Public chain;如接入某类型,必须通过 eventId 定位、坏行/截断报告、digest、scope 和重放 fixture。 +- 阶段:P0/P1/P3;不通过只阻塞该 event type 接入,不阻塞默认隔离方案。 + +### EG-CONV-004:Writer cutover + +- 对应:`IC-CONV-010`。 +- 要求:全部 `append_local_conversation_message` 调用方三选一;GUI final/event autosave 停止;同 source 单 writer;Public scope 无缺少 messageId append。 +- 阶段:P0/P3/P5/P6。 + +### EG-CONV-005:Cursor 与历史完整性 + +- 对应:`IC-CONV-008`、`IC-CONV-009`。 +- 要求:origin/tail、分页、永久 sequence 空洞、duplicate、非法 cursor、index/source/digest/correlation 损坏、无 partial page、session-lifetime cursor。 +- 阶段:P3/P5/P6。 + +### EG-MIG-001:跨 Consumer golden replay + +- 对应:`IC-ARC-001`、`IC-MIG-001~004`。 +- 要求:GUI、普通 CLI、进程内测试和 Runner transport 对同一输入产生等价 request/result/Snapshot/conversation 语义和副作用计数。 +- 阶段:P3/P5/P6。 + + +### EG-INGRESS-001:P3 正式写入口与 writer cutover + +- 对应:`IC-CMD-010`、`IC-CONV-010`、`IC-MIG-005`。 +- 要求:legacy Tauri/CLI/`swarm_cli`/helper 的正式写入口全部转发同一 Shell handler 或禁用;Public Conversation adapter 启用前所有 writer 已接管、隔离或停止;cutover watermark 后零旧正式 writer、零派生双写。 +- 阶段:P3;阻塞 P3 完成与 P5 Consumer 迁移。 + +### EG-RUNNER-001:P4 跨重启发现与安全恢复 + +- 对应:`IC-OWNER-001~002`、`IC-IDEMP-004`、`IC-MIG-006`。 +- 要求:trusted discovery registry 的注册/删除/损坏/权限 fixture;Runner 重启后重新解析 manifest、重取 owner、按 durable evidence wake/reconcile/no-op;未知真实副作用零自动重放。 +- 阶段:P4;阻塞 P4 完成。 + +### EG-DEL-001:旧公开面删除 + +- 对应:`IC-MIG-003`。 +- 要求:正式 invoke handler、transport、Consumer、Public DTO 不再引用旧协议;内部 primitive 与回归测试仍存在;Preview/resource/session 管理面未误删。 +- 阶段:P6。 + +--- + +## 4. 证据更新规则 + +1. `EV-*` 只能由代码读取、定向测试或运行证据支持;README/旧设计声明不能单独成为事实。 +2. 代码与 `EV-*` 冲突时先更新 evidence 和迁移矩阵;若冲突使 `IC-*` 不可实现,再提交 Contract 变更评审。 +3. `EG-*` 失败的默认处理是隔离、阻塞阶段或进入 reconciliation,不是增加 Consumer fallback。 +4. 每个 P0–P6 PR 必须列出所实现的 `IC-*`、受影响 `MX-*` 和关闭的 `EG-ID@P阶段`;不得把跨阶段门禁标记为提前整体完成。 +5. 本附录不保存密钥、Token、绝对本地私密路径、Provider 原文、会话记录或构建产物。 diff --git a/docs/technical/【迁移方案】AI游戏创作Agent Runtime交互边界迁移矩阵-2026-08-17.md b/docs/technical/【迁移方案】AI游戏创作Agent Runtime交互边界迁移矩阵-2026-08-17.md new file mode 100644 index 000000000..e2d97c94a --- /dev/null +++ b/docs/technical/【迁移方案】AI游戏创作Agent Runtime交互边界迁移矩阵-2026-08-17.md @@ -0,0 +1,161 @@ +# AI 游戏创作 Agent Runtime 交互边界迁移矩阵 + +> 文档角色:把 Interaction Contract 映射到当前代码、阶段和验收证据 +> 状态:P0 inventory;矩阵不得修改 `IC-*` 语义 +> 总览入口:[`【技术方案】AI游戏创作Agent Runtime交互边界重构实施计划-2026-08-12.md`](./【技术方案】AI游戏创作Agent%20Runtime交互边界重构实施计划-2026-08-12.md) +> 规范来源:[`【技术协议】AI游戏创作Agent Runtime交互合同V1-2026-08-17.md`](./【技术协议】AI游戏创作Agent%20Runtime交互合同V1-2026-08-17.md) +> 证据来源:[`【设计依据】AI游戏创作Agent Runtime交互边界证据与决策附录-2026-08-17.md`](./【设计依据】AI游戏创作Agent%20Runtime交互边界证据与决策附录-2026-08-17.md) + +## 0. 使用规则 + +每一行包含: + +```text +当前 source/入口 +→ 适用 IC 规则 +→ 当前差距 +→ 唯一迁移动作 +→ 阶段 +→ 完成证据 +``` + +状态值: + +- `baseline`:现状能力,尚未迁移; +- `isolate`:不满足 Public Contract,默认隔离; +- `adapt`:复用现有事实并通过 Shell Adapter 接入; +- `replace-consumer`:后端能力就绪后替换 Consumer; +- `remove-public`:P6 删除公开注册/调用; +- `decision`:需要显式产品决定,但不得改变 Contract。 + +--- + +## 1. 正式 ingress 与执行位置 + +| MX ID | 当前入口/source | 当前事实 | Contract | 目标动作 | 阶段 | 状态/证据 | +|---|---|---|---|---|---|---| +| MX-ING-001 | GUI Supervisor chat,`apps/ai-game-creator-shell/src/App.tsx` 与 `SupervisorChatOnlyView.tsx` | GUI 仍参与 start/steer、状态合并和输出同步 | `IC-ARC-002`、`IC-CMD-003`、`IC-CMD-004` | GUI 只提交 capability 中的 `submit_intent`,不选择 disposition | P5 | `replace-consumer`;GUI 调用图无 Runtime primitive | +| MX-ING-002 | 普通 Agent chat,`apps/ai-game-creator-shell/src/App.tsx` | user/assistant 直接 append,并调用内部 Agent 能力 | `IC-CMD-010`、`IC-CONV-010`、`IC-MIG-005` | P0 归类;正式路径在 P3 接 Shell、开发路径在 P3 隔离;P5 只清理旧 Consumer 分支 | P0/P3/P5 | 全调用图、ingress cutover 与 UI 清理 | +| MX-ING-003 | Tauri commands,`apps/ai-game-creator-shell/src-tauri/src/commands.rs` | 暴露旧 Runtime 和 conversation write wrapper | `IC-OWNER-001`、`IC-CMD-010`、`IC-MIG-005` | P3 先转发同一 Shell endpoint;旧注册 P6 删除 | P3/P6 | transport fixture + invoke handler 静态检查 | +| MX-ING-004 | CLI commands,`apps/ai-game-creator-shell/src-tauri/src/cli.rs` | `AgentSteer` 等路径直接调用 Runtime primitive;CLI 可启动受限 Runner | `IC-CMD-001`、`IC-CMD-010`、`IC-MIG-005` | P3 先收口为 Shell transport;P5 再迁 Public CLI read/UX;保留无 GUI 启动 Runner 能力 | P3/P5 | CLI golden replay;无直接 start/steer/resume | +| MX-ING-005 | `--swarm-chat`,`cli.rs` 与 `swarm_cli/turn_dispatch.rs` | 读取专业 Agent 状态并直接 dispatch/append | `IC-ARC-005`、`IC-CMD-010`、`IC-MIG-004`、`IC-MIG-005` | P3 先让正式写走 Shell 或禁用;P5 按 `FD-001` 选择 Public/Developer read 呈现 | P3/P5 | Shell writer fixture;`decision`;Public/Developer DTO 零交叉 | +| MX-ING-006 | Runner `runtime.*` RPC,`src-tauri/src/runner/dispatch.rs` | 已有 resume/steer/cancel/pause/compact 等内部 RPC,request cache 仅内存 | `IC-CMD-001`、`IC-CMD-010`、`IC-IDEMP-001~005` | 仅作为 Shell 内部实现/委托 Shell;不得把 cache 当 durable read-back | P1/P3/P6 | crash 后同 requestId read-back + dispatch 调用图 | +| MX-ING-007 | 进程内测试 transport | 可绕过 External Runner 直接调用实现 | `IC-OWNER-001`、`IC-CMD-010` | 复用同一 Shell handler,并在任何 Shell/Runtime 写前取得同一 OS owner lock;per-Agent lock 不等价 | P1/P3 | 同 fixture 跨 Runner/进程内 replay | +| MX-ING-008 | Runtime 内部 wake/recovery | timer/lane/schedule/owner recovery 不属于用户意图;Runner known roots 当前仅在内存 | `IC-CMD-009`、`IC-ARC-004`、`IC-MIG-006` | 保持内部 recovery intent,不导出为 Public resume;P4 建跨重启候选项目发现 | P4/P6 | 静态 Public DTO 检查、重启 discovery 与恢复测试 | + +--- + +## 2. Read model 与 Consumer 决策 + +| MX ID | 当前 read/source | 当前事实 | Contract | 目标动作 | 阶段 | 状态/证据 | +|---|---|---|---|---|---|---| +| MX-READ-001 | manifest/Session catalog/Runtime state/task/Shell binding 读取 | GUI/CLI 分别解释 status/phase;缺少完整 project/session/binding witness | `IC-READ-001`、`IC-READ-004`、`IC-ARC-002` | Shell 按完整 source dependency matrix 形成 valid/failClosed/read-error,稳定投影 status/stage/waitingOn/nextStep | P1/P2/P5 | manifest/catalog/binding/journal、bootstrap、active-task 缺失、损坏、跨 Consumer Snapshot fixture | +| MX-READ-002 | pending action、user-input、tool confirmation sidecar | 当前由不同 UI/CLI 分流;无 open interaction 可缺失 | `IC-READ-004`、`IC-CAP-003`、`IC-INT-001`~`IC-INT-007` | P2 只读物化稳定 Interaction,并生成与 interactionId/revision 一致的 response capability;required sidecar 缺失/损坏则 fail-closed,P3 接管 answer/approve | P2/P3 | identity/revision/audience/capability/required-source fixture | +| MX-READ-003 | response stream | 是 optional Runtime final-reply 实时/恢复辅助,写错误可能被忽略 | `IC-READ-004`、`IC-CONV-005`、`IC-IDEMP-004` | 只作为短期 source evidence;不能单独证明 committed;完成证明依赖它时损坏/缺失 fail-closed | P0/P2/P3 | optional、写失败、截断、ready→committed fixture | +| MX-READ-004 | GUI Runtime state/event merge | Consumer 自行拼接多个 source | `IC-READ-001`、`IC-EVT-001` | P5 删除 normalize/merge 决策,只渲染 Snapshot | P5 | 前端类型/调用图检查 | +| MX-READ-005 | Tauri best-effort update event | 不提供按 project/view scope 可靠补读历史 | `IC-EVT-001`~`IC-EVT-003` | 替换为按 `(projectId, view)` 路由的 snapshot-first subscription;V1 不补历史 event,缺口/重连均重读完整 Snapshot | P2 | 首次 revision1/sequence0、Public/Developer 隔离、重复/乱序/缺口/重连 fixture | +| MX-READ-006 | Developer Agent panel | 可读私有 Agent 状态并直接操作 | `IC-ARC-005`、`IC-READ-003`、`IC-MIG-005` | P2 建独立 Developer DTO;正式 Supervisor 写在 P3 走五命令,Developer-local 写在 P3 隔离;P5 只清理旧 UI 分支 | P2/P3/P5 | 未授权拒绝、ingress cutover、Public 字段零泄漏 | +| MX-READ-007 | Preview/resource/session 管理面 | 独立现役合同 | `IC-ARC-002`、`IC-MIG-003` | 保持 sibling contract,不从 Snapshot nextStep 重造 | P5/P6 | 调用图证明未误删 | +| MX-READ-008 | command capability 投影 | 当前 Consumer 由 status/phase 自行判断按钮 | `IC-CAP-001~003`、`IC-READ-004` | 按 Contract issuance matrix 从完整 witness 必签/撤销 submit、interaction、cancel、resume 与 Developer reconcile capability | P2/P3 | 每类 capability 正反状态、witness 漂移、Public/Developer audience fixture | + +--- + +## 3. 身份、Session 与 owner + +| MX ID | 当前 source | 当前事实 | Contract | 目标动作 | 阶段 | 状态/证据 | +|---|---|---|---|---|---|---| +| MX-ID-001 | 项目 manifest/路径 | manifest 有 project identity;transport 大量使用绝对路径定位,尚无 projectId 反向 root registry | `IC-ID-001` | Public 只传 projectId;宿主 resolver 产出并复核 `TrustedProjectContext`,locator 留在受信任边界 | P1/P2/P3 | path-free schema + resolver/subscription mismatch fixture | +| MX-ID-002 | Session catalog,`project/conversation.rs` | 每 Agent 一份 catalog;无独立 revision;live task 禁止变更 | `IC-ID-002`、`IC-ID-003` | 对 Project Supervisor catalog 计算 opaque digest;不写回、不扩权 | P0/P2/P3 | live-task 与跨 Agent Session fixture | +| MX-ID-003 | collaborator/child history | 各自具有 agentId/sessionId/runId,但无稳定公开协作实体 | `IC-ID-003` | 首次 binding durable 分配 collaborationId;retry/successor 保持 ID,fallback 被 Runtime binding 替换;无 parent run 的 fallback 绑定 project/session/manifest digest/group;不使用 Supervisor 当前 Session 重新归属 | P2/P3 | parentRun multi-child、retry lineage、fallback replacement、交叉 mutation fixture | +| MX-OWNER-001 | `.agent/runtime/execution-owner.lock` | OS 排他锁是真正 owner;现仅 Runner production path 获取 | `IC-OWNER-001`、`IC-OWNER-002` | 直接复用;进程内 Shell 也必须在写前取得同一实现,不新增 generation/lease | P0/P1/P4 | 双 Runner、进程内冲突、drain、失锁测试 | +| MX-OWNER-002 | `.agent/project.lock` | create-new 文件锁按 PID/时间/mtime reclaim,不是 owner | `IC-OWNER-002` | 不可作为 Shell protocol lock;保留其现役业务用途 | P1 | stale reclaim 与 Shell lock 分离 fixture | +| MX-OWNER-003 | `execution-owner.json` 与 bootId | 仅诊断/实例关联 | `IC-OWNER-001` | 保持私有诊断,不用于接管/CAS | P0/P4 | 时间/mtime/诊断冲突 negative fixture | +| MX-OWNER-004 | GUI-owner watchdog | GUI 启动 Runner 时的生命周期门禁 | `IC-OWNER-001` | 保留 GUI-owner 路径;不扩张为 CLI control lease | P4 | GUI-owner 丢失与 drain fixture | +| MX-OWNER-005 | CLI `--config-dir` Runner | 当前可无 GUI 启动/连接受限 Runner | `IC-OWNER-001` | 保留现有终端会话能力;不承诺常驻 | P4/P5 | CLI Runner 回归 | + +--- + +## 4. Command 与现有 Runtime identity + +| MX ID | Public command | 现有内部能力/source | Contract | Adapter 要求 | 阶段 | 证据 | +|---|---|---|---|---|---|---| +| MX-CMD-001 | `submit_intent` DirectReply | CLI Reply/Execute kernel、conversation append | `IC-CMD-003`~`IC-CMD-005`、`IC-CONV-004` | 预分配 user/assistant messageId,先 user commit 再 reply | P3 | crash-point + same request replay | +| MX-CMD-002 | `submit_intent` Start | Runtime start/pending/task/status | `IC-CMD-004`、`IC-CMD-005` | 绑定 input envelope、现有 task/run/status identity | P3 | user→status→queued crash fixture | +| MX-CMD-003 | `submit_intent` Steer | 现有 V1.13 steer ledger | `IC-CMD-004`、`IC-CMD-005` | prepared 时绑定 steerId/cursor;不复制 steer 生命周期 | P3 | same-run、重复和 deferred fixture | +| MX-CMD-004 | `answer` | user-input sidecar/answer primitive | `IC-CMD-006`、`IC-INT-001~007` | 物化稳定 interaction,按 response/revision 解决 | P2/P3 | option/freeform/stale/replay fixture | +| MX-CMD-005 | `approve` | tool/policy confirm 与 reject primitive | `IC-CMD-007`、`IC-INT-001~007` | audience/policy/target set/artifact binding 锁内复核 | P2/P3 | approve/reject/requestChanges matrix | +| MX-CMD-006 | `cancel` | Runtime cancel primitive | `IC-CMD-008` | 精确 Session/Run/revision;唯一 cancel operation;不伪造终态 | P3 | cancel revision/state matrix | +| MX-CMD-007 | `resume` ContinueRun | paused Run resume | `IC-CMD-009` | 同 Run + expected revision | P3 | paused/running/waiting/finalizing negative fixture | +| MX-CMD-008 | `resume` RetryTerminalRun | terminal retry/successor lineage | `IC-CMD-009` | 唯一 successor runId;保存 predecessor/source identity;绑定 terminal revision 与 retry policy digest | P3 | policy drift、concurrent retry + crash fixture | +| MX-CMD-009 | `resume` ReconcileRun | 受信任 reconciliation | `IC-CMD-009`、`IC-IDEMP-004` | 只读/修复已知事实,不重放未知副作用 | P3/P4 | Developer capability + provider/tool count | +| MX-CMD-010 | Goal replacement | 现有 replacement primitive | `IC-CMD-004` | 仅显式 Goal management operation;不由普通 execute intent 触发 | P3 | frozen Goal Contract negative fixture | + +--- + +## 5. Conversation source 与 writer cutover + +| MX ID | Source/writer | 当前事实 | Contract | 目标动作 | 阶段 | 状态/证据 | +|---|---|---|---|---|---|---| +| MX-CONV-001 | Session user message | 现有 conversation 正文 source | `IC-CONV-002`、`IC-CONV-004` | 复用正文;Public index 只保存 source metadata | P3 | source digest/read-back fixture | +| MX-CONV-002 | DirectReply | `swarm_cli`/GUI 可直接 append user+assistant | `IC-CONV-004`、`IC-CONV-010` | Shell 接管稳定 identity 和写入顺序 | P3/P5 | 零重复 user/assistant | +| MX-CONV-003 | RuntimeFinalReply | finalization + response stream + conversation | `IC-CONV-005` | 保存三层 identity binding;长期正文从 conversation 回读 | P3 | sidecar 清理后历史回读 | +| MX-CONV-004 | 根 Supervisor start/terminal status | 稳定 messageId 但正文写 project conversation | `IC-CONV-006` | 以 task/run correlation 显式绑定 Supervisor Session | P0/P3 | correlation 缺失/冲突 fixture | +| MX-CONV-005 | 专业 Agent terminal status | 写其 Agent Session conversation | `IC-CONV-006` | 按该 agent/session/run 回读,不重归属 | P0/P3 | session scope fixture | +| MX-CONV-006 | receipt/isolated join status | 当前不写 Session status message | `IC-CONV-006` | 默认不进入 Public Conversation | P0 | `isolate` negative fixture | +| MX-CONV-007 | 普通 Runtime event | eventId 依赖 pid/时间/进程计数 | `IC-CONV-007` | V1 默认隔离 | P0 | `isolate`;call site inventory | +| MX-CONV-008 | action-identity event | 部分 event 可按 action identity 幂等 | `IC-CONV-007` | 仅在规范 reader/digest/scope 全闭合后显式登记 | P0/P1/P3 | 默认 `isolate`;event replay fixture | +| MX-CONV-009 | recent-events reader | 静默跳过坏行,只返回最近 20 条 | `IC-CONV-007`、`IC-CONV-009` | 不作为 Public source reader;若接 event 必须补新 reader | P0/P1 | 损坏/截断/定位 fixture | +| MX-CONV-010 | GUI final autosave | `response-stream → game-chat-final-reply:* → autosave` | `IC-CONV-005`、`IC-CONV-010` | P3 adapter 启用前停止正式写入;P5 只删除旧消费/展示分支 | P3/P5 | cutover watermark 后零派生 writer;GUI 调用图清理 | +| MX-CONV-011 | GUI event autosave | `Runtime event → game-chat-runtime-event:* → project conversation` | `IC-CONV-007`、`IC-CONV-010` | P3 adapter 启用前停止正式写入并隔离历史跨 scope 项;P5 清理旧 UI 分支 | P0/P3/P5 | source scope inventory + cutover 后零派生 writer | +| MX-CONV-012 | 普通 Agent chat append | `App.tsx` user/assistant 可无 messageId append | `IC-CONV-010` | P3 前将正式 Supervisor 接 Shell、Developer/local 显式隔离;P5 只清理旧 Consumer 分支 | P0/P3/P5 | writer 三选一清单 + cutover fixture | +| MX-CONV-013 | Developer panel append | Developer user history 直接写 | `IC-ARC-005`、`IC-CONV-010` | P3 前标记 Developer-local 且永不进入 Public,或接正式 Shell;P5 清理旧调用面 | P0/P3/P5 | DTO/调用面隔离 + cutover fixture | +| MX-CONV-014 | project pending-message autosave | 项目级 conversation writer | `IC-CONV-010` | P3 adapter 启用前接 stable source binding 或停止;P5 只删除旧 Consumer 分支 | P0/P3/P5 | writer cutover fixture | +| MX-CONV-015 | Public Conversation cursor | 当前无统一永久 source index | `IC-CONV-002`、`IC-CONV-008`、`IC-CONV-009` | P3 建无正文 index、origin/tail/cursor chain | P3 | 分页、空洞、损坏、全量补读 | + +--- + +## 6. Public / Developer 字段边界 + +| MX ID | 数据 | 当前风险 | Contract | 动作 | 阶段 | 证据 | +|---|---|---|---|---|---|---| +| MX-DATA-001 | project path / LocalConversationResult.path | GUI/CLI 可读本地路径 | `IC-ID-001`、`IC-READ-002` | Public DTO 零 path;local transport 单独返回 | P2/P5/P6 | schema/static check | +| MX-DATA-002 | Provider、tool、observation | Developer/runtime records 含私有原文 | `IC-READ-002`、`IC-ERR-003` | Public 严格白名单;Developer 仍脱敏有界 | P2 | sensitive fixture | +| MX-DATA-003 | dynamic child identity | GUI 可聚合专业/child Runtime | `IC-ID-003`、`IC-READ-002` | Public Snapshot/event/error/capability 只显示 durable collaborationId/组摘要;真实 child agent/session/parentRun/run/delegation identity 只留 private binding | P2/P5 | parentRun multi-child、retry、fallback replacement、Public zero-leak、权限 fixture | +| MX-DATA-004 | interaction private prompt/policy | sidecar 可能含原始模型内容 | `IC-INT-005`、`IC-ERR-003` | 生成独立 Public presentation;不安全则 Developer/reconciliation | P2 | redaction fixture | +| MX-DATA-005 | finalization/provider identity | 恢复和调试需要,正式 UI 不需要 | `IC-CONV-005`、`IC-READ-002` | 保留 private binding,Public message 仅 provenance allowlist | P3 | Public schema zero-leak | + +--- + +## 7. P6 删除清单 + +| MX ID | 删除范围 | 保留范围 | Contract | 完成证据 | +|---|---|---|---|---| +| MX-DEL-001 | 正式 transport 旧 start/steer/confirm/reject/answer/cancel/retry/resume/schedule/read 注册 | Runtime 内部 primitive | `IC-MIG-003` | handler/route 静态检查 | +| MX-DEL-002 | GUI/CLI 旧生命周期判断和 fallback | Public Consumer + Developer read | `IC-ARC-002`、`IC-MIG-002` | Consumer 调用图 | +| MX-DEL-003 | GUI Runtime output 派生 autosave | 原 conversation/finalization/event source | `IC-CONV-010` | writer cutover + 零 duplicate | +| MX-DEL-004 | Public DTO 的 path/finalization/provider/private fields | 受信任本地/Developer DTO | `IC-READ-002`、`IC-READ-003` | schema diff | +| MX-DEL-005 | Public scope 无稳定 messageId append | 明确 Developer/local history | `IC-CONV-010` | 所有 append caller 已分类 | +| MX-DEL-006 | migration unknown-command fallback | 内部回归测试 | `IC-MIG-002`、`IC-MIG-003` | transport fixture | + +--- + +## 8. 当前冻结前缺口 + +| ID | 缺口 | 性质 | 阻塞阶段 | +|---|---|---|---| +| FD-001 | `--swarm-chat` 是普通 Public Supervisor CLI 还是显式 Developer CLI | 产品兼容决策 | P5 产品绑定/呈现;不阻塞 Contract 核心冻结 | +| GAP-001 | 全部 legacy Runtime ingress 的实际写入进程调用图尚未形成正式 artifact | P0 evidence | P3 | +| GAP-002 | 全部 conversation append writer 的接管/隔离/禁用归类尚未闭合 | P0 evidence | P3/P5 | +| GAP-003 | event type/call site identity inventory 尚未形成正式 artifact | P0 evidence | PublicEvent 接入;默认隔离不受阻 | +| GAP-004 | 按 eventId 定位、报告坏行/截断、校验 digest 的 reader 尚不存在 | implementation gap | PublicEvent 接入;默认隔离不受阻 | +| GAP-005 | Rust→TypeScript strict schema/golden fixture 尚未实现;冻结前只定义规范与向量 | P1 implementation | P2/P3 | +| GAP-006 | `projectId → TrustedProjectContext` 的受信任宿主 resolver 尚未实现 | P1 implementation | P2/P3;Public DTO 始终保持无路径 | +| GAP-007 | 现有 `.agent/project.lock` 具有 stale reclaim,不能当 Shell protocol lock | P1 implementation boundary | P1;须与 execution owner 下串行分离 | +| GAP-008 | RFC 8785 canonicalization 尚无单一复用实现 | P1 implementation | P1;checksum/fingerprint/hash 不可各自序列化 | +| GAP-009 | Shell record 尚无唯一 append order authority;不能由多份 sidecar 自行分配 ledgerVersion | P1 implementation | P1;建立专用 ledger,sidecar/index 只能派生 | +| GAP-010 | Projection reader 尚无 witness、一致 observation、source absence/corruption matrix、fail-closed publication | P2 implementation | P2;不能直接公开现有聚合 read | +| GAP-011 | Runner 的 known roots 与 request dedupe 都是内存态 | P3/P4 implementation | P3 durable read-back;P4 restart discovery | +| GAP-012 | Snapshot subscription 尚无按 `(projectId, view)` 路由、durable sequence 与原子 initial Snapshot | P2 implementation | P2;V1 使用 snapshot-first/no-backlog,不能复用全局 best-effort event | +| GAP-013 | collaborator/child 到 durable collaborationId 的 binding/lineage 尚不存在 | P2 implementation | P2;Public 不得临时以 agentId/组名拼接 identity | + +这些缺口不得被解释为 Contract 规则未决定:除 `FD-001` 外,现状不满足即按 Contract 默认隔离或失败关闭。 -- 2.52.0