Research

朴素的工具,自动驾驶式开发:Muse 如何自我构建

走进 Meshy 内部模型评测平台 Muse:Slack、Linear、GitHub 和 Claude Code 这套朴素技术栈,如何让 AI 编写的代码变得可信。

Andrew
发布时间:2026年8月3日

TL;DR Muse 用每个软件团队本就常开的四款工具——Slack、Linear、GitHub 和 Claude Code——取代了定制化的 agent 编排控制台。没有仪表盘,没有 SPA,没有人写的代码。每个 PR 都要通过 7 道 CI 关卡和一个三角色 AI 评审团的审查,且必须做到零遗留问题才能合并。已收集 40,000 张投票,基础模型评测速度提升 80%。

Muse 是 Meshy Universal System for Evaluation 的缩写,是我们内部的模型评测平台。Meshy 的各个团队每天都会用它来判断新的模型 checkpoint 是否比旧版本表现更好。

有一天,一个刚出炉的热门新 checkpoint 需要一场竞技评测来决定下一步的训练策略。团队发现,要支持这次评测,需要给 Muse 添加一个新功能。

"@Linear 帮我就这个建一个 Muse issue"

产品经理在 Slack 上这样说道。

一分钟后,Linear issue MES-12345 就创建好了。

Muse 平台的负责人——也就是我——看到了这个 issue。它需要尽快上线才能解除评测任务的阻塞。于是,我把这条消息发给了 Claude:

"调查并修复 MES-12345"

Claude 查看了这个 issue,随后提出了两个需要人来决定的设计问题。做出决定后,我就离开终端去忙别的事了。

一个半小时后,Muse 的新版本就部署上线了。搞定。

自主软件工程不必依赖花哨的 agent 编排控制台或仪表盘。 在这篇文章里,我会解释我们是如何做到这一点的。

一个画廊、一个查看器、一个投票按钮

Muse 要回答的问题看似简单,实则很难:新的模型 checkpoint 真的更好吗? 当然,每次训练过程中都有可以追踪的量化指标,但这些指标远不足以准确代表 3D 生成中"好"到底是什么样子。所以 Muse 采用了简单却有效的做法:双盲测试。它把两个模型的输出并排放在一起,使用相同的输入图片,左右位置随机打乱,让团队成员在不知情的情况下投票。

一次评测就是一批这样的配对。有人把 Muse 指向两组生成的网格模型和一个参考图片文件夹,接下来的事就交给应用去做:把模型配对、打乱分配顺序、每次呈现一组对比。只要大家有空闲的几分钟,投票就会不断累积。整个界面被设计成可以随手完成的"轻量任务",几乎完全可以用快捷键操作。一个还得伸手去够鼠标的投票者,投的票自然会更少。

Muse 评测界面:两个 3D 模型的输出并排显示,用于盲投

Muse 的投票界面:两个生成的网格模型,相同的输入图片,左右位置随机打乱。

这就是这款产品的全部。一个画廊、一个查看器、一个投票按钮。Muse 的_功能_本身毫不复杂,这恰恰是它能成为一个干净利落的案例研究对象的原因。不同寻常的,只是它的构建方式。

一个 agent 能看懂的架构

Muse 项目始于 2025 年末,那时"vibe coding"刚刚成为一个新的流行词。除了服务于 Meshy 在训练和发布周期中日益增长的评测需求,Muse 还有一个雄心勃勃的目标:为整个公司构建软件,且不含一行人类写的代码。

如今,成果相当亮眼:

  • 已收集超过 40,000 张投票(自 Muse 首次交付以来),每月评测项目超过 30 个
  • 超过 4 个团队正在将 Muse 用于日常工作
  • 首个内部代码库,至少有 1 名非技术团队成员在积极参与贡献
  • 首个内部代码库,实现了完全自主的开发

总的来看,我们估计Muse 至少让我们的基础模型评测速度提升了 80%,并在每次模型发布中至少节省了 10 人日的工作量。如果没有 Muse,要维持 Meshy 生成式模型集合(比如几何、纹理和智能拓扑)目前的开发和发布吞吐量,几乎是不可能的。

