蛋仔派对云音穿梭如何通关
2026-07-05 3382432
2026-07-05 0
想象你走进一家很特别的餐厅,点了一道菜。这家餐厅的运作方式,跟你理解的 AI 编程助手,是同一回事。

它特别在哪?这家店的大厨,是个失忆症患者。
这位大厨厨艺天下第一,任何菜他都会做、任何难题他都能判断。但他有个毛病:每做完一件事,挂断电话后,就把刚才的事忘得干干净净。 他甚至不在店里——他在很远的地方,你只能打电话找他。
于是这家店雇了一个接线员,坐在店里。接线员不会做菜,一道菜都不会,他唯一的工作就是打电话问大厨,然后照做。店里还有一块大白板,从你进门那一刻起,你说的每句话、每次对话的结果,都被记在上面。
现在,你下单了。看这道菜怎么做出来的:
接线员先看一眼白板,然后拿起电话打给远方的大厨。因为大厨失忆,接线员必须把白板上的内容从头到尾念一遍给他听,不然大厨根本不知道现在是什么情况。念完,问一句:"接下来该干嘛?"
大厨在电话那头动脑子,给出两种回答之一:
如果大厨要求动手,接线员就照办——他自己不做菜,但他会去按那些"料理机"的按钮(拿食材、开火、切菜)。做完,把结果写到白板上。
然后接线员再打一次电话,又把白板上的内容从头念一遍,再问:"接下来呢?"……
这个"打电话问 → 照做 → 记白板 → 再打电话问"的循环一直转着,直到某一次大厨说"菜好了"。接线员这才把菜端给你。
就这么简单。一道复杂的菜,就是靠接线员反复打电话,一步步做出来的。
这里藏着两个反直觉、但一旦想通就豁然开朗的点:
把这两点记住,你已经理解了 Claude Code 的灵魂。剩下的,全是围绕"这位失忆天才"搭起来的脚手架。
真实的活往往没这么简单。比如你点的这道菜,需要先翻遍整个大冷库,把所有能用的食材都找出来——冷库里塞了成百上千样东西,得一格一格翻。
如果接线员亲自去翻、还边翻边往白板上记,会发生什么?白板会被写满。 翻一样记一条、再翻一样再记一条……几百条全堆在白板上。而每打一次电话都要把整块白板念一遍,白板越长,电话打得越慢,最后长到大厨那边根本"听不完"(比如每次打电话只能说1000个字)。
这家餐厅的解法很妙:再开一间隔壁厨房,雇个临时工去干这种脏活。
临时工也有一整套班子——他有自己的接线员、自己的白板、自己电话那头的大厨。他关起门在自己那间厨房里把大冷库翻个底朝天,把自己的白板写得再乱也没关系。干完,他只回主厨一句话:"能用的就这 3 样,在这儿。" 那几百条翻找记录,全留在他自己的白板上,主厨这边的白板干干净净,只多了一句结论。
这就是 subagent(子袋里)——它的头号价值不是"分担工作量",而是"分担白板污染"。
而这种"另起一套班子"的合作,按照关系的不同,一共有四种玩法。这四种,是本文后半程的重点,我先用一句话让你有个印象:
到这里,你已经在脑子里有了一整幅画面:一个失忆大厨 + 一个跑腿接线员 + 一块白板;活太脏就外包给隔壁厨房。 接下来,我们把这幅画面一层层落到真实的代码上——从这里开始会出现文件名和行号,但每一处,你都能对应回刚才那间餐厅。
先认人。上面那间餐厅里的每个角色,在源码里都有对应:
| 代码里的东西 | 餐厅角色 | 职责 | 会思考吗 |
|---|---|---|---|
| 模型(远程 LLM) | 失忆的大厨 | 所有"下一步怎么做"的判断 | ✅ 唯一的智慧来源 |
query.ts 的主循环 | 接线员 | 传话、按工具、记账、决定再不再转一圈 | ❌ 只跑腿 |
messages 数组 | 白板 | 记录整段对话——大厨唯一的记忆 | — |
services/api/claude.ts | 唯一那条对外专线 | 把白板打包发给大厨、解析回话 | — |
tools/ 工具 | 后厨的手脚/料理机 | 真正读文件、跑命令、改代码 | ❌ 按一下自动出活 |
| subagent | 临时工 / 分身 | 另起一套完整班子去干脏活 | (是另一个大厨) |
记住三个关键角色:失忆大厨(模型)、接线员(query.ts)、白板(messages)。整篇文章都绕着它们转。
开场那个"打电话转圈"的故事,落到代码里,核心就是 query.ts:307 那个 while(true) 循环。我把一整圈拆成几个动作。
// query.ts,主循环开头let messagesForQuery = [...getMessagesAfterCompactBoundary(messages)]// ... microcompact / autocompact 在这之后
打电话给大厨之前,接线员先看白板长不长。因为打电话 = 把整块白板从头念一遍给大厨听(大厨失忆,只能这样),白板太长有两个后果:念不完(超 token 上限被拒接)、念一遍贵得离谱。
所以太长了先压缩:
query.ts 里的 deps.callModel)接线员拿起唯一那条对外专线,把整块白板念给大厨。大厨的回话是一个字一个字流式传回来的。
有个聪明细节:大厨话没说完,只要冒出一句"去查冰箱",接线员当场就派人去查,不等他挂电话(StreamingToolExecutor)。省时间。
大厨说完,接线员只问自己一个机械问题(query.ts:1062 附近):
if (!needsFollowUp) {// 大厨没要工具 → 这单可能做完了}
needsFollowUp 是什么?就是"大厨的回话里,有没有冒出工具调用请求(tool_use)"。有就是有,没有就是没有,读个标志位而已。 这不是判断力——它只是检查大厨要不要动手。这恰好印证了开篇那句:接线员不产生一点智慧。
没要工具 → 这单做完了,走打烊流程(stop hook、token 预算检查),然后 return { reason: 'completed' }。圈结束。
要了工具 → 真正干活:
// query.ts 尾部:把这轮的话 + 工具结果,全部续到白板末尾,再回到动作 Aconst next = {messages: [...messagesForQuery, ...assistantMessages, ...toolResults],// ...}
这一句"续到末尾再回头",就是【转圈】的字面本体。 白板每转一圈变长一截,所以动作 A 才要压缩——不压,白板迟早撑爆。
一张图钉死这个圈:
┌─────────────────────────────────────┐ │ A. 白板太长?先压缩│ └──────────────────┬──────────────────┘↓ ┌─────────────────────────────────────┐ │ B. 把白板念给大厨(流式回话)│ └──────────────────┬──────────────────┘↓┌───────────────────────┐│ C. 大厨要工具吗?│ ← 接线员唯一的"判断"└─────┬───────────┬─────┘没要 │ │ 要了↓ ↓┌────────────┐┌──────────────────────┐│ 打烊 → 收工 ││ D. 派工具干活│└────────────┘│结果续到白板末尾───┼──┐└──────────────────────┘│回到 A,再转一圈 ←────────┘
逻辑就上面这么点,凭什么写 1700 行?我读下来的结论:真正属于"转圈主干"的不到三成,剩下七成全是"临场救火纪律"。
代码里有个 state 结构体,在 7 个不同的 continue 点被改写——意思是"接线员决定再转一圈"有七种理由,正常的"用了工具所以继续"只是其一,其余六种全是救火:
所以那句话在代码层面完全成立:接线员的全部本事,是在大厨那通电话的两头,把圈转得又稳又不丢信息。智慧全在电话那头,纪律全在这 1700 行里。
理解了单个循环,就能理解 subagent 了。很多人以为 subagent 是主 agent 手边一个跑腿小弟。大错。
关键在 runAgent.ts:748:
// runAgent.ts —— subagent 启动时for await (const message of query({ messages: initialMessages, ... })) {// ...}
看到没?它又调了一次 query()——就是上面那个 while(true) 转圈机。
所以派一个 subagent,等于在隔壁房间凭空支起一套一模一样的电话亭:里面坐着另一个失忆大厨,配自己的白板、自己的专线、自己的工具、自己的身份 ID。
| 主班子 | 一个 subagent |
|---|---|
| 一个接线员 | 自己的接线员(独立 query 循环) |
| 一面白板 | 自己的白板(独立 messages) |
| 一条专线 | 自己的专线 |
| 一个大厨 | 另一个失忆大厨(对主对话一无所知) |
为什么要这么重地另起一套? 目的只有一个,也是理解全部四种模式的总纲:
subagent 的头号价值不是"分担工作量",是"分担白板污染"。 记住这句,四种模式全是它的变体。
同样是"另起班子",按关系拓扑分四种。区别只在三个旋钮:喂什么系统提示、给不给继承上下文、开放哪些工具权限。底层都是同一套 query(),能力同构。
最常见。主厨派一个 subagent,它在自己白板上干完,回一句结论就消失。
它的一辈子(runAgent.ts):发工牌(:347)→ 搭全新空白板(:373)→ 配一套重新筛过的工具(:500)→ 写专属岗位说明书(:508)→ 开自己的转圈机(:748)→ 回一句话 → finally 里彻底拆台(:816,关服务、撤钩子、杀掉它后台开的进程、删待办)。
三个亮点:
runAgent.ts:386):Explore 这种只读临时工,出生时主动删掉 CLAUDE.md 和最大 40KB 的 git 状态。据该处源码注释,Explore 一周被派约 3400 万次,砍这一刀每周省下约 50~150 亿 token(数字引自注释原文,未独立复核)。runAgent.ts:469):给临时工配权限时清单清空重设,主厨之前点过的"同意"绝不偷传给它。prompt.ts:91):后台临时工干活时不许中途偷看它的草稿——一读,那些过程垃圾就又被搬回主厨白板,外包图的"干净"当场白费。什么时候用:大海捞针式搜索、开放式调研、要一个"没被你带偏的第二意见"、一批能并行的独立活、又长又脏只要结论的活。
什么时候别用(prompt.ts:235):读某个具体文件、找某个具体函数、2-3 个文件的小范围——自己干,别为芝麻小事搭厨房。
特殊的主仆:省略工种时,subagent 不是新雇的陌生人,而是主厨的一个克隆——继承整面白板、共享缓存(forkSubagent.ts:60 的 FORK_AGENT:model: 'inherit'、tools: ['*'] + useExactTools)。
分身 vs 临时工,核心不是"贵不贵",是"这次要不要那面白板":
为什么分身便宜:靠"让一群分身的白板前缀逐字节相同"来共享提示缓存(forkSubagent.ts:99:"all fork children must produce byte-identical API request prefixes")。构造时连"工具占位结果"都用完全相同的占位词(:93),只有末尾的专属指令各不相同(:163)。
省钱带来的副作用要打补丁:分身原样抄了主厨说明书,而说明书里写着"优先 fork"——分身照读就会无限套娃。于是两道闸:软闸(:171 守则开头吼"STOP,你是分身,别派下属")+ 硬闸(:78 isInForkChild 扫白板里的分身标记,有就拒绝再 fork)。
开一个开关(coordinatorMode.ts:36 的环境变量),主厨的系统提示被整个换掉(:116):
协调者不是新程序,就是被换了岗位说明书的主 agent。 它像个包工头——自己不砌墙,只拆活、写精确施工单、派工人、验收综合。
跟主仆的三个关键区别:
SendMessage 给同一个 worker 反复追加指令(靠 worker 身上挂的 pendingMessages 待办队列,LocalAgentTask.tsx:162),这才是真正"继续之前的活"。(LocalAgentTask.tsx:252)、**伪装成一条"用户消息"**贴回协调者白板(coordinatorMode.ts:144)。这个"伪装成用户消息"是全系统最巧的设计之一:
coordinatorMode.ts:213):能同时派的绝不串行。给 worker 配工具:一份全量,过四层筛子(agentToolUtils.ts:122 + constants/tools.ts):
全量工具池→ ① 全体 subagent 禁用(Agent 防套娃 / TaskOutput / PlanMode…)→ ② 自定义 agent 额外禁→ ③ 异步 worker 只放行白名单(Read/Grep/Bash/Edit… 十几样)← 白名单制!→ ④ 工种自己的黑名单= worker 实际拿到的工具
注意③是白名单(默认不给、只给明确安全的),和①②的黑名单逻辑相反——worker 的能力被有意收得比主线窄。 而协调者自己的工具是另一份"只管人不干活"的名单(constants/tools.ts:107:Agent/TaskStop/SendMessage/SyntheticOutput,一个 Bash/Edit 都没有)。
前三种里,agent 之间要么不说话、要么只跟上级单线联系。队友是唯一能横向对话的——队员 A 能直接给队员 B 发消息,像真人团队。
它是一群长期存活、地位平等的完整 Claude Code 实例,各开各的进程(spawnMultiAgent.ts 里 tmux 分屏 / 独立窗口 / 同进程三种后端),靠信箱文件通信。
为什么必须"投文件":队友是独立进程,内存不共享,A 想跟 B 说话没有共享内存可用,只能靠文件系统当公共黑板(teammateMailbox.ts:4):每个队友一个 .claude/teams/{队名}/inboxes/{名字}.json,别人往里加锁写(:134,:165 文件锁防并发),收件人自己轮询读。
系统提示专门追加一段告诉队友(teammatePromptAddendum.ts:15):"光在文本里写回复,队友是看不见的——你必须用 SendMessage 工具。" 因为白板私有,别人读不到,要让队友听见必须显式投信。
但要泼一盆冷水:队友在代码项目里大多是过度设计。
它的设计灵感来自人类团队,可代码项目的协作范式根本不同:
| 人类团队 | 代码项目的 agent | |
|---|---|---|
| 信息在哪 | 各人脑子里,必须开口问 | 落在文件里,谁都能读 |
| 怎么同步 | 靠对话 | 靠读同一份代码 |
人类需要横向对话,是因为知识锁在脑子里;agent 的成果都落在文件系统上、是公开的。 所以"A 问 B 你干了啥"大多能被"A 直接读 B 改的文件"替代——队友的核心卖点被共享文件架空了。它真正不可替代的只剩一个夹缝:协作依赖运行时状态(没法落成文件)、且参与方须长期共存时(比如一个 agent 挂着服务、另一个实时打它)。
这也解释了它至今仍是 EXPERIMENTAL + 带远程 killswitch——适用面窄、成本高(另起进程 + 文件信箱 + 海量维护代码),是四种关系里最拟人、也最少真正用得上的一种。
| 模式 | 谁创造谁 | 信息方向 | 子方寿命 | 能否横向对话 |
|---|---|---|---|---|
| 外包(主仆) | 主派子 | 下派↓ + 一次回↑ | 用完即焚 | 否 |
| 分身(fork) | 我裂我 | 下派↓(带全部记忆)+ 一次回↑ | 用完即焚 | 否 |
| 协调者-工人 | 协调者派工人 | 下派↓ + 反复差遣 + 异步通知↑ | 可反复唤起 | 否(worker 互不可见) |
| 队友(swarm) | 队友拉队友 | 任意点对点 ↔ + 广播 | 长期独立存活 | 是 |
读到这你会发现,四种模式再花哨,都逃不出同一条底层规矩:
runAgent.ts:373 用 [...contextMessages, ...promptMessages] 新建数组)。克隆完各自生长,主厨后续改动分身看不到(buildWorktreeNotice 甚至提醒分身"文件可能已被改过,动手前重读")。为什么这条不能破? 因为共享白板 = 两个大厨抢同一个脑子。fork 之后主厨和分身各走各的路,白板各自生长,根本不存在一个"最新版"可以同步。所以唯一干净的做法就是:分家那刻拍张快照,之后互不干涉;要沟通就走外部中介(伪装消息 / 信箱文件)。
跨 agent 沟通必须显式(创建时塞入,或事后投信),正是白板隔绝的必然结果——这不是设计选择,是"大厨各有各的记忆"推导出来的硬约束。
全文的收尾,也是最容易被搞混、最值得刻进脑子的一点:Claude Code 是 CLI,不是模型。
services/api/claude.ts 的网络专线被调用。以"协调者模式"为例,它准确拆成三层:
| 层 | 是什么 | 扮演 |
|---|---|---|
| Claude Code(CLI) | 这些代码 | 搭台+执行:决定喂哪份提示词、真正 spawn worker、把结果贴回白板 |
| 模型 | 远程大脑 | 做判断:读了"你是协调者"提示后,倾向于"派 worker 去干" |
| 系统提示 | coordinatorMode.ts:116 的文字 | 剧本 |
也就是说:是 CLI 让远程模型"扮演"协调者(喂它一段提示词),但模型只负责"想说什么/想派谁",真正 spawn worker、加锁写信箱、把结果伪装送达的全是 CLI。
把整篇文章压成几句:
query.ts 不聪明,它是纪律;聪明全在电话那头的模型。读源码最大的收获,往往不是记住某个函数,而是看清一个反直觉的设计取向。Claude Code 给我的那一下,就是:做一个强大的 AI agent,难点从来不在"让代码变聪明",而在"如何组织一个不思考的脚手架,让那个聪明但健忘的大脑,稳定地把复杂的活一步步干完"。