技术 · 深入
同一件事跑两遍,它两次看的地方不一样
收敛的锚:把结构事实钉在每次运行都读到的位置
你有没有遇到过:同一个任务、同一份代码、同一个提示词,跑两次结果不一样 —— 改动的文件不同、走的路子不同。多数人把这归成「模型不稳定」,但有一半是导航漂移:agent 靠关键词搜索找代码,第一个搜索结果会偏置下一个工具调用,偏差一路累积,即使温度设成 0 也会分叉。对策不是换模型,是给它一个每次运行都一样的参照系:把静态分析出来的调用关系、继承层级、配置依赖渲染成纯文本注释,跟源码一起喂进去。这一页讲清三样:锚要放哪三类事实 · 为什么它能压方差(而不是提高准确率)· 以及三个让它白花钱的前提。
方法 · 一图看懂
3
然后选三类事实:调用关系 · 继承层级 · 配置依赖 —
它减的是方差,不是准确率
这一点最容易被误读:锚的价值不在「让 agent 更聪明」,在「让同样的输入落到同样的位置」。实验报的两个数字要分开看 —— 准确率的提升(Func@5 +2.2pp、Pass@1 +3.4pp)落在噪声带里;真正显著的是「link-following rate 从 0.15~0.18 升到 0.21~0.24」与运行间方差减半。机制:agent 每轮只做一个决策 —— 下一个打开哪个文件;当提示里每次都摆着同一套结构断言,这个决策的分布就收窄了。所以什么时候才值得付这 10% 的 token:当可复现本身是硬需求时 —— 审计回放、轨迹比对、回归式评估 agent 改动。跑一次就发布的场景,确定性买不到任何可测的东西。
三类事实,以及一个「别硬套」的边界
值得锚的是三类静态事实:① 调用关系(模块内外的 caller → callee 边)—— 免掉「谁调用了它」的反复搜索 ② 继承层级(类到父类、接口实现)—— 把源码一行看不出的多态分派摆出来 ③ 配置依赖(配置键 → 消费者、环境变量 → 读取点)—— 把运行时开关和它管的代码接上。边界写得很硬:这三个好处只对中等规模、结构稳定的代码库成立 —— 20 个文件以下,agent 直接读源码更便宜;超过一定规模,锚自己也装不下、或者在一个会话里就过期了。还有一条最容易翻车的:静态事实必须等于运行时事实。Rails 的 method_missing、Python 元类、JS 代理、宏重的 Rust —— 这些在运行时决定分派的地方,静态调用图与实际执行分叉,锚会把人带错(这正是「仓储地图」模式失效的同一类原因)。
和另外两条上下文纪律的分工
别把锚和「压缩」混成一件事。压缩解决的是「上下文快满了怎么办」—— 它丢弃;锚解决的是「每轮该看哪」—— 它固定,而且是在压缩之前就先摆好。同样也别和 JIT 检索混:JIT 是「只持有轻量标识符、按需加载」,锚是「把最常用的一小撮结构事实常驻」。三条的关系可以这么排:锚(常驻的结构事实)→ JIT(其余按需加载)→ 压缩(快满时总结)。顺序颠倒(先压缩再想锚)会把结构事实一起压掉,而那恰恰是唯一不该丢的东西。
落地:注入位置比内容更要紧
锚的效力一半来自「每次都在同一个位置」。同一份结构事实,如果这次放在系统提示里、下次塞在对话末尾,收窄方差的机制就没了 —— 因为 agent 每轮看到的位置都不一样。做法:把锚渲染成一个独立文件(或提示里一个固定段落),用固定的顺序与固定的格式,每次运行都在同一处注入,并把注入动作本身写进流程(不是「记得加」)。验证方式也很简单:同一个任务连跑三遍,看它落到的文件集合是不是收敛了 —— 收敛了就是锚在起作用;三遍还是三样,说明锚没被稳定注入,或者这个代码库根本不该用锚(见三个前提)。
原理 · 为什么有效
一句话:agent 的导航是一个有噪声的决策过程,锚的作用是把这个决策的输入固定下来。它不是新知识,而是把已经确定的静态事实换一种更稳的呈现 —— 从「让它自己搜出来」变成「每次直接给它」。为什么有效:搜索取到的第一个结果会偏置下一轮,而解码本身也有数值性抖动,偏差会累积;把结构断言固定在同一个位置、每次一样,就到断了这条累积链。为什么它只值 10% 的 token:它不增加 agent 的知识,只减少它的随机性 —— 所以只在「同一件事必须得到同一个结果」的地方值钱。
适用场景
适用于:反复跑同一类任务、且要求结果可复现的代码工程 —— 回归式评估 agent 改动 · 审计回放 · 轨迹比对 · 多轮重构。不适用于:小仓库(20 个文件以下直接读源码)· 结构剧烈变动的代码库(锚在一个会话里就过期)· 运行时决定分派的代码库(元类 / 代理 / 宏,静态锚会误导)· 一次就跑完发布的场景(确定性没有消费者)。
自测 · 5 问
「同一件事两次结果不同」在你的流程里会造成真实损失吗(还是只是看着不爽)?
你的代码库规模与结构稳定性,落在「锚装得下、不太会过期」的区间里吗?
你的分派是静态决定的,还是靠元类 / 代理 / 宏在运行时定的?
锚的注入位置固定吗 —— 每次运行是不是都在同一处、同一格式?
锚加了之后,你有没有用「同一任务连跑三遍」验证过结果是收敛的?
怎么上手 · 验证步骤
先做第 1 问。如果「跑两遍不一样」在你这里不造成损失,这一页对你就是纯开销,直接跳过 —— 这不是懒,是判据。答案如果是「会造成损失」,再依次过第 2、3 问(规模与分派方式),两条都过才动手。动手时只锚三类事实里的第一类(调用关系):一个模块的 caller → callee 边,渲染成纯文本贴在固定位置。跑三遍同一任务看收敛,再决定要不要加继承层级与配置依赖。做完自己核三样:① 锚的注入位置在代码里是固定的一处,不是每次手工加 ② 同一任务连跑三遍,落到的文件集合收敛 ③ 你能量化代价(多花了多少输入 token)—— 只要这三个里有一个答不上来,这一轮就没做完。
五问逐条自查:① 可复现是硬需求吗 ② 规模与稳定性在适用区间吗 ③ 分派是静态的吗 ④ 注入位置固定吗 ⑤ 连跑三遍收敛吗。第 ①②③ 问任一为否 ⇒ 不该用锚;④⑤ 为否 ⇒ 用了但没起作用。