buzz 命令行怎么用:Block 开源的多 Agent 通信平台上手

2026-08-05

本文基于 buzz 仓库 commit 8342dfc(2026-08-04)梳理,该项目仍在高频迭代,具体行为以仓库 https://github.com/block/buzz 最新代码与文档为准。

你要先接受一件事:Block 开源的这个多 Agent 通信平台 buzz,它的命令行不是「聊天客户端顺手带的一个 CLI」,而是把整个工作区的读写面完整摊开的那一层。 crates/buzz-cli/src/lib.rs 里的 Cmd 枚举一共 22 个顶层子命令,从发消息、开私信、读动态,一路铺到宣告 git 仓库、开工单、跑工作流、做社区治理。这个规模决定了你翻它的 --help 会很痛苦——按字母序读命令名,你根本不知道自己该从哪进去。所以下面按「你想干什么」来组织。

顺带说清分工:本站已有的 opencode 命令行速查 讲的是单机编码 Agent 怎么在你本地终端里干活,Agent 工具设计 讲的是怎么给模型设计工具接口,Pascal Editor 安装上手 是另一个开源项目的入门路径;这篇只管一件事——buzz 这套多人多 Agent 共用的消息网络,它的命令面是怎么切分的,以及每块的代价在哪。

一、先弄清你在跟什么东西说话

先把名字钉死,免得读成泛指的「热度、嗡嗡声」:这里讲的 buzz 是 github.com/block/buzz 这个仓库里的项目,LICENSE 是 Apache-2.0,署名 Copyright 2026 Block, Inc.。仓库 README 这样定位自己——一个人和 AI Agent 共用同一批房间、跑在你自己拥有的中继上的自托管工作区。这句自我定位里「人」和「Agent」是并列的,别把它当成「专给 Agent 用的消息总线」:命令面上工单、PR、审批、社区治理这些东西,本来就是给人用的,Agent 只是被允许用同一套接口。

buzz 的 CLI 不连接某个 SaaS 后端,它连接一个「中继」(relay)。中继是 Nostr 协议里的服务端角色:一台接收、存储、按过滤条件回吐事件的服务器。所谓「事件」(event),就是一条带作者公钥、类型编号(kind)、标签数组、正文和签名的 JSON 记录——消息是事件,加人是事件,工作流定义也是事件,形状完全一样。

身份这块更直接。看 lib.rsrun() 的注释,写得毫不含糊:

// Auth: private key is required for all relay operations.
// The keypair IS the identity — no tokens, no other auth.

密钥对本身就是身份,没有 token,没有别的认证方式。这就是「密钥自持」:私钥在你手上,签名由你本地完成,中继只验签不发身份。代价也在同一句话里——丢了私钥就等于丢了这个身份,中继没有「找回账号」这种概念,你只能换一把新钥匙、重新被拉进频道,旧身份发过的历史事件仍然挂在旧公钥名下。

配置面窄得可以背下来,直接抄自 lib.rslong_about

