Skip to content

spec.json 规范

spec.json 是协议的共享契约。它定义了双方 Agent 交换的结构化消息格式、交互流程和可配置参数。Host 和 Guest 各自运行本地 hooks.py,但都遵循同一份 spec.json 的约束。

最小结构

json
{
  "name": "Guess Number",
  "spec_version": "1.0",
  "description": "Host picks a secret number, Guest guesses",
  "type": "game",
  "messages": [],
  "flow": {"phases": []},
  "rules": {},
  "parameters": {}
}

namemessages 是注册时必须具备的关键字段。真实协议还应写清 flowrules 和结束条件。

完整结构

一个完整的 spec.json 包含以下部分:

json
{
  "name": "协议名称",
  "spec_version": "1.0",
  "description": "协议描述",
  "type": "game | service | chat",

  "messages": [
    {"name": "join", "direction": "guest_to_host", "fields": {...}},
    {"name": "ready", "direction": "host_to_guest", "fields": {...}},
    {"name": "move", "direction": "guest_to_host", "fields": {...}},
    {"name": "result", "direction": "host_to_guest", "fields": {...}}
  ],

  "flow": {
    "mode": "session_loop",
    "phases": [
      {"phase": "join", "exchange": [...]},
      {"phase": "round", "repeat": "...", "exchange": [...]}
    ]
  },

  "rules": {
    "game_over": "描述结束条件",
    "scoring": "描述计分规则"
  },

  "choices": ["rock", "paper", "scissors"],
  "commit_reveal": {
    "algorithm": "SHA256",
    "format": "hash = SHA256(choice:nonce) as lowercase hex",
    "nonce_length": 16,
    "nonce_charset": "hex (0-9a-f)"
  },

  "parameters": {
    "best_of": {"type": "integer", "min": 1, "max": 99}
  },

  "decision": {
    "mode": "auto",
    "timeout_seconds": 120,
    "timeout_action": "fallback"
  }
}

协议 ID

bash
aigenora protocol hash <spec.json>

protocol_id 是协议契约子集的 SHA256,不是整个展示文档的 hash。

参与 hash 计算:messages、flow、rules、choices、commit_reveal、parameters 不参与 hash 计算:name、description、type、decision、pricing、spec_version、shadow_judge

修改规则会产生新 protocol_id;修改标题和描述不会。

标准版本

当前支持:

json
{"spec_version": "1.0"}

规则:

  • 缺失 spec_version 的旧 spec 默认视为 "1.0"
  • 未知版本(如 "2.0")在 register/fetch/test/host/join/guest/validate 阶段被拒绝
  • protocol hash 对未知版本只输出 warning,仍可计算 hash

Messages

每条消息定义一个方向上的 JSON 结构:

json
{
  "name": "guess",
  "direction": "guest_to_host",
  "fields": {
    "action": {"type": "enum", "values": ["guess"], "required": true},
    "attempt": {"type": "integer", "min": 1, "required": true},
    "number": {"type": "integer", "required": true}
  }
}

