逐日AI
第 3 周 · D17约 4 小时

子 Agent 与并行:独立上下文、工具白名单、worktree 隔离与结果汇总

一个人干不完就分头干:实现子 Agent 机制,让每个子 Agent 有自己的上下文与工具白名单,在各自的 git worktree 里改文件互不干扰,最后把结果结构化汇总回主会话,并想清楚什么任务不该并行。

今日目标 0/3

登录后可以勾选并保存进度。

今日目标

  1. 能实现子 Agent 的派发与结果回收,并说清它与主会话的上下文关系
  2. 能用工作树做文件隔离,并处理并行改到同一文件的冲突
  3. 能判断哪些任务适合并行、哪些并行只会更慢更贵

昨天是把一段文字在合适的时候放到模型面前,今天是把一整个模型派出去干活。做完之后回到页面顶部把三条目标勾掉。

小白版讲解

带徒弟:分头干活,各占一间工位

活儿多了,老师傅会带两个徒弟。带徒弟这件事里,有三个安排是老师傅想都不用想就会做的。

第一,交代清楚这一件事就行,不会把今天上午所有的电话内容都复述一遍。第二,给他的工具箱是配好的:让他打磨零件就给砂纸和卡尺,不给切割机——不是信不过他,是工具少了手就不会滑。第三,各占一间工位,不能两个人围着同一个台钳抢同一个零件。

最后还有一件事,是新手师傅最容易忘的:收工时要看一眼两个人的活儿能不能拼到一起。两个徒弟各自把同一个零件磨了一遍,磨法还不一样,这时候不能闭着眼睛把后交上来那个装上去——那不叫合并,那叫把先交的那份悄悄扔了。

今天要给 mca 加的就是这四件事:独立的上下文、独立的工具白名单、独立的工位、以及一次诚实的合并。

子 Agent 是什么:一次性的循环,不是另一个用户

先把最容易走偏的那个印象掰过来。子 Agent 不是「另一个用户在跟你的 Agent 说话」,也不是什么新的架构层。它就是再跑一遍第五天那个 run() 循环,只不过换了四样东西:换一个消息数组、换一张更小的工具表、换一份更紧的预算、换一个工作目录。

这句话有一个很硬的验证方式:今天的实验里,kernel/ 目录下一行代码都没有改。消息、工具、事件三套协议一个字段都没加,循环也没动。子 Agent 是套在循环外面的一层,不是循环里面的一个分支。

那「独立」到底独立什么?三条,各挡住一种失控。

独立消息数组,挡的是污染。 一个子 Agent 干一件小事可能要来回七八轮:读文件、改一处、发现改错了、再改一次。这七八轮往返对主会话毫无价值,而它们会实打实地占掉主会话的窗口。更麻烦的是它们会把主会话带偏——主会话本来在想「这个需求要怎么拆」,结果上下文里堆满了「第 37 行的缩进不对」。

独立工具白名单,挡的是跑偏。 工具越少,选错的机会越少。一个只需要改文档的子 Agent,给它跑命令的工具没有任何好处,只有它突然决定跑一次构建的坏处。

独立预算,挡的是连坐。 一个子 Agent 打转打到上限,不该把另一件事的额度也烧光。第六天的 LimitTracker 在这里原样复用,只是换一组更紧的数字——不要为子 Agent 另造一套记账,那样你很快会得到两份对不上的账。

还有一条同样重要,但它是反过来说的:子 Agent 绝不该继承主会话的系统提示。主会话那份提示里写满了只对主会话成立的事——对话可能被压缩过、用户会用 @ 引用贴资料、有技能会自动出现、不清楚可以问用户。子 Agent 一条都不适用:它没有历史、没人可问、也没有下一轮。继承过去的后果不是「多知道一点」,而是它会去等一件永远不会发生的事

白名单:授权发生在派发那一刻

白名单的实现不复杂,就是从父注册表里按名字挑几个 ToolDef 出来重新注册一遍。关键是它复用同一批工具对象而不是重新造:截断(第三天)、审批(第五天)、打转检测(第六天)全都挂在 ToolDef 这个接口上,复用对象就等于把那些机制原样带进子 Agent,一行都不用重写。

