Skip to content

协议解剖(anatomy)

本页横向对比 5 个基础内置协议(rps / coinflip / guess-number / weak-wins-all / human-chat),提炼通用结构、典型差异和"选哪个协议作模板"的决策路径。棋类(完全信息博弈)、扑克牌(Mental Poker 隐藏信息)、Hero Duel(声明式数值 + 影子裁决)是后续扩展,各有独立页。读完此页应当知道:写一个新协议时该抄哪个,以及哪些结构必须保留。

协议横向对比表

维度rpscoinflipguess-numberweak-wins-allhuman-chat
typegamegamegamegamechat
familyrpscoin-flipguess-numberweak-wins-allhuman-chat
flow.modesimultaneous_roundsimultaneous_roundsession_loopsimultaneous_roundfree
消息数78674
commit-reveal
决策模式同时决策同时决策渐进收敛同时决策自由聊天
信息对称性对称对称不对称(host 持秘密)对称对称
核心数据类型enumenuminteger + enum hintinteger (bid)text
业务参数数31310
回合数动态(best_of)动态(best_of)动态(≤max_attempts)固定(5 轮)N/A
终止条件first_to_win / fixed_roundsfirst_to_winhit / exhaustedtotal_rounds 满either_ends
draw 状态N/A
资源约束✅ total_points
strategy.json mode 数33340
双开测试可行❌ (stdin/inbox)
典型 hooks.py 行数303200226245~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 协议必备的三件套。errorcode enum 应覆盖该协议的所有失败路径。

通用字段类型:

字段角色推荐类型反模式
业务选项有限值enumstring
业务数值integer + min/maxfloat(除非真的需要)
业务布尔boolean"yes"/"no" enum
自由文本(仅 chat 协议)text + max_length无长度限制
加密哈希hash自定义 hex
一次性随机串nonce时间戳、UUID
标识符id自由 string
网络票据ticket自由 string
Mental Poker 密文/OTciphertext / 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 派生,可直接复用:

  • StateStoreaigenora.proto.sdk):键值持久化,跨消息保存 commit/nonce/明文
  • commit_hash / verify_commitaigenora.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 时的最小集合:

python
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 校验完全对称——对端校验通过的字段,本地也必须校验通过才能写状态

违反任意一条都可能让协议在生产环境被对端利用。