Skip to main content
mcode-agent-table 架构:每个人带自己的 bot 来,我们负责摆桌子
在一个互相信任的团队里,每个人每天都在用自己的 bot 干活。人负责把会上、群里的讨论带给 bot,再把 bot 的结果转述给同事。我们想试的是:直接把大家的 bot 放到一张桌子上,一起推进一个项目。
我们已经把这套协作方式写成了协议 BYOB Table Protocol v0.1,并把协议规范、JSON Schema、桌子宿主、MCode 接入端和 MCP server 都开源。👉 MiniMax-AI/mcode-agent-table

从 Personal Agent 到 Agent Team

我们团队现在的日常是这样的:每个人身边都有一个 bot,写代码、查问题、写文档、排日程,大部分活是 bot 在干。人做的事变了,更多是在两个世界之间搬运:把会上、群里、和同事聊出来的东西带给自己的 bot,让它去解决;bot 做完,再把结果整理一遍,发给其他人。 这条链路里,人成了中继。同事的 bot 想知道我的 bot 查到了什么,得先经过我,再经过同事,每转一手都会丢信息、加延迟。一个项目四五个人、四五个 bot,人花在转述上的时间,常常比 bot 干活的时间还长。 在一个本来就互相信任的团队里,这层中继可以拿掉。大家信得过彼此,也信得过彼此的 bot,那就让 bot 直接坐到一起:共享项目进展,彼此讨论、分工、交接,人只在需要拍板的地方出现。项目不再靠人在 bot 之间来回传话推进,而是一桌 bot 在人的注视下推进。这是一种新的协作体验。 所以 Personal Agent 之后,我们要做的是 Agent Team。队伍里每个 bot 都有自己的主人,用的也未必是同一家的产品:有人用 MCode,有人用 Grok Bot。 我们称这个现象叫 BYOB,Bring Your Own Bot。每个人带自己的 bot 入座,bot 带着主人的记忆、身份、权限和账号。桌子不拥有、也不托管任何一个 bot,只给它们一张桌子:共享账本、席位、发言权和一个提交网关。

为什么单个 Personal Agent 会碰到天花板

既然团队互信,最省事的做法似乎是让一个 agent 替全队干活,或者让一个平台托管所有人的 agent。小范围内确实省事,但会碰到四个上限。 **第一,上下文是预算。**它受 prompt cache 成本约束。一个 agent 要同时理解你的邮件、团队的代码库、每个同事手上的进度,前缀就会不断膨胀,缓存命中越来越差。主循环加执行单元的结构能处理“一个人的多件事”,处理不了“多个人的多件事”,因为别人的上下文本来就不该进你的前缀。 **第二,身份和权限只能属于一个主人。**你的 agent 能动你的日历,不能动同事的日历。要让一个 agent 替整个团队行动,就得把所有人的账号授权都集中给它(或给它背后的平台)。互信是人和人之间的,不等于把所有人的账号交给同一个 agent,团队再小,这一步也没人愿意迈。 **第三,一种视角。**同一个模型、同一套 prompt 演出来的“多个角色”,其实是一个人在自言自语。有用的分歧来自不同主人手里不同的信息和利益:产品经理的 bot 知道客户承诺了什么,研发的 bot 知道哪块代码不能碰。 **第四,账号的归属。**Personal Agent 最值钱的东西是它对主人长期积累的记忆和接入的账号。这些不会、也不应该迁移到某个“协作平台”上。用户愿意让 bot 去开会,不等于愿意把 bot 交给会议室。 所以即使在互信的团队里,协作的单位也应该是各自独立的 bot,同一个系统里扮演出来的多个角色替代不了它们。互信改变的是规则的松紧:大部分动作可以设成自动批准,但边界还在。 图 1:平台托管 vs BYOB。左边所有 agent 在一个租户里;右边 bot 归各自主人,桌子只管协作规则

BYOB:桌子不拥有 bot

企业 IT 经历过类似的转变。BYOD(自带设备)之前,公司发电脑、管电脑;之后,员工用自己的手机办公,公司只管“能接入什么、数据怎么隔离”。BYOB 的思路类似,但对象换成了有记忆、能行动的 agent,要求更高:设备不会自己决定把你的邮件内容告诉别人,bot 会。 BYOB 下,桌子和 bot 的分工是这样的: 和今天的“平台托管”比,最大的区别在最后两行。平台托管时,平台既管协作也保管凭证;BYOB 下,桌子只管协作,凭证留在每个主人自己的系统里。 这样做要回答三个问题:
  • **互操作。**不同厂商的 bot 要能听懂同一套“入座、发言、提议、回执”消息,这需要一个开放协议,某一家的私有 API 做不到。
  • **互信,但有边界。**每个 bot 只能以自己主人的权限行动。你的 bot 在桌上说“我把会议改到周四”,只能改你的日历;要改别人的日历,必须变成一条提议,由那个人(或者他授权的规则)批准。团队越互信,自动批准的规则就可以写得越宽。
  • **隐私。**bot 带着主人的记忆入座,但不能把记忆倒在桌上。它说出口的每句话,都等于主人的一次信息披露,需要有披露规则约束。

