2.5 迈向自动驾驶代码库 · Cursor
  这篇博客是对上一次 multi-agent实验的具体描述,并做了进一步探索。
  Lin(这篇博客的作者)最初想要引导opus 4.5构建一个浏览器引擎,他先用opus4.5写一个详细规划,然后不断提示agent“继续”,看看它能推进到什么程度。但很快就失败了,模型会搞不清自己在做什么,往往会在离成功很远的时候就停下来,宣称自己已经完成,也会卡在十分复杂的实现细节上。
  接着,Lin让 agent 规划出一个由主要工作项组成的依赖图,让多个 agent 可以并行承担工作。但由于agent之间无法交流,也无法对整个项目提供反馈,实验依然失败。因此Lin认为系统需要变得更加动态。
  在多agent实验前期,cursor就投入了很多精力来做好系统可观测性。他们记录了所有 Agent 消息、系统操作和命令输出,并附带时间戳,方便分析和回放会话。这不仅便于人工审查,也便于将这些数据回传给 Cursor,用来筛选海量数据并快速发现模式。
  cursor最初的实验思路很简单:让处于同一层级的各个 Agent 使用一个共享状态文件,查看其他 Agent 在做什么,决定自己要做什么,并将更新写回该文件。他们尽量少规定“应该怎么做”,而是让各个agent之间探索如何协作,而这种方式很快就失败了。具体原因是先前博客提到过的“锁”机制和agent不愿意承担责任。
  我在3月左右尝试过multi-agent协作。当时使用的方法是一个主agent了解人类意图,和人类汇报,以及掌控不同的agent。不同的agent去读一个主agent的任务md,完成任务后更新任务md,并且他们会在自己的模块中维护自己的文档。但当时没有持续下去的原因是,我没有给它们一个明确的目标,并且实际执行的效率也很低。
  cursor随后对agent的角色进行了划分:image  这种分工一目了然,不需要详细介绍,并且还和我最近的项目中agent分工非常相似,因此也激发了我的阅读兴趣。
  在下一版本中,cursor移除了独立的planner,executor在生成任务之外,还会规划如何实现目标。由于它是唯一的 agent,它不需要把计划写到任何地方,也不必拘泥于一份静态不变的计划。我的项目中将executor和worker合并,plan的生成也是来自executor,而不是来自planner。因为我认为executor在做plan之前需要按照需求阅读代码,生成plan,这个过程积攒的context将会有利于后续的开发,因此不应该让别的agent代劳。不过他们这样设计,是想要整体不被静态的plan约束。
  为了确保各个角色的agent在长时间运行中不偏离预期,cursor引入了“新鲜度”:

  1. 与不断在其中追加内容相比,应经常从头重写 scratchpad.md。
  2. 各个 agent 在接近上下文长度上限时应自动进行总结。
  3. 我们在 system prompts 中加入了自我反思和目标对齐提醒。
  4. 鼓励 agents 随时调整方向并质疑既有假设。

但executor仍然会出现异常行为。因为它同时被赋予了太多角色和目标,包括:规划、探索、研究、派生任务、检查 workers、审查代码、执行编辑、合并输出,以及判断循环是否完成。回过头看,它会被压垮也就不足为奇了。
  而cursor最终的系统设计长这样:
image  一个根规划器(root planner)负责用户指令的整体范围。它负责理解当前状态,并给出具体、明确的任务,使工作能够朝着目标推进。它本身不编写任何代码。它不知道自己的任务是否被领取,也不知道由谁来执行。

  1. 当一个规划器认为自己的范围可以进一步拆分时,它会生成子规划器,这些子规划器将完全接手被委派的那一小块范围,并以类似方式对其负全责,但仅限于各自那一块。这一过程是递归的。
  2. Worker 负责领取任务并全程推动其完成。它们不了解更大系统的存在,也不会与其他任何规划器或 worker 通信。它们在自己拷贝的代码仓库上工作,完成后会写出一份交接说明,由系统提交给发起该任务请求的规划器。

