news 2026/9/29 8:43:22

block/buzz 深度拆解:一条签名事件日志,装下人类、Agent 和 Git

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
block/buzz 深度拆解:一条签名事件日志,装下人类、Agent 和 Git

1. 一条事件日志为什么能同时装下人类、Agent 和 Git

如果你正在做多主体协作系统,大概率遇到过这个别扭的局面:人类的消息走一套表,Agent 的执行记录走另一套日志,Git 提交又是第三套数据结构。三套东西各自有 ID、各自有权限、各自有审计,等到要回答“这个改动到底是谁在什么时候基于什么上下文做的”时,你得写三段查询再把结果拼起来,拼完还不一定对得上。

block/buzz 这个项目给出的答案很干脆:别分三套,全部塞进同一条签名事件流。人类发的消息、Agent 执行的动作、Git 的补丁提交,在协议层面是同一种数据结构——Nostr 事件。区别只有一个:签名用的密钥对不同。身份是密钥对,权限是“你是不是这个频道的成员”,审计是“这条事件有没有合法签名、有没有进哈希链”。

这篇不聊它的产品定位,只聚焦一件事:这条签名事件日志到底长什么样,字段怎么配,怎么在本地把它解析出来并回放验证。适合已经在写 Agent 协作、事件溯源、或者多主体审计系统的同学。读完你应该能自己搭一个最小事件流,把人类操作、Agent 行为、Git 提交统一编码进去,并跑通验签和回放。

我试过把这套结构套到一个内部小工具上,最大的感受是:一旦身份和事件统一了,后面加任何新主体(新的 Agent、CI 机器人、定时任务)都只是“再生成一对密钥”,不用改表结构,也不用加权限位。

2. 前置:TaoToken 在这条链路里扮演什么角色

要复现事件解析和回放,你需要一个能签发密钥、能调用模型做事件语义解析的入口。TaoToken 在这里的作用是提供统一的 API 接入层:你用它拿 API Key,然后在本地脚本里调用模型,把原始事件 JSON 翻译成可读的操作描述,或者让 Agent 根据事件流决定下一步动作。

官网地址是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 入口是 https://taotoken.net/api (这个不加 UTM)。注意区分:官网带推广参数,API 地址是纯接口地址,写代码时用后者。

具体到本篇的场景,你会用到两类能力:

一类是模型对话,用来做事件内容的语义解析和回放时的自然语言摘要,入口在模型对话页面;另一类是 API Key 管理,所有请求都要带 Key,入口在 API Keys 页面。如果你打算让 Agent 长期挂在事件流上做自动响应,那更适合用 Coding Plan,因为它是面向持续编码和 Agent 场景的订阅形态,比按次调用更稳。

接入文档在 doc 页面,里面有完整的请求格式和鉴权说明。Claude Code 这类工具的用户可以看 ClaudeCodeAnthropic 对应的接入说明。控制台在 console 页面,用来查看调用量和余额。

一句话:TaoToken 不参与事件签名本身(签名是你本地密钥对的事),它负责的是事件流的“语义层”——把机器可读的事件翻译成人类可读的操作,以及让 Agent 基于事件流做决策。

3. 可复制的事件字段配置骨架

下面这套骨架是我实际跑通过的,字段命名尽量贴近 Nostr 的 NIP-01 规范,同时留了扩展位给 Agent 和 Git 场景。

3.1 基础事件结构

一条事件的核心字段就这几个,顺序不能乱,因为事件 ID 是对固定顺序的字段数组做哈希:

{ "id": "<事件内容的 SHA-256,hex 编码>", "pubkey": "<作者公钥,hex 编码>", "created_at": 1730000000, "kind": 9, "tags": [ ["e", "<被回复事件 id>"], ["p", "<被提及人公钥>"], ["t", "agent-task"] ], "content": "{\"text\":\"hello world\"}", "sig": "<对 id 的 Schnorr 签名,hex 编码>" }

几个关键点必须说清楚。id不是随便生成的 UUID,而是对[0, pubkey, created_at, kind, tags, content]这个数组做规范化 JSON 序列化后取 SHA-256。sig不参与哈希,它签名的是id。这意味着内容改一个字节,id就变,签名就失效。