要求:

  • name 在协议内唯一
  • directionhost_to_guestguest_to_hostboth
  • fields 是非空 object
  • 推荐每条消息都包含 action enum,值与消息名称一致(如 {"action": "guess"}

消息设计最佳实践

  • 每条消息至少有一个 action 字段标识消息类型
  • 使用 roundattempt 字段标记消息序号,便于审计
  • game_over 使用 boolean 字段标记结束条件,不要依赖消息类型隐含结束
  • 包含 error 消息处理异常情况(commit hash 不匹配、非法操作等)

Flow

Flow 定义消息交换的时序和循环。当前支持的 flow.modesession_loopsimultaneous_roundrequest_responsefreemental_poker;缺省等同于 session_loop

json
{
  "flow": {
    "mode": "session_loop",
    "phases": [
      {
        "phase": "join",
        "exchange": [
          {"message": "join", "direction": "guest_to_host"},
          {"message": "ready", "direction": "host_to_guest"}
        ]
      },
      {
        "phase": "play",
        "repeat": "until game_over",
        "exchange": [
          {"message": "guess", "direction": "guest_to_host"},
          {"message": "hint", "direction": "host_to_guest"}
        ]
      }
    ]
  }
}

会话循环模式(session_loop)

  • 协议引擎按 phase 顺序执行
  • repeat 可以是 "until game_over" 或引用参数(如 "best_of"
  • 每个 exchange 定义一轮消息交换

同时回合模式(simultaneous_round)

json
{
  "flow": {
    "mode": "simultaneous_round",
    "round": {
      "value_field": "choice",
      "value_type_ref": "reveal.fields.choice"
    },
    "phases": [...]
  }
}

适用于 RPS、Coin Flip、竞价等双方同时选择的协议。round.value_fieldround.value_type_ref 是必填合约。

请求响应模式(request_response)

json
{
  "flow": {"mode": "request_response"}
}

适用于请求、接收、交付、确认这类服务流程。可用 messages 和 hooks 定义请求与交付语义。

自由模式(free)

json
{
  "flow": {
    "mode": "free",
    "phases": [...],
    "end_when": "either_ends"
  }
}
  • 双方可以随时发送消息(如聊天场景)
  • end_when: "either_ends" 表示任一方发送 end 消息即结束
  • 引擎不强制消息顺序,由 hooks 自行处理

Mental Poker 模式(mental_poker)

json
{
  "flow": {"mode": "mental_poker"}
}

适用于隐藏手牌 + 公平发牌的 1v1 扑克牌游戏。该模式由引擎接管发牌控制面:双方分层 AEAD 加密牌堆,通过 OT 私密恢复摸到的牌,出牌用 nullifier 校验,对局结束做 opening/witness 审计和 transcript receipt。协议 hooks 只实现牌堆、初始发牌、出牌策略、出牌合法性和胜负判断。

该模式会使用 ciphertextkeyot_blobarray 等机器字段,详见 字段类型扑克牌游戏

Parameters 与 Options

Parameters 声明协议支持的可配置项,Host 通过 --options 传入具体值:

json
{
  "parameters": {
    "best_of": {"type": "integer", "min": 1, "max": 99},
    "termination": {"type": "enum", "values": ["first_to_win", "fixed_rounds"]},
    "round_delay_seconds": {"type": "integer", "min": 0, "max": 300}
  }
}

创建邀约时传入:

bash
aigenora host --protocol-dir <dir> --options "{\"best_of\":3}"

只有 spec.parameters 中声明的字段会被类型和范围校验。未声明的 options 字段只适合承载价格声明、展示提示等非合约元数据;hooks 不应基于未声明字段改变业务规则。

参数类型

parameters 支持的字段类型:

类型说明
integer整数,配 min/max
boolean布尔
enum枚举,配 values 白名单
table受约束嵌套数值表(v015):声明协议数值,随 options 分发

table 类型(v015 声明式 balance)——让协议数值(如游戏平衡表)作为 options.balance 声明式传递:双方一致持有,且不进 protocol_id(改数值不改协议号,保留可调平衡)。columns 用字段树声明结构白名单(叶节点 integer/boolean/enum + min/max/values,内部节点 object + fields);多余字段 / 缺字段 / 非法类型 / 越界值都会被拒。hooks 从 options.balance 读取数值裁决,不再硬编码。

json
{
  "parameters": {
    "balance": {
      "type": "table",
      "columns": {
        "hp": {"type": "integer", "min": 1, "max": 99999},
        "atk": {"type": "integer", "min": 0, "max": 9999},
        "skill": {"type": "object", "fields": {
          "dmg": {"type": "integer", "min": 0, "max": 99999},
          "cd": {"type": "integer", "min": 0, "max": 99}
        }}
      }
    }
  }
}

完整样例见内置 Hero Duel 协议(英雄 HP/蓝量/普攻 + 3 技能的 MOBA 数值表)。

影子裁决(shadow_judge,v015 M2)

顶层可选布尔字段 "shadow_judge": true 声明该协议启用 Guest 影子裁决:Guest 收到 Host 的 round_result 后不直接信任,而是用本地同一份 options.balance + 同一份裁决规则重算该回合,逐字段 diff Host 的结果,把"信任 Host 裁决"降级为"可验证 Host 裁决"。

  • opt-in:spec 声明 shadow_judge: true hooks 实现无副作用的 proto_round_judge_pure 才启用;旧协议 / 单边升级 → Guest 不验证(退化但不阻断)。现有协议零改动。
  • diff 范围:只比对裁决产出字段(*_hp/*_mana/*_damage_dealt/*_cd_*/round_winner/game_over/game_winner),不含双方动作(commit-reveal 已防改)和机器字段。
  • 不一致 → 中止:发 balance_mismatch_detected 事件 + 中止会话(snapshot aborted),与 commit_mismatch_detected 同等处理,让 Host 作弊可证伪。
  • shadow_judge 不进 protocol_id(行为开关,非消息契约)——开关它不改变协议身份,新旧客户端面对同一协议仍可互操作。

协议收敛原则

创建新协议前,先检查现有协议是否可以通过参数配置满足需求。

text
只需改数值(局数、范围、次数)  →  加 parameters,不改协议
需要不同的消息结构             →  才创建新协议

一个 RPS 协议通过 parameters 覆盖所有变体:三局两胜、五局三胜、固定五局等。社区里只有一个 RPS 协议,而不是一堆变体。

choices

声明协议中玩家可以选择的值:

json
{
  "choices": ["rock", "paper", "scissors"]
}

与 commit-reveal 配合使用,声明合法的承诺内容。choices 参与 protocol_id 计算。

commit_reveal

声明 SHA256 commit-reveal 防作弊机制:

json
{
  "commit_reveal": {
    "algorithm": "SHA256",
    "format": "hash = SHA256(choice:nonce) as lowercase hex",
    "nonce_length": 16,
    "nonce_charset": "hex (0-9a-f)",
    "verification": "On reveal, recompute SHA256(choice:nonce) and compare."
  }
}

工作流程:

text
1. 选择方: 生成随机 nonce,计算 hash = SHA256(choice + ":" + nonce)
2. 选择方: 发送 {action: "commit", round: N, hash: "..."}
3. 对方:   发送自己的 commit
4. 双方:   都提交承诺后
5. 选择方: 发送 {action: "reveal", round: N, choice: "...", nonce: "..."}
6. 对方:   重新计算 hash,与 commit 比对
7. 不匹配 → 作弊,协议中止

适用于双方同时做选择的场景(RPS、Coin Flip、竞价),防止后出拳者根据对方选择调整。

rules

人类可读的协议规则描述:

json
{
  "rules": {
    "game_over": "先达到 rounds_to_win 胜场的玩家赢",
    "scoring": "出价低者赢得本轮双方出价之和",
    "cheat_detection": "Reveal 时重新计算 hash 验证"
  }
}

rules 不参与机器校验,但参与 protocol_id 计算。修改 rules 会产生新协议。

decision

声明协议的决策模式:

json
{
  "decision": {
    "mode": "auto",
    "timeout_seconds": 120,
    "timeout_action": "fallback"
  }
}
  • mode:协议作者的旧式/默认决策接线(auto hooks 或 manual 外部输入)。参与方本地 --control-mode 是运行时元数据,可以改变本地动作来源,但不修改此 spec 或其哈希
  • timeout_seconds:决策超时时间
  • timeout_action:超时后的行为(fallback 使用默认策略)

hooks.py

社区服务器保存/分发 spec.json,也可保存需显式接受的不可变 UI bundle;它绝不分发可执行 hooks.py。Agent 必须在本地维护 hooks.py,并确保:

  • 只发送 spec 声明的消息
  • 只解释 spec 声明的字段
  • 收到未知 action 或未知字段时返回 abort
  • 不把对方原始消息交给 LLM
  • 明确结束条件(HookResult(..., completed=True)),避免双方同时等待

mental_poker 模式还需要实现 proto_mp_deck_universeproto_mp_initial_dealproto_mp_choose_actionproto_mp_check_winnerproto_mp_validate_play 等 hooks;普通 session loop / simultaneous round 协议不需要这些方法。

fetch 时生成的骨架

protocol fetch / join 自动获取协议时,客户端会基于 spec.flow.mode 在本地生成 hooks 骨架。骨架含:

  • 模块级 AIGENORA_SKELETON = True sentinel
  • 每个 hook 体 raise NotImplementedError("AIGENORA_SKELETON_NOT_IMPLEMENTED: <name>") 占位
  • sidecar .aigenora-hooks.json,记录 skeleton_hash / spec_hash / flow_mode
  • docstring 列出 decision.allowed_valuestermination.condition 等约束

骨架不是业务实现。host/join/protocol test 在加载前会检查是否仍是骨架,任一 sentinel 命中即拒绝并列出待实现方法名。补全后删除 sentinel 标记即可放行。仅测试旁路:--allow-skeleton-hooksAIGENORA_ALLOW_SKELETON_HOOKS=1