# Stripe Minions 第二部分:底层是怎么搭建的
本文是 Stripe 编码 Agent 系列的第二篇。回顾一下第一篇:Minions 是 Stripe 自研的无人值守 Agent 编码流程。**每周有超过 1,300 个 Stripe Pull Request(第一篇发布时还是 1,000)完全由 Minion 生成**,经过人类审查,但不包含任何人类编写的代码。
如果你还没读第一篇,建议先看一下,了解 Minions 的开发者体验。本文深入探讨构建细节,聚焦 Minion 流程中 Stripe 特有的部分。
### Devbox:热乎的,准备好了
为了在大规模下实现最高效率,无人值守 Agent 编码需要一个可并行、可预测、隔离的云端开发环境。人类应该能够给多个 Agent 分配逻辑上独立的工作。Agent 应该有干净的环境和工作目录:如果 Agent 之间互相干扰对方的改动,就会白白浪费 token 在冲突解决上。完全自主还要求 Agent 在系统层面与特权或敏感机器隔离开,尤其不能使用人类的个人凭据。
要让 Agent 在开发者的笔记本电脑上满足所有这些条件,非常困难。容器化或 git worktree 可以帮上忙,但很难组合使用,而且从根本上说,要构建一个既拥有开发者 shell 的全部能力、又被适当约束的本地 Agent,是极具挑战的。
不过,Stripe 的 Minions 天然具备这些特性,因为它们运行在 Stripe 工程师使用的标准开发环境上:**devbox**。
一个 Stripe devbox 是一台 AWS EC2 实例,包含我们的源代码并运行正在开发中的服务。大多数人类编写的 Stripe 代码,本身就是在通过 SSH 远程连接到 devbox 的 IDE 中产生的。用 DevOps 的话说,devbox 是"牲口,不是宠物":它们是标准化的、易于替换的,而不是定制的、长期存活的。
许多工程师每个任务用一个 devbox,一个 Stripe 工程师可能同时开着半打。
> 一张工程师活跃 devbox 列表截图,包含 Minion 运行
我们希望启动新 devbox 毫不费力,因此目标是 10 秒内就绪。为了达到这个"热乎且就绪"的标准,我们主动预配和预热一个 devbox 池子,让它们在开发者需要时处于待命状态。这包括克隆巨大的 git 仓库、预热 Bazel 和类型检查缓存、启动在 devbox 上持续运行的代码生成服务等等。10 秒后,devbox 的所有者就拥有了一台检出了 Stripe 所有主仓库最新 master 分支的机器,可以立即打开 REPL、运行测试、修改代码并做类型检查,或者启动一个 Web 服务。
devbox 是为人类工程师的需求而构建的,远在 LLM 编码 Agent 出现之前。事实证明,**并行、可预测、隔离**这些特性也正是 Stripe 工程师高效工作所需要的。对人好的,对 Agent 也好。建立在这个基础设施原语之上,为 LLM Agent 提供了一个天然的归宿,产生了复利式的回报。
### Agent 本身
与早已服务于人类开发的 devbox 不同,我们的 Agent 框架是专门为 Minions 这个场景定制构建的。
2024 年底,随着编码 Agent 在行业中兴起,我们内部 fork 了 [Block 的 goose](
github.com/block/goose),这是最早被广泛使用的编码 Agent 之一。我们定制它以适配 Stripe 的 LLM 基础设施。随着时间推移,我们把 goose 的功能开发聚焦于 Minions 的需求,而非人类监督工具的需求:后者已经被 Cursor 和 Claude Code 等第三方工具很好地满足了,我们的工程师已经在使用它们。
事实上,**Minions 最独特的特征是没有人监督。** 现成的本地编码 Agent 通常被优化为工程师的搭档,工作时工程师基本上"在旁看着"。但 Minions 是完全无人值守的,因此我们的 Agent 框架不能使用面向人类的功能,比如可打断性或人类触发的命令来启动或引导 Agent 运行。
另一方面,隔离的 devbox 环境意味着 Agent 不需要确认提示;任何 Agent 可能犯的错误,都被限制在一个 devbox 的有限爆炸半径内,因此我们可以安全地以完整权限运行 Agent,跳过确认提示。
我们还可以精确调校优化,完全适配 Stripe 的开发流程。我们基于 Stripe 系统的特性做了许多小的优化。一个更大的优化,事实证明对 Minions 的实现更为根本,是**蓝图(blueprint)** 的概念。
### 蓝图
编排 LLM 流程最常用的原语是[工作流和 Agent](
anthropic.com/engineering/bu…)。工作流是一种通过固定的步骤图来运行的 LLM 系统,图中的每个节点负责整体目标中一个窄范围的部分,预定义的边控制着这些离散节点之间的执行流程。
而 Agent 通常是一种更简单的"循环加工具"的编排模式,LLM 依靠自己的判断反复调用手中的工具,并根据工具调用的结果决定下一步该做什么。
Minions 用我们称之为"蓝图"的原语来编排。**蓝图是在代码中定义的工作流,用来指挥一次 Minion 运行。** 蓝图结合了工作流的确定性和 Agent 在面对未知时的灵活性:一个给定的节点可以运行确定性代码,也可以运行一个专注于特定任务的 Agent 循环。本质上,蓝图就像一组 Agent 技能与确定性代码的交织编排,让每个子任务都能以最合适的方式处理。
比如,在驱动 Minions 的蓝图中,有标签为"实现任务"或"修复 CI 失败"的 Agent 式节点。这些 Agent 节点被给予很大的自由度,可以根据输入自己做决定。然而,蓝图中也有标签为"运行配置的 linter"或"推送更改"的节点,它们是**完全确定性**的:这些节点根本不调用 LLM——它们只是运行代码。
因此,蓝图是一种在 Agent 运行中保证某些子任务被确定性完成的方式。Minion 的蓝图最终看起来像一个状态机,交织着确定性代码节点和自由流动的 Agent 节点。
> 示例蓝图。确定性节点用矩形表示,Agent 子任务用云形表示。
根据我们的经验,**用代码确定性完成那些我们可以预见的小决策**——比如"在运行结束时总是 lint 修改"——在大规模下节省了 token(和 CI 成本),也让 Agent 少了一些出错的机率。总体而言,我们发现"把 LLM 放进围起来的盒子里",会累积成系统级的可靠性提升。蓝图机制让这些子 Agent 的上下文工程变得容易,无论是约束工具、修改系统提示,还是根据需要简化对话上下文。
各个团队也可以设置针对自己特定需求优化的蓝图。例如,有团队构建了自定义蓝图,用来在代码库中运行棘手的、LLM 辅助的迁移,而这种迁移无法用简单的全确定性 codemod 完成。
### 上下文收集:规则文件
在像 Stripe 这样的大型代码库中,一个毫无指引的 Agent 可能会在遵循最佳实践或使用正确库方面遇到麻烦,即使有好的 linter 也无济于事。为了解决这个问题,各种 Agent 规则格式——比如 CLAUDE.md 或 AGENTS.md——允许 Agent 在遍历目录结构时自动"了解"代码库。
由于我们仓库的规模,我们对无条件全局规则的使用非常克制,否则 Agent 的整个上下文窗口还没开始就会被规则填满。相反,**我们几乎完全让 Minions 从限定在特定子目录或文件模式下的文件中获取上下文**,这些文件在 Agent 遍历文件系统时自动附加。
从我们的角度看,最好避免规则文件的重复,让我们的 Agent 读取与人类导向的 Agent 相同的上下文。基于此,我们标准化了支持这些功能的主流规则格式——[Cursor 的规则格式](
cursor.com/docs/context/rule…)——并修改了我们的框架,使 Minions 既能读取这些规则,也能读取之前的自研格式。
我们现在还将 Cursor 规则同步为 Claude Code 也能读取的格式,这样我们三个最流行的编码 Agent(Minions、Cursor 和 Claude Code)都能从 Stripe 工程师在代码库中搭建的规则文件中受益。
### 上下文收集:MCP
从文件系统读取对静态上下文收集很有效,但 Agent 经常需要通过联网工具调用动态获取信息。特别是,为了充分充实用户请求,Minions 需要检索内部文档、工单详情、构建状态、代码智能等信息。MCP(Model Context Protocol)发布后迅速成为联网工具调用的行业标准,我们将 Minions 与之集成。
Stripe 已经构建或集成了大量运行在不同框架上的 Agent:无代码内部 Agent 构建器、在专用服务上运行的自定义 Agent、第三方现成 Agent、命令行 Agent 工具和其他编码 Agent,以及 Agent Slack 机器人。所有这些 Agent(不仅仅是 Minions)都需要 MCP 能力,而且常常包含重叠的工具集。
为了支持所有这些,**我们构建了一个中心化内部 MCP 服务器,叫做 Toolshed**,它让 Stripe 工程师可以轻松编写新工具,并使其自动被我们的 Agent 系统发现。我们所有的 Agent 系统都能够将 Toolshed 作为一个共享能力层来使用;向 Toolshed 添加一个工具,就立即赋予了我们整个由数百个不同 Agent 组成的舰队新的能力。
Toolshed 目前包含近 **500 个 MCP 工具**,涵盖内部系统和我们使用的 SaaS 平台。Agent 在被给予一个"更小的盒子"和精心筛选的工具集时表现最佳,因此我们配置不同的 Agent 只请求与它们任务相关的 Toolshed 工具子集。Minions 也不例外,默认只提供一个有意缩小范围的工具子集,但每个用户可自定义,允许工程师为自己的 Minions 配置额外的按主题分组的工具集。
由于 Minions 完全自主运行,可以自由调用其 MCP 工具,我们还有一个内部安全控制框架,确保它们不能使用工具执行破坏性操作。不过,作为第一道防线,我们的 devbox 本身就运行在 QA 环境中,因此 Minions 无法访问真实用户数据、Stripe 的生产服务或任意外部网络。这不是巧合:我们有意构建了隔离的 devbox,让人类有一个可以安全实验的环境。而正如其他很多事情一样,**对人类安全的开发环境,对 Minions 同样有用。**
### ……然后迭代
虽然我们构建 Minions 的目标是一次性完成任务,但给 Agent 提供可以迭代对抗的自动化反馈,是推动进展的关键。Stripe 庞大的既有测试库——超过 300 万个测试——可以提供这种反馈。然而,虽然推送的分支会在 CI 中运行所有相关测试,但我们不想过度依赖 CI 来获取所有代码反馈。
在考虑开发者生产力时,我们尽量遵循"反馈左移"的原则。这个说法意味着,如果我们知道某个自动化检查会在 CI 中失败,最好让它在 IDE 中也被强制执行,并立即呈现给工程师,因为这是向用户提供反馈的最快方式。
例如,我们有 pre-push 钩子来修复最常见的 lint 问题。一个后台守护进程会预计算适用于某次改动的 lint 规则启发式,并缓存运行这些 lint 的结果,因此开发者在推送时通常可以在远少于一秒的时间内获得 lint 修复。
Minions 自然也融入了这个框架,因此它们不必浪费 token 或 CI 时间在与自动格式化工具之类的反复交互上。我们在 Agent 的开发循环蓝图中将一组 linter 作为确定性节点运行,并在推送 Agent 分支之前在本地对该 lint 节点进行循环,这样分支在第一次跑 CI 时就有较大机率通过。
在本地运行所有测试是不可行的,因此我们也在标准 Minion 蓝图中包含了一轮针对完整 CI 套件的迭代。在 Minion 推送更改后,我们运行 CI,并自动应用任何针对失败测试的自动修复。如果有无法自动修复的失败,我们将失败信息返回给蓝图中的 Agent 节点,给 Minion 一次在本地修复失败测试的机会。在第二次推送和 CI 运行之后,我们将分支交还给人类操作员,由人工审查。
为什么只有一到两轮 CI?在速度和完整性之间需要平衡;CI 运行消耗 token、算力和时间,我们认为让 LLM 无限运行多轮完整 CI 循环,边际收益递减。我们认为这个策略在各方考量之间取得了很好的平衡。
### 总结
Minions 只是 Stripe 使用 AI 加速工程师工作的方式之一,但我们认为它是如何将行业标准概念——如 Agent 框架和 MCP——与我们自己的内部工具和基础设施融合的一个很好的范例。这些工具和基础设施是 Stripe 工程师多年来为最大化开发者生产力而不懈调校的成果。
无论是通过改进文档、开发者环境,还是迭代循环,我们一次又一次地发现,**这些年来在人类开发者生产力上的投入,在 Agent 的世界里产生了复利式的回报。**
Minions 已经改变了 Stripe 软件工程的格局。我们持续改进它们,融入行业内最新最好的成果,并适配 Stripe 的规模。结合我们在人类开发者体验的硬仗中获得的口味和专业知识,我们会将 Minions 打造到最好的水平。
有兴趣使用 Minions 或参与 Minions 的开发?[Stripe 正在招聘](
stripe.com/jobs)。
---
原文:
stripe.dev/blog/minions-stri…
#Stripe #AI编码 #Agent #MCP #软件工程