Buzz 深度拆解:Jack Dorsey 的 Block 开源「人机蜂巢」,为什么说它可能是 Slack 之后团队协作的下一站
一、背景:当 Agent 成为「同事」,聊天工具就不够用了
2026 年 7 月,GitHub Trending 榜首出现了一个新面孔:block/buzz——Jack Dorsey 掌舵的 Block(原 Square)开源的一个「hive mind communication platform(蜂巢思维通信平台)」,短时间内冲到近 1 万 Star,Issues 343 个、PR 435 个,社区活跃度肉眼可见。
一句话概括它是什么:一个自托管的团队工作区,人类和 AI Agent 在同一个房间里干活,底层是一个 Nostr Relay。
先别急着翻白眼说「又一个 AI 协作工具」。我花了一晚上把它的 README、ARCHITECTURE.md 和 NOSTR.md 全部啃完,发现这个项目跟市面上那些「Slack + ChatGPT 机器人」的缝合怪有本质区别。它回答的是一个更根本的问题:
当 AI Agent 从「聊天窗口里的玩具」变成「真正执行任务的数字员工」时,团队协作的基础设施应该长什么样?
现有工具的答案都很尴尬:
- Slack/飞书/钉钉:Bot 是二等公民,走的是 Webhook + Bot Token 那套补丁式 API,权限模型和人类完全两套,审计日志基本没有
- GitHub:代码协作没问题,但讨论、CI、发布、值班这些散落在七八个系统里
- 各家 Agent 平台:Agent 能干活,但它干了什么、为什么这么干、谁批准的,全靠平台方的黑盒日志
Buzz 的答案很激进:人和 Agent 用同一套身份模型(secp256k1 密钥对)、同一个事件日志、同一套权限边界。Agent 不是 Bot,是拥有自己密钥的正式成员。
这篇文章我会从协议设计、系统架构、事件管线、订阅系统、安全模型五个层面把 Buzz 拆开,配上可以直接跑的命令和代码,最后聊聊它的局限和这类「Agent-native 协作平台」的行业走向。
二、核心理念:一切皆签名事件
Buzz 的整个世界观建立在一个极简的原语上——Nostr NIP-01 事件:
{
"id": "<对事件规范化序列化后的 sha256>",
"pubkey": "<secp256k1 公钥, hex>",
"kind": 9,
"tags": [["h", "<channel-uuid>"], ["e", "<event-id>"], ["p", "<pubkey>"]],
"content": "消息正文或 JSON 载荷",
"sig": "<对 id 的 Schnorr 签名>"
}
六个字段,没了。聊天消息是事件(kind:9),表情回应是事件(kind:7),删除是事件(kind:5),工作流的每一步是事件(kind:46001–46012),Git 的 patch 和 CI 状态也是事件(NIP-34),甚至画布编辑、语音房间的生命周期都是事件。
这个设计带来三个直接后果:
1. 加新功能 = 定义新的 kind 数字。 老客户端看不懂新 kind 就直接忽略,什么都不会坏。Buzz 目前在 buzz-core 里定义了 81 个 kind 常量,其中 40000–49999 是自定义区间:
| Kind | 含义 |
|---|---|
| 9 | 频道聊天消息(NIP-29 群聊) |
| 7 | 表情回应(NIP-25) |
| 40003 | 消息编辑 |
| 43001 | Agent 任务请求 |
| 45001 / 45003 | 论坛主贴 / 回复 |
| 46001–46012 | 工作流执行事件 |
| 20001 / 20002 | 在线状态 / 输入中(临时事件,不落盘) |
2. 天然的全链路审计。 每个事件都带 Schnorr 签名,谁发的赖不掉。Agent 审查了一个 PR?签名事件。工作流自动发布了一个版本?签名事件。人类点了个 👍 批准?还是签名事件。buzz-audit crate 再用哈希链把这些事件串成防篡改日志。这对「Agent 干了什么必须可追溯」的合规场景来说,是从地基上解决问题,而不是事后补日志。
3. 人机真正同权。 Agent 有自己的密钥对、自己的频道成员身份、自己的审计轨迹。给 Agent 授权的方式和给新同事授权一模一样:把它拉进频道。不是「给 Bot 开个 scope 白名单」,而是「身份即边界」。
README 里有句话我很喜欢:
Agents are part of the room, not haunted cron jobs.(Agent 是房间里的成员,不是闹鬼的定时任务。)
三、架构分析:一个 Relay 统治一切
3.1 总体拓扑
┌────────────────────────────────────────────────────┐
│ 客户端 │
│ 桌面 App(Tauri+React) Agent(Goose/Codex/Claude) │
│ buzz-cli(JSON in/out) 任意 NIP-29 Nostr 客户端 │
└──────────────┬─────────────────────────────────────┘
│ WebSocket + REST
▼
┌────────────────────────────────────────────────────┐
│ buzz-relay (Axum) │
│ NIP-42 认证 · EVENT 管线 · REQ 订阅 · HTTP 桥 │
│ /media(Blossom) · /git(NIP-34) · 审计日志 │
└────┬──────────────────┬──────────────────┬─────────┘
▼ ▼ ▼
Postgres Redis S3/MinIO
(事件+FTS全文检索) (pub/sub+在线状态) (媒体存储)
与「去中心化」的 Nostr 原教旨不同,Buzz 做了一个务实的取舍:没有 P2P、没有 gossip、没有多 relay 同步,所有读写都经过唯一的 relay。这一刀砍掉了分布式系统 90% 的复杂度——一致性、冲突合并、事件传播延迟全都不存在了。你要的是团队工作区,不是加密货币论坛,单一事实源(single source of truth)就是最合理的架构。
但它保留了 Nostr 的接口兼容性:任何标准 NIP-29 客户端(如 Chachi、0xchat)都可以直接连上 Buzz relay 收发消息。这一点很妙——协议开放性没丢,生态兼容白送。
3.2 Rust Crate 分层:强制的模块隔离
Buzz 是一个 Rust monorepo,crate 依赖关系被刻意设计成星型:
buzz-core (零 I/O:类型、签名校验、filter 匹配、kind 注册表)
├── buzz-db (Postgres:事件、频道、token、工作流、审计)
├── buzz-auth (NIP-42/98、API token、scope、限流接口)
├── buzz-pubsub (Redis pub/sub、presence、typing)
├── buzz-search (Postgres FTS 全文检索)
├── buzz-audit (哈希链防篡改日志)
└── buzz-workflow (YAML 自动化引擎)
└── buzz-relay (唯一的组装点)
两个值得抄作业的工程决策:
其一,buzz-core 在 Cargo.toml 里显式禁止依赖 tokio、sqlx、redis、axum。 零 I/O 的核心 crate 意味着签名校验、filter 匹配这些纯逻辑可以在任何上下文里测试和复用,编译也快。
其二,子系统之间互相不可见,跨子系统协调只能通过 relay。 buzz-workflow 永远不会直接调 buzz-pubsub,buzz-search 永远不碰 buzz-db。所有编排逻辑集中在 buzz-relay 一处。这种「星型依赖 + 中心编排」的结构在大型 Rust 项目里能有效防止 crate 之间长出意大利面。
3.3 事件管线:12 步流水线的取舍艺术
客户端提交一个 ["EVENT", <event>] 后,relay 内部跑一条 12 步流水线:
1. AUTH 检查 — 已通过 NIP-42 认证?有 MessagesWrite scope?
2. 公钥匹配 — event.pubkey == 认证公钥?(防止代签)
3. 拒绝 AUTH 事件 — kind 22242 永不落盘
4. 临时事件分流 — kind 20000–29999 走旁路(不存储不审计)
5. 签名校验 — spawn_blocking 里跑 Schnorr 验签 + ID 哈希
6. 成员校验 — 事件带频道 tag?检查发送者是否是成员
7. DB 写入 — INSERT ... ON CONFLICT DO NOTHING(幂等)
8. Redis PUBLISH — 频道级事件广播到其他 relay 节点
9. 本地扇出 — 订阅注册表 → 连接管理器推送
10. 检索索引 — 发送到有界队列(容量1000),异步消费
11. 审计日志 — spawn 异步任务,不阻塞
12. 工作流触发 — spawn 异步任务,排除工作流自身事件防死循环
几个细节值得展开:
验签放进 spawn_blocking。 Schnorr 验签是 CPU 密集操作,直接在 tokio 异步任务里跑会阻塞 reactor 线程。丢进阻塞线程池是标准做法,但很多项目会忘。
步骤 10–12 全部 fire-and-forget。 搜索索引失败、审计写入失败、工作流触发失败,都不影响事件本身的提交成功。消息收发是主干,其余是旁路——这个优先级排序保证了核心体验的可用性。
工作流防循环。 工作流执行产生的事件(kind 46001–46012)、relay 签名的带 buzz:workflow 标签的消息,都被排除在工作流触发之外。否则「工作流 A 发消息触发工作流 B 发消息触发工作流 A」的死循环分分钟把系统打挂。做过事件驱动系统的人都知道这种防护有多重要,也知道有多少系统是上线之后被循环风暴教育了才补上的。
幂等写入。 ON CONFLICT DO NOTHING 意味着客户端重发同一事件(网络重试很常见)不会产生重复数据——事件 ID 本身就是内容哈希,天然去重。
3.4 订阅系统:三级索引的扇出优化
聊天系统的核心性能瓶颈在扇出(fan-out):一条消息进来,要推给哪些连接?Buzz 用 DashMap 做了三级索引:
pub struct SubscriptionRegistry {
// 全量订阅表
subs: DashMap<ConnId, HashMap<SubId, SubEntry>>,
// 一级索引:(channel_id, kind) → 订阅列表,O(1) 命中
channel_kind_index: DashMap<IndexKey, Vec<(ConnId, SubId)>>,
// 二级索引:channel_id → 订阅列表(未指定 kind 的通配订阅)
channel_wildcard_index: DashMap<Uuid, Vec<(ConnId, SubId)>>,
}
事件到达时按序查询:
| 层级 | 索引 | 适用场景 |
|---|---|---|
| 1 | (channel_id, kind) 精确索引 | 明确订阅了某频道某类事件,O(1) |
| 2 | channel_id 通配索引 | 订阅了整个频道所有事件 |
| 3 | 全表线性扫描 | 全局订阅(无频道约束)兜底 |
关键安全设计:频道级事件只推送给显式订阅了该频道的连接,全局订阅永远收不到私有频道的事件——哪怕 filter 条件匹配上了也不行。这是一条硬编码的安全边界,防止「订阅所有 kind:9」变成窃听全站私聊的后门。
另一个防竞态细节:REQ 订阅处理时,先检查频道访问权限,再注册订阅。反过来做的话,非成员会在「注册成功」到「权限检查踢出」之间的窗口期收到私有频道的实时消息。这种 TOCTOU 级别的细节能写进架构文档,说明团队是真的在安全上花了心思。
3.5 连接生命周期与慢客户端处理
每个 WebSocket 连接跑三个并发任务:recv_loop(收帧解析分发)、send_loop(排空 mpsc 队列写帧)、heartbeat_loop(30 秒 ping,3 次 pong 丢失即断开),用 CancellationToken 统一协调关闭。
慢客户端处理很干脆:try_send 发现发送缓冲满时累加计数器,连续 3 次满就直接掐掉连接。宁可断开慢客户端,也不让它拖垮 relay 的内存——群聊系统里一个 3G 网络的手机端能把服务器 buffer 撑爆的故事,经历过的人都懂。
四、代码实战:从部署到 Agent 接入
4.1 五分钟自托管
git clone https://github.com/block/buzz.git && cd buzz
. ./bin/activate-hermit # Hermit 固定工具链,自动下载依赖
just setup && just build # Docker 服务 + 数据库迁移 + 编译
just dev # relay(ws://localhost:3000) + 桌面 App 一起起
生产部署用 deploy/compose/ 下的 Compose bundle:Postgres + Redis + MinIO + 可选 Caddy/TLS,一台 VPS 就能跑。桌面客户端有 macOS/Linux/Windows 打包版,直接从 Releases 下载。
4.2 用 nak 直接说 Nostr 方言
因为 relay 说标准 NIP-29,用通用 Nostr CLI 工具 nak 就能操作一切:
# 创建频道(kind:9007)
nak event -k 9007 --tag "name=dev-channel" --tag "visibility=open" \
--auth --sec <私钥> ws://localhost:3000
# 发消息(kind:9,必须带 #h 频道 tag)
nak event -k 9 -c "Hello from NIP-29!" --tag "h=<channel-uuid>" \
--auth --sec <私钥> ws://localhost:3000
# 订阅频道实时消息
nak req -k 9 --tag "h=<channel-uuid>" --stream \
--auth --sec <私钥> ws://localhost:3000
# NIP-50 全文搜索(Postgres FTS 撑腰)
nak req -k 9 --tag "h=<channel-uuid>" --search "deploy failed" -l 20 \
--auth --sec <私钥> ws://localhost:3000
# NIP-10 线程回复
nak event -k 9 -c "回复内容" --tag "h=<channel-uuid>" \
--tag "e=<父消息id>;;reply" --auth --sec <私钥> ws://localhost:3000
管理端也是事件驱动的(NIP-43 管理事件):
# 添加成员(kind:9030,需 owner/admin 签名)
nak event -k 9030 --tag "p=<目标公钥>" --tag "role=member" \
--auth --sec <管理员私钥> ws://localhost:3000
# 提升为 admin(kind:9032)
nak event -k 9032 --tag "p=<目标公钥>" --tag "role=admin" \
--auth --sec <管理员私钥> ws://localhost:3000
连「加人踢人改角色」都是签名事件,管理操作自动进审计日志。对比一下某些 IM 的管理后台连操作日志都没有,高下立判。
4.3 Agent 接入:buzz-cli 与 ACP 桥
Agent 侧的入口是 buzz-cli——一个刻意为 LLM 工具调用设计的 CLI:JSON 进,JSON 出,没有花哨的 TUI:
export BUZZ_PRIVATE_KEY=<agent专属私钥>
buzz-cli send --channel <uuid> --content "CI 挂了,我看了下是依赖锁文件冲突"
更有意思的是 buzz-acp——一个 ACP(Agent Client Protocol)桥接层,把频道里的 @mention 转发给 Goose、Codex、Claude Code 这些编码 Agent。也就是说,你在频道里 @ 一个 Agent 让它修 bug,它通过 ACP 收到任务、用 buzz-dev-mcp 提供的 shell 和文件编辑工具干活、把 patch 以 NIP-34 事件发回频道。整条链路上每一步都有签名。
4.4 YAML 工作流:Reaction 驱动的发布流程
buzz-workflow 支持消息、表情回应、定时、Webhook 四种触发器。README 里那个「自己写发布说明的 release」的场景大致是这样的:
# 概念示意:tag 触发 → Agent 起草 → 人类 👍 批准 → 发布
on:
webhook: { id: release-tag }
steps:
- agent: release-bot
prompt: "读取本次 tag 涉及的已合并 PR,起草发布说明,发到 #release 频道"
- wait_for:
reaction: "👍"
from: [release-manager-pubkey]
- agent: release-bot
prompt: "发布已批准的 release notes 并打包"
注意那个 wait_for reaction——人类的批准动作就是一个 kind:7 表情事件,带签名、可检索、进审计链。「Human in the loop」不再是 PPT 词汇,而是协议层的一等公民。(工作流审批门控目前标记为 🚧 施工中,但基础设施已就位。)
五、安全模型与多租户设计
5.1 认证:没有密码,只有密钥
Buzz 没有传统的用户名密码。WebSocket 连接用 NIP-42(relay 下发随机 challenge,客户端签名应答,±60 秒时间容差),HTTP 端点用 NIP-98(对 URL + Method 签名的 kind:27235 事件)。另有 API token 体系给需要细粒度 scope 的场景,14 种 scope 覆盖消息、频道、用户、任务、文件的读写管理。
访问控制的几个 fail-closed 设计:
- 公钥白名单开启后,数据库查询失败时拒绝连接(而不是放行)
- 多租户模式下,未知域名直接拒绝,不会 fallback 到默认租户
- 涉及私信和成员变动通知的订阅,必须带
#pfilter 且只能是自己的公钥,否则 relay 直接拒绝订阅——从协议层杜绝窃听他人私信
5.2 私信:NIP-17 Gift Wrap
私信用 NIP-17 礼物包装(kind:1059):内容用临时密钥加密签名,relay 只知道「有一个加密包裹要给某公钥」,看不到内容和真实发送者。私信不进搜索索引。自托管场景下这意味着连 relay 管理员都读不了你的私聊——这是大多数企业 IM 做不到(或者说不愿做到)的。
5.3 多租户:URL 即社区
Buzz 的多租户模型很清爽:请求的域名决定社区(community),在任何 AUTH/EVENT/REQ/REST 处理之前先解析 TenantContext。默认自托管是一域一社区;托管服务商可以在共享的 Postgres/Redis/S3 上跑多个社区,但每个社区的数据、搜索索引、审计链、媒体元数据全部按社区隔离。客户端提交的频道 tag 必须能解析到当前域名对应社区内的频道,不能跨界。
同一个公钥可以加入多个社区,但资料和私信不跨社区继承——身份是全局的,数据是社区本地的。这个边界划得很清楚。
六、冷静看:局限与坑
夸了这么多,泼点冷水:
1. 限流还没真正实现。 架构文档自己承认:RateLimiter 只有测试桩,RateLimitConfig 定义的 4 档限流(human/agent-standard/agent-elevated/agent-platform)目前只是设计目标。生产环境裸奔限流,一个失控的 Agent 能把 relay 写爆。上生产前必须在前面加一层网关限流。
2. 单 relay 是单点。 没有 P2P 换来了简单性,也意味着 relay 挂了全员下线。多节点扇出通过 Redis pub/sub 打通了,但 presence 的多节点扇出还是「documented as future work」。高可用方案得自己搭。
3. 生态成熟度。 移动端(Flutter)还在施工,推送通知在「strong opinions, pending code」列(README 原话是「请不要把你的合规计划建立在 💭 那一栏上」,这种坦诚倒是难得)。邀请事件 kind:9009 收了存了但处理是 no-op。CLI 管理成员时并发调用会有时间戳冲突,官方建议循环加成员时 sleep 1——这种细节文档里写了,但也暴露了工程还在早期。
4. Nostr 的认知门槛。 对普通团队来说,「给每个成员生成 secp256k1 密钥对」比「发邮箱邀请」的心智成本高一个量级。密钥丢失、密钥托管、密钥轮换这些问题,文档还没给出企业级答案。
七、总结与展望:协作工具的「Agent-native 重构」才刚开始
回头看 Buzz 的三个核心押注:
- 身份统一:人和 Agent 都是密钥对,权限即成员身份,不搞两套系统
- 日志统一:聊天、代码、CI、审批、工作流全是同构的签名事件,一个搜索索引通吃
- 协议开放:站在 Nostr 生态肩膀上,任何 NIP-29 客户端即插即用
这三条恰好对应了现有工具链的三大痛点:Bot 权限体系的补丁化、信息散落七个系统的割裂感、平台锁定。用 README 的话说,Buzz 赌的是「一个社区能干掉团队目前用聊天软件 + 代码托管 + 机器人 + CI 面板 + 发布工具 + 搜索索引 + 一堆胶水代码假装在协作的整个拼盘」。
会不会成?不好说。Slack 的网络效应不是技术优势能轻易撬动的,Nostr 的密钥模型对非技术团队也确实不友好。但有一点我比较确定:当 Agent 真正开始承担生产任务,「Agent 干了什么、谁授权的、依据是什么」会从加分项变成硬需求。到那时候,「每个动作都是签名事件」的架构就不是极客审美,而是合规刚需。
Buzz 未必是终局赢家,但它把「Agent-native 协作平台」的架构标杆立在了这里:Rust 的性能底座、Nostr 的开放协议、哈希链审计、身份即权限。后来者绕不开这套参照系。
对我们程序员来说,现在就值得做两件事:一是 clone 下来跑一遍,感受一下「给 Agent 发密钥拉进频道」和「给 Bot 申请 Token 配 Webhook」的体验差异;二是把它的事件管线和订阅索引设计读一遍——就算不用 Buzz,这些模式在你自己的事件驱动系统里都用得上。
蜂巢已经开源,蜜蜂自带密钥。剩下的,交给时间。