代码不是真相:给 Agent 补上仓库里没有的几层
Agent 读得懂每个函数,却判断不了这次改动会影响谁。缺的不是代码注释,而是业务语义、隐式约定、运行时事实和历史决策——它们不在仓库里,也不该被塞进一个大而全的知识库。
真正决定这次改动能不能做的那些知识,大多不在代码里。一个 Agent 可以把仓库里的每个函数都读明白,仍然会做出一次错误的改动。
我现在的判断是:代码只是水面上的那一层,显式化出来的知识才是资产,代码不是真相。这部分知识没有别的补齐办法,只能把团队脑子里的东西显式化。停在「代码写得清楚一点」这种层面是不够的,它只能解决能不能读懂局部代码,解决不了在复杂系统里做出正确的工程判断。
显式化要占上下文,这笔账得单独算:这些知识在一次任务里到底占多少、预算该怎么切。每多写一条约束、每多落一块知识,就要多占一点模型读到的上下文。
读得懂代码,不等于知道能不能改#
举个具体的例子。模型知道 refund 是退款,却不知道在这个团队里,退款到底牵扯订单状态、支付单状态、履约状态、财务对账、客服工单还是风控策略。这类信息是这个团队对业务的定义,注释补不上它。
难处是这些东西压根没地方落,注释写得再多也装不下它。把缺的东西摊开,就是这几类:
- 业务语义:这个概念在本团队里到底涵盖哪些环节,边界画在哪。
- 隐式约定:某个看起来没人用的字段,其实是离线任务每天凌晨要扫的;某个消费分支不能删,因为还有历史服务在消费老格式。
- 运行时事实:超时、重试、熔断降级、限流、灰度策略、告警口径,以及出事之后该找谁。
- 历史决策:为什么留着这层兼容,为什么当年选了这个方案,哪些是业务妥协而不是技术最优。
- 约束与规范:哪些字段只能新增不能改语义,哪些状态流转必须审批,核心链路上不许新增强依赖。
这几类东西有个共同点:它们都是从代码里推不出来的。业务语义是团队定义的,隐式约定是历史留下的,运行时事实在线上配置里,历史决策只存在于当事人脑子里,约束来自事故与合规。指望模型读代码读出来,等于指望它在没有输入的情况下输出结果。
Agent 没有组织记忆,它只能读取你明确给它的东西,这一条是整个问题的根。人类工程师正是靠长期经验、团队沟通和事故记忆把这些补全的。
补哪些、不补哪些得有判据,不然缺口清单会把人淹掉。我的认定条件得同时成立:属于应该被覆盖的领域,当前版本确实没有覆盖,现有的引用里找不到有效答案,并且能指到一个明确的落点(文档 / 校验脚本 / 测试 / 接口)。只满足一两条的先放着,它们多半是长尾,还凑不成一条缺口。
这笔账得先算清楚:技术方案一旦错了,后面所有高效执行都会变成高效返工。模型生成代码越快,越会把一个错误的方案执行到底,这正是显式化要排在前面的原因。
知识不是越全越好,是角色不同#
不同问题对应不同的知识形态。一个库想装下所有问题的答案,等于放弃了按问题挑形态的机会;面对这个缺口,最自然的反应是建一个大而全的知识库,我不认同这个做法。把它们倒进同一个库里,每类知识都以最不适合它的方式存在:事实该自动更新却靠人维护,约束该人工确认却被机器生成,验证规则该逐条可执行却写成散文。
更麻烦的是另一层风险:知识库会退化成一份看起来完整、实际上过期的文档,而 Agent 最怕的不是没有上下文,是拿到了错误的上下文。没有上下文它会停下来问,拿到错误上下文它会信心十足地往错的方向走。一份结构完整、方向错误的方案,比起没有方案更容易制造返工。
大而全的知识库会以最快速度同时犯上这几样,坏法都跟「全」有关:过期、冲突、不可执行。过期是内容没跟上系统变化,冲突是同一个事实在两处写法不同让模型只能随机挑一个,不可执行是写成了描述性文字、机器没法据此做判断。
我按角色把知识分成四类,各自独立,各自过期之后的症状完全不一样:
| 角色 | 回答什么问题 | 过期之后的症状 |
|---|---|---|
| 事实 | 系统现在长什么样 | 改动落到一个早就下线的依赖上 |
| 映射 | 这次需求落在哪条链路上 | 跨系统需求被当成单服务改动 |
| 约束 | 什么不能改、为什么不能 | 代码能跑,但违反了不能改语义的规矩 |
| 验证 | 怎么证明这次改动是安全的 | 跑几个单测就报完成 |
这张表里缺掉一类,其余几类的价值都会大打折扣。这张表同时也是工作的顺序:知道系统事实才知道怎么改,知道映射才知道动哪条链路,知道约束才知道不能怎么改,知道验证方式才知道怎么证明安全。
按角色切知识决定它以什么形态存在,按认知半径切视野决定一次任务读多大范围。两把尺子各管一件事,混用的结果是一层里塞进形态完全不同的知识,治理方式也就跟着打架。
认知半径要分层,链路要钉到链路上#
纵向再切一刀,按认知半径分成四层。之所以叫认知半径,是因为每一层决定了 Agent 应该把视野放到多大。回答「为什么改」的时候不需要知道某个服务的目录结构,回答「这个服务内部怎么改才安全」的时候也不需要读全公司的架构图。
| 层 | 回答什么问题 | 形态 | 更新方式 |
|---|---|---|---|
| 业务层 | 为什么改,这次业务落在哪 | 按概念、场景、链路组织的文档 | 以人写为主,分领域各自维护 |
| 架构层 | 系统之间怎么协作 | 服务级知识加调用边 | 从服务治理信息自动生成 |
| 系统层 | 这个服务内部怎么改才安全 | 目录级结构化描述 | 工具生成,人工确认关键点 |
| 基建层 | 底座规则是什么 | 团队级规范条目 | 低频变更,集中维护 |
混在一起的结果通常是两头不讨好,分层的收益是能分别治理。业务层允许主观、允许写得散,因为它的读者需要理解背景;系统层必须结构化、必须可执行,因为它是要给 Agent 读的。
业务层的一份文档我按五段组织:元信息与归属、这个领域的原则、典型场景、可复用实践、历史变更。实践和历史必须分开,历史是推导过程,实践是当下结论,混在一起之后没人敢改,也就没人再维护。业务层真正值钱的是从场景一路落到消息的那条映射链。这里面还有一条要单独回答的问题:这条知识有没有经过确认、能不能直接拿去让 Agent 动手。
系统层要按机器能读的方式组织:这个服务是干什么的、有哪些核心对象、对外接口是什么、依赖哪些下游。跑在什么基础设施上、主流程怎么走、怎么验证、有哪些硬约束,这几样也得写清,缺任何一样 Agent 在这个服务里改代码就是盲改。服务还得声明自己不负责什么,否则 Agent 会把没归口的部分也当成可以改的。
粒度上有个容易被忽略的判断:业务层要按概念、场景、链路组织,按服务切会把跨服务链路切碎。按链路组织,一次改动影响哪几个服务是看得见的。
基建层看起来最不起眼,却是最该先写的一层:键的命名、慢查询阈值、发布流程、回滚策略,这些规则变化极慢,写一次能吃很久,而且它们天然是结构化的。把这一层做扎实,性价比高过上面任何一层。
架构层的做法是把跨服务关系收进一份可查的入口:谁调我、我调谁、走什么协议、哪个接口、超时多久。先挑链路清晰的服务做知识化治理,剩下的靠持续补。做出来之前,改一个接口要在群里问一圈;做出来之后,影响分析不再完全依赖人肉问同事。
知识从哪来,要分两头想。业务层和基建层主要靠人写,因为它们的来源是判断和事故;架构层和系统层主要靠工具生成,因为它们的来源是配置和代码结构。指望工具生成业务判断,等于让机器去猜人的决定;指望人维护调用关系,等于让人去追机器的变化。约束虽然不是一层,来源上却和业务层同源,同样得由人确认。
四类角色里,映射这一类最该单独拎出来,因为它的作用点在动手之前,这一次改动的范围画错了,后面每一步都在错的范围里打转。事实、约束、验证回答的都是「画定的范围里怎么判」,映射决定的是那个范围本身画在哪。
很多技术方案设计最怕的,是不知道改完影响谁。一个听着像单服务改动的需求,顺着链路走下去往往是跨好几个系统的改动:页面操作触发业务语义,语义落到客户端接口。接口过网关,网关转领域服务,领域服务再碰下游系统和消息。不在开头把这条链路画出来,方案就会按单服务改来做,做到一半才发现漏掉整段链路。
所以我把「场景到链路」的映射当成一等公民来维护:页面操作、业务语义、客户端 API、网关、领域服务、下游系统或消息,一层层钉下去。这条映射链的价值是让 Agent 在动手之前就知道这次改动的半径有多大。
映射的维护要有明确的归属,我把它挂在领域负责人身上,落笔的人不承担链路变化。维护时机也固定:接口变更、下游替换、消息格式调整发生时同步改,不做定期盘点。映射这一类知识还有个副作用:它把 PRD 到技术方案之间的转换显式化出来。那段转换最容易出错,以前靠人脑补,现在能对照。
知识生成本身要落成一项服务,能被调用、能订阅:把生成拆成一支独立的服务,把知识库做成可检索、可订阅的实体,中间是一条明确的写入管道。这么拆的好处是知识的新陈代谢有了固定执行者,更新不再看谁今天有空去补文档。生成和知识库糊在一起时,每有一处结构变化都要人手动对一遍,对到第三天就没人跟了。
约束和验证规则,都要能指到落点#
四类角色里最容易被忽略的是约束,而约束恰恰是杠杆最大的一类。它最容易漏,是因为漏掉不报错,改完照样能跑。
模型非常擅长告诉你怎么写,它不擅长告诉你哪样不能写。因为「不能」是从一次线上事故、一次合规审查、一次已经谈过的业务妥协里推出来的。一个新人工程师问「这个字段能不能删」,答案大概率不在代码里。
所以约束必须由人确认。我一直坚持的做法是:事实用工具生成,约束走人工确认,两者分开存放,连存放方式也刻意区分开。自动生成带来的自信感很危险:人会默认一份机器列出来的禁止项已经核对过,其实没有核对过。没经过人确认的那条知识要显式标成待确认,不能让 Agent 拿它当硬约束用。
约束要落在能拦住的位置,指不到检查点的那条等于没写。写在文档里的约束只能靠模型自觉,落在校验脚本里的约束才是真的。我要求每一条约束都能指到一个具体的检查点,静态检查、评审清单上的一项、一条测试都算。
约束的形态得结构化。写成散文的约束,模型读完之后要自己再做一次判断,等于没写。写成「这类改动禁止什么、哪些字段只能新增不能改语义、哪些状态流转必须审批」的条目,机器才能拿它做机械检查。判断一次改动到没到位,要看这次改动对应的验证方式有没有被执行,验证方式本身也要当成资产写下来。
写下来之后,「跑几个单测就报完成」这种弱验证就有了一个可对照的标尺。每一类改动——加接口、改表结构、修缺陷——都明确写下它的验证组合,包含要跑哪层测试、要观察哪个指标、要在什么环境里验。按变更类型分开写更清楚:加接口要过契约测试,改数据库要过迁移验证和回归测试,改状态机要过核心流程测试,改消息 schema 要同时验生产端和消费端的兼容。
验证规则跟约束是同一个道理:反馈要靠制度保证,不能靠自觉。有了这张对照表,「做完了」这三个字才有一个可检查的定义。
验证规则还是四类知识里最容易自证的一类:事实可能过期,约束可能有例外,验证规则拿当前这次改动跑一遍就知道还对不对。所以它值得先做,排在别的知识前面。凡是能反复执行的知识,都可以当成一轮回归的入口:拿当前改动跑一遍,跑通说明这条规则还成立,跑挂了说明规则和实践已经分家。这类知识自己会告诉你它过期没有。
验收没过的时候要有确定动作,把「需要人工确认」换成一件能派活的事。我的处理是:能自动修的交给工具,需要判断的生成一条明确的待办并挂到责任人,涉及业务取舍的才升级到人。含糊的「待确认」会一直堆在列表里,直到某天没人再看。
加载有协议,更新有分工#
知识分完角色和层级之后还剩一件事要定:一次具体任务该读哪一些。这个问题不定下来,前面分好的几层就落不到具体任务上,所以还有两件事要处理——加载怎么组织,更新谁负责。
我用的做法是把加载本身写成协议。一个很短的入口文件负责引导,后面挂一张按任务类型组织的路由表:加接口该读哪些、改库该读哪些、修缺陷该读哪些,各自列清必读清单。知识由此从一堆资料变成一份可审查的加载清单,审查看的是这张路由表有没有漏。
加载清单也得短。按任务类型分别列必读项:加接口必读接口契约、兼容性规则、测试和约束,改库必读库结构、测试和约束,修缺陷必读主流程和约束。清单之外的一律不进这一次任务的上下文。清单短,模型反而好判断;堆一堆候选让它自己挑,等于把选择成本转嫁给了它。
粒度也得控。一段内容要不要再往下拆,看它本身:文件有多大、内容有多杂、更新频率高不高、别人会不会单独引用里面的片段。这几条里有答不上来的,先别急着拆。
规模也得算进去:把几百个服务的知识全量同步一遍不现实,所以要按领域切分、各自维护,并且让入口只暴露跟当前任务相关的那一部分。规模上来之后,能不能只加载相关的那一点,才是决定成败的地方。
更新上按可自动化程度分工,这是让知识不过期的关键:
| 内容 | 谁生成 | 什么时候更新 |
|---|---|---|
| 系统事实 | 工具自动 | 每次结构变更 |
| 约束与策略 | 人工确认 | 规则发生变化时 |
| 过程记录 | 自动生成 | 每次评审与事故 |
能自动核对的事实每天重新生成,需要判断的约束走人工确认、变更才动。两者混在一个文件里,工具每天覆盖事实,人工维护的那部分就没人盯了,这是我见过最常见的腐化路径。结论和推导过程也必须分开:某些知识本身的论证和审核记录,要跟可复用的结论分开放。堆在一起的结果,是半年后没人分得清哪段是现在有效的规则、哪段是当初讨论时被推翻的想法。
反向沉淀的口子得留着,这条路径要通。每次方案评审遗漏的东西、每次评审里提出的风险、每次线上暴露的隐性依赖、每次为兼容做的特殊处理,都要有回灌的入口。这条路径不通,知识库只会越用越薄,变成一份只在刚建起来那几天有用的文档。
走完这一圈,值得留在链路上的知识长成一个统一的形状:它属于哪一层、由谁确认、在哪个环节真正查它。其余那些写得很全、读着也顺、却指不到落点的部分,最后都要靠人定期回头维护,这才是显式化真正的成本所在。