从第一天起,Muse 的设计目标就是尽可能让 agent 能"看懂"。 早在 2025 年,我们就发现,要实现同一个用例,agent 有时会在某些方面碰壁,却能在另一些方面一次成功。这些反复迭代最终造就了今天这套刻意做得朴素的技术栈。 后端是 Python:FastAPI,记录用 Postgres,网格模型和图片用 S3。前端是服务端渲染的:Jinja2 模板、用于局部页面更新的 HTMX、用于样式的 Tailwind,以及唯一一个真正的客户端组件——渲染网格模型的 WebGL 3D 查看器。没有 SPA,也没有独立的前端模块。这两套技术栈之间的耦合方式是 Web 上最古老的那一种:后端渲染 HTML,浏览器负责显示。当页面需要变化时,HTMX 会请求一个片段,服务端同样负责渲染这个片段。

以下是 Muse 核心评测应用的架构:

Muse 核心评测应用的架构图,展示 main、apps、adapters 和 libs 各层

Muse 核心评测应用的分层架构。依赖关系只能向下指向。

这种结构有两个特性对 agent 来说很重要。第一,前端状态存在于服务端。用户看到的 HTML 就是后端生成的 HTML,所以一个测试(或一个 agent)可以直接对其进行断言,而不需要驱动一个客户端框架去重建用户所看到的内容。第二,每个依赖关系都由机器强制执行,且只能向下指向。一个功能文件夹不能导入它的同级文件夹;如果两个功能彼此需要,组合根会把一个能力注入到另一个里面,这样依赖关系就只会出现在唯一一个文件中。每一层只负责一件事,而且这些职责被写在了 agent 一定会碰到的地方:

职责为什么 agent 能在这里安全地工作
main/只负责接线。注册各功能模块的 URL 路由。新增一个功能就是一个文件夹加一行 include。没什么可以臆造的空间。
apps/<feature>/每个功能一个文件夹:routes、pages、service、store、tests。一次修改的影响范围被限制在这个文件夹内。同级导入会导致构建失败,而不只是一条评审意见。
adapters/外部世界:网页渲染、数据库、对象存储。所有 I/O 都通过每个系统各自唯一的接口点。每个接口点都有对应的 fake 实现。
libs/共享的可安装库。像第三方代码一样进行版本管理。改动其中一个会被刻意地"高调"处理。

依赖关系只会沿着这个列表向下指,而且这个方向是由构建检查强制执行的,而不仅仅是一种约定。

还有三个特性,是刻意为了让代码库对 agent"可读"而设计的:

  • 每个目录都自带文档(说明这个服务做什么、它的分层规则、以及决定能否合并的具体命令),这样一个毫无背景信息就"空降"进来的 agent 也能自己摸清方向,而不需要发问或者自己臆造答案。
  • 非平凡的模块都带有 docstring,并回链到设计文档,这样"为什么这么做"就能和"做了什么"一起保留下来。
  • 任何文件都不得超过 500 行,少数例外情况设有单向限制。这些例外文件只能缩小,不能再长回去。Context window 是有限的,架构设计尊重这一点。

进展不该被关在一个小房间里

如今,由于 LLM agent 比 2025 年时强大得多,我们希望 Muse 的开发能做到完全自主。 也就是说,人类只需要把 agent 指向任务。agent 会自行在代码库和任务背景中定位方向,一路走完实现、CI 和代码评审,最终自己把 pull request 推向合并。人类只负责决定 issue 的优先级,并做出关键的设计决策。

一提到完全自主开发,人们最先想到的通常是定制化平台:编排控制台、agent 原生工作区,一个又一个承诺帮你管理整支 agent 舰队的应用。我们看过这些产品,但最终放弃了。不是因为它们不够好,而是因为——还是那个词——可读性。

我们希望 agent 的工作对人类是可读的,就像代码库对 agent 应该是可读的一样。 我们是一家规模不小的公司。如果把自主开发放进一个定制的控制台里,它的进展就只对那几个整天泡在这个控制台里的人可见。

所以,协作和开发工具栈同样是刻意做得很朴素。 我们这个故事里的一切,都运行在每家软件公司本就常开的四款工具上:Slack、Linear、GitHub 和 Claude Code。这让 agent 取得的进展,和任何一个团队成员的进展一样,可以被观察、被搜索。产品经理照旧打开 Linear 查看 issue、检查功能状态。GitHub 的 bug bot 或安全评审照旧读取 PR 讨论串。任何报告 bug 的人照旧把截图贴进同一个 Slack 频道,修复结果照旧以一个普通 pull request 的形式出现。没有人需要额外学点什么,才能监督这台"机器"。