Table 是什么:一个协作原语

我们对“桌子”的工作定义是:
Table 是一份只追加的共享账本,加上一组决定“谁能读到什么、谁能在什么时候说话、什么时候结束”的策略。席位是账本的读写入口,每个席位绑定一个 bot 和它的主人。
它由五部分组成:
  1. **账本(ledger)。**所有发言、提议、决议、回执按序号追加,Table Core 是唯一写者,条目只增不改。mcode-agent-table 把账本存成 JSONL,服务重启后按账本重放,floor、席位、任务和预算的状态都能原样恢复。桌子常驻的是账本,没有哪个 bot 的进程需要一直在桌上。
  2. **席位表(membership)。**每个席位记录 bot 的身份、所属主人、接入方式,以及一张能力卡:它能动哪些资源、做哪些动作。入座请求要带主人的 Ed25519 签名和一次性 nonce,桌子用主人公钥验过才放行,别人冒充不了。
  3. **寻址。**消息可以发给全桌、某个席位、某个角色(“主持人”“评审”),或某个子桌。寻址是策略可见性的基础。
  4. **拓扑策略。**决定可见性和发言规则,见《任意拓扑:拓扑是策略,不是代码》一节。
  5. **提交网关。**账本里的东西都只是“说”;要对外部世界“做”,必须经过提交网关,见《记忆与权限:提议和提交分开》一节。
图 2:Table 的分层架构。Table Core 只持有协作状态;每个 bot 的推理、记忆和凭证都留在主人那边 协议里的一条账本条目长这样:
effects 写明这条决议会动谁的什么资源,审批按它路由,不靠 bot 自己判断。budget 记下这条发言花了多少 token。条目还可以带 refs,里面只放 entry:<seq> 这样的引用 ID、不放内容,需要时再按引用逐跳展开。

任意拓扑:拓扑是策略,不是代码

多 agent 系统常见的做法是为每种协作形态写一套编排代码:辩论一套、流水线一套、主管和下属一套。每套单独维护,彼此切换不了。 在 BYOB Table Protocol 里,拓扑就是三件事:
  • **可见性:**每个席位能读到账本的哪些条目;
  • **发言规则:**当前谁拿着 floor,说完以后 floor 交给谁;
  • **终止条件:**达成决议、轮次用完、token 预算耗尽,或条目数到顶。
同一本账本换一组策略,就是一种新拓扑,数据结构不变,席位也不用重新接入。mcode-agent-table 里已经有 broadcast(轮流发言)、debate(正反方加裁判)、pipeline(流水线)、hub_spoke(主持人点名)、delegation(按汇报关系逐级委派)五种策略,再加上子桌。 图 3:五种拓扑加动态子桌,都表达成同一本账本上的策略 一张辩论桌的配置是这样:
动态子桌是“任意拓扑”能成立的关键。圆桌讨论到一半,发现两个 bot 要对一个接口细节反复确认,没必要让全桌陪着。
这时开一张子桌,带 parent 指向父桌,只拉相关席位进去,它有自己的席位、拓扑和账本。子桌关闭时,Table Core 只往父桌追加一条 subtable_result,里面只有子桌的决议和引用,其他细节都留在子桌里。子桌可以继续嵌套:主桌是主持人点名,子桌是辩论,辩论的一方内部又可以是逐级委派。

发言权与终止:防止 bot 互相回复停不下来

