> For the complete documentation index, see [llms.txt](https://yeasy.gitbook.io/openclaw_guide/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://yeasy.gitbook.io/openclaw_guide/di-san-bu-fen-shi-xian-yuan-li-yu-gong-cheng-luo-di/09_gateway_protocol/9.4_event_idempotency.md).

# 9.4 事件幂等与一致性保障

在分布式系统中，网络是不可靠的：消息可能丢失、延迟、重复。OpenClaw 当前公开协议把幂等重点放在有副作用方法的 `idempotencyKey`、事件中的 `seq` / `stateVersion` 以及客户端重取快照上，而不是承诺整个数据平面与控制平面都具备统一的 at-least-once 交付语义。本节深入事件驱动系统中的一致性问题与解决方案。

## 9.4.1 为什么重复、丢失和未知状态都要处理

网络请求有三种可能的结果：

| 结果          | 含义               | 设备侧感受                                 |
| ----------- | ---------------- | ------------------------------------- |
| **Success** | 服务器收到且处理成功       | 收到 `type:"res"`、`ok:true` 与 `payload` |
| **Failure** | 服务器收到但处理失败       | 收到 `type:"res"`、`ok:false` 与 `error`  |
| **Unknown** | 请求可能丢失，或成功了但响应丢失 | 超时，不知道发生了什么                           |

当遇到“Unknown”时，调用方很可能会选择**重试**：

```
发送 event_A (version=1)
  ↓
超时，无响应
  ↓
重试 event_A (version=1)
  ↓
收到 SUCC
```

问题来了：如果第一次请求其实成功了，第二次重试会导致 `event_A` 被再次提交。从服务器的视角，它看到了同一个副作用意图，需要识别出这是重复，而不是两个不同的操作；如果中间事件丢失，客户端还需要通过 `seq` gap 触发快照刷新。

## 9.4.2 幂等性的三个层次

1. **天然幂等**：PUT/GET/DELETE 类操作，多次执行结果不变
2. **条件幂等**：附带 `idempotencyKey`，服务器识别重复请求；重复请求可能返回已完成的缓存结果，也可能返回正在处理的 acknowledgement / 状态提示，而不是再次执行副作用
3. **非幂等**：需分布式锁或单步提交机制确保只执行一次

## 9.4.3 幂等密钥

当前更准确的说法是：**OpenClaw 协议 schema 中部分会产生副作用的 RPC 明确要求 `idempotencyKey`；另一些方法把该字段设为可选或由服务端生成，具体以当前 schema 为准**。字段名采用 camelCase；至于服务端内部如何落盘、是否跨进程共享去重记录，属于实现细节，不应直接写成公开协议承诺。下面的 JSON 是**概念伪代码**，不是当前 Gateway 的真实 WS 帧：

```json
{
  "type": "conceptual-side-effect-call",
  "sessionKey": "agent:main:main",
  "toolName": "send_email",
  "result": { "email_id": "msg_456" },
  "idempotencyKey": "ack_device001_req001"
}
```

工程上，一个常见实现模式是维护幂等记录：

```javascript
idempotency_table[idempotency_key] = {
  "status": "success",
  "result": { ... },
  "timestamp": 1711100000,
  "expires_at": 1711200000
}
```

处理流程：

```python
received_request = parse(message)
key = received_request.idempotencyKey

if key in idempotency_table:
    # 重复请求
    cached = idempotency_table[key]
    if cached.status == "success":
        return cached.result  # 返回缓存结果，不再执行
    elif cached.status == "processing":
        return ack("still_processing")  # 仍在处理，让调用方等待或刷新
    elif cached.status == "failure":
        return error("previous_failure")  # 上次失败，返回同样的错误
else:
    # 新请求，执行并记录
    idempotency_table[key] = { "status": "processing" }
    try:
        result = execute(received_request)
        idempotency_table[key] = { "status": "success", "result": result }
        return result
    except Exception as e:
        idempotency_table[key] = { "status": "failure", "error": e.msg }
        return error(e.msg)
```

幂等密钥的 TTL 管理属于实现层：

* **保留期**：是否保留 24 小时、7 天或更短窗口，应由具体实现权衡请求重试窗口与存储成本
* **清理策略**：可以由后台定时任务删除超过 TTL 的条目，也可以使用带过期机制的存储；但这些都不应写成当前公开协议里的固定配置键

## 9.4.4 事件顺序保障

在消息可能乱序的网络中，必须明确定义“事件顺序”的含义。

### 顺序保障的三个级别

| 级别         | 定义                | 成本 | 适用场景          |
| ---------- | ----------------- | -- | ------------- |
| **无序**     | 事件可以任意顺序执行        | 最低 | 多个独立工具调用      |
| **会话级序列化** | 同一会话内的事件必须按顺序执行   | 中等 | 大多数任务（对话、工具链） |
| **全局顺序**   | 所有用户的所有事件按全局时间戳排序 | 最高 | 财务交易、审计日志     |

对 OpenClaw 当前实现，更稳妥的表述是：

* **副作用去重**：协议要求调用方为指定方法提供 `idempotencyKey`，以便服务端识别重试与重复提交。
* **事件序列**：广播事件里的 `seq` 更接近“客户端观测序列”，用于发现丢事件、重复消费或刷新时机；它不是公开承诺的“服务端按会话维护乱序缓冲并自动重组”的协议。
* **出现缺口时**：客户端应把 `seq` gap 当成“本地状态可能不可信”的信号，重新获取快照或刷新状态，而不是假设 Gateway 一定会 replay 中间事件。

**关键点**：`idempotencyKey` 负责**去重与重试安全**，`seq` 更偏向**观测与刷新触发**；两者的职责不要混写。

## 9.4.5 一致性保障的多层设计

OpenClaw 通过以下四层降低分布式系统中的不一致风险：

* **层 1：协议级幂等**——幂等密钥识别重复请求，`seq` 仅作为客户端观测游标帮助发现缺口
* **层 2：会话存储与锁**——会话索引和 transcript 以本地文件状态为主，配合会话锁降低并发写冲突；不要把它写成数据库事务回滚保证
* **层 3：应用级约束**——工具执行前检查结果是否已存在，配合会话锁防止并发冲突
* **层 4：审计与恢复**——结合方法级去重状态、session transcript、任务状态与日志，便于事后验证和恢复；不要假设存在覆盖所有去重与执行事件的统一完整操作日志

## 9.4.6 本节小结

1. **幂等密钥**：识别重复请求，返回缓存结果或 in-flight acknowledgement，而不是重复执行
2. **序列号**：更适合作为 socket 级观测游标，用于发现 gap 与触发刷新；`sessions.send` 这类接口在调用方省略 key 时可由服务端生成 UUID，但跨重试幂等仍应由调用方显式传入稳定 key
3. **缺口处理**：发现 gap 时优先刷新快照 / 重取状态，而不是假设 Gateway 会自动重排或补齐
4. **事务与审计**：存储与应用层的约束提升最终一致性，但不等同于跨组件事务保证
