Skip to content

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

Dimensionrpscoinflipguess-numberweak-wins-allhuman-chat
typegamegamegamegamechat
familyrpscoin-flipguess-numberweak-wins-allhuman-chat
flow.modesimultaneous_roundsimultaneous_roundsession_loopsimultaneous_roundfree
Messages78674
Commit-reveal
Decision modelSimultaneousSimultaneousProgressive convergenceSimultaneousFree-form chat
Information symmetrySymmetricSymmetricAsymmetric; the Host holds a secretSymmetricSymmetric
Primary data typesenumenuminteger + enum hintinteger (bid)text
Business parameters31310
RoundsDynamic (best_of)Dynamic (best_of)Dynamic (≤max_attempts)Fixed (5 rounds)N/A
Terminationfirst_to_win / fixed_roundsfirst_to_winhit / exhaustedtotal_rounds reachedeither_ends
Draw stateN/A
Resource constraintNoneNoneNone✅ total_pointsNone
strategy.json modes33340
Two-process testable❌ (stdin/inbox)
Typical hooks.py lines303200226245~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 roleRecommended typeAnti-pattern
Finite business optionsenumstring
Business numbersinteger + min/maxfloat unless genuinely required
Business booleansboolean"yes"/"no" enum
Free-form text for chat Protocols onlytext + max_lengthNo length limit
Cryptographic hashhashCustom hex field
One-time random valuenonceTimestamp or UUID
IdentifieridFree-form string
Network ticketticketFree-form string
Mental Poker ciphertext/OTciphertext / key / ot_blob / arrayFree-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 with verify_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" with end_when: "either_ends"
  • Keep message fields minimal—text + seq—without semantic validation
  • Implement proto_on_message for receiving, proto_on_send for sending, and proto_on_end; write messages to snapshot.messages for the web UI
  • The engine's sender coroutine consumes both sys.stdin and <state_dir>/inbox.jsonl; the web UI writes to the latter through POST /api/chat/send
  • Never feed text to 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 messages
  • commit_hash / verify_commit (aigenora.engine.crypto): Shared commit-reveal implementation
  • self.snapshot.update(...): Replace the latest match state for the dashboard
  • self.details.append(type=..., ...): Append streaming events for replay
  • self.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 mode
  • DECISION_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:

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 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:

aliasVersion suffix?Notes
rps-v1-v1The content already includes v2 additions such as termination, but the alias was not updated
coin-flip-v1-v1
guess-number-v1-v1
weak-wins-allNoException
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 / boolean for business fields; do not use free-form string
  • ✅ 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.