src/agents/run.ts
export function buildSubRegistry(parent: ToolRegistry, allow: string[]): SubRegistry {
  const registry = new ToolRegistry()
  const unknown: string[] = []
  const blocked: string[] = []
  for (const name of [...new Set(allow)]) {
    // 派发工具自己永远不进子表,哪怕白名单里写了它
    if (name === DISPATCH_TOOL) {
      blocked.push(name)
      continue
    }
    const tool = parent.get(name)
    // 写错的工具名必须报出来:静默忽略的表现是「这个子 Agent 什么也没干成」
    if (!tool) {
      unknown.push(name)
      continue
    }
    registry.register(tool)
  }
  return { registry, unknown, blocked }
}

那行「派发工具自己永远不进子表」是一条硬规则,不是保险起见。允许子 Agent 再派子 Agent,派发树就没有边界了:预算算不出来(第三层的花费算在谁头上)、出了事你也说不清是第几层那个在改文件。要多层就显式做成多层,不要让递归默认发生。

白名单还顺手回答了一个权限问题:审批到底在哪一步发生。

答案是只在派发那一刻发生一次。dispatch_agents 自己是个会改东西的工具,所以它要过第五天那道审批门;而子 Agent 内部不再设门。这不是图省事,是因为逐次审批在这里是走过场:用户批得懂「派两个人去干这两件事,它们只能读文件和改文件」,批不懂子 Agent 的第七次 edit_file——他根本看不到子 Agent 的上下文,没有任何判断依据。授权的边界改由白名单承担:白名单里没有跑命令的工具,它就永远跑不了命令。一次看得懂的授权,胜过十次看不懂的确认。

隔离靠工作树:共享工作区一定会互相踩

现在到了今天最实的一块。两个子 Agent 同时在一个仓库里改文件,会发生什么?

不是「有时候会冲突」,是你无法知道发生了什么。A 读了 calc.js,B 也读了 calc.js;A 改完写回去,B 拿着它读到的那份旧内容做精确替换——第四天那条「旧内容必须逐字匹配」的规则这时候会救你一次,B 的替换会失败。但下一次 B 改的是另一个片段,匹配成功,于是文件里同时有了 A 的改动和 B 的改动,而这两处改动从来没有被一起设计过。测试跑绿了不代表它对,只代表这两处恰好没打架。

所以隔离不是优化,是并行的前提。git 的 worktree 正是为这件事准备的:同一个仓库可以签出到多个目录,每个目录有自己的 HEAD 与索引,而对象库是共享的——开一间工位是常数级开销,不会因为仓库有两个 G 就复制两个 G。

真实实现要跑的就是这四条命令,一条都不神秘:

BashBash
git worktree add --detach ../.mca/agents/calc-guard HEAD   # 开一间工位
# ...子 Agent 在这个目录里干活,cwd 指向它...
git -C ../.mca/agents/calc-guard diff HEAD                 # 收它的产物
git worktree remove --force ../.mca/agents/calc-guard      # 收摊

这里必须说清一件事:本课实验里那份不是真的 git worktree 我们的沙盒仓库 work/repo 从第一天起就不在 git 里(生成它的 seed.ts 是冻结文件,全课不改),没有对象库可用,所以实验用的是「复制一份目录 + 记下派单那一刻的内容」来模拟。两者共享同一套语义——派单时拍基线、干活只在自己那间屋、回收时拿「基线到现在」的差异——所以下面讲的冲突规则换成真 git 一样成立;但简化版不认识重命名、不处理二进制文件、也不保留权限位,这三件事真 git 都做得到。别把简化版当成实现参考直接搬到生产里。

有一个时机很容易搞错:基线必须在派单那一刻取,不能等回收时拿主工作区去比。 子 Agent 干活的这几秒里主工作区可能已经被别人动过了(另一个子 Agent、或者用户手里的编辑器),拿一个已经变了的现场当基线,算出来的差异里会混进不是它改的东西。

各改各的 改了同一处 主会话 模型决定拆成两张派单 审批门 用户批一次 工位 calc-guard独立消息 白名单 预算 工位 doc-writer独立消息 白名单 预算 基线到现在的差异 基线到现在的差异 三方比较 落盘 并回灌结构化回执 报冲突 一份都不落
Mermaid 源码
mermaidmermaid
flowchart TD
  A[主会话 模型决定拆成两张派单] --> B[审批门 用户批一次]
  B --> C1[工位 calc-guard<br/>独立消息 白名单 预算]
  B --> C2[工位 doc-writer<br/>独立消息 白名单 预算]
  C1 --> D1[基线到现在的差异]
  C2 --> D2[基线到现在的差异]
  D1 --> E[三方比较]
  D2 --> E
  E -->|各改各的| F[落盘 并回灌结构化回执]
  E -->|改了同一处| G[报冲突 一份都不落]