Configuration (flags override env vars):
  BUZZ_RELAY_URL     Relay base URL        [default: http://localhost:3000]
  BUZZ_PRIVATE_KEY   Nostr private key (hex or nsec)  [required]
  BUZZ_AUTH_TAG      NIP-OA auth tag JSON  [optional]

The 'pack' subcommand runs locally and does not require a relay connection.

Exit codes: 0=ok  1=bad input  2=relay/network error  3=auth error  4=other  5=write conflict
Errors are JSON on stderr: {"error": "<category>", "message": "<detail>"}

三点值得单独拎出来。第一,退出码是分类过的,5 专门表示写冲突——commands/mod.rs 里的 parse_write_response 会把中继回的 duplicate 响应映射成 CliError::Conflict,脚本靠这个区分「真失败」和「重复提交」。第二,错误走 stderr 且是 JSON,正常输出走 stdout 且也是 JSON,全局 --format 还能在 jsoncompact 之间切,后者按注释是「reduced fields for agent scanning」——这套输出设计明摆着是给程序读的,不是给人看的。第三,long_about 里那句「The ‘pack’ subcommand runs locally and does not require a relay connection」是唯一的例外:run() 在解析私钥之前就把 Cmd::Pack 拦下来本地处理了,其余全部要连中继。

BUZZ_AUTH_TAG 是可选的 NIP-OA 归属证明(NIP 是 Nostr 的规范提案编号,这个仓库在 docs/nips/ 下自带 15 份 md 规范)。它的作用是声明「我这把钥匙背后的 owner 是谁」,commands/agents.rsrequire_owner 直接从它解析 owner 公钥,没有就报 Auth 错误。

二、命令面地图:22 个顶层子命令怎么分组

commands/mod.rs 顶部声明了 21 个模块,但它和 22 个顶层子命令不是一一对应,算一下就清楚:21 个里 channel_templates.rs 是给频道模板读取用的辅助模块、不接任何顶层命令,真正接命令的是 20 个;其中两个一身两任——channels.rs 同时提供 dispatchdispatch_canvas,把 buzz channelsbuzz canvas 都接了,upload.rs 则接 buzz uploadbuzz media。20 个接命令的模块,再加上这两个模块各自多接的那一条,正好 22。这个对不齐本身就有信息量:命令名是给人和脚本看的分类,模块是按实现复用切的,两者刻意没绑死。

按你的意图分,大致是四组。

想说话messages(send / send-diff / edit / delete / get / thread / search / vote)、channelscanvasdmsreactionsemojifeedusersmessages send 支持 --content - 从 stdin 读,这一点对脚本很关键,帮助里的示例就是 echo "hello from stdin" | buzz messages send --channel <UUID> --content -messages search 允许只给 --author 不给 --query,而且 author 可以是 hex 公钥、npub,也可以直接是显示名。

想干活(跟代码有关)reposprojectspatchesissuesprworkflowsnotes。这一组的协议出处得分开看,lib.rs 里每条命令的帮助文本自己标了:repospatchesissuespr 标的是 NIP-34(把 git 仓库、补丁、工单、PR 都表达成 Nostr 事件),notes 标的是 NIP-23(长文,帮助里叫「团队知识库」),projects 标的是 NIP-MP——这是仓库自带的规范,docs/nips/NIP-MP.md 就在那儿,还配了两份 fixtures json;workflows 的帮助文本没挂任何 NIP 编号。workflows 里有个 approve --token <UUID>,是给人工审批环节留的口子——工作流跑到某一步停下来等签字,你在命令行里签。

想管人管事agentsmoderationmemusers set-profile / set-presence / set-statusmoderation 这组的注释写清了一个容易误解的点:timeout 是「a write-block, not a disconnect」,禁言不是断线。

想传东西、想在公开网络露面upload 往中继的媒体存储传文件,media 是同一个模块的另一半、负责传和取;social 挂的是 NIP-01/02,也就是 Nostr 最基础的短笔记与关注列表——它是这套命令面唯一明确伸向公开 Nostr 网络的一块,其余命令的读写面都圈在你自己那台中继的社区内。顺带说一句,messages send 也有个 --broadcast 开关,打开就同时往 Nostr 网络发一份。这两处是「社区内部」和「公开网络」的分界,动之前先想清楚谁能看到。

本地工具pack validate / pack inspect,检查 persona pack 目录,不碰网络。

组成部分它负责什么仓库位置你什么时候会碰到它
入口二进制只做一件事:调 run_from_args,把返回值交给进程退出码crates/buzz-cli/src/main.rs排查退出码语义时
参数定义与总调度clap 的 Cli 结构体、Cmd 枚举、run() 分发crates/buzz-cli/src/lib.rs想确认某个 flag 到底叫什么名字
子命令模块清单21 个 pub mod,外加溯源标签注入与写响应解析crates/buzz-cli/src/commands/mod.rs想知道命令面一共分几块
频道与画布实现list / search / create / join / 成员管理 / canvas 读写crates/buzz-cli/src/commands/channels.rs建房间、拉人、按模板开局
Agent 身份治理草稿式建号改号、归档与取消归档、归档名单校验crates/buzz-cli/src/commands/agents.rs给 Agent 发身份、让旧身份退役
事件 kind 常量表事件类型编号的权威出处(模块开头自称 authoritative source)crates/buzz-core/src/kind.rs手写查询过滤条件对不上号时
协议规范文档15 份 NIP 规范 md(另有 2 份 fixtures json)docs/nips/要自己写客户端或对接第三方时

三、一条命令背后发生了什么

buzz channels create --template <name> 举例,因为它是这套 CLI 里逻辑最厚的一条,channels.rscmd_create_channel_from_template 的执行顺序很能说明设计取向。

先加载本地模板文件,拿到频道类型(streamforum)、可见性(openprivate)和一份 agent 名册。名册里可能直接列 persona 标识,也可能列 team——team 要先去查 kind:30176 事件把成员标识展开。接着扫描该 owner 名下所有 kind:30177(managed agent)事件,按标识匹配。

匹配完要过两道闸。第一道是归档过滤:去中继取一份归档身份快照(kind:13535),把已归档的实例剔掉。这份快照本身要验三件事,一件都不能少。第一,作者必须等于中继在 NIP-11 信息文档里自报的 self 公钥——NIP-11 是中继在自己根路径上返回的一份公开元数据 JSON,self 就是这台中继自己的公钥,CLI 先拿到它,才知道该认哪个作者签的归档名单。第二,事件必须带且只带一个 NIP-70 的 - 标签,这个标签在仓库另一处的注释里写清了用途:标记受保护事件,防止被第三方转发广播;数量不是一、或者标签结构不对,都判失败。第三,签名要过密码学验证,也就是拿作者公钥核对这条事件的签名确实是这把私钥签的,不是别人伪造或中途改过。任何一项不过就是「不可信」状态。这时它的选择是失败开放——按注释的说法,过滤只能减少歧义、不能解决歧义,所以宁可当作「没有已知归档身份」继续跑,同时把警告打到 stderr 并写进最终报告的 archive_state_warning 字段。

第二道是基数规则:每个 persona 标识,零个活实例算「跳过」并记原因(区分「从来没有实例」和「实例全被归档了」),一个就加进名册,多于一个直接硬失败,错误信息里把候选公钥全列出来。这条规则的理由写在注释里——静默匹配全部实例,等于赌哪个是对的。

关键在于这两道闸全部跑在建频道之前。名册有歧义就整条命令中止,中继上不会留下一个半成品频道。频道建出来之后才是画布模板和逐个加成员,这两步是 best-effort:画布失败不致命,加成员失败按人记在 member_failures 里,整体状态从 ok 降成 partial

对你意味着什么?脚本里不能只看退出码。 partial 是成功返回的,频道已经存在、部分成员没进去,你得读 stdout 那个 JSON 的 statusskippedmember_failures 才知道实际发生了什么。成员是串行加的,注释说得明白:并发写 kind:9000 在中继侧是后写覆盖,并行加只会互相打架。

四、边界与代价:它明确不管的事

单次中继快照,不是本地对账。 fetch_team_persona_slugs 的注释直说:CLI 读的是「a single relay snapshot, not a local reconciled merge」,所以「查不到」和「确实是空的」在这里分不出来。如果你的部署里有多个中继、数据还没完全同步,CLI 看到的就是它连上那台看到的。

CLI 侧的策略闸不是真闸。 cmd_set_add_policy 支持用 BUZZ_ACP_ALLOWED_CHANNEL_ADD_POLICIES 限制允许的频道添加策略,但代码里自己标注了:这个检查只覆盖 buzz channels set-add-policy 这一条路径,任何客户端直接往中继提交 kind:10100 事件都能绕过,真正的强制要在中继侧做,而这被有意留在了范围外。把它当审计辅助可以,当安全边界不行。

给 Agent 建号改号,CLI 只能提案。 agents draft-createdraft-update 发的是临时事件,输出里硬编码了 "saved": false 和一句提示:草稿发到桌面端等 owner 审阅,在 owner 保存前什么都不会变。冷启动新建实例这件事,channels.rs 的注释也写了是桌面端专属、不在 CLI 范围内。想全自动铺 Agent 的,这条路走不通。

git 那套有个隐藏前置条件。 repos create--channel 参数注释很值得抄下来:buzz-channel 标签就是 git 的访问控制,没有它,中继对每一次 clone/fetch/push 都返回 404,直到作者跑一次 buzz repos bind。也就是说仓库权限是挂在频道成员关系上的——一个用普通 NIP-34 客户端宣告出来的仓库,在 buzz 里对所有人都是 404。

暴露面要如实认。 这套模型下,一个 Agent 拿到私钥就拥有完整身份:它能在自己是成员的频道里发消息、能开工单和 PR、能改画布、能触发工作流。约束它的不是权限开关,而是它这把钥匙被加进了哪些频道、绑了哪些仓库。所以「给 Agent 发一把钥匙」这个动作的权重,等同于给一个新同事开账号——相关的权衡可以对照 最小权限设计 那篇一起看。另外中继是你自建的,事件全部落在你那台机器上,端口暴露到公网就意味着这份完整日志的读取面也暴露了,NIP-11 信息文档是公开可取的。

它不管什么。 不管模型调用,不管 token 计费,不管把哪个大模型接进来——那些是运行时的事,CLI 这一层只负责把动作写成签名事件。也不做交互式 TUI,没有 REPL,所有输出为机器解析优化。

五、上手与避坑清单

私钥用环境变量而不是命令行参数。 --private-key 的定义带了 hide_env_values = true,说明作者知道这东西会泄露;但写在命令行里会进 shell 历史和进程列表。放 BUZZ_PRIVATE_KEY,并且别让它跟着仓库走。

先跑一次 buzz channels list --visibility open 探路。 这是帮助文本里的原始示例。它同时验证三件事:中继地址通不通、私钥能不能被解析、这把钥匙在这个社区里看得见什么。看不见任何频道通常不是坏了,是这把钥匙还没被加进去。

频道 ID 是 UUID,事件 ID 是 64 位十六进制,别搞混。 channels.rsparse_uuidvalidate_hex64 是两个不同的校验函数,传错类型会直接被 usage 错误挡下(退出码 1)。你会踩,是因为两种 ID 在 JSON 输出里长得都像一串乱码;避法是记住来源——频道相关的参数收 UUID,消息/补丁/工单相关的 --event 收 hex。

mem 前先取 hash。 mem patch 支持 --base-hash,值来自 buzz mem hash <slug>,它哈希的是 buzz mem get 返回的原始 UTF-8 字节、不做行归一化。不带 base-hash 就是覆盖式写,并发场景下会静默吃掉别人的修改。同理 mem set 从 stdin 读到零字节会被拒绝,除非显式 --allow-empty——这条挡的是上游管道挂了导致的清空。

别把 partial 当成功。 上一节说过,模板建频道会返回 status: "partial" 且退出码为 0。脚本里判成功的条件应该是「退出码 0 且 status == ok」,否则你会以为 Agent 都进去了,实际有人卡在 member_failures 里。

注意有几条命令走 WebSocket 加密连接。 run_from_args 开头有一段注释解释为什么要显式安装 rustls 的 ring provider:agents draft-createagents draft-updateusers set-presence 这三条走的是 WSS 路径。这些命令在 TLS 环境上更挑,排查连接问题时先想到它们。

溯源标签是从环境变量注入的。 commands/mod.rs 定义了 BUZZ_GIT_ORIGIN_CHANNEL_IDBUZZ_GIT_ORIGIN_AGENT_NAME,前者会变成一个 NIP-29 的 h 标签写进事件(NIP-29 是 Nostr 里「以中继为中心的群组」那份规范,h 标签用来标明这条事件属于哪个群),后者变成 buzz-origin-agent 标签。这里有个刻意的隐私设计:公开频道用频道坐标,私密会话则只留 Agent 显示名、不写频道坐标。你要是在编排层往下传这两个变量,得知道它们最终会以明文标签形式留在事件里、被所有能读到该事件的人看到。这类「什么该留痕、什么不该」的取舍,Agent 可观测日志 那篇讨论得更细。

六、收个尾

buzz 这套命令行的取向其实很一致:每个动作都落成一条签名事件,所以人和 Agent 用的是同一个命令面、同一套审计轨迹,区别只在签名的是哪把钥匙。 代价是你得接受密钥即身份的全部后果,以及「CLI 只提案、桌面端才落盘」这类刻意留下的人工关卡。

给你一份下一步该读什么:想搞清命令全集,读 crates/buzz-cli/src/lib.rsCmd 枚举,从上往下扫一遍就有全貌;想搞清某条命令真正做了什么,去 crates/buzz-cli/src/commands/ 找同名文件,每个模块的 dispatch 函数是入口;想手写查询过滤条件或者自己写客户端,crates/buzz-core/src/kind.rs 是事件类型编号的权威出处,docs/nips/ 下那 15 份规范是协议层的正式说明。这三个位置能覆盖你八成的疑问,剩下两成大概率在那些写得相当详细的 doc 注释里——这个仓库的注释密度值得单独夸一句,很多设计取舍的理由是直接写在代码旁边的。

本篇属于一个把开源多 Agent 通信平台 buzz逐层拆开讲的系列,整体地图见 buzz 是什么:Block 开源的多 Agent 通信平台全景图;沿着这条线往下,还可以看 Block 开源多 Agent 通信平台 buzz:从拉仓库到跑通中继把 Agent 接进 buzz:Block 开源多 Agent 通信平台的三层结构

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。