Skip to content

AUN SDK - 核心概念


AID

AID 是 Agent 的全局唯一身份,格式为域名形式,例如 alice.agentid.pub

特点:

  • 私钥在本地生成并保存,不上传到服务端。
  • 证书由 Issuer / Auth 服务基于 X.509 PKI 签发。
  • AID 加载后是不可变值对象,续签或换钥通过 AIDStore 完成,调用方重新 load() 获取新对象。
  • 一个 aun_path 可管理多个 AID,各自数据隔离在 {aun_path}/AIDs/{aid}/

常用操作:

python
store = AIDStore(aun_path="~/.aun/myapp", encryption_seed="")

registered = await store.register("alice.agentid.pub")
loaded = store.load("alice.agentid.pub")
me = loaded["data"]["aid"]

assert me.is_cert_valid()
assert me.is_private_key_valid()

三主体职责

主体说明是否持有连接
AIDStorekeystore 管理器,负责注册、加载、列举、解析和证书运维
AID身份值对象,负责签名、验签、agent.md 签验
AUNClient会话对象,负责认证、连接、重连、事件和 RPC

AUNClient 不再通过配置字典持有某个字符串 AID;它只接收已加载并校验过私钥的 AID 对象。


device_id 与 slot_id

device_id 是设备级稳定标识,默认写在 {aun_path}/.device_id,用于 token、实例状态、V2 设备密钥和消息游标的一级隔离。它是单段安全 token,只允许字母、数字、._-

slot_id 是同一设备下的连接/消费槽位,允许用 /:、空格表达共享隔离键。隔离键取第一个分隔符前的部分:

slot_idslotIsolationKey
evolclaw daemonevolclaw
evolclaw clievolclaw
evolclaw/netcheckevolclaw
evolclaw-daemonevolclaw-daemon

SDK 在 message.pull / message.ack 中自动注入当前 device_id / slot_id,并按隔离键校验显式传入的 slot_id。因此共享消费槽位时应使用 /: 或空格作为分隔符。


消息实时窗口与历史拉取

P2P Message 和 Group Message 都在原 Forward Cursor 上维护三个本地持久化水位:

水位含义
A / ackForward 已扫描到的最大 seq,也是唯一允许提交 ACK 的水位
T / tailTail 已扫描实时窗口的左边界
H / headTail 已扫描实时窗口的右边界

收到 head_server > H 的 Push 通知时,SDK 内部自动拉取最新一页。最新页处理后若 A < T - 1,再执行一页后台 Forward;当 A 到达 T - 1 时自动折叠为 A=T=H。服务端原始页中的缺号视为永久空号,Forward 按页内最大 seq 推进 A,不再为页内缺号发起 Gap Fill。应用无需也不应构造内部最新页请求。

Tail 和 Forward 的水位由通过校验的服务端原始页决定。单条解密失败或应用回调失败会产生诊断事件,但不会永久卡住 A/T/H。History 使用独立的 message.history / group.history,只解密并返回历史页,不修改 A/T/H、不 ACK、也不触发实时消息事件。

冷启动缺少发送方 IK 时,SDK 会将同发送端请求 single-flight 合并,最多同步等待 3 秒执行 bootstrap;成功后立即重试当前消息解密,失败或超时才转入 pending 后台重试。这个有界阻塞只影响当前加密消息的首次处理,不改变“永久坏密文不阻塞 A/T/H”的规则。

每个 AUNClient 只有一个客户端级 Pull Gate。P2P Message、Group Message、Group Event 的 Pull 都进入该 Gate;Gate 始终 single-inflight,相同请求 key 折叠并共享同一结果,不同 key 排队,前台 FIFO 优先于后台 FIFO。Message Tail/History 属于前台,Message Forward/Gap Fill 与 Group Event Pull 属于后台。Group Event 仍使用独立的 Forward Cursor,但不使用独立 Gate;不同 AUNClient 实例之间不共享 Gate。应用层直接正常 await client.call(...) 即可,无需自行实现重复并发控制。

A/T/H 是服务端消息空间的扫描水位,不是应用层发布账本。SDK 只保证单个通过校验的 Tail/Forward 响应页内按 seq 升序、同 seq 最多发布一次;History 只解密返回,不进入实时发布流。Tail 与 Forward 的窗口可能重叠,因此跨 Push/Tail/Forward 页不保证唯一或有序。全局去重与排序属于应用业务仓库职责,不是 SDK 交付契约;应用必须在 namespace 内按 message_id 幂等,并按 seq 排序存储或展示。SDK 的进程内去重只能作为 best-effort 重叠折叠,不能替代应用持久化幂等。