结果怎么回来:回执不是转录

子 Agent 干完了,什么该回到主会话?

直觉答案是「把它说的话贴回来」,而那正好把刚省下的窗口又还了回去。回来的应该是一份结构化回执:它是谁、派单是什么、做完没有、改了哪些文件、用过哪些工具、花了多少、以及它自己那句收尾话。「它一路上说了些什么」留在它那边,随它一起被扔掉。

实验里那份回执长这样,17 行、517 字符(这两个数字离线可复现):

TextText
派出了 2 个子 Agent,回执如下:
 
【calc-guard】完成 派单:给 src/calc.js 的 divide 加上除零保护,除数为 0 时抛 divide by zero
  改了:src/calc.js
  用过的工具:edit_file
  用量:1 次工具调用 / 约 398 token / 458ms
  回执:已改 src/calc.js:divide 在除数为 0 时抛 divide by zero,其余行为不变。
 
合并结果:2 个文件落盘,0 处冲突。
  README.md → doc-writer 改的,落盘
  src/calc.js → calc-guard 改的,落盘

三个细节值得单独说。只留最后一轮说的话:中间那些「我先看看」是过程不是结论,分界点取在每次工具调用处就够了。回执要截断:一个话痨子 Agent 不该把主会话的窗口撑爆。冲突写在最前面:一次派发里最需要人接着做决定的就是冲突,而模型读长文本时前几行的权重最高。

汇总时的冲突:两个人改了同一处怎么办

这一步最容易被想简单。直觉写法是「按顺序把每个子 Agent 的改动写回去」,而那等于后写的赢:两份回执都写着「已完成」,主工作区里却只剩一份改动,屏幕上不会有任何提示。这是并行 Agent 最典型的一种静默数据丢失,也是今天最该带走的那个坑。

正确的做法是三方比较:基线是什么、这一边改成了什么、另一边改成了什么。规则只有三条,粗但诚实。

src/agents/merge.ts
for (const [file, edits] of [...byPath].sort((a, b) => a[0].localeCompare(b[0]))) {
  const by = edits.map((e) => e.by)
  const versions = new Set(edits.map((e) => e.after))
  // 规则三:多个人改成了不同版本 —— 一份都不落,因为你没办法判断哪份对
  if (versions.size > 1) {
    entries.push({ path: file, by, action: 'conflict', why: `${by.join(' 与 ')} 改出了 ${versions.size} 个版本` })
    continue
  }
  // 主工作区自己也会漂移:基线对不上就别落盘,否则会无声覆盖掉别人刚写的东西
  if ((current.get(file) ?? null) !== (edits[0]?.before ?? null)) {
    entries.push({ path: file, by, action: 'conflict', why: '主工作区里这个文件在派发之后被改过' })
    continue
  }
  // 规则一与规则二:一个人改,或者多个人改成了同样的内容 —— 落盘一次
  applied.set(file, edits[0]?.after as string)
  entries.push({ path: file, by, action: 'apply', why: by.length > 1 ? '改成了同样的内容' : '落盘' })
}

有两条判断值得解释。

为什么冲突时一份都不落,而不是挑一份? 能自动挑的前提是你有办法判断哪份对,而你没有。真 git 的行级三方合并能在改动不重叠时自动合上,那需要基线的行信息与一套合并算法;本课把它留给真实实现,这里只保证一件事:不静默丢东西。报一个人能看懂的冲突,永远好过悄悄产出一个没人设计过的版本。

为什么改成相同内容不算冲突? 那只是同一件事做了两遍,落盘一次就行。如果把它也报成冲突,冲突这条信号就变廉价了,人很快就不看了——而一条没人看的告警等于没有。

并行的成本:什么时候不值得

前面全在讲怎么把并行做对,这一节讲什么时候不该做

