# Runtime Delivery Ownership 格式兼容

> Canonical path: `specs/daemon/runtime-state-compatibility.md`
> Related specs: [AgentTeam Daemon](./daemon.md)、[AgentTeam Desktop Architecture](./desktop-architecture.md)、[Agent Runtime Delivery Reliability](../message/delivery-runtime.md)、[AgentTeam Versioning 规范](../versioning/versioning.md)

Status: current.
Scope: Desktop daemon 的 `runtimeDeliveryOwnershipVersion` 读取、写入、升级与回滚规则。
Non-goals: 通用 schema migration framework、任意 JSON unknown-field preservation、Server 数据库迁移，以及恢复已经退休的产品功能。

## 1. 结论

`runtimeDeliveryOwnershipVersion` 保留，但只承担一个职责：阻止 daemon 在无法理解消息所有权安全语义时启动对应 agent。它不决定 provider session 是否存在、是否可 resume，也不覆盖当前 `RuntimePolicySnapshot` 或 thread-contract instruction fingerprint。

兼容机制保持为一个普通的显式分支：

```text
active write format = 2
supported read formats = {0, 2, 3}
```

State store 按 version 执行直接转换，不建设 codec registry、adapter interface、compatibility disposition、opaque extension 或独立迁移服务。已知历史格式转换为当前内存模型；真正未知的格式继续 fail-closed。

## 2. 为什么仍需要 format version

Runtime delivery ownership 位于 Server ACK 之后的持久化责任边界。Daemon 已 ACK 的输入必须保持唯一 owner，直到它成为 terminal evidence。错误解释未来格式可能造成：

- 把仍在 runtime 中的 `inflight` 输入当成可重放输入，产生重复执行；
- 忽略一个已 ACK 的 `queued` 输入，造成消息丢失；
- 把未知状态当成空状态，再由旧 writer 覆盖原文件。

因此，完全删除 format version 会失去必要的安全闸门。但 version 不是产品能力版本，也不应要求 `version == current writer`。旧 daemon 少功能是允许的；只有可能破坏 input ownership 的语义差异才需要拒绝。

## 3. 所有权与代码边界

`.daemon-state.json` 的读写由 `desktop/internal/dispatcher/resume_state_store.go` 所有；ownership 的版本判断和持久化操作位于 `desktop/internal/dispatcher/runtime_delivery_ownership.go`。`desktop/internal/agent` 继续拥有当前 `queued`、`inflight` 和 `terminal` 状态机及 record validation。

约束如下：

- `internal/dispatcher` 是该文件的唯一 schema 和 writer owner；
- 历史格式只在 dispatcher 转换，不进入 `internal/agent` 的公共模型；
- channel、runtime driver、API handler 不得自行修复或删除该文件；
- 兼容读取复用现有 agent state lock、大小限制和原子保存路径。

## 4. 安全不变量

任何已知格式转换都必须保持以下语义：

1. `queued` 的 MessageID 和完整 replay body 不变；
2. `inflight` 仍不可直接 replay，只能经过既有 authoritative reconciliation；
3. `terminal` 不得返回 live，`surfaced` 不得降级为 `failed`；
4. record 必须通过当前 ownership validation，不能把 decode 零值当作空记录；
5. 未知 version 不得触发同一文件的保存、清空或删除；
6. 格式不兼容只阻止对应 agent 的 runtime admission 和新 handoff，不影响 daemon 与其他 agent。

## 5. 最小读取协议

实现只需要一个清晰的 `switch`，不需要可扩展框架：

```text
switch runtimeDeliveryOwnershipVersion:
  case 0: read legacy phase records
  case 2: read current records
  case 3: read known v3 records and project to v2 behavior
  default: return unsupported-format error
```

当前格式规则为：

| Format | 读取规则 | 后续写入 |
| --- | --- | --- |
| `0` | 无 records 时视为空；有 legacy `phase` records 时复用现有 v0 转换 | 有 records 或新增 ownership 时写 `2`；空 ownership 可保持 `0` |
| `2` | 直接解码并校验当前 ownership records | `2` |
| `3` | 按第 6 节执行已知降级，再校验核心 records | `2` |
| 其他 | 不解释、不改文件，阻止对应 agent 启动 | 无 |

Format ID 是已经落盘的数据标识，不得复用。`1` 保持未使用，`3` 保持 retired；下一种不兼容格式使用 `4` 或更大的新 ID。

## 6. Format 3 的直接降级规则

Format 3 的 `queued`、`inflight`、`terminal` 核心字段与 format 2 相同，仅增加了已退休的 visible-reply recovery 状态，并可能与已退休的 provider-session rotation 状态共存。因此 v3 直接复用当前 core record 解码和校验；dispatcher 对 `providerSessionSeal` 这一已知 retired 字段保持忽略，不把它投影为 provider-session guard，不修改当前 `agent.DeliveryOwnershipRecord`，也不重复解析或复制 record 数组。

### 6.1 Core ownership

v3 record 删除 v3-only 字段后，按当前 v2 record 处理：

- `queued` 保持 queued；
- `inflight` 保持原 inflight reason；
- `terminal` 保持原 outcome、MessageID、routing identity、时间与诊断字段；
- 核心字段非法时拒绝该 agent，不猜测修复。

