让 AI 在项目里加一个"修改订单状态"的接口,它一分钟就交差了。你打开 diff 一看,HTTP 路由层直接 import 了数据访问层的查询函数,业务规则层那套状态机校验全被绕了过去。代码能跑,review 绝对过不了。这种场景我遇到的次数已经数不过来了。问题不在 AI 不聪明,而在于它根本不知道你的项目里哪些门能进、哪些墙不能拆。以前这些"项目事实"装在我们脑子里,靠口口相传;现在 AI 一天帮你写几百行代码,你必须把这些事实固化下来,让它在动笔之前先读到。这就是圈子里讨论越来越多的"代码上下文工程",落地最常见的载体就是 AGENTS.md 文件。AGENTS.md 是放在仓库根目录(也可以放子目录)的一个 Markdown 文件,主流的 AI 编程工具在扫描代码库时会自动读取它。它跟 README 不一样:README 是给人看的项目介绍,AGENTS.md 是给 AI 定的操作规程。它回答的问题非常朴素:AI 在动手写代码之前,必须知道哪些项目事实。如果你也被 AI 改坏过架构、绕过封装、写出和团队风格完全不一致的代码,这篇内容就是为你准备的。我会把四种我认为最值得写进 AGENTS.md 的项目事实一条条拆开,给一份可直接抄的模板,再说说落地过程中最容易踩的坑。1. 为什么 AI 总是写出"对的废代码"1.1 先理解 AI 编码工具的"上下文盲区"我一开始也以为 AI 看得懂整个仓库。用久了才发现,它的工作方式是先做检索和局部读取,再基于附近代码做预测。它看到的是散落的文件块:一个路由函数、一个模型定义、一段 service 方法。这些碎片能告诉它"这里大概写了什么",但没法告诉它"这个项目为什么要这样组织"。架构分层、封装边界、依赖方向、异常约定——这些都属于全局事实。全局事实的分布很分散:一部分写在 README 里,一部分写在评审意见里,还有相当大一部分只存在于写这个模块的人脑子里。AI 读不到脑子里,于是它只能按看到的局部模式去猜。猜对了是运气,猜错了就成了我开头说的那种"对的废代码":语法正确、局部逻辑正确、整体架构错误。1.2 统计学习的双刃剑大模型写代码本质上在模仿训练数据和当前上下文里的模式。这个特点有个很实际的影响:AI 会把你仓库里"已有的写法"当成"应该的写法"。假设项目里有三十处绕过封装的坏例子,AI 大概率会学出第三十一处;反过来,如果仓库里每一处代码都严格走 service 层,AI 也很少会自己发明一条跨层的路。所以 AGENTS.md 的真正作用,不是给 AI 上课,而是把概率往正确方向推:让"好代码的样子"从隐性知识变成显性上下文,让 AI 在预测下一个 token 时能看到那条"禁止跨层"的规则。1.3 把 review 从猜谜变成对表还有一个被低估的理由:人肉 review 的成本。AI 代码评审里最费时间的不是找语法问题,而是判断"这个调用合不合架构"。如果规则只在某位老同事脑子里,评审就是猜谜;规则落进 AGENTS.md 之后,评审就变成了对表——对着文件里的约束逐条过,发现有违反就是实锤。更进一步,这类规则还能被脚本化检查,把一部分问题在提测前就拦住。2. 四种能真正改变 AI 行为的项目事实项目事实不是把 README 复制一遍那么简单。我梳理过很多次,最终发现真正能影响 AI 行为的,可以归成四类:架构分层、封装边界、实现约定、工程操作。四种解决四类不同的破坏方式。2.1 第一种事实:架构分层与依赖方向事实内容:系统的分层结构、每层的职责边界、依赖流向,以及新代码应该从哪一层进入。为什么对 AI 关键:跨层调用是 AI 写代码时最高频的架构破坏方式。原因很现实:跨层调用是最短的路径。AI 的目标是"生成能完成需求的代码",它不天然关心分层。如果数据访问函数就摆在眼前,它不会先去 service 层绕一圈,除非有人告诉它"必须绕这一圈"。怎么写:用一句话定义每一层做什么明确依赖方向:只允许谁依赖谁指出新需求的默认入口路径明确列出禁止的跨层组合我在实际项目里总结了一段可以直接用的写法:- 分层:router(HTTP 入口)→ service(业务规则)→ repository(数据访问)→ model(数据定义) - 依赖只能从上往下。禁止 service import router;禁止 router 直接调用 repository。 - 新功能默认链路:router → service → repository。 - 特殊场景需要跨层调用时,先问维护者,不要自行引入依赖。最后一句看着像废话,但对 AI 很有用。它在权衡"最短路径"和"绕路"时,如果有一条"先问维护者"的规则,至少会倾向于保守处理,而不是默认走捷径。2.2 第二种事实:封装边界与访问通道事实内容:哪些模块/方法是"公共门面",哪些是"内部实现";关键数据必须通过哪个通道读取或修改。为什么对 AI 关键:绕过封装比跨层更隐蔽。有些代码没有明显的分层问题,但它直接操作了本不该碰的内部字段或方法。AI 读到一个模型的字段定义和一个更新方法,它更倾向"直接赋值"因为那看起来更简单。可现实项目里,字段往往有业务不变量需要保护——比如订单状态不能从"已支付"直接跳回"待支付"。怎么写:列出必须通过指定方法修改的关键字段列出禁止直接 import 的内部路径给出正确通道的完整表达说明为什么,让 AI "理解"而不是"死记"示例片段:- 订单状态 order.status 是受保护字段,只能通过 order_service.transit_status() 修改,禁止在 repository 或模型层直接赋值。 - 价格计算统一走 pricing.compute_total(),禁止在业务代码里手写折扣累加;业务方传入的金额一律以分为单位。 - 禁止 import app.repositories.order_repo._internal 下划线前缀的私有模块;这些是历史遗留实现,新代码不得引用。这里有个细节值得强调:规则里最好带上具体的符号名,比如order.status、transit_status()。AI 对具体符号的匹配能力远强于对"请尊重领域模型"这类抽象表述的理解。你给它一个抽象口号,它给你一个抽象遵守。2.3 第三种事实:实现约定与模式规范事实内容:错误处理、日志、事务、命名、并发模型这类"写法层"的统一规则。为什么对 AI 关键:这一层是 AI 模仿能力最容易出问题的战场。它会模仿离它最近的那一段代码——不管那一段是不是好样板。如果你的项目里恰好在某个角落有一段 print 调试日志、一段吞异常的逻辑,AI 就会在新区块里复刻出来,因为"这就是这个项目的风格"。怎么写:明确"必须用什么"和"禁止用什么"给出一个指向好样例的路径(例如:新模块参考app/services/payment_service.py的写法)对容易出错但高频的操作给固定模板片段示例:- 业务错误统一抛 BizError,全局异常处理器负责转成 HTTP 响应;禁止在 service 里 try/except 后静默返回 None。 - 日志使用 logging.getLogger(__name__),禁止使用 print。 - 涉及多表写入必须走 service 层的事务上下文;repository 方法禁止自行 commit/rollback。 - 新代码必须是 async 风格,禁止新增同步阻塞调用。 - 新增业务规则时,同步补充 pytest 单测;测试禁止访问外部网络。每次我写完这类规则后,AI 生成代码的错误处理质量会明显提升一个档次。因为这部分在普通文档里最容易被省略,而对 AI 来说恰恰是它最容易"自由发挥"的地方。2.4 第四种事实:工程操作与变更约束事实内容:构建命令、测试门槛、生成文件清单、依赖引入流程、环境版本。为什么对 AI 关键:AI 有时会为了"完成任务"做出破坏工程的操作,比如去修改自动生成的迁移文件、为一个小工具函数引入一整个第三方库、删掉看起来多余但其实是运行必需的配置。这些动作在单一任务的视野里常常"合理",但从工程全局看是灾难。怎么写:把必须通过的检查命令写清楚明确哪些目录是生成产物、禁止手改规定依赖引入的流程写明运行环境和版本约束片段示例:- 提交代码前必须通过:pytest -m "not integration" 和 make lint。 - migrations/ 目录由迁移工具自动生成,禁止手工修改;模型变更请新增迁移文件。 - 新增第三方依赖需要提交理由并说明替代方案,禁止为单个小函数引入完整框架。 - 运行环境:Python 3.11+、PostgreSQL 15+;配置统一读 settings 模块,禁止硬编码。四种事实可以用下面这张表快速对照:事实类型解决的破坏规则示例衡量方式架构分层跨层调用、反向依赖禁止 router 直接调 repositoryreview 检查 import 方向封装边界绕过门面改内部字段/私有模块状态只能通过 service 方法修改review 检查字段赋值来源实现约定错误处理、日志、事务不一致禁止吞异常、禁止 print静态检查 + review工程操作改生成文件、乱加依赖、跳过测试迁移文件禁止手改CI 脚本校验3. 一份可以抄作业的 AGENTS.md 完整示例看概念不如直接上成品。下面这个示例来自一个虚构的订单服务项目(内部代号 order-svc),结构是一个很常见的后端服务:分层目录加 PostgreSQL。你不需要真的了解这个项目,只需要看一条条规则是怎么组织在一起的。3.1 示例项目的目录骨架order-svc/ ├── app/ │ ├── main.py │ ├── routers/ │ │ └── orders.py │ ├── services/ │ │ ├── order_service.py │ │ └── stock_service.py │ ├── repositories/ │ │ ├── order_repo.py │ │ └── stock_repo.py │ ├── models/ │ │ └── order.py │ ├── utils/ │ │ └── pricing.py │ └── settings.py ├── migrations/ ├── tests/ └── AGENTS.md3.2 完整的 AGENTS.md 文件下面这个文件是我建议的"起步版"。它不需要一次写完美,先覆盖最容易出问题的四类,然后在实际使用中迭代。# AGENTS.md 你是本项目(order-svc)的编码助手。在生成任何代码之前,先阅读本文件, 并把它当成不可违背的项目事实。如果本文件与仓库代码存在冲突,以本文件 为准,并向用户说明冲突点。 ## 1. 架构分层与依赖方向 - 分层:router(HTTP 入口)→ service(业务规则)→ repository(数据访问)→ model(数据定义) - 依赖只允许从上往下:禁止 service import router;禁止 router 直接调用 repository; 禁止 repository 依赖 service。 - 新功能默认链路:router → service → repository。不要在中间省略层级。 - 如果确实需要跨层,先在回复里说明理由,并建议用户与维护者确认,不要直接写。 ## 2. 封装边界与访问通道 - 订单状态 order.status 是受保护字段,只能通过 order_service.transit_status() 修改。 禁止在 repository、model 或 router 层直接赋值。 - 库存扣减只能调用 stock_service.deduct();禁止绕过 service 直接写 stock 表。 - 金额计算统一使用 pricing.compute_total(),金额单位一律为分;禁止在业务代码里手写 累加折扣或金额换算。 - 禁止 import 下划线前缀的私有模块(如 order_repo._internal);它们是历史遗留实现。 - 所有对外读接口必须经过 repository 层,禁止 router 直接拼接 SQL 或直接访问模型类。 ## 3. 实现约定与模式规范 - 业务错误统一抛 BizError,由全局异常处理器转成 HTTP 响应;禁止在 service 里 try/except 后静默返回 None。 - 日志使用 logging.getLogger(__name__);禁止 print。 - 涉及多表写入必须使用 service 层提供的 transaction() 上下文管理器;repository 方法 禁止自行 commit 或 rollback。 - 新代码必须使用 async/await 风格;禁止新增同步阻塞调用。 - 命名:repository 方法用 create_/get_/update_ 前缀;service 方法用业务动词 (submit_/cancel_/transit_),不要写成 CRUD 风格。 - 新增业务逻辑时必须补充 pytest 单测;测试文件放 tests/ 对应目录, 禁止访问外部网络。 ## 4. 工程操作与变更约束 - 提交前必须通过:pytest -m "not integration" 和 make lint。 - migrations/ 目录由迁移工具自动生成,禁止手工修改;模型变更请生成新的迁移文件。 - 配置项统一在 app/settings.py 中声明,禁止在代码里硬编码环境地址、密钥、超时时间。 - 新增第三方依赖需要说明理由并评估替代方案;禁止为小工具函数引入完整框架。 - 运行环境:Python 3.11+,PostgreSQL 15+。 ## 5. 交付前自检清单 在你完成代码后、输出之前,逐条检查你的改动: 1. 是否有任何新 import 违反了第 1 节的依赖方向? 2. 是否直接读写过第 2 节提到的受保护字段或私有模块? 3. 错误处理、日志、事务是否遵循第 3 节? 4. 是否碰过 migrations/ 或硬编码过配置? 5. 是否补充了对应的测试? 把检查结果和解答一并写在回复里。3.3 这个模板的写法拆解你可能会问,为什么第 5 节要加一个"交付前自检清单"?这是我自己实测最有效的一个技巧。模型在生成代码之后的"反思"阶段,如果拿到一份明确清单,它会真的逐条自查一遍,而不是在生成完输出后立刻结束。让 AI 把自查结果写进回复,也方便你在评审时直接看到它的判断依据——它到底注意到了哪些规则,忽视了哪些规则。还有一个关键点:文件开头那句"如果本文件与仓库代码存在冲突,以本文件为准",很有必要。因为模型对既有代码的信任度通常高于文档,如果仓库里已经混入了一些历史遗留的坏味道,AI 会倾向模仿坏代码而不是遵守规则。加这句话,相当于给规则一个更高的优先级。模板里每条规则都尽量带上了具体符号名,比如order.status、transit_status()、pricing.compute_total()。这是故意的:具体符号能触发模型的知识匹配,而抽象表述只能触发模型"点头"。你写抽象规则,AI 会抽象地遵守;你写具体规则,AI 才会具体地执行。4. 从"能用"到"好用":四条写法原则有了模板还不够。我见过很多团队把 AGENTS.md 写上几百行,最后发现一点用没有。问题常常不是"写没写",而是"怎么写"。下面这几条原则是从不好用到好用的关键。4.1 每一条规则都要能被验证判断一条规则写得好不好,最快捷的方式是问:它能不能在五分钟内通过检索或静态检查验证?反例:"请保持代码整洁,遵循项目架构。"——什么是整洁?什么是项目架构?AI 无法验证,于是它当成客气话。正例:"禁止 router 层直接 import repositories 下的模块。"——可以用一条 grep 验证,出现了就是违反。我建议每写一条规则都做一次"grep 测试":如果你自己没办法用一个简单的搜索命令确认这条规则是否被违反,AI 更做不到。4.2 "禁止"和"允许"要成对出现只写一堆"禁止",AI 会陷入两难:不许这样做,又不让那样做,那到底怎么做?正确姿势是每条"禁止"都配一条"替代路径"。比如:"禁止 router 直接调 repository"这条,后面一定要跟"新功能必须走 router → service → repository 的链路"。禁止改了内部实现,就给一条"统一走 pricing.compute_total"的允许通道。AI 是路径依赖的,你只关掉一条路,它会另找一条路;你把正确路径同时画出来,它才会安心走那条。4.3 把规则指向权威出处AGENTS.md 不应该是孤立的第二套规则,它最好是代码和文档的"索引"。项目中架构会持续演进,如果 AGENTS.md 脱离真实代码,很容易过时。我的习惯是:在 AGENTS.md 里写规则的同时,用一行注明权威出处。比如"分层定义详见 docs/architecture.md 第 2 节""错误处理基线参考 app/services/payment_service.py"。这样两件事同时发生:AI 需要更多细节时能自己去读;读者维护 AGENTS.md 时也能顺着索引找到源头,不会让规则和代码漂移成两套事实。4.4 长度失控,就是规则失效的开始AGENTS.md 写太短等于没写,写太长同样等于没写。模型对上下文的注意力是有限的,几百行规则塞进去,真正被记住的可能是开头和结尾那几条。以我的经验,单个 AGENTS.md 控制在 80 到 200 行最合适。如果项目复杂,内容超过这个量,优先做拆分:根目录放全局规则(分层、依赖、通用约定),在子模块目录里放局部规则(比如app/repositories/AGENTS.md专门写仓库层的注意事项)。多数主流工具支持子目录级上下文文件,AI 读代码时能看到就近加载的内容,效果比把所有东西堆在一起好得多。4.5 AGENTS.md 要跟着架构一起走最后一条原则是维护节奏。我强烈建议把 AGENTS.md 的更新纳入代码评审范围:只要对分层、封装、依赖做了调整,同一个 PR 里顺手改 AGENTS.md。否则文件一旦过时,AI 就会发现"规则和代码对不上"而选择信任代码,你的规则体系就崩了。过时的规则比没有规则更危险,因为它会消耗信任。5. 落地过程中的常见问题与排查思路写 AGENTS.md 很容易,落地过程几乎是必然踩坑的。把我遇到过的高频问题整理一下,给你做个排障对照。5.1 AI 好像没读到规则怎么办第一步不是改规则,而是先确认文件真的被读取。你可以在对话里直接问 AI:"你刚才参考了仓库里的 AGENTS.md 吗?如果参考了,列出你认为优先级最高的三条规则。"如果它答不上来,说明读取链路出了问题:要么文件名不匹配你的工具约定,要么工具本身没启用仓库级上下文。多提一嘴工具差异:不同 AI 编程工具对"上下文文件"有不同的默认文件名,有的读 AGENTS.md,有的读 CLAUDE.md,有的用 .cursor/rules 这种目录。兼容做法是在仓库根目录放一份 AGENTS.md,再按你的主力工具要求把关键规则复制到它认的位置。社区目前正在往 AGENTS.md 这个约定靠拢,但生态还没完全统一,先确认你的工具认哪个。5.2 规则和现有代码明显冲突这是最常见也最麻烦的情况。仓库里已经存在大量违反规则的坏代码,AI 读着读着就开始学坏代码。处理方式我分两种:如果坏代码量少,直接改掉它们,让代码符合新规则,兼修 AGENTS.md。如果坏代码量很大一时间改不完,在 AGENTS.md 里明确标注"历史遗留反例":写明哪些路径是已知违规,新代码不得参考、不得复刻。这比假装坏代码不存在更有效,因为 AI 迟早会读到那些坏代码。5.3 规则写了,AI 还是违反规则会被违反是常态。我的排查思路是按顺序检查:首先看规则是否足够具体。把"请遵循架构"改成"禁止 router import repository"往往就好了。其次看规则是否被淹没。你的 AGENTS.md 是不是已经写了三百行?如果是,把最关键的十条移到文件最前面。再看规则是否有歧义。如果"service 层不得访问 model"这种说法可能被理解为"service 不能用模型类",那语义就跑偏了。最后一条路是加反例。把 AI 上次写坏的那段代码抽象成"错误示例",写进 AGENTS.md,并附一句"不要这样做"。对模型来说,一个反面样例比三句正面道理都管用。5.4 怎么度量"少绕过封装、少破坏架构"效果要拿数据说话。我在团队里用过一套很轻量的度量方案:把 AI 代码评审的违规分类,每一类对应 AGENTS.md 的一种事实,每次 review 时打标签,每周看一次分布。一个简单表格就够用:违规类别对应 AGENTS.md 章节本周次数趋势跨层调用架构分层3下降绕过封装封装边界5持平模式不一致实现约定1下降工程违规工程操作2上升几周之后你会发现一件有意思的事:违规最多的地方,往往就是 AGENTS.md 里写得最模糊的地方。哪个类别上升,就回去把对应的规则再写具体一档。这就形成了一个"写规则 → 跑代码 → 度量 → 改规则"的闭环。有时候我还会做一种"对抗测试":故意给 AI 派一个跨层任务,比如"直接在订单列表接口里加上未支付金额的汇总,不做接口变更。"如果它真的在 router 里写了一番金额计算逻辑,说明 AGENTS.md 的封装规则还没写透;如果它会停下来问"金额计算应该走 pricing 模块,需要我加接口吗",说明上下文工程起效了。6. 一些关于 AGENTS.md 的个人体会最后说点没有数据支撑、但几年下来的经验吧。我刚接触 AGENTS.md 的时候,以为它就是写点规则给 AI 看,后来发现它对人的价值比想象中大。把四种项目事实写清楚的过程,本身就是一次对项目架构的梳理:很多你以为"大家都知道"的约定,真的落到文件里才发现到处都是例外,那些例外恰恰是技术债最集中的地方。另外一个体会是,AGENTS.md 不是写给 AI 的一次性文件,它更像是给项目的"宪法"。它要跟代码一起演进,要在评审里被引用,要能在 AI 犯错时成为"实锤依据"。你不一定一次到位,可以先只写架构分层和封装边界这两类,跑顺了再补另外两类,效果往往比贪多一次写完要好。最后分享一个小技巧:如果你只想做一件事,就先加那个"交付前自检清单"。让 AI 在回复里逐条说明它有没有违反规则,这一个动作就能把大多数封装破坏拦在提交之前。我试过不同规模的几个项目,这个技巧的性价比最高,也最容易被团队接受。代码上下文工程这门手艺还远远没有定型,但方向已经很清楚:越早把项目事实显式交给 AI,AI 就越早变成真正"懂规矩"的队友,而不是一个每天制造架构债的实习生。
让 AI 在项目里加一个"修改订单状态"的接口,它一分钟就交差了。你打开 diff 一看,HTTP 路由层直接 import 了数据访问层的查询函数,业务规则层那套状态机校验全被绕了过去。代码能跑,review 绝对过不了。这种场景我遇到的次数已经数不过来了。问题不在 AI 不聪明,而在于它根本不知道你的项目里哪些门能进、哪些墙不能拆。以前这些"项目事实"装在我们脑子里,靠口口相传;现在 AI 一天帮你写几百行代码,你必须把这些事实固化下来,让它在动笔之前先读到。这就是圈子里讨论越来越多的"代码上下文工程",落地最常见的载体就是 AGENTS.md 文件。AGENTS.md 是放在仓库根目录(也可以放子目录)的一个 Markdown 文件,主流的 AI 编程工具在扫描代码库时会自动读取它。它跟 README 不一样:README 是给人看的项目介绍,AGENTS.md 是给 AI 定的操作规程。它回答的问题非常朴素:AI 在动手写代码之前,必须知道哪些项目事实。如果你也被 AI 改坏过架构、绕过封装、写出和团队风格完全不一致的代码,这篇内容就是为你准备的。我会把四种我认为最值得写进 AGENTS.md 的项目事实一条条拆开,给一份可直接抄的模板,再说说落地过程中最容易踩的坑。1. 为什么 AI 总是写出"对的废代码"1.1 先理解 AI 编码工具的"上下文盲区"我一开始也以为 AI 看得懂整个仓库。用久了才发现,它的工作方式是先做检索和局部读取,再基于附近代码做预测。它看到的是散落的文件块:一个路由函数、一个模型定义、一段 service 方法。这些碎片能告诉它"这里大概写了什么",但没法告诉它"这个项目为什么要这样组织"。架构分层、封装边界、依赖方向、异常约定——这些都属于全局事实。全局事实的分布很分散:一部分写在 README 里,一部分写在评审意见里,还有相当大一部分只存在于写这个模块的人脑子里。AI 读不到脑子里,于是它只能按看到的局部模式去猜。猜对了是运气,猜错了就成了我开头说的那种"对的废代码":语法正确、局部逻辑正确、整体架构错误。1.2 统计学习的双刃剑大模型写代码本质上在模仿训练数据和当前上下文里的模式。这个特点有个很实际的影响:AI 会把你仓库里"已有的写法"当成"应该的写法"。假设项目里有三十处绕过封装的坏例子,AI 大概率会学出第三十一处;反过来,如果仓库里每一处代码都严格走 service 层,AI 也很少会自己发明一条跨层的路。所以 AGENTS.md 的真正作用,不是给 AI 上课,而是把概率往正确方向推:让"好代码的样子"从隐性知识变成显性上下文,让 AI 在预测下一个 token 时能看到那条"禁止跨层"的规则。1.3 把 review 从猜谜变成对表还有一个被低估的理由:人肉 review 的成本。AI 代码评审里最费时间的不是找语法问题,而是判断"这个调用合不合架构"。如果规则只在某位老同事脑子里,评审就是猜谜;规则落进 AGENTS.md 之后,评审就变成了对表——对着文件里的约束逐条过,发现有违反就是实锤。更进一步,这类规则还能被脚本化检查,把一部分问题在提测前就拦住。2. 四种能真正改变 AI 行为的项目事实项目事实不是把 README 复制一遍那么简单。我梳理过很多次,最终发现真正能影响 AI 行为的,可以归成四类:架构分层、封装边界、实现约定、工程操作。四种解决四类不同的破坏方式。2.1 第一种事实:架构分层与依赖方向事实内容:系统的分层结构、每层的职责边界、依赖流向,以及新代码应该从哪一层进入。为什么对 AI 关键:跨层调用是 AI 写代码时最高频的架构破坏方式。原因很现实:跨层调用是最短的路径。AI 的目标是"生成能完成需求的代码",它不天然关心分层。如果数据访问函数就摆在眼前,它不会先去 service 层绕一圈,除非有人告诉它"必须绕这一圈"。怎么写:用一句话定义每一层做什么明确依赖方向:只允许谁依赖谁指出新需求的默认入口路径明确列出禁止的跨层组合我在实际项目里总结了一段可以直接用的写法:- 分层:router(HTTP 入口)→ service(业务规则)→ repository(数据访问)→ model(数据定义) - 依赖只能从上往下。禁止 service import router;禁止 router 直接调用 repository。 - 新功能默认链路:router → service → repository。 - 特殊场景需要跨层调用时,先问维护者,不要自行引入依赖。最后一句看着像废话,但对 AI 很有用。它在权衡"最短路径"和"绕路"时,如果有一条"先问维护者"的规则,至少会倾向于保守处理,而不是默认走捷径。2.2 第二种事实:封装边界与访问通道事实内容:哪些模块/方法是"公共门面",哪些是"内部实现";关键数据必须通过哪个通道读取或修改。为什么对 AI 关键:绕过封装比跨层更隐蔽。有些代码没有明显的分层问题,但它直接操作了本不该碰的内部字段或方法。AI 读到一个模型的字段定义和一个更新方法,它更倾向"直接赋值"因为那看起来更简单。可现实项目里,字段往往有业务不变量需要保护——比如订单状态不能从"已支付"直接跳回"待支付"。怎么写:列出必须通过指定方法修改的关键字段列出禁止直接 import 的内部路径给出正确通道的完整表达说明为什么,让 AI "理解"而不是"死记"示例片段:- 订单状态 order.status 是受保护字段,只能通过 order_service.transit_status() 修改,禁止在 repository 或模型层直接赋值。 - 价格计算统一走 pricing.compute_total(),禁止在业务代码里手写折扣累加;业务方传入的金额一律以分为单位。 - 禁止 import app.repositories.order_repo._internal 下划线前缀的私有模块;这些是历史遗留实现,新代码不得引用。这里有个细节值得强调:规则里最好带上具体的符号名,比如order.status、transit_status()。AI 对具体符号的匹配能力远强于对"请尊重领域模型"这类抽象表述的理解。你给它一个抽象口号,它给你一个抽象遵守。2.3 第三种事实:实现约定与模式规范事实内容:错误处理、日志、事务、命名、并发模型这类"写法层"的统一规则。为什么对 AI 关键:这一层是 AI 模仿能力最容易出问题的战场。它会模仿离它最近的那一段代码——不管那一段是不是好样板。如果你的项目里恰好在某个角落有一段 print 调试日志、一段吞异常的逻辑,AI 就会在新区块里复刻出来,因为"这就是这个项目的风格"。怎么写:明确"必须用什么"和"禁止用什么"给出一个指向好样例的路径(例如:新模块参考app/services/payment_service.py的写法)对容易出错但高频的操作给固定模板片段示例:- 业务错误统一抛 BizError,全局异常处理器负责转成 HTTP 响应;禁止在 service 里 try/except 后静默返回 None。 - 日志使用 logging.getLogger(__name__),禁止使用 print。 - 涉及多表写入必须走 service 层的事务上下文;repository 方法禁止自行 commit/rollback。 - 新代码必须是 async 风格,禁止新增同步阻塞调用。 - 新增业务规则时,同步补充 pytest 单测;测试禁止访问外部网络。每次我写完这类规则后,AI 生成代码的错误处理质量会明显提升一个档次。因为这部分在普通文档里最容易被省略,而对 AI 来说恰恰是它最容易"自由发挥"的地方。2.4 第四种事实:工程操作与变更约束事实内容:构建命令、测试门槛、生成文件清单、依赖引入流程、环境版本。为什么对 AI 关键:AI 有时会为了"完成任务"做出破坏工程的操作,比如去修改自动生成的迁移文件、为一个小工具函数引入一整个第三方库、删掉看起来多余但其实是运行必需的配置。这些动作在单一任务的视野里常常"合理",但从工程全局看是灾难。怎么写:把必须通过的检查命令写清楚明确哪些目录是生成产物、禁止手改规定依赖引入的流程写明运行环境和版本约束片段示例:- 提交代码前必须通过:pytest -m "not integration" 和 make lint。 - migrations/ 目录由迁移工具自动生成,禁止手工修改;模型变更请新增迁移文件。 - 新增第三方依赖需要提交理由并说明替代方案,禁止为单个小函数引入完整框架。 - 运行环境:Python 3.11+、PostgreSQL 15+;配置统一读 settings 模块,禁止硬编码。四种事实可以用下面这张表快速对照:事实类型解决的破坏规则示例衡量方式架构分层跨层调用、反向依赖禁止 router 直接调 repositoryreview 检查 import 方向封装边界绕过门面改内部字段/私有模块状态只能通过 service 方法修改review 检查字段赋值来源实现约定错误处理、日志、事务不一致禁止吞异常、禁止 print静态检查 + review工程操作改生成文件、乱加依赖、跳过测试迁移文件禁止手改CI 脚本校验3. 一份可以抄作业的 AGENTS.md 完整示例看概念不如直接上成品。下面这个示例来自一个虚构的订单服务项目(内部代号 order-svc),结构是一个很常见的后端服务:分层目录加 PostgreSQL。你不需要真的了解这个项目,只需要看一条条规则是怎么组织在一起的。3.1 示例项目的目录骨架order-svc/ ├── app/ │ ├── main.py │ ├── routers/ │ │ └── orders.py │ ├── services/ │ │ ├── order_service.py │ │ └── stock_service.py │ ├── repositories/ │ │ ├── order_repo.py │ │ └── stock_repo.py │ ├── models/ │ │ └── order.py │ ├── utils/ │ │ └── pricing.py │ └── settings.py ├── migrations/ ├── tests/ └── AGENTS.md3.2 完整的 AGENTS.md 文件下面这个文件是我建议的"起步版"。它不需要一次写完美,先覆盖最容易出问题的四类,然后在实际使用中迭代。# AGENTS.md 你是本项目(order-svc)的编码助手。在生成任何代码之前,先阅读本文件, 并把它当成不可违背的项目事实。如果本文件与仓库代码存在冲突,以本文件 为准,并向用户说明冲突点。 ## 1. 架构分层与依赖方向 - 分层:router(HTTP 入口)→ service(业务规则)→ repository(数据访问)→ model(数据定义) - 依赖只允许从上往下:禁止 service import router;禁止 router 直接调用 repository; 禁止 repository 依赖 service。 - 新功能默认链路:router → service → repository。不要在中间省略层级。 - 如果确实需要跨层,先在回复里说明理由,并建议用户与维护者确认,不要直接写。 ## 2. 封装边界与访问通道 - 订单状态 order.status 是受保护字段,只能通过 order_service.transit_status() 修改。 禁止在 repository、model 或 router 层直接赋值。 - 库存扣减只能调用 stock_service.deduct();禁止绕过 service 直接写 stock 表。 - 金额计算统一使用 pricing.compute_total(),金额单位一律为分;禁止在业务代码里手写 累加折扣或金额换算。 - 禁止 import 下划线前缀的私有模块(如 order_repo._internal);它们是历史遗留实现。 - 所有对外读接口必须经过 repository 层,禁止 router 直接拼接 SQL 或直接访问模型类。 ## 3. 实现约定与模式规范 - 业务错误统一抛 BizError,由全局异常处理器转成 HTTP 响应;禁止在 service 里 try/except 后静默返回 None。 - 日志使用 logging.getLogger(__name__);禁止 print。 - 涉及多表写入必须使用 service 层提供的 transaction() 上下文管理器;repository 方法 禁止自行 commit 或 rollback。 - 新代码必须使用 async/await 风格;禁止新增同步阻塞调用。 - 命名:repository 方法用 create_/get_/update_ 前缀;service 方法用业务动词 (submit_/cancel_/transit_),不要写成 CRUD 风格。 - 新增业务逻辑时必须补充 pytest 单测;测试文件放 tests/ 对应目录, 禁止访问外部网络。 ## 4. 工程操作与变更约束 - 提交前必须通过:pytest -m "not integration" 和 make lint。 - migrations/ 目录由迁移工具自动生成,禁止手工修改;模型变更请生成新的迁移文件。 - 配置项统一在 app/settings.py 中声明,禁止在代码里硬编码环境地址、密钥、超时时间。 - 新增第三方依赖需要说明理由并评估替代方案;禁止为小工具函数引入完整框架。 - 运行环境:Python 3.11+,PostgreSQL 15+。 ## 5. 交付前自检清单 在你完成代码后、输出之前,逐条检查你的改动: 1. 是否有任何新 import 违反了第 1 节的依赖方向? 2. 是否直接读写过第 2 节提到的受保护字段或私有模块? 3. 错误处理、日志、事务是否遵循第 3 节? 4. 是否碰过 migrations/ 或硬编码过配置? 5. 是否补充了对应的测试? 把检查结果和解答一并写在回复里。3.3 这个模板的写法拆解你可能会问,为什么第 5 节要加一个"交付前自检清单"?这是我自己实测最有效的一个技巧。模型在生成代码之后的"反思"阶段,如果拿到一份明确清单,它会真的逐条自查一遍,而不是在生成完输出后立刻结束。让 AI 把自查结果写进回复,也方便你在评审时直接看到它的判断依据——它到底注意到了哪些规则,忽视了哪些规则。还有一个关键点:文件开头那句"如果本文件与仓库代码存在冲突,以本文件为准",很有必要。因为模型对既有代码的信任度通常高于文档,如果仓库里已经混入了一些历史遗留的坏味道,AI 会倾向模仿坏代码而不是遵守规则。加这句话,相当于给规则一个更高的优先级。模板里每条规则都尽量带上了具体符号名,比如order.status、transit_status()、pricing.compute_total()。这是故意的:具体符号能触发模型的知识匹配,而抽象表述只能触发模型"点头"。你写抽象规则,AI 会抽象地遵守;你写具体规则,AI 才会具体地执行。4. 从"能用"到"好用":四条写法原则有了模板还不够。我见过很多团队把 AGENTS.md 写上几百行,最后发现一点用没有。问题常常不是"写没写",而是"怎么写"。下面这几条原则是从不好用到好用的关键。4.1 每一条规则都要能被验证判断一条规则写得好不好,最快捷的方式是问:它能不能在五分钟内通过检索或静态检查验证?反例:"请保持代码整洁,遵循项目架构。"——什么是整洁?什么是项目架构?AI 无法验证,于是它当成客气话。正例:"禁止 router 层直接 import repositories 下的模块。"——可以用一条 grep 验证,出现了就是违反。我建议每写一条规则都做一次"grep 测试":如果你自己没办法用一个简单的搜索命令确认这条规则是否被违反,AI 更做不到。4.2 "禁止"和"允许"要成对出现只写一堆"禁止",AI 会陷入两难:不许这样做,又不让那样做,那到底怎么做?正确姿势是每条"禁止"都配一条"替代路径"。比如:"禁止 router 直接调 repository"这条,后面一定要跟"新功能必须走 router → service → repository 的链路"。禁止改了内部实现,就给一条"统一走 pricing.compute_total"的允许通道。AI 是路径依赖的,你只关掉一条路,它会另找一条路;你把正确路径同时画出来,它才会安心走那条。4.3 把规则指向权威出处AGENTS.md 不应该是孤立的第二套规则,它最好是代码和文档的"索引"。项目中架构会持续演进,如果 AGENTS.md 脱离真实代码,很容易过时。我的习惯是:在 AGENTS.md 里写规则的同时,用一行注明权威出处。比如"分层定义详见 docs/architecture.md 第 2 节""错误处理基线参考 app/services/payment_service.py"。这样两件事同时发生:AI 需要更多细节时能自己去读;读者维护 AGENTS.md 时也能顺着索引找到源头,不会让规则和代码漂移成两套事实。4.4 长度失控,就是规则失效的开始AGENTS.md 写太短等于没写,写太长同样等于没写。模型对上下文的注意力是有限的,几百行规则塞进去,真正被记住的可能是开头和结尾那几条。以我的经验,单个 AGENTS.md 控制在 80 到 200 行最合适。如果项目复杂,内容超过这个量,优先做拆分:根目录放全局规则(分层、依赖、通用约定),在子模块目录里放局部规则(比如app/repositories/AGENTS.md专门写仓库层的注意事项)。多数主流工具支持子目录级上下文文件,AI 读代码时能看到就近加载的内容,效果比把所有东西堆在一起好得多。4.5 AGENTS.md 要跟着架构一起走最后一条原则是维护节奏。我强烈建议把 AGENTS.md 的更新纳入代码评审范围:只要对分层、封装、依赖做了调整,同一个 PR 里顺手改 AGENTS.md。否则文件一旦过时,AI 就会发现"规则和代码对不上"而选择信任代码,你的规则体系就崩了。过时的规则比没有规则更危险,因为它会消耗信任。5. 落地过程中的常见问题与排查思路写 AGENTS.md 很容易,落地过程几乎是必然踩坑的。把我遇到过的高频问题整理一下,给你做个排障对照。5.1 AI 好像没读到规则怎么办第一步不是改规则,而是先确认文件真的被读取。你可以在对话里直接问 AI:"你刚才参考了仓库里的 AGENTS.md 吗?如果参考了,列出你认为优先级最高的三条规则。"如果它答不上来,说明读取链路出了问题:要么文件名不匹配你的工具约定,要么工具本身没启用仓库级上下文。多提一嘴工具差异:不同 AI 编程工具对"上下文文件"有不同的默认文件名,有的读 AGENTS.md,有的读 CLAUDE.md,有的用 .cursor/rules 这种目录。兼容做法是在仓库根目录放一份 AGENTS.md,再按你的主力工具要求把关键规则复制到它认的位置。社区目前正在往 AGENTS.md 这个约定靠拢,但生态还没完全统一,先确认你的工具认哪个。5.2 规则和现有代码明显冲突这是最常见也最麻烦的情况。仓库里已经存在大量违反规则的坏代码,AI 读着读着就开始学坏代码。处理方式我分两种:如果坏代码量少,直接改掉它们,让代码符合新规则,兼修 AGENTS.md。如果坏代码量很大一时间改不完,在 AGENTS.md 里明确标注"历史遗留反例":写明哪些路径是已知违规,新代码不得参考、不得复刻。这比假装坏代码不存在更有效,因为 AI 迟早会读到那些坏代码。5.3 规则写了,AI 还是违反规则会被违反是常态。我的排查思路是按顺序检查:首先看规则是否足够具体。把"请遵循架构"改成"禁止 router import repository"往往就好了。其次看规则是否被淹没。你的 AGENTS.md 是不是已经写了三百行?如果是,把最关键的十条移到文件最前面。再看规则是否有歧义。如果"service 层不得访问 model"这种说法可能被理解为"service 不能用模型类",那语义就跑偏了。最后一条路是加反例。把 AI 上次写坏的那段代码抽象成"错误示例",写进 AGENTS.md,并附一句"不要这样做"。对模型来说,一个反面样例比三句正面道理都管用。5.4 怎么度量"少绕过封装、少破坏架构"效果要拿数据说话。我在团队里用过一套很轻量的度量方案:把 AI 代码评审的违规分类,每一类对应 AGENTS.md 的一种事实,每次 review 时打标签,每周看一次分布。一个简单表格就够用:违规类别对应 AGENTS.md 章节本周次数趋势跨层调用架构分层3下降绕过封装封装边界5持平模式不一致实现约定1下降工程违规工程操作2上升几周之后你会发现一件有意思的事:违规最多的地方,往往就是 AGENTS.md 里写得最模糊的地方。哪个类别上升,就回去把对应的规则再写具体一档。这就形成了一个"写规则 → 跑代码 → 度量 → 改规则"的闭环。有时候我还会做一种"对抗测试":故意给 AI 派一个跨层任务,比如"直接在订单列表接口里加上未支付金额的汇总,不做接口变更。"如果它真的在 router 里写了一番金额计算逻辑,说明 AGENTS.md 的封装规则还没写透;如果它会停下来问"金额计算应该走 pricing 模块,需要我加接口吗",说明上下文工程起效了。6. 一些关于 AGENTS.md 的个人体会最后说点没有数据支撑、但几年下来的经验吧。我刚接触 AGENTS.md 的时候,以为它就是写点规则给 AI 看,后来发现它对人的价值比想象中大。把四种项目事实写清楚的过程,本身就是一次对项目架构的梳理:很多你以为"大家都知道"的约定,真的落到文件里才发现到处都是例外,那些例外恰恰是技术债最集中的地方。另外一个体会是,AGENTS.md 不是写给 AI 的一次性文件,它更像是给项目的"宪法"。它要跟代码一起演进,要在评审里被引用,要能在 AI 犯错时成为"实锤依据"。你不一定一次到位,可以先只写架构分层和封装边界这两类,跑顺了再补另外两类,效果往往比贪多一次写完要好。最后分享一个小技巧:如果你只想做一件事,就先加那个"交付前自检清单"。让 AI 在回复里逐条说明它有没有违反规则,这一个动作就能把大多数封装破坏拦在提交之前。我试过不同规模的几个项目,这个技巧的性价比最高,也最容易被团队接受。代码上下文工程这门手艺还远远没有定型,但方向已经很清楚:越早把项目事实显式交给 AI,AI 就越早变成真正"懂规矩"的队友,而不是一个每天制造架构债的实习生。