Agent 时代,我为何重新拾起 Monorepo
概念说明:Monorepo(单体仓库)是把多个应用、服务和共享代码放在同一个仓库中统一管理,Google、Meta 是 Monorepo 的典型实践者。Polyrepo(多仓库)则是为不同应用或服务分别建立仓库。前者强调共享上下文和原子化变更,后者强调边界隔离和独立演进。
作为前 GitLab 员工、GitHub 的重度使用者。很长一段时间里,我都认为 Monorepo 属于过去式 —— 只有 Google、Meta 这类拥有深厚工程师文化和长期业务积累的团队,才能玩得转单体大仓库。
不过现在,我的想法彻底改变了。
随着 Coding Agent 融入日常开发流程,我意识到一个被忽略的事实:代码仓库的边界早已不止于代码存储,它正在成为 AI Agent 的执行边界。正是这一趋势,重新把我推向了 Monorepo。
一个完整需求,天然跨越多仓库
拿一个很普通的需求举例:为用户新增头像上传功能。
真落地的时候,它一定是贯穿整条链路的:
flowchart LR
DB[Database] --> API[Backend API]
API --> SDK[Types / SDK]
SDK --> FE[Frontend]
FE --> Tests[Tests]
如果这些代码分散在不同仓库:
api.git、sdk.git、web.git
一个任务就被拆成了多个 PR,还得按顺序来:
flowchart LR
API[API PR] --> Release[SDK Release]
Release --> Web[Web PR]
人来做这件事的时候,多仓库之间的依赖关系是脑子里自带的常识。但 Agent 不是——每个仓库对它来说就是一道上下文的墙。
Agent 在 api.git 里改完代码,很难主动想到 web.git、admin.git、mobile.git 这些下游消费方也得跟着改。
所以问题的本质变了。以前问的是"代码怎么写好",现在问的是"怎么协调多个 Agent、多个仓库、多个 PR"。
Monorepo 给 Agent 的是完整上下文
Monorepo 下目录长这样:
flowchart TB
Product[product/]
Product --> Apps[apps/]
Apps --> Web[web]
Apps --> Admin[admin]
Product --> Services[services/]
Services --> API[api]
Product --> Packages[packages/]
Packages --> SDK[sdk]
Packages --> Types[types]
Product --> Database[database/]
Agent 改了一个类型定义,直接全局搜:
| |
一次就能定位到 API、SDK、Web、Admin、Tests 里所有相关的调用和引用。
这对 Agent 来说不是"一个仓库还是多个仓库"的选型问题,是它拿到的是全局完整上下文还是碎片化局部信息的问题。而上下文完整,恰恰是 Agent 能干好活的前提。
原子化变更,Monorepo 天生适配
理想的 Agent 开发流应该是一条直线:
flowchart LR
Task[1 Task] --> Branch[1 Branch]
Branch --> PR[1 PR]
PR --> CI[1 CI]
CI --> Merge[Merge]
比如一次 API 破坏性变更,要同步动 types、api、sdk、web 四处。Monorepo 里一次提交搞定,整体验证、整体合并。
Polyrepo 下它就变成了:
flowchart LR
Task[1 Task] --> Repos[4 Repositories] --> PRs[4 PRs]
然后你还得处理 PR 依赖、版本发布、合并顺序、部署节奏——全是协调工作。
一天十几个 PR,人肉还能盯过来。等 Agent 一天产出几百个 PR,这种跨仓库协调的开销就是指数级的了。
划分仓库,得加一个「变更边界」维度
以前拆仓库基本看两个维度:
团队边界(Team Boundary)和服务边界(Service Boundary)。
比如 Frontend Team 管 frontend.git,Payment Team 管 payment.git,Auth Team 管 auth.git。
但 Agent 主导开发之后,我觉得必须加第三个维度:
Change Boundary(变更边界)
问自己一个问题:一个真实的业务需求,通常会联动改哪些代码?
如果大量需求都要同时碰 Web、API、数据库、SDK、公共类型,那硬拆成五个仓库,看起来边界优雅,实际上全是无谓的跨仓库协调成本。
所以我现在的判断标准很朴素:
经常一起变化的代码,就考虑放在一起。
Monorepo 的代价:并发冲突
Agent 多了之后,Monorepo 最头疼的问题是并发修改和合并冲突。
几十个 Agent 并行干活,很容易同时改 package.json、锁文件、公共类型、路由配置这些核心文件。一堆 PR 基于旧的 main 分支开发,一个 PR 合并进去,后面的连锁反应就来了:
flowchart TB
MergeA[PR A merge] --> OutdatedB[PR B outdated]
MergeA --> ConflictC[PR C conflict]
MergeA --> RerunD[PR D CI rerun]
这一点上 Polyrepo 有天然优势:
flowchart LR
AgentA[Agent A] --> Payment[payment.git]
AgentB[Agent B] --> Search[search.git]
AgentC[Agent C] --> Auth[auth.git]
各改各的,互不干扰,并发度天然就高。
说白了:仓库边界同时也是并发安全边界。
更务实的选择:混合式架构
我不觉得 Agent 时代就该把所有代码塞进一个巨型 Monorepo。
我现在更倾向混合架构:
flowchart TB
Org[GitHub Organization]
Org --> Product[product.git
产品级 Monorepo]
Product --> Web[web]
Product --> API[api]
Product --> SDK[sdk]
Product --> Types[types]
Product --> Database[database]
Org --> Payment[payment.git
独立服务仓库]
Org --> Infra[infrastructure.git]
Org --> Security[security.git]
产品内部高频联动的部分——Web、API、SDK、公共类型、数据库——收进产品级 Monorepo;团队、权限、生命周期、部署节奏都相对独立的系统,继续独立仓库。
Monorepo 需要工程能力兜底
面向 Agent 的 Monorepo,绝不是"把代码放一起"就完事了。
最起码得做到"一个 Agent 任务对应一条独立分支 / 一个工作区",然后配套一整套工程体系:
Affected CI(增量构建)、Dependency Graph(依赖图谱)、CODEOWNERS(权限管控)、Small PR(小粒度提交)、Merge Queue(合并队列),以及 Agent Instructions(Agent 操作规范)。
完整链路是这样的:
flowchart LR
Task[Task] --> Agent[Agent]
Agent --> Worktree[Worktree]
Worktree --> PR[PR]
PR --> AffectedCI[Affected CI]
AffectedCI --> Review[Review]
Review --> Queue[Merge Queue]
Queue --> Main[main]
这些东西缺一样,几十个 Agent 同时往主分支推代码,CI 很快就被拖垮,main 分支陷入冲突和返工的循环。
结语
我重新开始使用 Monorepo,不是因为它突然变先进了。是 Coding Agent 改写了软件工程的成本结构。以前我们优化的核心是开发者体验;现在还得兼顾 Agent 上下文完整性、任务协调成本、跨项目变更效率、CI 反馈速度、合并冲突治理。仓库边界的划分也不该只看团队边界和服务边界,还得看变更边界和 Agent 任务边界。
所以我心里的最优解从来不是"Monorepo 比 Polyrepo 好",而是:
产品级 Monorepo + 独立服务 Polyrepo。
写代码的成本被 Agent 拉下来之后,真正贵的就不是"谁来写"了,是怎么给 Agent 准确的上下文,以及怎么让海量变更安全、低成本地进主分支。想明白这件事,重新开始使用 Monorepo 就是顺理成章的事情了。