下面是单个自主 issue 从头到尾的完整形态:

Meshy 单个自主 issue 的流程图,从 Linear issue 到生产环境部署

一个自主 issue 从头到尾的完整流程。人类只在两个地方接触这个循环。

人类恰好只在两处接触这个循环:把 agent 指向 Linear issue,以及回答 agent 在写代码之前提出的少数几个决策问题。中间的一切——实现、检查、评审对话、合并、部署——都是在无人值守的情况下运行的。当某项检查失败,或某位评审者提出异议时,agent 会读取失败信息,修复问题,再次推送,循环就再跑一圈。只有当分支变绿、每一个发现的问题都被解决、并且队列已经把它合并,这个循环才会退出。

让这一切成为可能的,不是什么定制集成,而是这三款工具本身就已经暴露出了 agent 可以直接操作的命令接口——和人类使用的是同一套:

  • GitHub,通过它的 CLI。 agent 会打开 pull request、读取每个 CI 任务的状态和日志、在评审讨论串中回复并将其标记为已解决、把分支加入合并队列——全都通过普通的 gh 命令完成。
  • Linear,通过它的 MCP server。 agent 会读取被指派的 issue、顺着链接查看相关 issue 以获取背景信息、更新状态,并为途中发现的后续工作创建新的 issue——这一切都是一等公民级别的工具调用。
  • Claude Code 自身的 monitor 循环。 CI 需要几分钟,合并队列耗时更长,而且没有人在盯着。取而代之的是 agent 自己在盯着,而它"盯"的方式,正是把一次无人值守的运行,变成实际可行的成果,而不是白白烧掉 token。

当 agent 打开一个 pull request 后,它不会坐在那里不断刷新页面;它会记录下自己在等待什么——CI 运行、评审讨论串、合并队列条目——然后结束这一轮。Claude Code 的 Monitor 功能只会在被监视的状态发生变化时才重新唤起它:某个任务变红了、评审团发布了一个问题、队列合并了分支。因为 agent 是被 Monitor 关于每次状态更新的消息唤醒的,它会据此采取下一步行动,比如拉取失败任务的日志并推送修复、在评审讨论串中回复并标记为已解决,或者在每一道关卡都变绿后把分支加入队列。

这些做法都不是 Muse 专属的。任何这三款工具能够访问到的代码仓库都可以用同样的方式来驱动,这正是我们选择它们的原因。

信任是一个构建产物

我们不信任 LLM 本身。我们信任的是身处某个框架之中的 LLM agent,而这个框架可以被工程化为可靠性的守护者。具体来说,这个框架就是我们的 CI/CD 流水线,加上叠加在其上的评审层。这是每一个 pull request 都必须经历的流程,没有例外:

Muse CI/CD 流水线与 AI 评审层示意图,每个 pull request 都要经过这里

每个 pull request 都经过同一套框架:先是自动化检查,然后是 AI 评审团。

各项任务,以及基于近期真实 PR 运行测得的典型耗时:

任务强制执行的内容典型耗时
Guardrails分层规则(禁止同级导入)、文件夹结构、文件大小上限、命名规范~60 s
Lintruff 格式化和 lint 规则~30 s
Type checkpyrefly,覆盖整个项目~20 s
Unit + integration testspytest,针对根据 schema 构建的真实 Postgres 运行~90 s
Visual tests在 CI 自身的渲染器中,将 Playwright 截图与已提交的基准图做 diff~140 s
Securitysemgrep 静态分析加密钥扫描~40 s
Container boot test应用必须能启动,而如果不是每条路由都声明了谁可以调用它,应用就会拒绝启动~215 s

这些 guardrails,就是我们前面描述的架构被机器化之后的样子。在这里,一次同级导入不会只是一条评审意见,而是一次红色的构建失败。视觉基准图是在 CI 自身的渲染环境中采集的,所以"在我的机器上能跑"永远不能成为把一个 bug 合并进 main 的理由。

