M2 · fork 机制 + 安全四层 —— 开发文档(面试向)#
讲为什么这么做、做了哪些取舍,不讲代码。读完能用大白话讲出来。
一句话概括#
M2 让主 Agent 能”分身”——遇到能并行或会拖累自己的活儿,就复制一个一模一样的自己去干,干完只把结论拿回来。这是整个项目最核心、也最容易失控的一环(分身再分身就会无限繁殖、烧光资源),所以这一关的铁律是先把刹车做好,再谈功能。
打个比方:主 Agent 像个研究员,活儿多了就喊几个”克隆人”分头去查,自己只等结论。M2 既要让克隆能力跑起来,又要给它装上四道保险,防止克隆失控。
1. 为什么要”分身”,不能一个人从头干到尾#
一个 Agent 单打独斗有三个绕不开的瓶颈:
- 干等:要在 4 个平台搜同一件商品,一个人只能一个一个搜,串着来,30 秒才搜完。
- 被带偏:要对比三家的售后条款,得把十几页原文都读进来——这些一股脑塞进主线对话里,把”我本来在挑旅行三件套”这个正事给淹没了。
- 链条太长:一个子任务自己就要搜商品→查规格→查评价→算运费走五步,全堆在主线里,注意力就散了。
这三个问题都指向同一个解法:让主 Agent 复制一个自己出去,单独把这摊子事干完,只把结论拿回来。 并行的几个克隆同时搜,30 秒变 8 秒;克隆的一堆中间过程留在它自己那儿,不脏主线。
面试怎么讲:单 Agent 在”要并行、怕被中间数据带偏、子任务链太长”这三种情况下扛不住,所以引入”分身”——并行提速、隔离脏数据、收敛只回传结论。
2. 什么时候该分身:三件事判断#
分身不是免费的——复制一个、等它启动、把结论传回来都有开销。所以定了一个三件事判断:满足任意一条才分身,否则主 Agent 自己干。
- 能并行:几个子任务互不依赖,能同时跑(典型:跨多个平台同时搜)。
- 要隔离:子任务会冒出一大堆中间数据,塞回主线会污染上下文(典型:读十几页条款做对比)。
- 链够深:子任务自己内部就要走三步以上(典型:搜→查规格→查评价→算运费)。
反过来,追问一句、只调一次工具、闲聊、收尾这些——分身的开销比收益还大,主 Agent 自己干更快。
面试怎么讲:分身有成本,不是越多越快。我定了”能并行/要隔离/链够深”三条,满足一条才分身,简单任务自己扛。这是个”投入产出”判断。
3. 分身为什么要”一模一样”(同质),怎么做到#
关键决定:克隆出来的子 Agent,和主 Agent 用完全相同的工具和指令,只是各自独立干活、互不干扰。
为什么不给克隆”专门定制”:传统做法是养一堆专家小弟,每个只会一招(搜索小弟只会搜、数据库小弟只会查表)。我们偏不——因为克隆遇到意外情况时,如果能力被砍过,就抓瞎了。让它和本体能力完全一样,遇到啥都能灵活应对;而且维护一套就够,不用维护一堆不同的小弟。
一个实现上的巧思(白话版):克隆要拿到”完整工具箱”,而这个工具箱里也包含’分身’这个工具本身(这样克隆还能再分身)。这就有个”先有鸡还是先有蛋”的问题。解决办法是:不把工具箱写死给它,而是给它一个”用的时候现去取”的取货单——等真要分身时才去拿当前最新的完整工具箱。这样既保证克隆和本体永远一致,又自然支持”克隆再克隆”。
面试怎么讲:“分身”和”养一堆专家小弟”是两条路。分身的好处是统一、灵活、能层层嵌套;难点是工具箱要包含分身工具自己,用”延迟取货”的方式解开这个循环依赖。
4. 安全四层:四道防失控的闸门#
这是 M2 的重头戏。让 Agent 自由分身、自由循环,最大的风险是失控。所以装了四道闸门,每道治一种失控:
| 闸门 | 治什么失控 | 怎么做 |
|---|---|---|
| ① 深度上限 | 克隆再克隆、无限繁殖、资源指数爆炸 | 记录”分身到第几层”,超过 2 层就拒绝再分 |
| ② 超时 + 步数上限 | 某个克隆卡死、或自己绕圈停不下来 | 给克隆设一个总时限和最多走几步,超了就掐 |
| ③ 结果截断 | 某个工具一次吐回几万字,把上下文撑爆 | 结果太长就截断,并留一句”已截断”提示 |
| ④ 循环检测 | 模型一直重复调同一个工具不收敛 | 最近几次调用里同一个工具出现太多次,就提醒它换思路 |
面试怎么讲:自由度越高越要装刹车。无限繁殖、卡死、单次结果炸内存、原地打转——这四种失控各对应一道闸门。这是做 Agent 系统最该先想清楚的事。
5. 最重要的设计哲学:克隆失败,绝不拖垮全局#
决定:克隆任务无论怎么出错——被深度拦截、超时、绕圈超限、或任何意外异常——都转成一句话的”失败说明”返回,绝不把异常往上抛、绝不让整个系统崩。
为什么:从主 Agent 的角度看,“分身”就是它调的一个普通工具。工具失败了,它该像收到任何工具结果一样,读到”这个子任务失败了,原因是超时”,然后自己决定换个思路。如果克隆一崩就把主 Agent 也带崩,那等于一个小弟摔了一跤,整个团队全趴下——不可接受。
实测里就验证了这点:示例里一个并行子任务超时了,主 Agent 收到”[超时]“这句话后,自己改用别的方式把活儿干完了,整体没崩。安全网真的接住了。
面试怎么讲:把”子任务失败”降级成”一条普通的工具结果”,而不是”一个会冒泡的异常”。局部故障不扩散,是这类系统稳定性的命门。
6. 并发分身下,怎么保证不串号#
之前(M0)解决了”很多用户同时在线不串号”。现在多了一层挑战:一个任务还会分身出好几个克隆并行跑——这些克隆各自的”我是第几层分身""我属于哪个用户”不能互相打架。
用的还是 M0 那套”每个任务一份独立记录”的机制:每个并行克隆启动时,会自动拿到一份父级记录的副本,各改各的、互不干扰。所以”分身深度”这个计数,在十个并行克隆里是十份独立的,不会你加我也加地乱套。
面试怎么讲:并发分身最怕共享状态打架。靠”每个任务一份独立副本”的机制,让深度计数、身份信息在并行克隆之间天然隔离。这是 M0 那套上下文隔离的延续和实战检验。
7. 一个诚实的边界:哪些做完了,哪些等主循环再接#
M2 把四道闸门的零件都做好并测透了。但其中两道——“主线里逐个工具的截断”和”循环检测”——需要挂在主循环上才能真正实时生效,而主循环要到后面(M14)才组装。
所以我明确标注:①②和”分身边界上的截断”现在已经生效;③的主线逐工具截断、④的实时循环检测,等主循环搭起来再接上。没有假装全做完了——零件做对、测透,但诚实说明哪些还没接线。
为什么值得讲:工程上最忌”看起来做完了其实没接上”。与其留个空挂的假完成,不如把边界标清楚。这也呼应了项目的一条原则:砍了什么、缓了什么,都要明说,不搞静默。
面试怎么讲:做基础设施要分清”零件做好了”和”接到主干上了”。我把能独立做对的零件先做透,把依赖主循环的接线明确推迟并标注,避免虚假完成。
8. 踩坑实录:克隆”超时”反而教会我”同质”的真正含义#
情况:写演示时,并行分身出去的克隆超时了。
查下来:克隆虽然用了和本体一样的工具,但我一开始让它用的是完整正式版指令,而演示的本体用的是简化版指令——两者指令不一样,克隆就”跑偏”了:它拿着”鼓励分身、提到九个工具”的正式指令,手里却只有演示用的玩具工具,于是犯迷糊、甚至想再分身,越搞越慢直到超时。
怎么修:让克隆和它的本体用同一份指令。改完,超时消失,并行分身干净跑通。
真正的教训:原来”同质”不只是工具一样,指令也必须一样——克隆得是本体的完整复制,差一点都会跑偏。(顺带,这次超时也正好证明了安全网有效:克隆超时被接住、主线自动改道,没崩。)
面试怎么讲:“分身要一模一样”这句话,工具一致还不够,连指令都得一致,否则克隆会跑偏。这个坑让我对”同质”理解得更具体了。
9. 后续落地中补全的三个机制亮点#
M2 做的是 fork 的原语和安全四层。后续里程碑在它之上接了三个具体的机制,值得一讲:
平台覆盖保障(_ensure_platform_coverage)#
模型在跨平台 fork 时经常「漏列平台」——demands_list 里写了 3 个平台、漏了 2 个。prompt 写「必须覆盖 5 个平台」,弱模型不听。解法:代码自动补齐。fork 发出前,检测 demands 里提到了哪些平台,缺的自动用现有 demand 当模板克隆一条、前面加「只在 {platform} 检索」的强制指令。这是项目一贯的「机制兜行为、不靠 prompt」——平台覆盖是业务红线,不能让模型决定覆不覆盖。
深度闸的双语义(能力同质但授权不同质)#
子 Agent 拿的工具集和主 Agent 完全一样(同质 fork),但有两类工具在子里被中间件硬拦,拦的理由不同:
- 需要全局视图的工具(
price_compare、item_picker、shopping_summary):子只搜了一个平台,没有跨平台全貌,调这些没意义 → 回「需要跨平台全局视图,请在主流程调用」。 - 主流程已做过的上下文工具(
planner、category_insight):主流程已经拆过意图、查过品类,结论写在 demands 里了,子再做是重复 → 回「主流程已做,结果在 demands 里,请直接检索」。
同一个权限边界(depth==0 only),两种拒绝语义——子收到的提示不是笼统的「你不能调这个」,而是具体告诉它为什么不能、该怎么做。这比一刀切拦截更容易让弱模型理解并切换行为。
URL 旁路:不让模型碰真实链接#
item_search 召回后,每条候选的 URL/image_url 被登记到会话级注册表(_candidates.py),然后从模型可见的候选形态中剥除。整条流水线(比价 → 到手价 → 精挑)里模型看到的都是没有 URL 的紧凑版。最终 shopping_summary 收尾时,按 item_id 从注册表回填真实链接给前端商品卡。
为什么这么做:URL 又长又对推理无用(它不帮模型判断「这件 T 恤值不值得推荐」),但模型会在回吐候选时原样复述——每跳透传四次,每次都浪费输出 token 解码一堆 URL。更危险的是:模型偶尔会修改或拼凑 URL,产出看起来像真链接、实际指向不存在页面的幻觉链接。旁路一举两得:省输出 token(实测单次检索省 ~25%)、杜绝链接幻觉。
M2 验证了什么#
| 验证点 | 结果 |
|---|---|
| 能按三件事判断分身、跨平台并行 | ✅ 实测主 Agent 自主并行 fork 三个平台 |
| 分身无限繁殖被拦住 | ✅ 超过 2 层直接拒绝 |
| 单次结果过长被截断 | ✅ 截断并留提示 |
| 同工具刷屏被发现 | ✅ 达阈值触发换思路提示 |
| 克隆失败不拖垮全局 | ✅ 超时/异常都转成一句话、主线自动改道 |
面试一句话总结:M2 给主 Agent 装上了”分身”能力,但更重要的是先装好了四道防失控闸门和”局部失败不扩散”的底线——这是整个项目风险最高的一环,体现的是”做强能力前先做好安全”的工程判断。最值得讲的是”同质 fork(连指令都要一致)“和”所有异常都降级成普通工具结果、绝不崩”。