在2.5的最终版中,cursor的设计和我的还是很不一样的。第一点的设计我和cursor相同,我将其称为coordinator,用来做需求理解,但不同的是,我的coordinator是根据节点启动对应的executor,而cursor这里是让executor自己来领取任务。这两种设计方式哪一个更优,我认为需要通过实验来证明。
  在一周时间内,这些agent进行了1000万次的工具调用,整个系统的峰值吞吐为每小时 1,000 次 commit。到这里你也许会明白,这不是一个通用场景。在实际的开发中,不可能在最开始就列清楚所有的目标,且完成这些目标有一个清晰的路线,更多的是一个探索的路线。而cursor给这些agent的任务是建立一个浏览器引擎,这方面已经有很多开源项目,理论上并不是一个从0到1的探索性问题。但过程中的经验依然值得我们学习。
  如果要求在提交前的代码都达到100%正确,则会导致严重的串行化执行,大幅拖慢了有效吞吐量。一个很小的错误,比如一个拼写错误,就会让整个系统几乎停摆。worker会开始修复与自己领域不相干的内容,许多agent会一拥而上,彼此“踩踏”着尝试修复同一个问题。
  这是一个非常有趣的问题,只有真正实践过的人才会发现这种细节。很多人在设计multi agent协作时想法很多,蹦出来很多看起来非常优秀的协作架构,而没有通过实践去证明这种想法是可行的。这种“空想”会带来灾难性的后果。
  因此cursor让错误率保持在一个较小且稳定的水平,也许很少完全为零,但平稳且可控,不会失控飙升或持续恶化。允许错误存在,因为错误会很快被其他agent修复。cursor认为:
  理想的高效系统会接受一定的错误率,但仍需要一个最终的“绿色”分支,由某个 agent 定期对其进行快照,并在发布前做一次快速的修复清理。
  回到之前谈论的问题,多个agent会同时修改一个文件。cursor的方法是不做设计,允许偶然发生这种“乱流时刻”,让系统在短时间内自然收敛并恢复稳定。虽然这会多消耗一些 token,并在局部造成竞争,但整体上让系统更简单:更容易对齐模型、避免让它们不堪重负,更容易管理和观测,阻力更小,全局生产效率更高,也避免了引入过于复杂的方案。
  但显然,数百个agent在同一个机器上同时编译运行,会带来庞大的读写吞吐量。而传统的commit设计在这种情况下成为了负担,但这种情况在生产级存储系统已经有相应的设计,能否将这些设计引入到multiagent?这是cursor设计origin的原因。
  而在这个multi agent系统中,初始的prompt非常重要。cursor最开始没有着重设计prompt,而是将注意力集中到稳定的harness系统设计。其实本质上,我们还是在和一个agent对话,只是赋予他高出好几个数量级的时间和算力,这会使次优或含糊不清的指令的危害被放大。
  此外,在浏览器系统这个实验中,即使要求agent从0到1构建一个浏览器系统,但agent仍然引入了一些依赖,即使这些依赖agent可以自己实现。后来,实验人员阐述了依赖的整体理念以及哪些库绝对不能使用,才解决了这个问题。
  其实在我自己开发的过程中,也是一种波浪形。最开始,我会对整个系统非常清晰,整体架构设计,接口设计,agent编排等都形成了设计。但是随着项目进一步深入,产生的bug越来越多,往往会陷入解决局部问题,导致整体项目进展缓慢,甚至agent会为了解决某些bug而修改最初的接口设计。并且agent产生的垃圾文档,垃圾代码越来越多,我对整体的代码掌握越来越少,整个项目会走向一个失控的状态。而当遇到这些困难,回过头仔细思考之前的设计,就会发现漏洞百出,从而重构系统,规范文档,才会使后续的项目走向正规。
  agent的工程能力非常强,但是它会将prompt执行到底,不管prompt好坏。从过于复杂的度量指标设计和完全无结构的自由中找到平衡并不容易,这也表明:引出、明确和理解意图至关重要。而agent的可控性和可观测性也是非常重要的研究方向。
  我非常认同这一点。在我的项目中,我只关注明确/理解意图,而忽视了“引出”意图的设计。举个例子,我让agent将本地的博客部署到服务器上,agent明确了这个任务,也向我索取了服务器的地址,存储的路径等等。此时已经完整理解了用户的意图,并且拿到了充足的信息,但是agent完成任务后没有给我抛出一个地址让我核验。我还没有在codex,claude code中对相同的任务做测试,后续抽时间做一个实验。image  也许强大的模型会在执行过程中就去思考如何向用户呈现结果,因此即使你没有明确要求,agent也会给你一个可访问的链接让你审核。但在一些更复杂的任务中,模糊的指令会导致产出不可控。而用户不可能最开始就输入一个全面,完整的prompt,所以意图的”引出“非常重要。
  如何学习先进平台,反思自己平台的问题,就要对agent的可控性,可观测性进行系统设计。
  关于prompt如何优化,cursor给出的建议是:
最好不要为模型本来就会的事情下指令,而只针对它“不知道的东西”(例如多智能体协作),或者与你的领域强相关的内容(例如如何运行测试、你的部署流水线)。
  要把agent当作一位极其聪明但刚入门的工程师,它懂工程,但是对我们的代码库不够熟悉。
  并且,cursor发现,约束往往会比指令更有效,约束是在划定边界。"No TODOs, no partial implementations" 比 "remember to finish implementations." 更好用。
  有一个非常有趣的问题,cursor认为,在在处理更高层级或更深入的任务时,要避免“todo list”的心态。我们可以详细说明自己的意图,但如果给模型列出了很多具体要做的事项,往往会使模型过于专注于完成这些点,而忽略更广的范围。同时,这也会降低未列出事项的优先级。
  同时,cursor发现在讨论任务规模时,给出具体的数量或区间非常有用。像“generate many tasks“这类的提示词往往会让模型倾向于生成少量任务,是一种保守的默认行为。而"Generate 20-100 tasks"则传达出一种更大范围的目标,模型会明显具有更广泛的行为表现。
  cursor做了简单的总结:

  • 系统应具备反脆弱性。系统需要能承受单个agent的失败,并允许其他agent恢复或尝试替代方案。
  • 以实证为先,而非假设驱动。基于数据和观察进行调整,而不是预先带着关于“系统应该如何工作”的假设——无论这些假设来自人类组织形式还是现有系统设计。
  • 明确为吞吐量而设计。接受一个较小但稳定的错误率,不要追求100%完美运行的代码,否则会拖慢整个系统。

   在2月5号的时候,cursor就开始尝试用ai来开发ai,并且涌现出的良好的协作方式呼应了当今一些软件团队的运作模式,这很可能是组织软件项目的正确方式。