AI 评审层才是真正有意思的地方。每个 PR 都会被我们自己的一个三人评审团读取——三个 coding agent 评审者对照仓库既定的设计原则来审视同一份 diff,每人被赋予不同的人设。下面展示的是给每位评审者的 prompt 大纲。

主任工程师:

人设:主任工程师,设计评审,大尺度

你在系统尺度上做评审:这项改动是否属于这里、是否契合架构、是否复用了已有的东西、是否只做加法——而不是去看某一行代码是否整洁。

你看到的是 diff 加上 Guides,而不是整个仓库。 判断复用情况和模式一致性……;不要声称自己做了一次实际上无法执行的全仓库搜索。

你的视角: 对照所提供 Guides 中的这些部分来评判这份 diff……

判定标准: "如果我是这个代码库的主任工程师,我会不会因为这个改动放错了地方、重复了已有能力、或者为一件已经有解法的事又造了第二种做法,而把它打回去?" 如果不会,返回 []

资深工程师:

人设:资深工程师,逻辑 / 实现 / 质量

你只在改动本身范围内做评审:逻辑是否正确、是否干净、是否可测试、是否安全。这是最需要用心的一轮评审,要去追踪代码路径,不能只是略读。聚焦在这份 diff 以及它涉及的文件上(要完整地读完)。

你的视角: 对照所提供 Guides 中的这些部分来评判这份 diff……

判定标准: "我会批准这段逻辑吗,还是仔细读下来会发现一个 bug、一处无法测试的接缝、或是一个安全问题?" 要具体指出是哪个输入或代码路径会失败。如果逻辑站得住脚,返回 []

QA 工程师:

人设:QA 工程师,它能用吗、有没有测试、会不会挂

你评审的是行为和风险:这项改动是否真的做到了它所声称的事,是否有测试覆盖,是否可能在线上或生产环境中造成问题?先读 PR 的标题和描述,再检查这份 diff 是否真的兑现了它。

你的视角: 意图是否匹配……;测试覆盖情况……;回归风险……;生产环境中的隐患……

判定标准: "如果我是签字放行的 QA,我会不会因为这个改动没有做到它声称的事、上线了未经测试的行为、或者会弄坏生产环境而拦下它?" 如果它是安全且有覆盖的,返回 []

除了从不同角度审视同一份代码 diff 之外,这三位评审者还共享一套统一的规则,用来规范整个代码库的开发——也就是 Guides:

  • 一起变化的东西放在一起;不相关的东西保持可分离。 修改或删除一个关注点,只需要触碰一个地方。
  • 业务逻辑不依赖外部世界的具体细节。 一个业务逻辑函数在测试中传入 fake 就能运行,不需要真实的 I/O。更换后端不会触碰任何业务逻辑。应用永远不会调用自己的 HTTP 端点。
  • 复用,而不是重新发明。 每个能力只有一份实现;新的调用方去调用已有接口,而不是照抄一份。
  • 用新增的方式添加功能,而不是靠修改。 添加一个功能就是"新文件 + 一行注册代码",删除它也不会牵动其他任何功能。
  • 每件事只有一种既定做法。 新人只需要照抄现有模式,而不用做选择;测试也不需要服务器或数据库。
  • 代码一旦不再被使用,就立刻删除。 没有任何东西调用的代码不会上线;"以防万一先留着"不是理由,因为 git 会记得。
  • 优先选择能用的最简单方案;只有被逼无奈才增加复杂度。 每一个抽象都必须凭一个具体的当下需求赢得自己的存在。改动要做到刚好能解决问题、不多不少。

评审团会把每一个会阻塞合并的问题,作为一条锚定在具体行上的行内评论发布出来,这些评论会一直卡住合并,直到被解决为止。

下面是最近一个功能 PR 收到的一条评审意见:

[medium] 回归风险。这个函数是共享的创建并同步的关键节点;现在它会把带 view 后缀的文件合并成一行分组数据。任何在旧方案下摄入的既有数据集,在下一次同步时都会被静默地重新分组,删除掉正在运行的评测仍在引用的旧样本身份标识。没有任何测试覆盖"通过新的分组逻辑重新同步一个既有数据集"这种情况。

