buslet
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@busletsend alice a message that the deploy finished, then check my inbox"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
buslet
中文 | English
本机 agent 之间的消息总线——local-first:默认只在本机跑,不依赖云;以后要跨机器,再通过受控的远程入口接出去。让两个(或多个)独立的 agent 进程互相收发消息,而不是所有话都从编排者那里中转。
只有一个运行时依赖:cron-parser(见定时任务)。其余全部是 Node 内置模块(net、node:sqlite、node:crypto),消息只落本机磁盘,不出网。需要 Node >= 22.13.0——node:sqlite 从这个版本起不再需要 --experimental-sqlite(22.5 引入,23.4 / 22.13 起转正;node cli.mjs version 会打印当前要求)。
这是一个独立项目目录:以下命令都在本目录(buslet/)里执行。名字的来历(bus + -let,以及内部线路 AKB-49)见 docs/origin.md。
npm install
node cli.mjs start状态(消息库、密钥、token)默认在本目录下的 .buslet/,已在 .gitignore 里。换目录运行时要显式 BUSLET_DIR。
为什么不直接用现成的
现成的"agent 通信"方案基本分两类:
云端的 agent 协议(A2A 之类):要注册、要暴露端点,消息出门。
PyPI 上的
agent-bus-mcp/crosstalk-mcp/agent-group-mcp:能用,但装进来的是第三方代码,且它们必然能看到你所有 agent 之间的明文消息。
这个实现的取舍是:协议用现成的 MCP(任何支持 MCP 的客户端都能接),传输用最小实现(Node 内置 net + 127.0.0.1),存储用 Node 内置 SQLite,代码几百行,可以自己读完再决定信不信。对这三个包的具体拆解见 docs/prior-art.md。
Related MCP server: agent-handoff-mcp
语义:追加日志 + 游标
消息是只追加的日志,不是会被取走的队列:
每条消息有全局递增的
seq;每个 agent 有一个游标(
cursors.last_seq),inbox返回seq > 游标的消息并推进游标;所以崩溃、重启、重复读都不会丢消息;
--peek读而不推进,--since N从任意位置重放;listen有自己的一套游标(按--consumer区分),不会吃掉inbox还没读的邮件;要让它也推进收件箱,加--consume;ask走独立的await通道按reply_to等回复,不动收件箱游标;拿到回复后只 ack 这一条(--keep可保留),所以既不会二次处理,也不会吞掉别的邮件;超时时回复仍留在 inbox;需要更细的控制时用
ack <seq>按条目标记已处理;广播只发给"发送时已经在总线上"的 agent,后来加入的不会被旧广播刷屏;
保留期默认 7 天 / 最多 2 万条,可用
BUSLET_RETENTION_DAYS、BUSLET_MAX_ROWS调整;死信默认同保留期一起过期(BUSLET_DEAD_TTL_DAYS可单独调),悬挂死信(正文已被清理)随清理周期一并回收;
代价:消息会落盘(bus.db)。如果某次对话不该留痕,结束后删掉 .buslet 目录即可。
它是被动的:不启动 agent
buslet 是消息总线,不是进程管理器。它不会启动、拉起、重启或叫醒一个没有在跑的 agent。
broker 能“唤醒”的只有已经存在的等待:
inbox --wait、listen、bus_wait、wait --group这类调用会被新消息或到期任务解除阻塞;这只是让调用方去读库,不是启动进程。agent 进程没在跑时,消息留在 SQLite;下次它启动并读
inbox才看到。没有 headless 命令的 agent(例如桌面版豆包)只能由人手动提示它去读总线;buslet 不会替它操作 UI,也不内置 CDP/UI 自动化。
轮询型客户端只按自己的调度读消息。豆包桌面版的定时任务最短 1 小时一次,所以它这一侧最坏延迟约 1 小时;buslet 不控制这个周期。
任何能在非交互环境执行命令的 agent,都可以用自己的调度器轮询
inbox;延迟等于它的调度周期。接入方式见 docs/onboarding.md。supervise只保证 broker 自己活着,不负责 agent 进程。
需要“消息到达后启动一个命令”的 runner 是另一个功能;目前没有实现,也不依赖任何不存在的 CLI。
安全模型
项目 | 做法 |
网络暴露 | 只 bind |
认证 | 每次启动随机生成 24 字节 token,存在 |
外发流量 | 没有。broker 不发起任何出站连接,不上报、不检查更新 |
落盘 | 只有本机 SQLite( |
生命周期 | broker 停了历史仍在;删掉状态目录即彻底清除 |
它防不住什么:同机器上能读你工作目录的其他程序/用户(token 和消息库都在 .buslet/ 里);本机管理员。回环流量不加密——需要防本机抓包就是另一个量级的设计(mTLS),这里没做。
多 agent 并行写文件
总线不能让别的进程写不了文件,但能把"谁在改哪个文件"变成可见、可协调的状态:
node cli.mjs claim README.md docs/limits.md --as dsh --ttl 600 --task t7
node cli.mjs claims
node cli.mjs release README.md docs/limits.md --as dshclaim是建议性租约:冲突时后到者拿到conflict和持有者(holders),不会静默覆盖;忘记释放会在 TTL 后过期(默认 900s)。它只约束愿意先 claim 的 agent;不 claim 的进程照样能写。真正的强制要么靠调度(同一文件同一时间只派给一个 agent),要么靠隔离(每个 agent 一个
git worktree/分支,最后合并)。同一 checkout 里用仓库相对路径;跨 worktree/不同机器用绝对路径——claim 按字符串比较,Windows 下不分大小写。
claim 存在 broker 内存里:broker 重启即清空,适合"短时间改一批文件",不是分布式锁。
配套的调度约定:任务消息里写死文件范围;派发者不再碰已派出的文件;写前 claim、提交后 release。
评审也走总线
代码修改意见可以直接发到对方的 inbox,不需要另开一个聊天窗口:
# 评审方:对着任务(seq 130)发意见
node cli.mjs send --to workbuddy --kind review --thread t7-mcp --reply-to 130 `
--meta '{"files":["mcp-server.mjs"],"severity":"blocking"}' `
--text "listResources 里 whoami 失败时不应该缓存 false,重连后会一直少 groups。"
# 双方都能读整条 thread(绑定 token 只看到自己发/收的)
node cli.mjs thread t7-mcp
# 作者回复
node cli.mjs send --to dsh --kind review-reply --thread t7-mcp --reply-to 138 --text "改了,见 3f2a1c。"kind只是标签(review/review-reply/question都行),真正把对话串起来的是thread和reply_to;meta适合放结构化字段(文件、行号、severity),正文放人读的意见;评审是异步的:消息进 inbox,对方下次
inbox/thread才看到——它不会打断正在跑的 agent;要"阻塞等一个答复"才用
ask,且双方都得在线。
完整格式、可见性规则和 T7 那次"任务前提被代码推翻"的例子见 docs/review.md。
快速开始
# 起 broker(后台常驻,自动脱离终端;CLI 也会自动拉起)
node cli.mjs start
# 发 / 收
node cli.mjs send --to bob --from alice --text "任务"
node cli.mjs inbox --as bob --wait 60
node cli.mjs listen --as bob # 实时流
node cli.mjs log --limit 20 # 看总线上最近的流量请求/响应:
# alice 发问并阻塞等回复(拿到 question 的 seq,回复用 --reply-to 关联)
node cli.mjs ask --as alice --to bob --text "6*7 等于几?" --timeout 60
# bob 侧:收件箱里看到 #2 {question},回复时带上 --reply-to 2
node cli.mjs send --from bob --to alice --text "42" --kind answer --reply-to 2命令
命令 | 说明 |
| 管理 broker |
| 体检:库大小、保留策略、积压、死信、过期租约;有警告时退出码 1 |
| 打印可直接粘贴的 MCP 配置(路径绝对化,可带上当前 token) |
| 直接把 MCP 条目写进客户端配置(workbuddy / cursor / claude / vscode / codex;默认只打印, |
| 在线备份:SQLite 快照 + 密钥 + sha256 清单(broker 不用停) |
| 校验清单、停 broker、覆盖状态、重启;原状态留在 |
| 本机只读面板:peers、组水位、死信、调度、最近流量 |
| 每个 agent 的 token:签发、查看、吊销、看当前绑定 |
| 查看静态加密状态; |
| 已知 agent 与最后活动时间 |
| 最近 N 条流量(审计视角,不分收件人) |
| 发消息; |
| 取消息; |
| 订阅推送(NDJSON);独立游标, |
| 发问并等 |
| 把某条消息标记为已处理(不再出现在 inbox) |
| 列出共享同一 thread id 的所有消息 |
| 一条消息的完整投递轨迹:队列、尝试次数、租约、ack、死信 |
| 从消息日志投影任务板(读 |
| 从消费者组领活(租约制、在途上限) |
| 确认组里的活已处理,租约结束、不再重投 |
| 没做完:释放租约,按退避/指定秒数排下一次 |
| 每个组的积压:total / acked / leased / dead / sched / pending / 尝试上限 |
| 阻塞到该组落定(无 pending/leased/scheduled);超时退出码 4 |
| 清空一个队列 / 清空所有消息和队列状态(admin 才允许) |
| 列出死信(重试耗尽的活) |
| 把死信放回队列,尝试次数清零 |
| 固定间隔任务: |
--dir PATH 对所有命令都有效,用来指定状态目录。--json 不是所有命令都有:listen 本身就是 NDJSON 流(一行一条消息);supervise / ui / connect 是把控制权交给前台子进程,不存在"换输出格式";mcp-config 打印的本来就是 JSON 配置;stop / uninstall 只有几行人读状态。这几个传了 --json 也不会变成机器可读。start 特殊:默认打一句人读的,显式传 --json 才输出状态对象。其余命令(status、version、doctor、peers、log、thread、trace、tasks、groups、dead、inbox、send、work、wait、ack、ask、reset、secure、backup、restore、install、service,以及 schedule list、auth list / auth whoami)都支持 --json。
消费者组(工作队列)
发给 group:名字 的消息不进任何人的个人收件箱,而是进一个队列,由 work 领取:
node cli.mjs send --from alice --to group:builders --text "job 1"
# 三个 worker 同时跑,每条活只会交给其中一个
node cli.mjs work --as w1 --group builders --max 2
# #18 ... job 1
# #19 ... job 2
# leased 2 for 60s — ack with: buslet ack --group builders 18 19
node cli.mjs ack --group builders 18 19
# 协调者不用轮询:阻塞到队列"落定"(没有 pending/leased/scheduled)
node cli.mjs wait --group builders --timeout 300
# settled: total 2, acked 2, pending 0, leased 0, scheduled 0, dead 0语义:
每条消息只交给一个 worker:领取时写入租约,其他人看不到;
at-least-once:
--lease秒内没ack,租约过期后重新可见;失败先退避:
nack(或租约过期)不会立刻重投,而是按base * 2^(n-1)+ 抖动排到下个时间点,默认 base 5s、上限 300s(BUSLET_BACKOFF_BASE/BUSLET_BACKOFF_CAP)。到点由 broker 唤醒正在等待的连接(不是启动 worker 进程);进度不靠游标:组内已处理的消息记在
group_acks,所以重启、换 worker 都不会重放;不让人吃独食:每个 worker 在途(已领未 ack)有上限,组级
--max-inflight(默认 10,0 = 不限)。领满了就返回throttled,把剩下的留给别人;优先级:
send --priority N(默认 0),work按priority DESC, seq ASC取。适合"紧急单插队到批量任务前面";可选老化(aging):设BUSLET_PRIORITY_AGING_SECONDS=N后,每多等 N 秒有效优先级 +1,被高优先级洪流饿住的低优先级任务最终能排上(默认 0 = 关闭,doctor可见);失败有刹车:同一条消息投递满
--max-attempts次(默认 5)就进死信,不再占用 worker。这个上限是组级配置:work里显式传了才改,之后全组按最后一次传的值算;work返回里带attempts / max_attempts / held / throttled和stats,可以判断重试剩几次、自己占了几个槽、队列是否见底;groups一次看所有组:total / acked / leased / dead / scheduled / pending / 尝试上限(--json里还有max_inflight)。
# 做不完就先还回去,3 秒后再来(不写 --retry-in 就用指数退避)
node cli.mjs nack --group builders 28 --retry-in 3
# 紧急任务插队;每个 worker 最多同时占 2 个
node cli.mjs send --to group:builders --text "hotfix" --priority 10
node cli.mjs work --as w1 --group builders --max-inflight 2死信要人管,不会自动复活;但会过期:默认跟保留期一样(7 天)后由清理周期回收,正文已被保留期删掉的悬挂死信也会一并清掉:
node cli.mjs dead --group builders # 列出死信、原因、原文
node cli.mjs requeue --group builders 28 # 修好之后再放回队列(尝试次数清零)MCP 里对应 bus_work / bus_ack / bus_nack / bus_groups / bus_dead / bus_requeue。
定时任务
两种写法:固定间隔,或 cron + 时区。
node cli.mjs schedule add --group builders --every 5m --text "巡检队列水位"
node cli.mjs schedule add --group standup --cron "0 9 * * 1-5" --tz Asia/Shanghai --text "工作日早会"
node cli.mjs schedule list
node cli.mjs schedule remove tYnKHlNPjO7O到点就往组里投一条
job消息(from是scheduler),走的是普通队列语义:优先级、重试、死信都照常;投递和推进
next_at在同一个事务里,所以崩溃不会重复触发;停机期间错过的窗口不补发——启动时最多补一次"已到期"的触发,不会连发一串;
消息
meta带{schedule_id, fire_at},消费者要去重可以用它。
--every 接受 30s / 5m / 1h / 1d / 500ms。cron 用标准五段式,--tz 是 IANA 名称(默认 broker 本地时区):夏令时跳变里不存在的时刻会跳过,重复的时刻只触发一次。细节见 docs/scheduler-options.md。
现成的派发器示例(生产者 + 两个 worker + 失败重试)在 examples/:
node examples\demo.mjs接入 MCP 客户端
mcp-server.mjs 是 stdio MCP server,暴露 17 个工具:bus_peers、bus_send、bus_inbox、bus_wait、bus_ask、bus_log、bus_thread、bus_trace、bus_work、bus_ack、bus_nack、bus_groups、bus_wait_group、bus_schedule、bus_dead、bus_requeue、bus_backup(仅 admin token——备份里含解密全库的 key)。
"流式"在 MCP 里有两个层次:
bus_wait—— 长轮询,阻塞到有新消息或超时(最长 1800s),每次返回一条。所有 MCP 客户端都能用,这是可移植的那条路。resources + 订阅 —— 支持 resources 的客户端可以读
buslet://inbox/<name>和buslet://groups;支持订阅的客户端在resources/subscribe后会收到notifications/resources/updated,再自己resources/read。
注意 MCP 没有"服务端把内容推进模型上下文"的标准能力,所以真正的推送只对支持 resources 的客户端成立;其他客户端等价于轮询。细节和限制见 docs/limits.md。
不要手写路径,让 CLI 生成(它会把 BUSLET_DIR 钉成绝对路径,避免客户端用不同的 cwd 启动 server 时连到另一个状态目录):
node cli.mjs mcp-config --name claude-code{
"mcpServers": {
"buslet": {
"command": "node",
"args": ["C:\\...\\buslet\\mcp-server.mjs"],
"env": {
"BUSLET_NAME": "claude-code",
"BUSLET_DIR": "C:\\...\\buslet\\.buslet"
}
}
}
}身份从 BUSLET_NAME 来,工具参数里没有 from——模型没法以别人的名义发言。每个参与的 agent 用不同的名字。
没有 MCP 的 agent(只会跑 shell 的),直接用 CLI。
身份绑定(可选)
默认只有一个 admin token(在 bus.json 里):任何拿到它的进程都能用任意名字发言、读任何人的收件箱。要收紧,就给 agent 发各自的 token:
node cli.mjs auth add claude-code # 只打印一次,保存在环境变量里
# 然后让这个 agent 带 token 启动:
$env:BUSLET_TOKEN = "abk_..."可以再收窄到"只读"或"只发"的 bot,并加有效期:
node cli.mjs auth add reviewer --scope read --ttl 7d
node cli.mjs auth add build-bot --scope send,work --ttl 30dscope 有四个:send / read / work / schedule(ack 个人消息算 read,组 ack 算 work)。每个 scope 可以再加窄到单个目标:
node cli.mjs auth add reviewer --scope read --ttl 7d
node cli.mjs auth add bot --scope send:alice,send:group:builders,work:builders
node cli.mjs auth add cron-bot --scope schedule:builderssend:alice 只能发给 alice,send:group:builders 只能发给该组,work:builders 只能领这一个组,schedule:builders 只能给该组建调度。不带 scope 的 token 保持原来的全量 agent 权限;到期后 token 直接失效,auth list 里状态是 expired。这是允许列表,没有"排除"或按消息内容过滤。
这个 token 会被 broker 绑定到名字:
不能以别的名字
send/hello/read/work/ack(返回forbidden);不能读别人的收件箱;
不能读总线级视图:
log、dead、stats、doctor都是操作员动作;thread/trace只能看自己发、收或经手的消息(别人的forbidden);schedule list/remove只能看到、只能删掉自己创建的定时任务(created_by);peers/groups/wait_group这类只有名字和计数的视图仍然开放;不能发 token、不能
requeue、不能关 broker。
broker 只存 sha256(token),auth list 看得到谁有 token、最后用是什么时候,auth revoke NAME 立刻失效。auth whoami 显示当前 token 绑到谁。换 token 用 auth rotate NAME:原地替换,旧 token 立刻失效,scope 和过期时间保持不变。
MCP 侧把 token 写进 env 块即可:mcp-config --name X --with-token 会从当前环境读 BUSLET_TOKEN 一起输出。
一条命令写进客户端配置(默认只打印,--write 才落盘,先备份):
node cli.mjs connect --client workbuddy --name workbuddy --issue-token --write
node cli.mjs connect --client cursor --name claude-code --token $env:BUSLET_TOKEN --write
node cli.mjs connect --client vscode --name vscode --token $env:BUSLET_TOKEN --write
node cli.mjs connect --client codex --name codex --token $env:BUSLET_TOKEN --writeclient | 文件 | 格式 |
|
|
|
|
|
|
| Claude Desktop 的 |
|
|
|
|
|
|
|
vscode是工作区级配置,默认写当前目录的.vscode/mcp.json;用--config指到别的项目。codex会替换已有的[mcp_servers.buslet]表,保留其他表和注释;写前有.bak-*备份。其他客户端用
--config PATH --format json|vscode|toml,或直接打印出来手贴。
看总线:node cli.mjs ui 起一个只 bind 127.0.0.1 的只读面板(peers / 组水位 / 死信 / 调度 / 最近 40 条),两秒刷新;最近消息可以按组、按 agent、按关键词过滤(点 peers/组那一行也能直接筛),点一条消息看它的 trace。/api/state 要页面内嵌的随机 key,别的网页读不到。
接一个新 agent(WorkBuddy、Claude Code、Codex…)的完整步骤见 docs/onboarding.md。
它防不住什么:同机同用户的进程仍然能读 bus.db、.buslet/key(有钥匙就能解密)和 bus.json(admin token)。这是"防误用/防冒充"而不是"防恶意本机进程",完整威胁模型见 docs/limits.md。
静态加密
消息正文和 meta 用 AES-256-GCM 加密后落盘;路由字段(seq / from / to / kind / ts / thread)保持明文,索引和查询不受影响。
node cli.mjs secure # 看状态
node cli.mjs secure --migrate # 把旧库里还没加密的行一次加密密钥在首次启动时自动生成到
.buslet/key(0600)。也可以用BUSLET_KEY(base64 的 32 字节)或BUSLET_KEY_FILE指定,BUSLET_ENCRYPT=0关闭加密。旧库里的明文行仍然可读;迁移是显式的,不会在启动时偷偷改写你的数据。
换错密钥读出来的是
[encrypted: wrong key],不会崩溃。代价:直接对
bus.db做strings/grep 已经看不到内容了;log/thread仍然可读,因为 broker 解密后输出。
它防不住什么:密钥和库在同一个目录,同用户进程能同时拿到两者。它防的是"只拷走了 bus.db"——备份、同步盘、误发文件、把库贴给别人。要防本机攻击者,得把密钥放到别处(BUSLET_KEY 从环境注入、库放到另一个目录)。
备份与恢复
node cli.mjs backup --out D:\backups\buslet-2026-10-29broker 不用停:用 SQLite 的
VACUUM INTO取一致性快照,源库只读不写;产物是一个目录:
bus.db、key(如果启用了加密)、manifest.json(版本、消息数、每个文件的 sha256);默认输出到当前目录的
backups/buslet-<时间戳>/,--json输出清单。
恢复:
node cli.mjs restore D:\backups\buslet-2026-10-29 --force先按
manifest.json校验每个文件的 sha256,坏包直接拒绝;broker 在跑时默认拒绝,
--force才会停它;覆盖前把当前
bus.db/key存到<state>/restore-undo-<时间戳>/,并清掉旧的-wal/-shm;覆盖后自动重启 broker、读一次统计;启动失败会把 undo 拷回去并重启旧状态;
手工恢复仍然是:
stop→ 覆盖bus.db+key→start。
只备份 bus.db 不备份 key,等于拿到一堆密文;反过来只拷 key 也没用。
保活与自启
broker 是 CLI 按需拉起的 detached 进程;机器重启或进程崩了,它不会自己回来。要让它一直在,用监督进程:
node cli.mjs supervise # 前台跑:broker 死了自动重启(指数退避,最多 30s)
node cli.mjs install # 在启动目录写一个条目,登录时拉起 supervisor
node cli.mjs service # 看 autostart / supervisor / broker 三者的状态
node cli.mjs uninstall # 移除启动条目supervise会收养已经在跑的 broker,不会起两个;它每 2 秒检查一次,发现挂了就重启。install只往用户启动目录(Windows 是%APPDATA%\...\Startup,Linux 是~/.config/autostart)写一个.cmd/.desktop,内容就是node src/supervise.mjs --dir <绝对路径>——没有注册表、没有计划任务、没有 COM,卸载就是删文件。写入失败(比如受限环境)会打印可以手工粘贴的完整命令,退出码 3。
stop会先把 supervisor 停掉再关 broker,否则 supervisor 会立刻把 broker 拉回来。
单文件分发
npm install
npm run build:sea
# dist\buslet.exe (~96 MB,内含 Node 运行时)一个 exe 同时是 CLI、broker 和 MCP server(--broker / --mcp 是内部参数)。收发、组队列、调度、auth、backup/restore、trace 都在;ui / connect / 自启守护只在源码安装里提供,单文件版会明确报错。mcp-config 会直接输出 command=buslet.exe, args=["--mcp"] 的配置。
细节(平台签名、状态目录、升级、验证命令)见 docs/distribution.md。
排障
node cli.mjs doctor示意输出(端口、pid、组数、消息数都会随库和时刻变化,别把这里的 "13 个组" 当成固定值):
broker v0.19.0 port 49238 pid 19976 up 0s
database 152 KB (wal 4 KB) messages 55 max seq 55
retention 7d / 20000 rows dead letters 7d last prune 2026-10-29T14:00:18Z (removed 0, dead 0)
peers 16 known, 0 online
groups 13 — pending 12, leased 0, scheduled 0, dead 1
leases 0 active, 10 expired
aging off
auth 1 active agent token(s), 3 ever issued
encrypt on — 55/55 message(s) encrypted
schedules 0
! 1 dead letter(s) in group "demo-dead" — run: buslet dead --group demo-dead
! group "wake" has 1 pending and no worker holding anything有警告时退出码是 1,可以直接塞进脚本。--json 给机器读。检查项:broker 在不在、库多大、保留策略上次清了多少、组积压/死信/在途、过期租约、token、加密、调度。
再往下就是 groups(哪组积压)、dead --group(哪条卡住)、log(最近流量)。
协议
TCP 回环 + NDJSON,一行一个 JSON 对象:
// 请求:每个请求都要带 id 和 token(token 是 bus.json 里的 admin token,或 auth add 发的 abk_...)
{"id":1,"token":"...","cmd":"send","from":"alice","to":"bob","text":"hi","kind":"message","reply_to":null}
{"id":2,"token":"...","cmd":"read","as":"bob","wait":30,"max":50,"peek":false,"since":null}
{"id":3,"token":"...","cmd":"await","as":"alice","reply_to":2,"wait":120,"consume":true} // 等 #2 的回复
{"id":4,"token":"...","cmd":"ack","as":"bob","seq":7}
{"id":5,"token":"...","cmd":"work","as":"w1","group":"builders","max":2,"lease":60,"wait":0}
{"id":6,"token":"...","cmd":"ack","group":"builders","seqs":[18,19]}
{"id":7,"token":"...","cmd":"subscribe","as":"bob","consumer":"listen","consume":false} // 先回一行确认,之后同连接持续推流
{"id":8,"token":"...","cmd":"trace","seq":42}
{"id":9,"token":"...","cmd":"groups"} // peers / stats / log / thread / shutdown 同样格式
// 响应
{"id":2,"ok":true,"messages":[{"seq":3,"id":"...","from":"alice","to":"bob","kind":"message","text":"hi","ts":"...","reply_to":null,"thread":null,"meta":null}],"cursor":3,"has_more":false}投递语义:个人收件箱是追加日志 + 每 agent 游标(至少一次、可重放),subscribe 用独立的 sub_cursors,与 read 互不影响;acks(name, seq) 记录按条目的"已处理"标记。group: 地址走另一套:租约 + group_acks,每条消息只交给一个 worker,租约过期重投。
完整命令、错误码和兼容约定见 docs/protocol.md。
文件
buslet/
README.md # 中文说明
README.en.md # English README
AGENTS.md # agent 操作约定(启动时先读)
package.json # 唯一的运行时依赖:cron-parser
.github/workflows/ci.yml # CI:ubuntu/windows/macos × Node 22/24
.gitignore # node_modules / .buslet / .tmp
cli.mjs # 命令行入口
mcp-server.mjs # MCP stdio 入口
entry.mjs # 单文件入口:cli / --broker / --mcp
src/
broker.mjs # 常驻进程:回环监听、token、订阅推送、等待、到期唤醒等待连接
store.mjs # SQLite:schema、追加写、游标、租约、死信、退避、加密
client.mjs # 发现 + 自动拉起 broker + RPC
cron-adapter.mjs # cron 解析/下次触发(薄封装 cron-parser)
service.mjs # 自启条目(Startup 目录)+ 状态
supervise.mjs # 守护进程:broker 挂了自动重启
config.mjs # 生成 MCP 配置
connect.mjs # 一条命令写进客户端配置(workbuddy / cursor / claude / vscode / codex)
backup.mjs # VACUUM INTO 在线快照 + key + 清单
restore.mjs # 校验、停 broker、覆盖、重启(带 undo 回滚)
ui.mjs # 本机只读面板(HTTP + 内联页面)
version.mjs # package.json 注入(SEA 构建时内联)
notify.mjs # Windows 桌面通知(任务派发提醒)
examples/ # 派发器模板(worker + handler + demo)
test/bus.test.mjs
tools/ # smoke(工作流演练)、verify(一条命令跑全部)
docs/onboarding.md # 接入一个新 agent(WorkBuddy 等)
docs/prior-art.md # 同类实现怎么做的
docs/prior-art/ # 两家的逐行分析
docs/scheduler-options.md # 三档调度的设计与边界
docs/rehearsal.md # 三 agent 真实演练记录
docs/limits.md # 已知边界汇总
docs/protocol.md # RPC 协议规格(第三方客户端照着实现)
docs/review.md # 评审走总线的格式与可见性规则
docs/origin.md # 名字来历与 AKB-49 内部代号
docs/remote.md # 远程/多机设计 + 阶段 1 传输层(实验)
docs/collaboration.md # agent 协作约定(角色/任务状态/消息格式)
docs/release-notes/ # 每个 tag 的 release 说明(中英)
docs/distribution.md # 单文件可执行(SEA)构建与平台说明
docs/testing.md # 跑测试的三条约定与宿主环境的坑
tools/build-sea.mjs # npm run build:sea
LICENSE # MIT测试:
npm test # 在 buslet 目录下:单元/集成测试 + 工作流 smoke,一条命令只需要其中一层时用 npm run test:unit / npm run smoke。三层都不预起 broker、不联网。测试脚本的三条硬约定(每次独立目录、删目录前轮询 pid、收尾失败不改退出码)以及宿主环境把 fs.rmSync 换成 trash 时的表现,见 docs/testing.md。
=== summary ===
PASS unit + integration (18.2s)
PASS workflow smoke (priority, 2 workers, retry, wait, outputs) (1.5s)
ALL PASSED也可以单独跑:npm run test:unit、npm run smoke。失败时停在第一个失败的步骤,退出码就是那一步的。
(不要用 node --test,它需要 spawn 子进程,在受限沙箱里会 EPERM。)
已知限制
完整的边界清单(传输、投递语义、组队列、调度、MCP、存储、运维)在 docs/limits.md。最常踩的几条:
状态目录默认
<cwd>\.buslet。跨目录/跨工具时用BUSLET_DIR指到同一处;别指系统临时目录——每个 agent 会话的TMP往往是私有的,会各起一个 broker 互相看不见。没有"处理完成"确认:
read推进游标后进程崩溃,那批消息就跳过了(要严格至少一次,用--peek读完再手动--since推进)。回环不加密、token 同用户可读:同机同用户的进程可以冒充任何身份。
组消息至少一次投递,长任务要自己幂等;死信要人工
requeue。优先级老化默认关闭;开启后不设上限,极老的低优先级任务最终会超过新的高优先级任务(这是反饥饿的本意)。
MCP 没有服务端推送进模型上下文:只能用
bus_wait长轮询,resources 订阅取决于客户端。
许可证
MIT,见 LICENSE。
This server cannot be deployed
Maintenance
Related MCP Connectors
End-to-end encrypted messaging and work coordination for autonomous AI agents.
Messaging and inboxes for AI agents: register, send signed messages, check your inbox, find agents.
Durable agent-to-agent handoffs and shared scratchpad for multi-agent workflows.
Durable background job execution, async task scheduling, and state persistence for AI agents.
Related MCP Servers
- AlicenseAqualityBmaintenanceEnables local AI coding agents to message each other on one machine using a durable SQLite mailbox and live-ask tools.91MIT
- AlicenseNot gradedqualityCmaintenanceEnables AI agents to exchange structured work items with an auditable lifecycle, supporting send, acknowledge, block, complete, and cancel operations via a shared SQLite-backed inbox.454 npm2Apache 2.0
- AlicenseBqualityBmaintenanceEnables AI agents from different frameworks to communicate asynchronously via a persistent SQLite-backed bus, with doorbell wakeups for sub-second notifications and full message history.6MIT
- AlicenseNot gradedqualityBmaintenanceEnables coding-agent sessions on the same machine to collaborate locally and durably by exchanging requests, replies, and task results through an authenticated loopback server with SQLite-backed storage, joined via CLI or an MCP stdio bridge. It also handles message delivery by priority and wait time, deduplicates idempotent sends, enforces task versioning, and preserves artifacts by content hash.10 npmApache 2.0