我在用 Codex 开发大型 Agent 系统后的架构反思
TL;DR
在用 Codex 开发一个大型多角色 Agent 系统后,我逐渐意识到:
当 LLM 参与开发时,代码架构需要多一个新的优化目标:
降低模型的理解成本。
传统的软件架构主要优化的是:
- 解耦
- 分层
- 可维护性
- 团队协作
但当 LLM 成为开发参与者之后,还必须考虑:
代码是否容易被模型快速理解、修改,并且不会迅速腐化。
我把这种设计原则总结为:
最小理解闭包(Minimum Understanding Closure)
核心思路可以概括为 7 条:
- 设计目标不是优雅分层,而是最小理解闭包
- 垂直切片通常比纯水平分层更适合 LLM
- 稳定入口比抽象层数更重要
- 文件不是越小越好,而是越独立、越完整越好
- 窄契约、少跳板、集中副作用,可以显著降低 context 消耗
- 避免把关键行为隐藏在难以定位的自动机制中
- 架构需要能够抵抗 LLM 的反复修改与结构腐化
下面是我在实际开发中的一些观察和总结。
面向 LLM 的代码设计模式
我越来越觉得,LLM 参与开发之后,“写好代码”这件事的标准其实已经发生了一些变化。
过去我们讨论代码设计,更多是围绕:
- 解耦
- 复用
- 分层
- 可维护性
但今天还必须多一个维度:
代码是否适合被 LLM 快速理解、反复修改,并且不会迅速腐化。
我最近在用 Codex 开发一个大型多角色 Agent 系统。
在这个过程中,我不断遇到一个很具体的问题:
不是模型不会写代码,而是 context 很快就乱掉、满掉、失去连续性。
后来我慢慢意识到,问题不只是上下文窗口,而是 代码组织方式本身。
很多代码结构其实是为 人类工程师团队优化的,但未必适合 LLM 的阅读方式。
LLM 在阅读代码时通常具有一些特点:
- 上下文窗口有限
- 阅读是顺序的
- 依赖搜索与跳转来建立局部理解
这意味着,如果代码结构让模型必须频繁跳转多个文件,context 很快就会被消耗掉。
所以我开始重新思考一个问题:
如果今天写代码的主要读者之一,已经不只是人类工程师,而是 LLM,那么代码应该如何组织?
这篇文章不是某种成熟的方法论,而是我在实际开发中踩坑之后的一些总结。
先说结论
我现在的基本判断是,面向 LLM 的代码设计,核心不是追求传统意义上的“优雅分层”,而是追求 最小理解闭包。
这里的“闭包”并不只是文件数量,而是:
为了形成一个正确判断,模型需要建立多少个独立语义单元,以及这些语义单元之间需要跨多少跳才能连起来。
不同模型差异很大,但它们大都共享一种共同限制:
- 受限上下文
- 顺序阅读
- 依赖搜索与跳转建立局部地图
所以我目前把面向 LLM 的代码设计,总结为 7 个核心观点:
- 设计目标不是优雅分层,而是最小理解闭包。
衡量架构是否友好的标准,不是抽象是否漂亮,而是:
为了完成一个任务,模型需要读取多少文件、跨多少跳、读取多少真正有信息增量的内容。
- 纯水平分层未必比垂直切片更适合 LLM。
如果一个功能被拆散在很多层之间:
- router
- service
- model
- store
- utils
LLM 为了理解完整流程,就必须穿透很多文件。
相比之下:
按功能垂直收拢、切片内保留轻量分层,更容易形成紧凑的理解闭包。
- 稳定入口比抽象层数更重要。
一个好的入口文件应该让模型快速回答四个问题:
- 这个模块负责什么
- 对外暴露哪些能力
- 输入输出是什么
- 下一步该看哪个实现
一个薄而清晰的入口,本质上是一个 导航工具。
- 文件不是越小越好,而是越独立、越完整越好。
很多时候代码被拆成很多小文件:
- helpers
- utils
- mapper
- validator
每个文件都很小,但逻辑被切得很碎。
LLM 为了理解完整逻辑,反而必须把这些文件全部读进 context。
所以更好的标准是:
一个文件应该承载一个稳定责任,并且读完以后能理解一个完整的小单元。
- 窄契约、少跳板、集中副作用。
如果模块之间传递:
- 整个 context
- 整个 session
- 整个 state
那说明模块之间其实没有真正边界。
更好的方式是使用窄契约,例如:
- MemorySnapshot
- ExecutionRuntimeState
- SkillInvocationResult
这样 LLM 在理解模块关系时,不需要同时记住巨大对象。
- 避免把关键行为隐藏在难以定位的自动机制中。
例如:
- decorator
- middleware
- 框架 hook
- 自动注册逻辑
这些机制有时很优雅,但它们会让行为变得 不在主流程代码里直接出现。
对于 LLM 来说,这意味着:
- 需要额外搜索
- 需要额外读取
- 因果链被打断
所以一个更安全的原则是:
关键行为尽量显式表达,而不是隐藏在难以定位的自动机制中。
尤其是在关键路径上,例如:
- 状态更新
- 历史写入
- 事件分发
- 权限判断
这些行为越显式,LLM 越容易稳定理解。
- 架构要能够抵抗 LLM 的时间轴腐化。
LLM 在开发过程中有一些典型行为:
- 在现有逻辑后继续追加分支
- 为了兼容旧行为保留多条路径
- 在文件末尾补新逻辑
- 添加保护性判断
这些行为短期看是合理的,但长期会导致:
- 入口越来越模糊
- 状态越来越分散
- 文件越来越长
- 副作用越来越难追踪
所以好的结构不仅要支持一次修改,还要能够承受:
连续几十次修改之后仍然保持可读和可控。
一些同样重要的补充原则
1 文件名要可推断
避免:
- helpers
- common
- processor
文件名本身就是导航系统。
2 在模块开头写清楚边界
例如:
- 负责什么
- 不负责什么
- 上游是谁
- 下游是谁
这会让 LLM 更容易判断当前文件是不是目标文件。
3 尽量使用显式状态
例如:
status: pending | running | failed | completed
比多个布尔变量更容易理解。
4 测试可以成为“可执行文档”
好的测试能告诉模型:
- 典型输入
- 正常输出
- 异常路径
很多时候模型读测试比读实现更容易建立理解。
5 积极清理死代码
死代码对人类来说只是碍眼。
但对 LLM 来说,它会:
- 增加读取成本
- 增加猜测
- 降低修改准确率
不是所有代码都需要为 LLM 优化
最后还有一个边界需要说明。
我并不认为整个代码库都应该为了 LLM 重构。
更合理的做法是:
优先优化那些 高频修改区、高频阅读区、高搜索密度区。
例如:
- Agent orchestration
- 状态与上下文系统
- 工具调用系统
- 前后端接口层
这些地方最容易消耗 context,也最容易发生结构腐化。
结语
面向 LLM 的代码设计,本质上不是让文件最小,而是让 单次任务的理解闭包最小。
未来如果 LLM 会越来越多地参与开发,那么软件架构可能需要多一个新的优化维度:
不仅要让人类工程师容易理解,
也要让模型能够用更少的 context 建立完整理解。
换句话说:
好的架构,不只是优雅的抽象,而是能让理解闭包尽可能小。
附注
这篇文章的形成过程,本身也和主题有关。
它来自我在用 Codex 开发一个大型 Agent 系统时遇到的真实问题:
一开始是在开发过程中不断遇到 context 相关的困难,例如模型需要频繁跳转文件、上下文很快被消耗、结构逐渐变得难以修改。
后来我围绕这些问题不断和 Codex 讨论、整理思路,再由 Codex 协助起草部分段落,最后经过 Claude 的审查和补充修改,才形成现在这篇文章。
所以这并不是一篇“完全由 AI 自动生成”的文章,而更像是一个 人类开发者与 LLM 协作思考的产物。
它比较真实地反映了我在这段开发过程中的一些观察、判断和感悟。