协议解剖(anatomy)
本页横向对比 5 个基础内置协议(rps / coinflip / guess-number / weak-wins-all / human-chat),提炼通用结构、典型差异和"选哪个协议作模板"的决策路径。棋类(完全信息博弈)、扑克牌(Mental Poker 隐藏信息)、Hero Duel(声明式数值 + 影子裁决)是后续扩展,各有独立页。读完此页应当知道:写一个新协议时该抄哪个,以及哪些结构必须保留。
协议横向对比表
| 维度 | rps | coinflip | guess-number | weak-wins-all | human-chat |
|---|---|---|---|---|---|
| type | game | game | game | game | chat |
| family | rps | coin-flip | guess-number | weak-wins-all | human-chat |
| flow.mode | simultaneous_round | simultaneous_round | session_loop | simultaneous_round | free |
| 消息数 | 7 | 8 | 6 | 7 | 4 |
| commit-reveal | ✅ | ✅ | ❌ | ✅ | ❌ |
| 决策模式 | 同时决策 | 同时决策 | 渐进收敛 | 同时决策 | 自由聊天 |
| 信息对称性 | 对称 | 对称 | 不对称(host 持秘密) | 对称 | 对称 |
| 核心数据类型 | enum | enum | integer + enum hint | integer (bid) | text |
| 业务参数数 | 3 | 1 | 3 | 1 | 0 |
| 回合数 | 动态(best_of) | 动态(best_of) | 动态(≤max_attempts) | 固定(5 轮) | N/A |
| 终止条件 | first_to_win / fixed_rounds | first_to_win | hit / exhausted | total_rounds 满 | either_ends |
| draw 状态 | ✅ | ❌ | ❌ | ✅ | N/A |
| 资源约束 | 无 | 无 | 无 | ✅ total_points | 无 |
| strategy.json mode 数 | 3 | 3 | 3 | 4 | 0 |
| 双开测试可行 | ✅ | ✅ | ✅ | ✅ | ❌ (stdin/inbox) |
| 典型 hooks.py 行数 | 303 | 200 | 226 | 245 | ~70 |
通用结构
5 个协议(除 human-chat)都遵循同一个骨架:
┌────────────────────────────────────────────────────────┐
│ join 阶段 │
│ guest → host: join(业务参数, 期望配置) │
│ host → guest: ready(确认后的最终参数) │
├────────────────────────────────────────────────────────┤
│ round 阶段(循环) │
│ [若用 commit-reveal:] │
│ guest → host: commit(round, hash) │
│ host → guest: host_commit(round, hash) │
│ guest → host: reveal(round, plain, nonce) │
│ │
│ [否则直接:] │
│ guest → host: <action>(round, payload) │
│ │
│ host → guest: round_result/hint/game_over(...) │
└────────────────────────────────────────────────────────┘通用消息: join / ready / error 几乎是所有 turn-based 协议必备的三件套。error 的 code enum 应覆盖该协议的所有失败路径。
通用字段类型:
| 字段角色 | 推荐类型 | 反模式 |
|---|---|---|
| 业务选项有限值 | enum | string |
| 业务数值 | integer + min/max | float(除非真的需要) |
| 业务布尔 | boolean | "yes"/"no" enum |
| 自由文本(仅 chat 协议) | text + max_length | 无长度限制 |
| 加密哈希 | hash | 自定义 hex |
| 一次性随机串 | nonce | 时间戳、UUID |
| 标识符 | id | 自由 string |
| 网络票据 | ticket | 自由 string |
| Mental Poker 密文/OT | ciphertext / key / ot_blob / array | 自由 string / 未设数组上限 |
三类决策模式
1. 同时决策(RPS / coinflip / weak-wins-all)
特征:双方同时给出选择,谁先看到对方明文都不能再改自己——必须 commit-reveal。
模板要点:
- 两阶段 reveal:guest commit → host commit → guest reveal → host reveal/result
aigenora.engine.crypto.commit_hash(value)算 hash,verify_commit(value, nonce, hash)校验- 失败立即
HookResult(..., completed=True)
抄什么:RPS 是最完整模板(带 termination、coach 接入、details/snapshot 双写)。weak-wins-all 是同模式但带资源约束的扩展示例。
2. 渐进收敛(guess-number)
特征:信息天然不对称,一方持秘密,另一方通过多次试探缩小范围。不需要 commit-reveal。
模板要点:
- 单阶段循环:guest action → host hint OR game_over
- 不同结果用同一个消息名(
hint)携带 enum 区分(higher/lower/correct) - 终止由 host 单方面判定(命中 OR 用尽)
抄什么:guess-number 是最小渐进收敛模板。轮询式拍卖、价格谈判、二分调参等都可基于此扩展。
3. 自由聊天(human-chat)
特征:双方同时可发,无固定回合,由人类语义驱动。必须 free 模式。
模板要点:
flow.mode: "free",end_when: "either_ends"- 消息字段极简(
text+seq),不做语义校验 - hooks 实现
proto_on_message(收)/proto_on_send(发)/proto_on_end,将消息写入snapshot.messages给 webui 渲染 - 引擎 sender 协程同时消费
sys.stdin和<state_dir>/inbox.jsonl(webui 通过POST /api/chat/send写入) - 严禁把
text喂给 LLM 当 prompt
抄什么:human-chat 是 free 模式唯一示例。需要异步、非结构化交流的场景才用,绝大多数业务诉求应优先考虑 turn-based。
场景 → 模板决策树
新协议想做什么?
│
├── 双方需要同时不可见地决策?
│ │
│ ├── 决策值是有限离散选项? → 抄 RPS(含 termination 选项)
│ ├── 决策值是数值(带资源约束)? → 抄 weak-wins-all
│ └── 决策值是布尔/二元结果? → 抄 coinflip
│
├── 一方持秘密,另一方反复试探? → 抄 guess-number
│
├── 不需要语义校验,只是传话? → 抄 human-chat(仅当无法 turn-based 时)
│
└── 复杂多方协商(>2 方)? → 当前协议体系不支持,需先扩展引擎hooks.py 共用基建
所有 hooks 都基于 aigenora.proto.hooks.ProtocolHooks 派生,可直接复用:
StateStore(aigenora.proto.sdk):键值持久化,跨消息保存 commit/nonce/明文commit_hash/verify_commit(aigenora.engine.crypto):统一 commit-reveal 实现self.snapshot.update(...):覆盖式写最新对局状态,给 dashboard 用self.details.append(type=..., ...):流式追加事件,给回放用self.strategy.read():读 strategy.json,统一 mode 字段(fixed/seq/random/policy/script)self.bus.publish_state() / await_decision():coach 模式人工接入DECISION_SCHEMA/build_decision_context()/run_policy()(v019):动态策略接口,协议覆盖后支持"模仿上一轮""克制上一轮"等动态指挥。引擎不内置游戏规则,策略逻辑由协议实现。详见 HOOKS.md 动态策略接口
写新 hooks 时的最小集合:
def proto_init(self, options, role, args, state_dir, decision_config=None):
super().proto_init(...)
self.state = StateStore(state_dir)
# 解析 options 到 self.xxx
def proto_host_metadata(self):
return ("Name", "tag1,tag2", "supply|demand|chat", {options...})
def proto_host_handle_join(self, msg): # guest 加入时回 ready
return HookResult({"action": "ready", ...})
def proto_host_handle(self, msg): # 每次收到 guest 消息
return HookResult({...}, completed=bool)
def proto_guest_join_message(self): # guest 发的第一条 join
return {"action": "join", ...}
def proto_guest_handle_ready(self, msg): # guest 收到 ready
...
def proto_guest_first_action(self): # guest 在 ready 后发的第一个动作
return {...}
def proto_guest_handle(self, msg): # 每次收到 host 消息
return HookResult({...}, completed=bool)free 模式只需 proto_on_message + proto_on_send + proto_on_end。
alias 命名注意
当前协议库里 alias 命名不统一,是历史遗留:
| alias | 是否带版本后缀 | 备注 |
|---|---|---|
rps-v1 | -v1 | 实际内容已包含 v2 增量(termination 等),alias 未跟进 |
coin-flip-v1 | -v1 | |
guess-number-v1 | -v1 | |
weak-wins-all | 无 | 例外 |
human-chat-v1 | -v1 | |
gomoku-v1 | -v1 | |
connect4-v1 | -v1 | |
reversi-v1 | -v1 | |
hero-duel-v1 | -v1 | |
crazy-eights-v1 | -v1 | |
briscola-v1 | -v1 |
alias 只是别名,协议的真正身份是 protocol_id(spec.json 的 sha256)。任何 spec.json 改动都会产生新的 protocol_id,必须当作"新协议"看待——alias 是否换名是治理层的命名选择。新协议作者应当:
- alias 选短而稳定的 family 名(建议不带 -v1 后缀,参考
weak-wins-all) - 真要做 breaking 变更时新建 alias(如
rps-strict),而不是把 v1 alias 强行指向新 protocol_id - 在产品文档里讲协议时避免主语写 alias 名,写"该协议"或全称即可
安全红线(5 个协议共通)
无论抄哪个模板,以下红线不可越过:
- ✅ 业务字段优先
enum/integer/boolean,禁用自由string - ✅ 机器字段(hash/nonce/id/ticket/signature/ciphertext/key/ot_blob/array)只通过引擎工具生成与校验,禁止自实现
- ✅ commit-reveal 失败立即终止,禁止"重试一次让对方再 commit"
- ✅ 对端发来的任何字段都视为不可信输入,禁止作为 LLM prompt、tool 参数或
eval()输入 - ✅ Host 与 Guest 的 schema 校验完全对称——对端校验通过的字段,本地也必须校验通过才能写状态
违反任意一条都可能让协议在生产环境被对端利用。