这个发现是对的。这段代码已经通过了完整的测试套件,也拿到了第一轮全绿的 CI。对于当时写出来的代码而言,那些测试是对的。但评审者看出了:这段代码放在我们实际拥有的数据面前是错的。Claude 修复了这个问题,补上了缺失的回归测试,在讨论串里回复,并解决了这个发现。整个交流过程在 PR 里都是公开的,和你通过代码评审带教一位初级工程师,做法完全一样。

这只是一个 PR。随着长期运行这个 agent,我们进一步发现了一些其他的典型模式:

  • 一次修复引发了新问题。 我们上线的一个诊断工具用来测量下载速度。评审发现,计算用的是 MB(1,000,000 字节),但服务器发送的却是 MiB(1,048,576 字节),所以每个速度读数都会偏低 5%。几次推送之后,评审又指出,这次修复留下了两个需要手动保持同步的常量,于是要求改成根据实际接收到的字节数来计算速度。评审者抓到了一个 bug——而这个 bug,正是修复它之前那条意见时产生的。
  • 死胡同式的用例。 同一个页面上线时,它的链接对每个已登录用户都可见,尽管这个页面本身需要特定权限才能打开。没有这个权限的人点进去,就会遇到 403 错误。没有任何测试失败,因为链接确实显示出来了,权限拦截也确实生效了。QA 评审者把这两件事对照起来看,拦下了这次合并,直到链接只对真正能打开这个页面的人显示为止。
  • 靠取巧手段通过测试。 某次修复的测试人为制造出了它本应捕获的那个确切异常,所以哪怕真正的错误处理逻辑是坏的,测试依然会通过,因为它收到了那个"正确"的异常信息。评审团标记出了这一点,重写后的测试会让真实的错误流经真实的代码。同一个 PR 里还有一个测试,在开发者本机能通过,但在 CI 上会失败,因为它悄悄依赖了一个只存在于我们开发机上的凭证文件。这两个问题都是测试代码本身的 bug,被更上一层的评审抓了出来。

我们的合并策略是:AI 评审发现的问题必须为零。 在队列接手这个 PR 之前,每一个发现要么被修复,要么被拿出证据来反驳。我们之所以把反驳变成一个可行的选项,是因为评审团的过度标记率处在一个可以接受的水平。写下一个发现"为什么是错的",反过来偶尔也能抓出评审团自己推理中的一个错误。

通过所有检查之后,合并队列会针对不断变化的 main 分支最新提交,重新验证每一个分支,合并会自动部署到生产环境,全程没有人参与。

Muse 流水线末端的合并队列与自动生产环境部署

通过每一道关卡之后,合并队列会重新验证这个分支,合并会部署到生产环境。

代码行数只是故事的一小部分

在许多自动驾驶式 PR 的过程中,我们发现了一个令人惊讶却又合情合理的规律:在检查和评审上反复迭代所花的时间,要比写出第一版实现所花的时间长得多,而且这个比例还会因任务类型的不同而有很大差异。这里我们对比同一周内的两类开发任务。

案例 A:下线一个废弃功能。 一个体积不大的遗留功能被下线;它的链接、确认弹窗、翻译文案、测试和截图基准图都得一并移除。从 Linear issue 提交到 PR 合并进 main,只用了 16 分钟,其中头 2 分钟用来写代码。评审意见都只是建议性质。

案例 B:对网络诊断功能的一次重大改进。 按代码行数来看,这是一次规模相近的改动,第一版在 5 分钟内完成,而合并的总耗时约为 3 小时。在此过程中,agent 处理了 5 轮评审,解决了 7 个阻塞合并的发现。

时间线也大不相同:

案例 A:下线一个废弃功能案例 B:改进诊断工具
改动规模+10 / -125,共 8 个文件+439 / -19,共 9 个文件
第一个可运行版本2 min5 min
从提交 issue 到合并16 分钟3 小时
合并耗时相较第一版慢了多少8x36x
阻塞合并的评审发现无(仅为建议性质)7,分 5 轮
代码出错时最坏的情况一个不该有人点的链接每个用户都相信了一个错误的数字

案例 A 和案例 B 从提交 issue 到合并的时间线并排对比

两个代码行数相近的任务,验证投入的力度与风险成正比。