把几个 bot 拉进同一个 IM 群,最先遇到的问题是它们停不下来。每个 bot 都倾向于有人说话就回应:A 回 B,B 谢 A,A 再补一句,很快就刷满屏。 靠 prompt 让每个 bot 自觉“不该说就别说”并不可靠,模型的临场判断不稳定。所以 floor 在 Table Core 里是硬约束:
  • **没有 floor 的席位写不进发言。**say、pass、proposal、decision 只有 floor 持有者能写,其他席位会收到 E_NOT_FLOOR,可以写 raise_hand,由策略决定什么时候轮到它。人类席位随时可以说话。
  • **每次授予 floor 都带预算。**授予记录里写明字数上限,超了由 Table Core 直接截断并标上 truncated,不靠 bot 自己收尾。整张桌还有 token 总预算。
  • **bot 对 bot 的回复有深度限制。**人类发言把计数清零;连续 N 条只有 bot 在说话,floor 自动交给桌上的人类,桌上没有人类就直接关桌。
  • **终止需要显式条件。**决议、轮次用完、token 预算耗尽、条目数到顶,至少配一条。没有终止条件的桌子开不出来,创建时直接报错。
  • **沉默是合法输出。**被点名的席位可以回 pass,比勉强凑一句“我同意以上观点”有用。
直接拉一个飞书群难就难在这里。IM 群里没有 floor 这回事,只能靠每个 bot 各自遵守“只回被 @ 的消息”。这样能跑,但规则分散在每个 bot 的 prompt 里,有一个不守规矩,整个群就会失控。 图 4:一轮发言的完整路径:入账 → 策略求值 → 投影 → 点名 → 异步执行 → 回写 → 收敛 → 批准

每个席位的上下文:投影,而且要缓存友好

桌上的 bot 不读整本账本。一张开了两周的桌子,账本可能有几千条,全部塞进每个 bot 的上下文,既贵又稀释注意力。Table Core 为每个席位生成一份投影:
  • **稳定前缀:**桌规、席位表与能力卡、已生效的决议。只有新席位入座或出了新决议时才会变,投影里带着前缀的 sha256,bot 一比就知道前缀变没变。前缀放在最前面,留给 prompt cache。
  • **增量尾巴:**从该席位上次读到的 seq 之后、且策略允许它看到的条目。只追加,不重排。
  • **引用而非内容:**超过长度上限的内容会被截短,附上 entry:<seq> 引用,需要时再按引用取全文。
容易踩的坑是在投影里放每轮都变的东西。投影里没有时间戳、在线状态这类字段,条目的 ts 也会被去掉,否则前缀每轮都会失效。需要时间,就作为尾巴里的一条事件给出。我们在 MCode 工程笔记 05 里讲“Agent 怎么知道当前时间”时,处理的是同一个问题。 投影由 Table Core 生成,怎么拼进 bot 自己的上下文,是 bot 的事。协议只固定投影的格式和顺序。MCode 接入端的做法是:一个 session 的第一轮发前缀加尾巴,之后只发新增的尾巴。

记忆与权限:提议和提交分开

两种记忆

BYOB 下有两种记忆,必须分开:
  • 桌面记忆归桌子所有:议题、决议、分工、未完成事项。任何新入座的席位都能读到(受可见性约束)。
  • 私有记忆归各自主人:bot 对主人的长期了解。它永远不会被桌子读走。
两者之间只有一个通道,就是 bot 的发言。bot 说出口的内容进入桌面记忆;没说出口的,桌子不知道。所以每个 bot 带一份由主人设定的披露规则:哪些话题可以引用私有记忆,哪些只能说结论不能说来源,哪些一律不提。例如“可以说我周四下午有空,不能说为什么没空”。披露规则在席位一侧的客户端里执行,发言离开主人的机器之前就过滤掉,桌子从头到尾碰不到私有记忆。

提议与提交

我们一直守着一条原则:agent 只能提议,对外动作由它够不着的系统执行,隔离边界要和权限边界对齐。放到多主人的桌子上,这条规则变成:
桌上的任何 bot 都只能以自己主人的权限行动。影响别的主人资源的动作,只能写成提议,由那个主人批准,并由那个主人的 bot 执行。
具体说:
  1. proposal 的 effects 字段列出受影响的主人和资源;
  2. Table Core 按 effects 开一个任务,状态是等待对应主人审批;如果主人预先设好的自动批准规则覆盖了全部 effects(比如“我自己的任务板,打标签可以自动批准”),就直接放行;
  3. 主人的批准是一条用他的 Ed25519 私钥签名的条目;批准后,由该主人自己的 bot 在它自己的执行面落地,回写一条 receipt,再由另一个席位(独立的 bot 或确定性检查)写 verify,执行者不能自己验自己;
  4. 桌子本身不持有任何人的凭证,所以即便桌子被 prompt 注入攻破,攻击者拿到的也只是“提议”的能力。
按这条规则,桌上最坏的情况是有人说了错话;对外的动作都要经过对应主人审批。 图 5:桌上一件事的状态机。等批准、在跑、已回执、已核验都是账本里的显式状态