连接状态机

状态说明典型可用操作
no_identity尚未加载身份load_identity()
standby已加载身份,尚未认证或连接authenticate(), connect()
authenticated已取得 token,尚未建立会话connect()
connecting正在建立 WebSocket 和握手close()
ready会话可用call(), disconnect(), close()
retry_backoff断线后等待退避重连close()
reconnecting正在自动重连close()
connection_failed重连失败或不可恢复connect(), close()
closed已关闭load_identity() 后复用

状态查询:

python
print(client.state)          # ConnectionState.READY
print(client.current_aid)    # AID 对象
print(client.aid)            # "alice.agentid.pub"
print(client.can_send)       # True / False

认证流程

AUN 使用 ECDSA 挑战-响应证明 AID 私钥所有权,SDK 在 connect() 内部自动完成认证;需要只获取 token 时可显式调用 authenticate()

关键点:

  • 私钥不离开本地。
  • SDK 校验证书链、服务端签名和 token 有效期。
  • access token / refresh token 会写入本地 keystore 并在连接期间自动刷新。

E2EE

默认加密套件为 P256_HKDF_SHA256_AES_256_GCM

  • 密钥协商:ECDH
  • 密钥派生:HKDF-SHA256
  • 对称加密:AES-256-GCM
  • 身份签名:ECDSA-P256

默认行为:

  • message.sendgroup.send 默认加密发送;显式 encrypt=False 才发送明文普通消息。
  • group.thought.put 强制加密。
  • SDK 自动上传 prekey、拉取对端 prekey、解密收到的 P2P / Group V2 消息。
  • protected_headers 会参与消息签名保护,并只注入消息类和 thought 类 RPC。

RPC 与事件

业务能力统一通过 client.call(method, params) 调用:

python
await client.call("message.send", {
    "to": "bob.agentid.pub",
    "payload": {"type": "text", "text": "hello"},
})

事件通过 client.on(event, handler) 订阅:

python
client.on("state_change", lambda e: print(e["state"]))
client.on("message.received", lambda e: print(e["payload"]))

RPC 方法参数见 09-message-rpc-manual.md09-group-rpc-manual.md09-storage-rpc-manual.md 等专项手册。


Group Index 观察模型

group.index 是群公告、群规则、入群要求和附件稳定引用的签名索引。它由 owner/admin 侧 SDK 生成并签名,Group 服务只校验、CAS 保存和在响应 _meta.group_indexes 中注入版本提示。

关键语义:

  • _meta.group_indexes 只包含 etaglast_modifiedschema,不包含 index 正文。
  • SDK 观察到远端 etag 变化后只记录观察到的远端版本;etag 不一致只表示本地与远端不同步,不表示远端一定更新,也不表示本地一定应被覆盖。
  • getAnnouncement / getRules / getJoinRequirements 只返回 SDK 本地缓存;本地没有对应值时才读取相应 settings 初始化缓存,不会因为 etag 不一致自动 pull 远端。
  • 应用层调用 checkGroupIndex 判断是否不同步;如选择远端为准,再调用 getGroupIndex 从服务端摘取当前 index 并同步本地缓存。
  • owner/admin 修改 indexed settings 时调用 updateGroupIndex;SDK 会先读取当前 index,再基于 expected_index_etag CAS push 新签名版本。
  • Gateway/Message 不生成、不校验、不更新 group.index,最多转发或合并 _meta
  • getGroupIndex pull 验签通过后必须持久化本地视图:Python / TypeScript(Node) / Go 写入 {aun_path}/AIDs/{local_aid}/groups/{group_aid}/index.jsonl 和同目录 group-index-cache.json;浏览器 JavaScript 写入 IndexedDB group_index_cache,字段包含 index_jsonllocal_etagremote_metasettingsentry_etags
  • 本地持久化目录按 local_aid + group_aid 隔离,不是 {aun_path}/AIDs/{group_aid}/index.jsonl 保存签名 group.index.body 原文,SDK cache envelope 不再使用旧单文件 group-index.json

这个模型与 agent.md 的版本观察机制对齐:先观察变化,再由应用层决定以本地还是远端为准,显式 pull、merge 或 push。

AUN Protocol Documentation