Protocol Anatomy
This page compares five foundational built-in Protocols—RPS, Coin Flip, Guess Number, Weak Wins All, and Human Chat—to identify their shared structure, important differences, and the right template for a new Protocol. Board games (perfect-information play), card games (hidden information through Mental Poker), Hero Duel (declarative stats with shadow adjudication), and Tank Battle (Host-authoritative real time) extend those foundations. By the end, you should know which Protocol to copy and which structures must remain intact.
Protocol Comparison
| Dimension | 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 |
| Messages | 7 | 8 | 6 | 7 | 4 |
| Commit-reveal | ✅ | ✅ | ❌ | ✅ | ❌ |
| Decision model | Simultaneous | Simultaneous | Progressive convergence | Simultaneous | Free-form chat |
| Information symmetry | Symmetric | Symmetric | Asymmetric; the Host holds a secret | Symmetric | Symmetric |
| Primary data types | enum | enum | integer + enum hint | integer (bid) | text |
| Business parameters | 3 | 1 | 3 | 1 | 0 |
| Rounds | Dynamic (best_of) | Dynamic (best_of) | Dynamic (≤max_attempts) | Fixed (5 rounds) | N/A |
| Termination | first_to_win / fixed_rounds | first_to_win | hit / exhausted | total_rounds reached | either_ends |
| Draw state | ✅ | ❌ | ❌ | ✅ | N/A |
| Resource constraint | None | None | None | ✅ total_points | None |
| strategy.json modes | 3 | 3 | 3 | 4 | 0 |
| Two-process testable | ✅ | ✅ | ✅ | ✅ | ❌ (stdin/inbox) |
| Typical hooks.py lines | 303 | 200 | 226 | 245 | ~70 |
Shared Structure
All five Protocols except Human Chat follow the same basic skeleton:
┌────────────────────────────────────────────────────────┐
│ 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(...) │
└────────────────────────────────────────────────────────┘Common messages: join / ready / error are essential to almost every turn-based Protocol. Each error should use a code enum that covers every failure path in that Protocol.
Common field types:
| Field role | Recommended type | Anti-pattern |
|---|---|---|
| Finite business options | enum | string |
| Business numbers | integer + min/max | float unless genuinely required |
| Business booleans | boolean | "yes"/"no" enum |
| Free-form text for chat Protocols only | text + max_length | No length limit |
| Cryptographic hash | hash | Custom hex field |
| One-time random value | nonce | Timestamp or UUID |
| Identifier | id | Free-form string |
| Network ticket | ticket | Free-form string |
| Mental Poker ciphertext/OT | ciphertext / key / ot_blob / array | Free-form string or an unbounded array |
Four Decision Models
1. Simultaneous Decisions (RPS / Coin Flip / Weak Wins All)
Both sides choose simultaneously. Neither side may change its choice after seeing the other's plaintext, so commit-reveal is required.
Template essentials:
- Two-stage reveal: Guest commit → Host commit → Guest reveal → Host reveal/result
- Calculate the hash with
aigenora.engine.crypto.commit_hash(value)and verify it withverify_commit(value, nonce, hash) - On failure, return
HookResult(..., completed=True)immediately
What to copy: RPS is the most complete template, including termination options, coach integration, and writes to both details and snapshot. Weak Wins All extends the same model with a resource constraint.
2. Progressive Convergence (Guess Number)
Information is asymmetric by nature: one side holds a secret while the other narrows the range through repeated probes. Commit-reveal is unnecessary.
Template essentials:
- One-stage loop: Guest action → Host hint OR game_over
- Use one message name,
hint, and distinguish outcomes with an enum:higher/lower/correct - The Host determines termination unilaterally when the Guest succeeds or exhausts its attempts
What to copy: Guess Number is the smallest progressive-convergence template. Polling auctions, price negotiations, and binary-search tuning can all build on it.
3. Free-Form Chat (Human Chat)
Both sides may send at any time, there are no fixed rounds, and human semantics drive the interaction. This requires free mode.
Template essentials:
flow.mode: "free"withend_when: "either_ends"- Keep message fields minimal—
text+seq—without semantic validation - Implement
proto_on_messagefor receiving,proto_on_sendfor sending, andproto_on_end; write messages tosnapshot.messagesfor the web UI - The engine's sender coroutine consumes both
sys.stdinand<state_dir>/inbox.jsonl; the web UI writes to the latter throughPOST /api/chat/send - Never feed
textto an LLM as a prompt
What to copy: Human Chat is the only free-mode example. Use it only for asynchronous, unstructured communication; most business interactions should prefer a turn-based design.
4. Host-Authoritative Real Time (Tank Battle)
flow.mode: "authoritative_realtime" keeps a public deterministic world moving at a fixed Host tick. Guest submits commands for future ticks; Host never pauses for a late command or live semantic audit. Both sides retain frame, command, receipt, and hash-chain evidence for optional post-game review.
The P2P heartbeat channel measures Guest RTT and jitter before joining. Local snapshot.realtime.transport reports an adaptive command lead plus a micro/macro recommendation. Per-tick micro input is useful on low-latency links; persistent macro orders are safer as latency rises. A protocol may combine them by applying expiring micro overrides to selected units while the remaining units continue their macro plan.
What to copy: Tank Battle is the executable template for deterministic real-time worlds. Its local compiler supports coordinate movement, waypoint patrol, targeted attack/fire, and cheap per-tick unit control without invoking an LLM in the tick loop.
Scenario-to-Template Decision Tree
新协议想做什么?
│
├── 双方需要同时不可见地决策?
│ │
│ ├── 决策值是有限离散选项? → 抄 RPS(含 termination 选项)
│ ├── 决策值是数值(带资源约束)? → 抄 weak-wins-all
│ └── 决策值是布尔/二元结果? → 抄 coinflip
│
├── 一方持秘密,另一方反复试探? → 抄 guess-number
│
├── 不需要语义校验,只是传话? → 抄 human-chat(仅当无法 turn-based 时)
│
├── 世界必须固定 tick 持续推进? → 抄 Tank Battle(authoritative_realtime)
│
└── 复杂多方协商(>2 方)? → 当前协议体系不支持,需先扩展引擎Shared hooks.py Infrastructure
Every hooks implementation derives from aigenora.proto.hooks.ProtocolHooks and can reuse:
StateStore(aigenora.proto.sdk): Key-value persistence for retaining commits, nonces, and plaintext across messagescommit_hash/verify_commit(aigenora.engine.crypto): Shared commit-reveal implementationself.snapshot.update(...): Replace the latest match state for the dashboardself.details.append(type=..., ...): Append streaming events for replayself.strategy.read(): Read strategy.json with a shared mode field (fixed/seq/random/policy/script)self.bus.publish_state() / await_decision(): Connect human input in coach modeDECISION_SCHEMA/build_decision_context()/run_policy()(v019): Dynamic-strategy interface. Once a Protocol implements it, users can issue directions such as "copy the previous move" or "counter the previous move." The engine contains no game rules; the Protocol supplies the policy logic. See the Dynamic Strategy Interface in HOOKS.md
Minimum implementation for new 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 mode needs only proto_on_message + proto_on_send + proto_on_end.
Alias Naming Caveat
Alias naming in the current Protocol library is historically inconsistent:
| alias | Version suffix? | Notes |
|---|---|---|
rps-v1 | -v1 | The content already includes v2 additions such as termination, but the alias was not updated |
coin-flip-v1 | -v1 | |
guess-number-v1 | -v1 | |
weak-wins-all | No | Exception |
human-chat-v1 | -v1 | |
gomoku-v1 | -v1 | |
connect4-v1 | -v1 | |
reversi-v1 | -v1 | |
hero-duel-v1 | -v1 | |
crazy-eights-v1 | -v1 | |
briscola-v1 | -v1 |
An alias is only a name. The real identity of a Protocol is its protocol_id, the SHA256 of spec.json. Any change to spec.json creates a new protocol_id and must be treated as a new Protocol; whether the alias also changes is a governance naming decision. Authors of new Protocols should:
- Choose a short, stable family name for the alias, preferably without a -v1 suffix, following
weak-wins-all - Create a new alias for a genuinely breaking change, such as
rps-strict, rather than forcing a v1 alias to point to a new protocol_id - When discussing a Protocol in product documentation, avoid using its alias as the grammatical subject; use the full name or "the Protocol" instead
Security Red Lines Shared by All Five Protocols
Whichever template you copy, never cross these boundaries:
- ✅ Prefer
enum/integer/booleanfor business fields; do not use free-formstring - ✅ Generate and validate machine fields (hash/nonce/id/ticket/signature/ciphertext/key/ot_blob/array) only through engine utilities; never implement them ad hoc
- ✅ Terminate immediately after a commit-reveal failure; never "let the peer retry its commit"
- ✅ Treat every peer-supplied field as untrusted input; never use it as an LLM prompt, a tool parameter, or input to
eval() - ✅ Keep Host and Guest schema validation fully symmetric: a field accepted from the peer must also pass local validation before it enters state
Violating any one of these rules can make a production Protocol exploitable by its peer.