异步完成信号

异步任务要让人分清“在跑、跑完了、等你批”,单个 agent 都没完全做好,多个 bot 更难,因为 bot A 看不到 bot B 的进程。 我们的做法是以账本状态为准,不以 bot 自述为准。任务在账本上有显式状态机(图 5):先等受影响的主人批准(awaiting_approval),批准后(approved)执行的 bot 回 running,后台继续跑,完成后写 receipt,状态变成 reported。批准后或运行中的任务在心跳时间内没有进展,Watchdog 把它标为 stalled。要等另一个席位或一个确定性检查写下 verify pass,任务才到 verified,这才算做完,执行者不能核验自己的回执。bot 说“我做完了”,在账本里只是一条 receipt。

协议层:怎么把不同的 bot 接上桌

不同 bot 的开放程度差别很大。传输层是 HTTP 加 SSE,mcode-agent-table 在上面提供了几种接入方式: MCP 管 agent 调工具,A2A 管 agent 对 agent。桌子在两者之上补了三样它们没定义的东西:floor(谁能说话)、effects(这条消息会影响谁)、主人签名(这个席位确实代表这个人)。BYOB Table Protocol v0.1 新增的就是这三样,规范和 Schema 都在仓库里,欢迎各家一起改。 今天的 Personal Agent 多数能接入你的邮箱和日历,却接不进别人的 agent。

会怎么失败

多 bot 协作有几种单 agent 没有的失败方式,设计时就要考虑:
  • **群体思维。**后发言的 bot 倾向于附和先发言的。缓解:重要议题先“背靠背”独立出意见(彼此不可见),再公开讨论。这在拓扑上就是先开 N 个单人子桌,再合并。
  • **谄媚放大。**每个 bot 都想讨好人类,几个 bot 一起讨好,会迅速收敛到人类最初的倾向。缓解:设一个红队席,策略上要求它必须提出至少一条反对意见。
  • 规格博弈。“删掉 src/,bug 就是 0”。多 bot 下更隐蔽:A 声称完成,B 只看了 A 的总结就签字。缓解:verified 必须由确定性检查或拿到原始产物的独立席位完成。
  • **成本失控。**N 个 bot、每轮每人一份投影,成本是 N 倍起步。缓解:floor 预算、桌级总预算、投影缓存命中率作为一等指标监控。
  • **披露越界。**bot 为了把事办成,引用了主人不希望公开的信息。缓解:披露规则 + 事后审计 + 敏感话题强制转人工。
  • **冒名。**一个席位声称代表某人。缓解:入座需要主人签名,能力卡由主人一侧签发。
评估指标也要变。单 agent 看任务完成率,桌子还要看达成决议用了多少轮、多少 token;人类介入了几次;有多少提议被拒;同一个议题换拓扑以后质量和成本怎么变。这组对比是我们接下来要跑的。

我们把这张桌子做出来了

前面讲的是一张桌子需要什么:账本、席位、拓扑、发言权、投影、审批。这些都已经写成了代码,就是 mcode-agent-table:一个桌子宿主(byob serve),里面跑着 Table Core 和一本 JSONL 账本,再加上几种让不同 bot 入座的接入端。bot 留在各自主人的机器上,主人的密钥不出本机。下面是这张桌子第一次坐满的样子。

一次真实的会话

议题很普通,每个团队每周都会争一次:这周只够做完一件事,先修 #461 还是 #459?#461 是 prefix cache 回归测试在 CI 上不稳定,卡着 0.7.2 发版;#459 是客户 Acme 的 CSV 导出丢行。 桌上有四个席位。前三个是真实的 MiniMax Code agent(mcode 0.6.5,模型 MiniMax-M3),通过 @minimax-ai/code-sdk 接入;第四个是一段不调模型的检查代码。
  • bot-tao:代表我,正方,主张先修 #461。
  • bot-jenny:代表 Jenny,反方,主张先修 #459。团队任务板归 Jenny 管。她的 bot 带着两条私有记忆:Acme 在电话里说过,下周一前修好就行;Jenny 周四要去看牙。Jenny 给它的披露规则是:可以说她周四不在,不能说为什么。
  • judge:裁判,主人是 ops,只看得到正反方的发言。
  • checker:核对执行结果,不调模型。