第二个案例耗时长了十一倍,不是因为代码更难写,而是因为风险不同,验证投入也随之成比例地增加。案例 A 是一次删除:最坏的结果是一个本来就不该被访问的失效链接。案例 B 是一个测量工具:最坏的结果是每个用户都相信了一个错误的数字。评审团发现了这些错误之后,又用了三轮时间拒绝各种"打半截补丁",直到错误处理具备完整的类型、完整、并经过彻底测试为止。每一轮都让实现变得更加严密。这些缺陷没有一个让测试失败,也就是说,它们本来全都会被上线发布。

这正是一次性(one-shot)代码生成从本质上就会做错的地方。一次性生成优化的是最容易被看到的那个指标:从 prompt 到代码能编译、能运行所花的时间。我们的两个案例都在几分钟内达到了这个指标,如果我们就此止步,案例 B 会在开始后几分钟就"完成",然后带着错误运行好几个月。

同一套流水线,让一次删除在 16 分钟内放行通过,却把一个测量工具卡了 3 个小时——期间没有任何人来决定每个改动该得到哪种待遇。当人们问 AI 写的代码能否在生产环境中被信任时,这就是答案:代码的可信程度,恰好等同于包裹在它周围的验证的可信程度,而验证的深度如今是可以用 agent 的工时来购买的,不再是工程师的人天。一旦有人拿着一个错误的速度数字去处理网络投诉,这个错误付出的代价,就会远远超过那三个小时。

我们学到了什么,接下来做什么

如今,自主性更多是一种框架的属性,而不是模型的属性。每一次模型升级都让我们的 agent 变得更强;但模型能力本身,并不自带"可以放心让 agent 自由合并 pull request"的保证。与其投入更多人力关注,或是盲目使用最贵的前沿模型,我们选择去构建"手"和"眼",让 agent 具备生产出正确的生产级代码所需要的一切。而随着模型能力不断进化,这样做也让我们能够顺着浪潮往前走,而不必为了弥补模型的局限而反复重建脚手架。

人类的角色变得更集中,而不是消失了。剩下来的是那个无法再被简化的核心:决定什么值得去做,把它写得足够清楚,并在机器标记出模糊之处时行使判断力。设计品味和需求描述的质量,如今是这个团队里杠杆率最高的工程技能。

关于 agent 团队,最有意思的一点在于:团队的价值来自冲突,而不是协作。 把一个全栈任务拆分成前端 agent 和后端 agent 来做,或许是合理的。但让一个前端 agent 和一个后端 agent 彼此对话,仅仅为了完成任务,就没那么有道理了。团队的价值,是在成员目标彼此冲突时才会显现出来的。我们的 Muse 开发 agent 想要的是把代码发布出去。我们的 AI 评审 agent 想要的是找出代码错在哪里、设计得不好在哪里。正是它们之间的这种张力,让结果变得可信,也如实揭示出一项开发任务真正需要付出的工作量。

更重要的是,我们发现自己的这些探索,正把我们引向一种正在浮现的软件开发方式:一个自主的工程组织。尽管我们已经证明了 agent 可以借助现有工具自主完成单个任务,但仍有几块缺失的拼图和悬而未决的问题,等待未来去继续探索:

  • 我们究竟能把这样的系统扩展到多大规模?当自主组织用更多并行的 agent 去处理越来越大的任务和项目时,会出现哪些新问题?
  • 框架自身产生的输出——比如首次 CI 通过率、AI 评审发现的问题数量——如何能变成评估信号?我们该如何衡量一个 agent 在这个组织里的表现,又该如何让它实现自我进化?
  • 人类的设计品味和产品品味,要如何融入这样的系统?如果工程可以在少量人类判断的介入下实现自主化,我们能把这条路走多远,去解决"我们该为谁构建软件"以及"软件该长什么样"这样的问题?

最后,感谢你读到这里。寻找这些问题答案的待办清单,恰如其分地躺在 Linear 里,等着我们的下一个 agent,或者你,把它接过去。

看看 Muse 帮我们发布了什么
Muse 就是我们用来判断每个新 Meshy 模型是否真的更好的方式。来试试它验证过的模型。

开始3D创作

无需信用卡。不到一分钟生成你的第一个 3D 模型。