kind是功能分派的核心。1 是普通笔记,7 是反应,9 是群聊消息,22242 是认证事件。buzz 自己占了 40000–49999 段做扩展,比如工作流执行、任务请求、画布操作。你自定义新功能时,选一个没被占用的 kind 就行,旧客户端收到不认识的 kind 会忽略,不会崩。

3.2 人类操作、Agent 行为、Git 提交的编码差异

三类主体共用同一结构,差异体现在kind、tags和content的约定上:

主体类型典型 kindtags 约定content 约定
人类消息9e/p 标记回复和提及文本或富文本 JSON
Agent 行为40000+t 标记任务类型,e 关联触发事件结构化执行结果
Git 提交NIP-34 段e 关联仓库公告,p 关联审查人补丁元数据

Git 这块走的是 NIP-34,补丁集、仓库公告、状态变更都是签名事件。配好git-sign-nostr和git-credential-nostr之后,git 操作用 nostr 密钥签名,提 PR 等于发一条事件,CI 结果等于回一条事件,Agent 首轮审查又是另一条事件。合并决策和全部证据躺在同一个频道里,可搜索、可验签。

3.3 哈希链审计字段

如果你要做防篡改审计,在事件之外再加一条链记录,每条链到前一条的哈希:

{ "seq": 1024, "ts": 1730000000, "event_id": "<对应事件 id>", "kind": 9, "actor_pubkey": "<操作者公钥>", "action": "message.create", "channel_id": "<16 字节频道 id,无则填 16 个零字节>", "metadata": {"source": "agent", "task": "review"}, "prev_hash": "<前一条链哈希>", "hash": "<本条链哈希>" }

哈希输入的确定性是重点:数值统一大端字节序,metadata用有序映射序列化保证键排序确定,channel_id为空时填固定长度的零字节。任何一处不确定性都会让链在跨机器验证时断掉。创世条目用 64 个零作为prev_hash。

4. 验证请求与成功结果

配好字段之后,下一步是跑通“提交事件 → 验签 → 回放”这条链路。

4.1 本地生成密钥对并签名

用 Python 快速验证签名逻辑,依赖coincurve或secp256k1库:

import hashlib import json import time from coincurve import PrivateKey def canonical_event(pubkey, created_at, kind, tags, content): return json.dumps([0, pubkey, created_at, kind, tags, content], separators=(',', ':'), ensure_ascii=False) def compute_event_id(pubkey, created_at, kind, tags, content): serialized = canonical_event(pubkey, created_at, kind, tags, content) return hashlib.sha256(serialized.encode('utf-8')).hexdigest() priv = PrivateKey() pubkey = priv.public_key.format(compressed=True)[1:].hex() created_at = int(time.time()) kind = 9 tags = [["t", "agent-task"]] content = json.dumps({"text": "agent started review"}, ensure_ascii=False) event_id = compute_event_id(pubkey, created_at, kind, tags, content) sig = priv.sign_schnorr(bytes.fromhex(event_id)).hex() event = { "id": event_id, "pubkey": pubkey, "created_at": created_at, "kind": kind, "tags": tags, "content": content, "sig": sig } print(json.dumps(event, ensure_ascii=False, indent=2))

跑完你会拿到一条完整事件。注意canonical_event里的separators参数,它去掉了 JSON 序列化时的空格,这是保证不同实现算出同一个id的关键。

4.2 用 TaoToken 做事件语义解析

拿到事件之后,把content丢给模型做解析,让它输出人类可读的操作描述。请求走 API 地址:

curl https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "claude-sonnet", "messages": [ {"role": "system", "content": "你是事件流解析器,把 JSON 事件翻译成一句操作描述。"}, {"role": "user", "content": "{\"kind\":9,\"content\":\"{\\\"text\\\":\\\"agent started review\\\"}\",\"tags\":[[\"t\",\"agent-task\"]]}"} ] }'

成功返回的choices[0].message.content应该是一句类似“Agent 在 agent-task 任务下开始执行审查”的描述。这一步的意义在于:事件流本身是机器可读的,但审计和回放需要人类可读,模型在这里做的是翻译层。