### 6.2 Visible-reply markers

`visibleReplyPending` 和 `visibleReplyResolved` 属于已退休的回复恢复能力，不改变原输入的 ownership。降级时直接丢弃这两个 marker：

- resolved marker 已经没有待处理义务；
- pending marker 可能使 daemon restart 后少发一次可见回复，这是允许的旧版本少功能；
- 无论 marker 值为何，都不得重放原始输入、改变 core ownership state 或伪造回复结果。

不为这些 marker 增加 compatibility extension，也不长期保存已退休功能状态。

### 6.3 Provider-session seal

若 v3 envelope 包含已退休的 `providerSessionSeal`，兼容读取必须忽略它，不得把它投影成 `SessionResumeSafe=false`，也不得因此清空或阻止 provider thread。下一次现有 state transaction 写入 format 2 时丢弃 seal、process-generation、completed-session 和 session-rotation 等 v3-only metadata。provider session 的跨 daemon 兼容只由当前 `RuntimePolicySnapshot` 与 `baseInstructions` + `developerInstructions` 的语义 fingerprint 决定；delivery ownership 仍可用自己的状态做 reconciliation，但不能据此 blind replay 或清除 thread。

该规则接受旧 rotation 状态的降级，不恢复已退休的 rotation/cleanup 行为，也不把 seal 当作新的 provider-session guard。

### 6.4 Provider-session compatibility metadata

`.daemon-state.json` 不再写入 `lastServerUrl`/`LastServerURL`。读取旧文件时 JSON unknown field 会被忽略，后续 canonical transaction 会自然删除该字段；daemon URL、port、proxy、MCP endpoint/command/args/CWD/tool surface 等易变接线信息都不能使 provider session 变成 fresh。

Codex 旧版本的 `InstructionsFingerprint` 可能带有 `:mcp:<hash>` 后缀或旧的 dynamic-tool representation suffix。兼容比较先去掉这些已知 legacy suffix，再比较原始 semantic `baseInstructions` + `developerInstructions` fingerprint。缺少 fingerprint 或真实 instructions 变化仍必须 fresh；MCP wiring 变化不属于 instructions 变化。

## 7. 保存与迁移

已知非空 ownership 先转换为当前内存模型，ownership writer 只写 format 2；从未持有 ownership 的 version 0 空状态可以继续省略 version。保存继续使用现有的临时文件、file sync、atomic rename 和 parent-directory `fsync`。

不需要主动扫描所有 agent，也不需要单独 migration transaction：

- 只读启动可以每次幂等读取历史格式；
- agent 启动前原本就存在的 state transaction 会把需要运行的状态写成 format 2；
- ownership mutation 会在同一个既有 transaction 中完成 canonical write；
- conversion 或保存失败时，不得 spawn runtime 或接受新 handoff。

未知格式不能进入保存路径。错误应包含 observed version 和 supported read versions，但不得包含消息正文、token 或 provider session ID。

## 8. 未来格式规则

### 8.1 何时不升级 format

以下变化不应升级 ownership format：

- 内存行为、retry、日志或调度变化；
- 旧 daemon 忽略后只会少功能的 optional 字段；
- 不影响 replay、reconciliation、terminal monotonicity 或 ACK responsibility 的附加信息。

Optional 字段被旧 writer 丢弃必须是产品明确接受的 downgrade 行为，不能隐藏 input ownership obligation。

### 8.2 何时必须升级 format

只有新旧 reader 对同一核心 record 的解释差异可能造成重复输入、丢失 owner 或非法 terminal transition 时，才使用新的 format ID。引入新 format 时必须：

1. 保留所有已发布 format 的直接读取分支；
2. 为新 format 增加明确到当前内存模型的转换；
3. 不删除或复用 retired ID；
4. 确保自动回滚目标能读取当前 writer 输出，否则该目标不得作为自动回滚版本。

功能回滚可以删除行为和 writer，但不能删除已经可能落盘的 reader。

## 9. 测试要求

Focused tests 至少覆盖：

- v0 legacy records 仍按现有规则迁移；
- v2 queued、inflight 和 terminal 直接读取；
- v3 core records 逐字段保持 ownership 语义；
- v3 pending/resolved markers 被丢弃，且不触发 input replay；
- v3 provider-session seal 被忽略，不阻止 runtime authority 恢复对应 thread；
- malformed v3 core 被拒绝；
- unknown version 阻止 agent admission，原文件保持不变；
- 含 legacy records 的 v0 以及 v3 的下一次保存写 format 2；
- format 2 的正常 delivery hot path 不增加额外解析或分配。

测试可以使用少量内联 JSON fixture；不要求建设 format corpus、release matrix 生成器或新 guardrail framework。

## 10. 明确取舍

该方案接受一个有界降级：从 v3 回到当前行为后，daemon restart 期间尚未完成的 visible reply 可能不再补发。它不接受 input 重放或 ACK owner 丢失；旧 provider-session seal 不再具有恢复阻断语义。

这一区分使旧版本能够“少功能但仍可启动”，同时把强制拒绝限制在真正未知且可能破坏 delivery correctness 的格式上。
