子 Agent 首步即停:Atlarix 故障复盘
文章介绍 Atlarix 的并行子 Agent:explore 负责只读探索,build 生成待批准的修改提案。15.12.0 至 15.13.3 因遗漏传递一个选项导致子 Agent 首步后停止,15.14.0 已修复。
AI 深度解读
Atlarix 的子代理曾连续多个版本只能干一步活:读一次文件,就报告「completed」。主代理随后接手,任务照样完成,用户却多花了时间和 token,也没得到并行执行的收益。问题不在模型,而在一次重构删掉了控制循环的参数。
这篇复盘值得程序员读的地方,是它把一个隐蔽的故障讲得很具体:SDK 默认值怎样改变业务行为,为什么正常结束不等于完成任务,以及测试应该怎样穿过真实执行循环,把这类退化拦下来。
并行子代理怎么协作,才能避免文件互相覆盖
Atlarix 的子代理是主代理为某个局部任务启动的助手,各自拥有上下文和工具。例如,分析 auth、billing、notifications 的交互,可以先拆成三路调查,主代理在等待期间继续工作,再陆续汇总结果。
内置类型有两种:explore 搜索代码和网络、读取文件,只读;build 起草限定范围的修改,但不直接写磁盘。用户还可以在 .atlarix/agents/ 定义自己的代理。
这里的关键设计是:并行产出修改方案,写入仍走统一审核。
build 按文件记录修改提案,主代理读取 diff 并决定是否接受。接受后,写入的是子代理生成的精确内容;文件不在 allow-list 上,仍然需要用户批准。委派出去的修改,沿用主代理修改文件时的审批约束。
这让并行工作更容易跟踪。每个子代理有独立卡片,依次展示思考和工具调用;耗时较长的任务转入后台,与后台命令一起显示在运行任务入口,用户可以查看叙述或停止任务。
但这些机制有个前提:子代理必须执行到能产出结果的那一步。此次故障恰好把它截在了最前面。
为什么删掉一个参数,所有模型都只读一次文件
Atlarix 使用 ai@7。每个子代理对应一次 streamText 调用,带有独立的 system prompt 和工具集合。一次调用可以包含多个步骤:模型请求工具,SDK 执行工具并返回结果,模型再决定下一步动作。
stopWhen 控制这个循环何时停止。它接收条件列表,SDK 在每一步之后检查条件;原文也提到,多步 generateText 调用使用这一选项。
故障来自 15.12.0 的上下文压缩重构。旧流程到压缩点就结束当前轮次,压缩后再启动新轮次;新流程改为在 SDK 的 prepareStep hook 中,每一步之前原地压缩。
子代理原来的 stopWhen 条件只负责在压缩点结束轮次。重构后,这项职责消失,开发者便删掉了条件。主代理保留了自己的条件列表,子代理调用却完全没有传 stopWhen。
在 ai@7 中,不传该选项会采用默认值 isStepCount(1),它还以旧名称 stepCountIs(1) 导出。结果是,从 15.12.0 到 15.13.3,每个子代理都在第一次工具调用后结束,通常只读了一个文件。
修复传入了 stopWhen: []。空列表没有停止条件,循环便能继续,直到模型不再调用工具,也没有额外的步数上限。
省略参数和显式传空数组,在这里是两种不同的执行策略。
作者还为这行配置加了注释,并移除了已经无法到达的压缩续跑循环。修复随 Atlarix 15.14.0 发布,覆盖桌面应用、CLI 和编辑器扩展。
「completed」为什么能掩盖故障,测试又该测什么
这次故障没有异常。SDK 正常结束,子代理显示「completed」,卡片上还能看到一次看似合理的文件读取。build 的修改提案集合只是空的。
主代理又提供了一层掩护:看到助手没交出多少东西,就自己把任务做完。最终结果可以交付,内部却发生了重复劳动。作者称问题对所有模型都一样,因为限制来自调用配置。
他们通过一段运行在 15.13.3 上的真实会话录像定位问题:每个子代理约消耗 3k tokens,只读取一次文件,没有返回提案,随后主代理重复执行任务。这是具体会话的观察,原文没有给出整体延迟或费用增幅。
新增测试没有 mock 掉 streamText,而是让真实子代理函数经过真实 streamText,用 AI SDK 测试辅助工具中的脚本模型替代实际模型服务:
explore按read, read, answer返回固定步骤,测试要求两次读取都执行。build按read, edit, answer返回固定步骤,测试要求编辑被记录为提案。
旧代码下,两项测试都只执行第一次工具调用,因此失败;修复后通过。随后,作者又在修复版本中安排三个子代理处理三项任务,每个实际运行了 3 到 8 步,完成读取和编辑,提案返回主代理并得到应用。
这里测的不是模型回答得好不好,而是工具结果返回后,循环能否继续,以及编辑能否抵达提案流程。mock 掉循环入口,反而可能把真正需要验证的行为一起替换掉。
我的判断
我认可这篇复盘最明确的判断:任务状态和任务产物要分开验证。「completed」只能证明运行结束,不能证明预期的读取、编辑或提案已经发生。多代理场景里,主代理还能接手补救,只看最终答案更容易漏掉退化。
测试方案也有针对性。脚本模型提供确定步骤,真实 SDK 负责推进循环,既能复现默认值问题,也不依赖在线模型临场发挥。不过,它验证的是循环和提案链路,不能据此证明复杂任务的方案质量或并行效率。
原文没有提供串行执行的对照耗时、token 成本,也没有交代多个提案修改同一文件时如何处理冲突。因此,「拆成子代理会更划算」仍需具体任务验证;这里的实测证明的是修复后能够连续工作。
另外,stopWhen: [] 适合他们取消压缩轮次边界后的流程。它没有步数上限,但模型停止调用工具也不自动保证任务完成。原文没有讲清预算、超时或循环异常的处理,普通团队不宜直接把空数组当成通用配置。
能拿来做什么
- 在 Node 项目升级 AI SDK 或重构上下文压缩时,把删除配置项也当作行为变更来审查,确认库在参数缺席时会采用什么默认值。
- 为 AI 编程工具保留一条经过真实 SDK 的多步测试,例如固定执行「读文件、再读文件、回答」,并检查工具确实调用两次。
- 对生成代码的代理检查实际产物:不仅看运行是否结束,还要检查预期提案是否生成、编辑是否进入审核流程。
- 评估并行子代理时,先选择可以独立调查的模块任务,并观察主代理是否重复劳动,再判断拆分是否节省时间和 token。