先看账。实验里那次派发,主会话那一轮的用量是输入 1405、输出 51 个 token,屏幕上滚过的也只有一行「已派出 2 个子 Agent」。而 /agents 会把藏起来的那部分摊开:calc-guard 约 398、doc-writer 约 363,合计 761 个 token——主会话一个都没看到,你一样要付。(这几个数字是离线估算器算出来的,同一台机器上连跑两次完全一样;耗时那几列只能看相对关系。)

这就是并行的第一笔成本:每个子 Agent 都要重新付一遍系统提示与工具表的钱。 上下文不能共享,是隔离的必然结果,不是实现没做好。任务越小,这笔固定成本占的比例越高——派一个子 Agent 去改一行文案,光是给它讲清楚「你是谁、能用什么」就比它干的活儿还贵。

第二笔成本是调试。串行的时候出了问题,你顺着一条事件日志往回看就行;并行之后有 N 条时间线,它们交错发生,而日志是按到达顺序写的。你会看到两个子 Agent 的输出彼此穿插,很难一眼看出哪句话属于谁。这也是为什么回执里一定要带工位号:那是你唯一的归属线索。

第三笔成本是合并。上一节那套规则已经是最简版本了,真实场景里还要处理重命名、删除、二进制文件——这些代价在你决定并行的那一刻就已经付了,只是账单晚一点到。

所以判据可以写得很直白,三类活儿不要派出去:

这类活儿为什么不该并行
有先后依赖(先改接口,再改所有调用方)后一步需要前一步的结果,派出去只能干等,最后还是串行
要改同一批文件(同一个模块的重构)必然撞上冲突规则,合并失败之后还得重做一遍
一步就能做完(改一行文案、查一个函数在哪)派发的固定成本比自己顺手做完还高

反过来,适合并行的有一个很好用的自查问题:这两件事如果交给两个真人同时做,他们需要互相说话吗? 需要,就别派;不需要,才是并行该出场的地方。实验里那两件事——给 divide 加除零保护、给 README 补一段说明——就是典型:改的不是同一个文件,谁先做完都不影响另一个。

最后补一句边界,免得和后面几天混在一起:今天讲的是同时干活互相不踩。明天的后台任务讲的是另一件事——一个长时间跑着的活儿怎么在不打断对话的前提下把结果通知回来,那里的难点是生命周期与通知,不是隔离。

源码导读

动手实验

🧪 D17 实验:子 Agent 派发、工作树隔离与结果汇总

代码位置:labs/my-coding-agent-21days/day-17-subagents

实验里挖了五个练习点,全是「不做也能跑,但会静默出错」的那一类:共享工作区、白名单形同虚设、子 Agent 用主会话的预算、合并时后写的赢、回灌整段白话而不是结构化回执。起点代码原样跑是十三项里过六项。

  1. 把开工位改成真的复制一份独立工作树,并在派单那一刻拍下基线,看隔离那一项从红变绿——主工作区在合并之前应该一个字节都没变。
  2. 把白名单裁表写对:只装点名的工具、写错的名字报出来、派发工具自己硬挡,看父表 9 个工具被裁成 2 个。
  3. 给子 Agent 传一份独立预算,然后把其中一个的轮数上限压到 1,看它撞上自己的上限而另一个不受连坐。
  4. 把合并改成三方比较,跑 README 里那条冲突命令:两个子 Agent 把同一个文件改成了不同版本,应该报冲突且一份都不落盘。
  5. 把回灌改成结构化回执,跑 MOCK=1 SELFTEST=1 pnpm start 看到十三项全过,再用 /agents 看那次派发一共偷偷花了多少 token。

验收看五条勾:自检十三项全过;两个子 Agent 的时间区间真的重叠;主工作区在合并之前没被改动;改同一文件不同版本时零落盘;work/agents 下没有残留工位。

面试题

今天三道题,考的是并行 Agent 的工程判断,不是「什么是多 Agent」:

  1. 子 Agent 该继承主会话的什么、绝不该继承什么?
  2. 并行改同一个仓库,你用什么手段隔离?冲突怎么发现和处理?
  3. 什么任务适合派给子 Agent,什么任务并行反而更差?

完整题干、分析过程与答题要点见本课面试题库的第十七天。第三题最有区分度——多数人会答「能拆开的就并行」,而能把「派发的固定成本」和「不需要互相说话」这两条判据说清楚的人很少。