4.3 回放验证

回放的核心是重算id并验签,两步都对才认为事件合法:

def verify_event(event): recomputed = compute_event_id( event["pubkey"], event["created_at"], event["kind"], event["tags"], event["content"] ) if recomputed != event["id"]: return False, "id mismatch" pub = PublicKey(bytes.fromhex("02" + event["pubkey"])) ok = pub.verify_schnorr( bytes.fromhex(event["sig"]), bytes.fromhex(event["id"]) ) return ok, "ok" if ok else "sig invalid"

回放时按created_at排序,逐条验签,再把kind分派到不同处理器:9 走消息渲染,40000+ 走 Agent 行为渲染,NIP-34 段走 Git 操作渲染。这样一条事件流就能同时回放出人类对话、Agent 动作和代码变更三条时间线。

5. 本篇常见错排查

事件 ID 对不上。九成是 JSON 序列化不一致。检查separators是否去掉了空格,检查ensure_ascii是否统一,检查字段顺序是否严格是[0, pubkey, created_at, kind, tags, content]。少一个空格、多一个转义,哈希就完全不同。

验签失败但 ID 正确。检查公钥格式。Schnorr 验签用的是 x-only 公钥,如果你拿的是压缩公钥(33 字节带前缀),要去掉第一个字节。签名是 64 字节,不是 DER 编码。

Agent 事件和人类事件混在一起分不清。别靠pubkey硬编码判断,用tags里的标记位。约定一个["t", "agent"]或["t", "human"],回放时按 tag 分派。身份是密钥对,但角色是标签,两者解耦。

Git 事件收不到。NIP-34 相关的事件类型在 buzz 里标注为正在接线,如果你用的是早期版本,Git 事件可能还没进主流程。先确认版本,再检查git-credential-nostr是否配置正确。

哈希链验证断在中间。检查metadata的序列化是否用了有序映射。Python 的dict在 3.7 之后保序,但如果你从数据库读出来再序列化,顺序可能变。用sort_keys=True或者显式构造有序结构。

调用模型返回 401。检查 API Key 是否带对了前缀,检查请求地址用的是https://taotoken.net/api而不是官网地址。官网地址带推广参数,不适合直接做接口调用。

回放顺序错乱。created_at是秒级时间戳,同一秒内多条事件会并列。加一个seq字段做次级排序,或者用事件 ID 做稳定排序。

6. 把这条事件流用起来

如果你只想快速验证,最小路径是:本地生成密钥对 → 构造一条 kind 9 事件 → 重算 ID 验签 → 用 TaoToken 的模型对话接口把 content 翻译成描述。这条链路跑通,你就理解了“统一事件日志”的核心。

如果你要做长期 Agent 协作,建议把 API Key 管理好,用 API Keys 页面生成专用 Key,然后按接入文档把 Agent 挂到事件流上。持续编码场景用 Coding Plan 更合适,因为 Agent 需要长期在线响应事件,按次调用在成本和稳定性上都不划算。

真正值得带走的是那个结构判断:当系统里混着人类和 Agent 两类主体时,别给 Agent 开后门加特权,让它们和人类共享同一套身份模型、同一个事件日志、同样的审计约束。权限问题退化成“这个密钥在不在成员表里”,审计问题退化成“查日志加验签”。复杂度没有消失,但被压缩进了三个正交的机制里,后面加任何新主体都只是再生成一对密钥的事。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/29 8:37:05

vLLM 与 SGLang 架构决战:双引擎调度器与执行模型的底层解剖

vLLM 与 SGLang 架构决战&#xff1a;双引擎调度器与执行模型的底层解剖在大语言模型&#xff08;LLM&#xff09;推理服务进入工业化成熟期的今天&#xff0c;vLLM 与 SGLang 已然成为高性能开源推理运行时&#xff08;Inference Runtime&#xff09;的绝代双骄。两者的核心目…

作者头像 李华
网站建设 2026/9/29 8:35:14

DeepSeek V4 Flash 快速上手与实战指南:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/29 8:35:03

UltraEdit 中 SQL 语句着色与格式化规范:用 TaoToken 统一配置骨架

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华