双方各说两轮,裁判判 #459 先做,决议的 effects 是重排 Jenny 的任务板。任务板不归裁判,也不归我,所以 Table Core 开了一个任务,等 Jenny 审批。审批入账后,由 Jenny 自己的 bot 动手:它在 Jenny 的工作区里改了 taskboard.json,在回执里写明改动,还提了一句“Acme 接受下周一前修好”,这在她的规则允许范围内。checker 读磁盘上的文件,确认顺序和批准的 effects 一致,任务才变成 verified。“看牙”从头到尾没进过账本。
桌上的 bot 可以争,也可以说错话。但动手改东西的只能是资源主人自己的 bot,而且要另一个席位核过才算数。
从开桌到 verified,账本一共 25 条。节选如下(原文是英文,长发言做了截断):
第 18 条的批准,是运行程序拿 Jenny 的主人密钥签的,代替她本人点击;Table Core 用 Jenny 的公钥验过签名才让它入账。三个 MCode 席位一共调了六次模型,用掉 48,381 个 token。完整账本、可读记录和每轮 token 用量都在仓库里。

五分钟摆一张自己的桌子

需要 Node 22.19 以上:
完整的配置格式(可见性、发言权、看门狗、子桌)见 SPEC.md。去掉 termination,create 会直接拒绝这张桌子:走 HTTP 返回 E_SCHEMA,Table Core 内部是 E_NO_TERMINATION。这是故意的。

你的 bot 怎么入座

  • **MCode。**接入端 src/adapters/mcode.ts 基于 @minimax-ai/code-sdk。每个席位只开一个 MCode 会话,第一轮发前缀加尾巴,之后只发新增的尾巴。
  • 任何支持 MCP 的 agent(Claude Code、Codex、Cursor……)。byob mcp 是一个跑在主人机器上的 stdio MCP server,12 个工具覆盖入座、读投影、发言、pass、举手、提议、决议、审批、进度、回执、核验和桌面记忆。能力卡从 BYOB_CARD_FILE 读,agent 自己改不了;byob_approve 会通过 MCP elicitation 请主人确认,客户端不支持确认就直接拒绝,主人也随时可以自己跑 byob approve。在客户端的 MCP 配置里这样写:
  • 自己写代码接入。src/client.ts 里的客户端库管密钥、签名、席位循环和披露检查:SeatClient.join() 入座,runSeat() 在轮到你时调用你的 agent。也可以直接用 byob 命令行,say、pass、propose、approve、read 都有。
不管哪种方式,入座都要带主人的 Ed25519 签名和一次性 nonce。所以你的 bot 一坐下,桌上每个席位都知道它代表谁、能动什么,别人也冒充不了它。

路线图

图 6:v0.1 已开源,接下来两步 v0.1 就是上面这套,只有一个桌子宿主,已经开源。接下来两步:
  • **更多真人、真 bot。**让 Grok Bot 等更多 bot 通过 MCP 入座;签名从入座和批准扩到每条发言;一条提议涉及几个主人,就让他们分别批准;终止条件里加上投票和法定人数。提示缓存也要补上:投影保住了稳定前缀,MCode 接入端每个席位复用一个会话,但命中率还没测过,也没调过(上面那次运行每轮只读到 128 个缓存 token)。
  • **开放协议。**桌子宿主之间做联邦,让别的机器、别的组织的席位也能入座;WebSocket 传输和 A2A 桥映射到同一组接口上;规范的修订流程公开。验收只看一件事:有一个不是 MiniMax 的 bot,照着规范自己接了进来。

几个值得讨论的问题

下面这些问题我们有倾向,但还没定。
  • **桌主该是谁?**主持人如果是某个参与者的 bot,就有利益冲突;如果是中立的 bot,它又归谁管?我们倾向于桌主也必须有主人,并且写在席位上。
  • **每句话都要签名吗?**v0.1 里主人只签入座和审批,普通发言靠席位 token 认证。每句都签更安全,也更重。这条线该画在哪?
  • **一条提议同时动三个人的资源,怎么批?**v0.1 一条提议只影响一个主人。全员同意、多数同意,还是按资源逐个批?
  • **披露规则谁来写?**普通用户不会写策略。我们的想法是让 bot 先问主人,再慢慢沉淀成规则。有更好的办法吗?
  • **协议由谁来定?**如果每家只支持自己的桌子,BYOB 就成了又一个封闭生态。我们会把规范的修订流程公开出来。

结语

在互相信任的团队里,每个人都已经有自己的 bot。下一步是别再让人在 bot 之间传话,让这些 bot 直接坐下来一起推项目,同时谁都不用交出自己的记忆和凭证。 桌子已经摆好了:MiniMax-AI/mcode-agent-table。带上你的 bot 来入座。