检查清单与明日预告

  • 能说清子 Agent 就是再跑一遍同一个循环,以及为什么今天没改协议层的任何一个字段
  • 能说出三条「独立」各自挡住什么失控
  • 能解释为什么子 Agent 不该继承主会话的系统提示
  • 能说清为什么派发工具自己永远不许进子 Agent 的白名单
  • 能说清审批为什么只发生在派发那一刻,以及授权边界由谁承担
  • 能解释共享工作区为什么不是「有时候会冲突」而是「你无法知道发生了什么」
  • 能说出真实 git worktree 的四条命令,以及它比复制目录便宜在哪
  • 能说清基线为什么必须在派单那一刻取
  • 能说出回执该带哪些字段,以及为什么冲突要写在最前面
  • 能解释合并为什么必须三方比较,以及冲突时为什么一份都不落
  • 能说出并行的三笔成本,以及三类不该并行的活儿

明天是 D18《hooks 与后台任务:生命周期钩子、确定性检查与不打断对话的通知》。今天是把活儿分出去同时干,明天是让某些检查在固定的时机自动发生——一个管空间上的分工,一个管时间上的节点。

面试题库

  • 子 Agent 该继承主会话的什么、绝不该继承什么?What should a subagent inherit from the main session, and what must it never inherit?
    国内高频海外高频基础#subagent#context-boundary

    分析过程 · 先想清楚再作答

    1. 这题在考「你有没有真的派过」。只看过文档的人会答「子 Agent 有自己的上下文」,实现过的人会先说清哪几样东西是复用的、哪几样是必须重造的,因为这两类的判据不同。
    2. 怎么拆:先给一条判据,再按这条判据把东西分成两堆,最后举一个继承错了会怎样的例子。
    3. 判据一句话:**能力可以继承,情境不能继承**。工具实现、截断规则、审批门、打转检测这些是能力,它们挂在 ToolDef 这个接口上,子 Agent 复用同一批工具对象就等于原样带走,一行都不用重写。
    4. 绝不该继承的第一样是**消息数组**。子 Agent 一趟往返七八轮很正常,那些往返对主会话毫无价值,却会实打实占掉它的窗口,还会把主会话带偏——它本来在想需求怎么拆,上下文里却堆满了第 37 行的缩进。
    5. 绝不该继承的第二样是**系统提示**。主会话那份里写满了只对主会话成立的事:对话可能被压缩过、用户会用 @ 贴资料、有技能会自动出现、不清楚可以提问。子 Agent 一条都不适用,继承过去的后果不是多知道一点,而是它会去等一件永远不会发生的事——比如问一个没人会答的问题。
    6. 绝不该继承的第三样是**预算**。共用一份额度意味着一个打转的子 Agent 能把整次任务的钱烧光,而另一件事莫名其妙没做完,派它出去的那个会话毫不知情。复用同一个记账器的类、但给一组更紧的数字,不要另造一套账。
    7. 还有一样必须显式截断的是**派发能力本身**:派发工具绝不能出现在子 Agent 的白名单里,否则派发树没有边界,预算算不出来,出了事也说不清是第几层在改文件。
    8. 可预期的追问:子 Agent 要不要拿到主会话的任务清单或计划;它的事件要不要写进主会话的日志;多层派发什么时候真的需要。

    How to reason about it · think before answering

    1. This tests whether you have actually dispatched one. People who only read docs answer "a subagent has its own context"; people who built one first separate what is reused from what must be rebuilt, because the two categories have different criteria.
    2. How to break it down - give one criterion, sort things into two piles by it, then show what breaks when the wrong thing is inherited.
    3. The criterion in one line - capability can be inherited, situation cannot. Tool implementations, truncation rules, the approval gate and loop detection are capabilities; they hang off the tool interface, so reusing the same tool objects carries them over with no rewriting.
    4. The first thing never to inherit is the message array. Seven or eight round trips for one small task is normal, they are worthless to the main session, they consume its window, and they drag it off course - it was reasoning about how to split the work and its context is now full of indentation on line 37.
    5. The second is the system prompt. The main one is full of things true only of the main session - history may have been compacted, the user pastes files with @, skills appear automatically, ask the user when unsure. None of that applies to a subagent, and inheriting it does not add knowledge - it makes the subagent wait for something that will never happen, such as an answer to a question nobody will read.
    6. The third is the budget. A shared allowance means one looping subagent burns the whole task's quota while some unrelated piece of work quietly fails, and the session that dispatched it never learns why. Reuse the same accounting class with tighter numbers; do not write a second set of books.
    7. One more thing must be cut off explicitly - the dispatch capability itself. The dispatch tool must never appear in a subagent's allow-list, or the dispatch tree is unbounded, the budget cannot be attributed, and after an incident you cannot say which level edited the file.
    8. Likely follow-ups - whether a subagent should see the main todo list or plan; whether its events belong in the main event log; when multi-level dispatch is genuinely needed.

    答题要点

    • 判据:能力可以继承(工具对象、截断、审批、打转检测),情境不能继承
    • 不继承消息数组:子 Agent 的七八轮往返对主会话没有价值,还会占窗口并带偏它
    • 不继承系统提示:主会话那份写满只对主会话成立的事,继承过去它会去等一件不会发生的事
    • 不继承预算:共用额度会让一个打转的子 Agent 连坐掉另一件事,复用记账器但给更紧的数字
    • 派发工具本身绝不进子 Agent 的白名单,否则派发树没有边界

    Key points

    • The criterion - capability is inheritable (tool objects, truncation, approval, loop detection); situation is not
    • Never inherit the message array - those round trips are worthless to the main session, consume its window and derail it
    • Never inherit the system prompt - it asserts things true only of the main session, and the subagent ends up waiting on what will never happen
    • Never inherit the budget - a shared quota lets one looping subagent starve unrelated work; reuse the tracker with tighter numbers
    • The dispatch tool itself must never be in a subagent's allow-list, or the dispatch tree becomes unbounded
  • 并行改同一个仓库,你用什么手段隔离?冲突怎么发现和处理?How do you isolate parallel agents editing one repository, and how are conflicts detected and handled?
    国内高频海外高频进阶#worktree#merge-conflict

    分析过程 · 先想清楚再作答

    1. 这题的题眼在后半句。隔离说得出 worktree 就算及格,真正分出高下的是「冲突怎么发现」——多数人默认合并是件顺手的事,而那恰恰是并行里最容易静默出错的一步。
    2. 怎么拆:先说共享工作区为什么不可接受,再说隔离手段,最后把合并规则一条条摆出来。
    3. 共享工作区的问题不是「有时候会冲突」,而是**你无法知道发生了什么**。两个 Agent 各自读了同一个文件,一个改完写回,另一个拿旧内容做替换:精确替换那条「旧内容必须唯一匹配」的规则会救你一次,但下一次它改的是另一段、匹配成功,于是文件里同时有了两处**从未被一起设计过**的改动。测试绿了只说明这两处恰好没打架。
    4. 隔离手段是 git worktree:同一个仓库签出到多个目录,每个目录有自己的 HEAD 与索引,而对象库共享——所以开一间工位是常数级开销,不会因为仓库两个 G 就复制两个 G。四条命令是 worktree add --detach、在那个目录里干活、diff HEAD 收产物、worktree remove --force 收摊。
    5. 一个时机很容易错:**基线要在派单那一刻取**,不能等回收时拿主工作区去比。子 Agent 干活那几秒里主工作区可能已经被别人动过,拿一个变了的现场当基线,差异里会混进不是它改的东西。
    6. 冲突发现靠**三方比较**:基线、这一边改成什么、另一边改成什么。直觉写法是按顺序把每个人的改动写回去,那等于后写的赢——两份回执都写着已完成,主工作区里却只剩一份改动,而且屏幕上没有任何提示。这是并行 Agent 最典型的静默数据丢失。
    7. 规则三条:只有一个人改就落盘;多个人改成相同内容落一次且不算冲突(那只是同一件事做了两遍,报成冲突会让告警变廉价);改成不同内容就报冲突、一份都不落。再加一条容易漏的:主工作区自己也会漂移(用户在编辑器里存了盘),基线对不上时落盘会无声覆盖他刚写的东西。
    8. 冲突时为什么不自动挑一份:能自动挑的前提是你有办法判断哪份对,而你没有。真 git 的行级三方合并能在改动不重叠时自动合,那需要行信息与一套算法;退化实现至少要保证不静默丢东西——报一个人看得懂的冲突,好过悄悄产出一个没人设计过的版本。
    9. 可预期的追问:删除与重命名怎么合;要不要让模型自己解冲突;工位残留没收会怎样。

    How to reason about it · think before answering

    1. The crux is the second half. Naming worktrees is a pass; what separates candidates is how conflicts are detected, because most people assume merging is trivial and that is exactly the step that fails silently.
    2. How to break it down - say why a shared working directory is unacceptable, then the isolation mechanism, then the merge rules one by one.
    3. The problem with a shared directory is not that conflicts sometimes happen but that you cannot know what happened. Two agents read the same file; one writes back, the other does an exact replacement against stale content. The rule that old content must match uniquely saves you once, but next time it edits a different span, matches, and the file now carries two edits that were never designed together. Green tests only mean those two edits happened not to collide.
    4. The isolation mechanism is git worktree - one repository checked out into several directories, each with its own HEAD and index while the object store is shared, so opening a workspace is constant cost rather than copying a two-gigabyte tree. The four commands are worktree add --detach, work in that directory, diff HEAD to collect, worktree remove --force to clean up.
    5. One timing detail is easy to get wrong - the baseline must be taken at dispatch time, not by comparing against the main workspace at collection time. The main workspace may have moved during those seconds, and a stale baseline mixes other people's edits into this agent's diff.
    6. Detection is a three-way comparison - the baseline, this side's version, the other side's version. The instinctive implementation writes each agent's changes back in order, which means last writer wins - both receipts say done, only one change survives, and nothing on screen says so. That is the classic silent data loss of parallel agents.
    7. Three rules - one editor, apply; several editors producing identical content, apply once and do not call it a conflict (it is the same work done twice, and crying conflict devalues the signal); different content, report a conflict and apply none. Plus one that is easy to miss - the main workspace drifts too, and when the baseline no longer matches, writing would silently overwrite what the user just saved.
    8. Why not auto-pick on conflict - auto-picking presupposes you can tell which version is right, and you cannot. Real git merges line-level when edits do not overlap, which needs line information and an algorithm; a degraded implementation must at least never lose work silently. A conflict a human can read beats a quietly produced version nobody designed.
    9. Likely follow-ups - how deletes and renames merge; whether the model should resolve conflicts; what happens when workspaces are left uncollected.

    答题要点

    • 共享工作区的真问题是「无法知道发生了什么」:两处从未被一起设计过的改动会同时留在文件里
    • 隔离用 git worktree:多个目录各有 HEAD 与索引,对象库共享,开一间是常数级开销
    • 基线必须在派单那一刻取,否则差异里会混进别人的改动
    • 冲突靠三方比较;直觉的顺序写回等于后写的赢,是最典型的静默数据丢失
    • 规则:单人改落盘、多人改成相同内容落一次、改成不同内容报冲突且一份都不落;主工作区漂移同样拒绝落盘

    Key points

    • The real problem with a shared directory is not knowing what happened - two edits never designed together end up in one file
    • Isolate with git worktree - several directories each with their own HEAD and index over a shared object store, so each one costs a constant
    • Take the baseline at dispatch time, or other people's edits leak into this agent's diff
    • Detect with a three-way comparison; writing changes back in order means last writer wins, the classic silent data loss
    • Rules - single editor applies, identical content applies once, differing content is a conflict with nothing applied; a drifted main workspace is also refused
  • 什么任务适合派给子 Agent,什么任务并行反而更差?Which tasks are worth dispatching to subagents, and which get worse when parallelized?
    国内高频海外高频深入#parallelism-cost#agent-design

    分析过程 · 先想清楚再作答

    1. 这题在考成本感。答「能拆开的就并行」是在复述定义;能把三笔成本报出来、并给一条可操作的自查问题的人,明显算过这笔账。
    2. 怎么拆:先把三笔成本摆出来,再由成本反推出不该派的三类活儿,最后给一条一句话的自查判据。
    3. 第一笔成本是 token,而且是实打实翻倍的:每个子 Agent 都要重新付一遍系统提示与工具表的钱。上下文不能共享是隔离的必然结果,不是实现没做好。更糟的是这笔钱**主会话看不见**——屏幕上只滚过一行「已派出 2 个子 Agent」,所以工具里必须有一条命令把这笔账摊开,否则没人知道自己花了多少。
    4. 第二笔成本是调试。串行时顺着一条时间线看就行,并行之后有 N 条线交错发生,而日志是按到达顺序写的,两边的输出彼此穿插。这也是回执里必须带工位号的理由——那是唯一的归属线索。
    5. 第三笔成本是合并:冲突规则、重命名、删除、二进制文件,这些代价在你决定并行的那一刻就付了,只是账单晚一点到。
    6. 由此反推三类不该派的活儿:有先后依赖的(后一步要前一步的结果,派出去只能干等,最后还是串行还多花一份钱);要改同一批文件的(必然撞冲突规则,合并失败还得重做);一步就能做完的(派发的固定成本比自己顺手做完还高——给它讲清楚「你是谁、能用什么」就比活儿本身贵)。
    7. 适合的那一类有一条很好用的自查问题:**这两件事如果交给两个真人同时做,他们需要互相说话吗?** 需要就别派,不需要才是并行该出场的地方。它比「能不能拆开」准得多,因为它问的是耦合而不是形式。
    8. 还有一个容易被忽略的正面场景:**只读的探查**。派三个子 Agent 分头去大仓库的三个角落找线索,各自只回一段摘要——没有写冲突、上下文污染也最严重地被挡在外面,这是并行收益最干净的一类。
    9. 可预期的追问:并行度该设多少、上限该按什么定;失败的那一个要不要重派;要不要让用户看得到每个子 Agent 的进度。

    How to reason about it · think before answering

    1. This tests cost awareness. Answering "parallelize anything separable" just restates the definition; naming the three costs and offering an actionable self-check shows you have done the arithmetic.
    2. How to break it down - lay out the three costs, derive the three kinds of work not to dispatch, then give a one-sentence test.
    3. The first cost is tokens, and it genuinely multiplies - every subagent pays again for the system prompt and the tool table. Context cannot be shared; that is a consequence of isolation, not a shortcoming. Worse, the main session cannot see that spend - the screen shows one line saying two subagents were dispatched - so the tool needs a command that opens the books, or nobody knows what they spent.
    4. The second cost is debugging. Serially you follow one timeline; in parallel there are N interleaved ones and the log is written in arrival order, so the two outputs are braided together. That is why the receipt must carry the workspace id - it is the only attribution you get.
    5. The third cost is merging - conflict rules, renames, deletes, binary files. You pay all of it the moment you decide to parallelize; the invoice just arrives later.
    6. From those, three kinds of work not to dispatch - anything with ordering dependencies (the second step needs the first, so it waits and you end up serial having paid twice); anything touching the same files (guaranteed to hit the conflict rule, and a failed merge means redoing the work); anything done in one step (the fixed cost of dispatching exceeds the work, since explaining who you are and what you may use costs more than the edit).
    7. For the tasks that do fit, one test works well - if these two pieces of work went to two people at the same time, would they need to talk to each other? If yes, do not dispatch. It beats "can it be split" because it asks about coupling rather than form.
    8. One positive case gets overlooked - read-only exploration. Dispatching three subagents into three corners of a large repository, each returning a summary, has no write conflicts and blocks context pollution most effectively; it is the cleanest win parallelism offers.
    9. Likely follow-ups - how to pick the degree of parallelism and what caps it; whether a failed subagent should be redispatched; whether the user should see per-subagent progress.

    答题要点

    • 三笔成本:token 实打实翻倍且主会话看不见、调试要在 N 条交错时间线里找归属、合并规则的复杂度
    • 不该派的三类:有先后依赖的、要改同一批文件的、一步就能做完的
    • 固定成本是关键:给子 Agent 讲清「你是谁、能用什么」可能比活儿本身还贵
    • 自查问题:这两件事交给两个真人同时做,他们需要互相说话吗?需要就别派
    • 最干净的正面场景是只读探查:没有写冲突,而且最有效地挡住了上下文污染

    Key points

    • Three costs - tokens genuinely multiply and stay invisible to the main session, debugging means attributing across N interleaved timelines, and merging carries its own complexity
    • Three kinds not to dispatch - ordered dependencies, work touching the same files, and anything done in a single step
    • The fixed cost is the crux - telling a subagent who it is and what it may use can cost more than the edit itself
    • The self-check - if two people did these at the same time, would they need to talk? If yes, do not dispatch
    • The cleanest win is read-only exploration - no write conflicts, and the strongest block on context pollution

评论