“你的编程 Agent 跑着跑着就「失忆」了?百万行仓库的上下文工程”
先说个我最近经常遇到、也经常被朋友问起的场景:你搭好了一套编程 Agent 工作流,让它去一个百万行级别的代码仓库里改需求。刚开始几步还挺正常,它能精准定位到某个服务、某个文件,甚至能顺着调用链找对修改点。但跑了十来分钟、处理了七八个文件之后,它突然像换了个人——忘掉了最开始约定的命名规范,把一个已经废弃的接口当成现状在改,甚至开始重复讨论五分钟前它自己刚刚确认过的方案。你要是盯着完整会话记录看,还会发现它的“记忆”其实一直都在,只是它自己“想不起来”了。这就是典型的“上下文失忆”:不是模型变笨了,而是我们给它的上下文组织方式出了问题。
这篇文章就围绕一件事展开:在百万行规模的代码仓库里,怎么用“上下文工程”的思路,让你的编程 Agent 从容易失忆变成稳定输出。我会讲清楚失忆的底层原因、上下文预算怎么分配、仓库级裁剪和检索的具体做法、以及我自己在实操中踩过的一堆坑。不管你是刚接触 AI 辅助编程,还是已经在调自己的自动化编码流程,这篇文章应该都能给你一套能直接上手的方法。
1. 先搞明白 Agent 为什么会“跑着跑着就失忆”
1.1 三种“内存”互相挤占,注意力却只有一扇小窗
很多人把大语言模型的“上下文窗口”理解成一块可以随便装东西的硬盘:只要没超出 token 上限,信息放进去就能随时取用。但这个理解偏差恰恰是上下文工程的起点——上下文窗口更像是工作台,不是仓库。模型在做推理时,真正能同时“聚焦”的内容非常有限,哪怕窗口里塞了 20 万 token,它实际处理时也是按注意力机制扫描,而注意力天然偏向段落开头和结尾,中间一大段信息很容易变成“看过了但没过脑子”。
编程 Agent 在改代码的时候,身上同时背着三类信息:一是系统提示词和任务背景,这是你最希望它永远不忘的;二是它自己一路操作下来的“近期历史”,包括读过的文件、执行过的命令、改过的 diff,这是它的工作记忆;三是为了理解当前需求而临时检索进来的代码片段和文档,这是临时记忆。三种信息在同一个窗口里抢位置,而 Agent 每一步动作都在往窗口里追加新内容。追加得多了,早期的重要内容就可能被“挤”出有效的注意力范围,甚至在超出长度上限之后被直接截断。
这还不算最麻烦的。更隐蔽的问题是“累计误差”:Agent 每完成一步,会把对现状的理解浓缩成一段内部状态(或显式摘要)带到下一步。如果这段浓缩本身就有偏差,下一步就是在这个有偏差的基础上继续思考,于是偏差越滚越大。我见过一个案例,Agent 在一次重构任务里把“旧接口已废弃,新接口在另一个包”这个事实写错了,之后它围绕这个错误事实展开了二十多轮讨论,每一轮都言之凿凿,但方向整体跑偏。这不是偶然事件,只要上下文组织不做干预,这种路径依赖几乎必然发生。
1.2 百万行仓库为什么尤其致命
仓库规模一大,上下文失忆的问题会成倍放大。首先,百万行级别的代码意味着你根本不可能把“全部相关内容”塞进窗口。一个稍微复杂的服务模块,连带配置、测试、生成文件、依赖声明,可能就有十几万 token;而整个仓库动辄几千万 token。任何想通过“喂得多”来解决问题的方式,在规模面前都是死路。
其次,大型仓库里代码之间的依赖关系极其复杂。一个函数可能在十几个文件里被调用,一个接口的契约散落在定义处、实现处、测试 mock 和三份文档里。Agent 要真正理解一次改动的影响面,往往需要同时看到五六个分散位置的代码。但上下文窗口不会自动为你拉齐这些位置,它只忠实地保存你放入的内容。如果你没有一套机制去“按需抽取”,Agent 就只能依赖它自己随机读取到的片段,自然容易漏掉关键约束。
另一个常常被忽视的因素是“Lost in the Middle”效应。大量实验已经证明,语言模型对输入序列中段内容的记忆和利用效果明显弱于开头和结尾。在编程场景里,这个效应会被代码本身的重复性放大:如果上下文中段塞了十几段相似度很高的工具函数代码,模型很难区分哪一段才是当前任务真正要改的目标。这就好比你在一个堆满相似文件的房间里找一份特定合同,光靠眼睛扫,很容易被干扰项带偏。
所以,上下文工程的核心不是“扩大窗口”,而是“组织注意力”:在有限的窗口里,用合理的结构让 Agent 始终能看见最重要、最相关、最实时的信息,把不重要的东西从工作台上拿走,放到它需要时能自己找到的地方去。
2. 上下文工程:与其硬记,不如做取舍
2.1 一个比喻:工作台、书架和笔记本
我在跟朋友解释上下文工程时,常拿一个程序员的真实工作台做类比。
你把 Agent 的上下文窗口想象成一张工作台面。台面只有那么大,上面放着当前正在改的文件、最近的命令输出、任务清单。你不可能把整个书架上的书都摊开放在台面上,那样的话真正要用的图纸反而找不到了。你需要的是:台面上放一份“索引卡片”,写着某某模块在哪本书的哪一章;需要用某个函数时,再去书架对应位置抽出那几页,看完放回去;同时有一个笔记本,记录你已经做过什么、确认过什么、改过哪里。这就是上下文工程的三个核心组件:
- 工作台内容(当前上下文):只放和当前几步操作强相关的片段;
- 书架(可检索的仓库级索引):代码全文放在仓库里,通过检索按需取出;
- 笔记本(会话内的摘要与决策记录):每一步操作后把状态压缩成结构化笔记。
很多 Agent 工作流的问题就在于:把整个书架往台面上堆。而正确的做法是建立一套“工作时取用、完成后归档”的机制。
2.2 静态清单与动态抽取的组合
落到具体设计上,上下文供给可以拆成两条线:静态清单和动态抽取。
静态清单是一次性生成、低频更新的仓库背景知识。它不需要精确到每一行代码,而是描述整个仓库怎么组织、每个模块的职责边界、关键的架构约束、常用的命名风格和构建命令。它解决的问题是“这个仓库大概是什么样”,让 Agent 从第一步就有基本的方向感。对百万行仓库来说,这份清单通常控制在 2000-4000 token 左右,相当于一份高度浓缩的 README+架构文档。
动态抽取则是在任务执行过程中,根据 Agent 当前的目标临时拉取内容。最典型的情况是:它要修改某个函数,那就把该函数的完整实现、它的直接调用方、它依赖的工具函数、对应的测试文件片段一起抽进来。动态抽取的粒度更小、更聚焦,目标是把“工作台上摆的东西”始终保持在和“当前这一步”强相关。
静态清单负责“方向”,动态抽取负责“聚焦”,两者结合才能让 Agent 的注意力始终花在刀刃上。如果只有静态清单,Agent 会在具体实现细节上反复试错;如果只有动态抽取,没有全局地图,它又容易在复杂的依赖网络里迷路。
2.3 把上下文当成一个预算来管理
既然窗口有限,就必须做预算。我习惯把一次编程任务的上下文分成几块,给每块设一个大致比例:
| 内容类别 | 预算占比 | 说明 |
|---|---|---|
| 任务描述与验收标准 | 10%-15% | 让 Agent 始终记得要做什么、怎么样算完成 |
| 仓库背景清单 | 20%-25% | 模块地图、架构约束、关键路径说明 |
| 近期操作记录 | 10%-15% | 最近几步做了哪些改动、验证结果如何 |
| 目标文件片段 | 30%-40% | 当前正在改/正在读的高相关代码 |
| 工具输出与命令结果 | 10%-15% | 最近的编译报错、测试输出、运行日志 |
这个比例不绝对,但它有一个重要的指导意义:目标文件片段永远是大头,任务描述永远不能丢,而仓库背景不是越多越好,一旦超过 25% 就会变成负担。
预算控制的实际操作是:当一段内容完成使命后,主动把它“降级”。比如 Agent 已经读完了某个工具函数的实现、也确认了它的行为,那这段实现就没有必要继续放在完整上下文里,应该压缩成一句话摘要:“utils/date.ts 中的 parseDate 已确认支持 ISO 格式输入”。这样既保留了关键事实,又释放了大量 token。这就像工作台上看完的图纸,不必一直摊着,在笔记本上记一句“这张图我已经看过了,关键尺寸是多少”就够了。
3. 实操:给 Agent 做一次仓库级上下文裁剪
说完了原理,下面进入可以照做的部分。我以一套我自己在用的通用方案为例,这套方案不绑定特定工具,你用的是商业版编程助手也好、开源 Agent 框架也好,思路都可以平移。
3.1 第一步:生成“仓库地图”,而不是搬运仓库
很多工作流最大的败笔,是让 Agent 自己读一遍仓库目录树,然后任由它逐个点开文件看。这在几百文件的项目里还能勉强跑,在百万行仓库里就是灾难——它会花大量的 token 在不相关的文件上,还没碰到真正要改的地方,就已经忘光了背景。
我建议的做法是:预先生成一份“仓库地图”文档,放在 Agent 每次会话开始时必读的位置。这份地图不是简单的 tree 输出,而是带注释的索引,格式大致像这样:
# 仓库地图(自动生成,每日更新) ## 服务层 - apps/api/:主服务入口,路由定义在 src/routes/ - modules/user/:用户模块,核心服务 UserService(src/services/user.service.ts) - modules/order/:订单模块,核心服务 OrderService(src/services/order.service.ts) - 用户模块依赖 order 模块的 QuoteService(跨模块调用需注意循环依赖) ## 数据层 - prisma/schema.prisma:所有数据库模型定义,修改表结构必须同步更新 - repositories/:数据访问层,禁止在 service 层直接写 SQL ## 构建与测试 - pnpm build 构建,jest 跑测试 - 新增文件后需要更新 barrel 文件(src/index.ts)关键点有三个:一是只写“重要知识”,不写文件清单;二是写清楚模块之间的依赖关系,尤其是那些容易让 Agent 误判的跨模块引用;三是明确“雷区”,比如哪些操作被禁止、哪些改动需要同步改另一处。这份地图控制在 200-300 行 markdown 内,token 占比很小,但能让 Agent 从第一步就不跑偏。
我自己是用脚本自动生成这份地图的:扫描目录结构、解析关键文件的头部注释和导出符号、读取构建配置里声明的命令,再人工补充一些架构决策说明。脚本的输出会带上生成日期,方便我知道它何时过期。
3.2 第二步:搭一个轻量级“代码检索书架”
地图负责全局方向,但 Agent 在干活时还是会需要看具体代码。这时候不能靠它自己漫无目的地翻文件,要给它一个可以精确取书的“书架”。所谓书架,就是一套代码检索服务:给定一个关键词或一句语义描述,返回仓库里最相关的文件路径和代码片段。
完整上 RAG 对很多团队来说太重了,我分享一套轻量做法,用关键词索引+简单的向量检索就能覆盖大部分需求。具体分三步:
第一步,离线建立仓库的代码块索引。把每个源文件按函数、类、方法切分成代码块,每个块记录文件路径、起止行号、包含的符号名。这一步用正则加基础的语法解析就能完成,不一定非得上完整 AST。如果你用的语言有现成的 tree-sitter 解析器,效果会更好。
第二步,给每个代码块生成一个可搜索的“关键字集”。除了块内的所有标识符,还可以把注释里的自然语言也加进去。这样当 Agent 想找“处理超时的逻辑”时,它能通过注释关键词定位到具体函数。
第三步,日常使用时,把 Agent 的“找代码”步骤显式改造成“检索后取关键片段”。不要直接把检索结果一股脑塞进上下文,而是只截取评分最高的 3-5 个代码块、每个块截取关键行,再拼成一段带有文件路径和行号上下文的“索引片段”。
这套轻量书架的搭建成本其实不高,我见过有人用几千行脚本加一个嵌入式向量库就跑起来了。如果你的 Agent 框架支持自定义工具调用,完全可以把它封装成一个search_code(query)的工具函数,Agent 需要时就自己调,不需要时它只是个备用书架,不会占用工作台空间。
3.3 第三步:把“动态抽取”做成显式的上下文组装流程
有了地图和书架,还差最后一环:怎么在任务执行过程中,把相关内容动态组装到上下文里。这一步直接决定 Agent 是“按需取用”还是“有什么看什么”。
我在实践里用的是很朴素的“计划-取用-执行-记录”循环。每一轮操作开始前,先让 Agent 输出一个简短计划,说明它下一步要做什么、要看哪些文件。然后由外层脚本拦截这个计划,调用书架把涉及的文件块抽进来,拼成一个结构化的“当前工作区”片段,再交给 Agent 执行。执行完成后,记录操作结果,并生成或更新一条摘要笔记。
举个例子,假设任务是把某个订单模块的折扣计算从固定折扣改成阶梯折扣。典型的一轮上下文组装大概是这样的:
## 当前任务 将 OrderService.calculateDiscount 改为阶梯折扣 验收标准:订单金额超过1000元时额外5%折扣;已有测试需更新 ## 当前目标文件 文件:modules/order/src/services/order.service.ts 符号:calculateDiscount(第182-210行) 相关调用方:modules/order/src/controllers/order.controller.ts(第88-96行) 折扣配置读取:modules/order/src/config/discount.config.ts ## 目标代码片段 (此处放 calculateDiscount 完整实现,以及调用它的 controller 片段) ## 最近操作记录 - 已阅读 discount.config.ts,发现当前折扣率从配置文件读取 - 已确认测试文件位于 modules/order/tests/discount.spec.ts这个结构的好处是:Agent 每次读到的都是当前最相关信息的紧凑集合,且有明确的文件路径和行号锚点,它知道自己正在看什么、在哪里看。相比一个人把整段对话历史原样传给模型,这种显式组装能显著减少“翻不到关键信息”的问题。
值得注意的是,动态抽取一定要带“锚点信息”。我见过一些方案直接塞代码块而不告诉 Agent 文件和行号,结果 Agent 虽然“看到”了代码,却不知道去哪改,或者改了之后在 diff 里定位错误。路径和行号就是上下文里的地址信息,没有地址的内容就是一堆无主信息。
3.4 第四步:让 Agent 学会写“笔记”,而不是依赖对话历史
最后一步可能是整个上下文工程里最容易被忽略、但见效最明显的:给 Agent 建立“会话内笔记”机制。
大多数 Agent 工作流都是纯对话式——所有信息都在连续的对话历史里。一旦上下文增长,前面的内容就被压缩或遗忘,没有中间态。我的做法是在对话之外维护一个笔记块,每一轮操作结束时,把这一轮的关键结论和当前进度写进去。这个笔记块在每一轮对话开始时作为固定前置内容注入,代替越来越长的对话历史作为事实来源。
笔记块的写法要精炼,只记录四件事:一是“已经确认的事实”(比如某个接口的入参定义、某个函数的副作用);二是“已经完成的步骤”(如已修改某文件某行、已跑过某测试);三是“当前未解决的问题”(如测试失败原因待查);四是“下一步计划”。控制在一百行以内。
这里有个小心得:笔记是给 Agent 看的,不是给人看的。所以不需要写完整句子,可以写成“order.controller:88 已改调用参数;等待格式化为单引号”这样的碎片风格。只要 Agent 自己能读懂即可。这个习惯一旦养成,Agent 的稳定性提升非常明显,因为它不再需要从冗长的历史里“回忆”之前干了什么,而是直接“读”笔记。
4. 工具选型与计算:配置细节怎么做才合理
上下文工程不只是方法和流程,还有一些具体的参数和选型问题。这节把我自己试过的一些配置细节整理出来,供你参考。
4.1 三个关键参数:块大小、重叠率、检索数量
如果你用向量检索来做代码书架,会遇到三个绕不开的参数:chunk_size(块大小)、overlap(重叠率)、top_k(返回数量)。我在这三个参数上调过不少次,简单说说经验。
块大小建议按函数级别切分,而不是按固定行数切分。固定行数切分很容易把一个函数拦腰截断,导致检索到的代码块不完整,Agent 看到个残片反而容易产生错误理解。函数级别切分之后,大函数的块自然大,小函数的块自然小,整体更适合编程场景。如果你的解析工具做不到函数级切分,那退而求其次用固定块大小时,我建议单块控制在 100 行以内,并且必须带行号前缀。
重叠率这个参数在代码场景里其实没那么关键。因为代码块之间不是自然语言那种“上下文连续性”的关系,函数之间是跳转关系,靠重叠来弥补衔接意义不大。真正有用的是给每个代码块附加它的“邻居信息”:比如当前函数的调用方、被调方、同级函数列表。这样即使切断了物理位置,逻辑上下文也还在。如果向量检索工具不支持这个,你可以在代码块内容里额外拼一行注释:
# [neighbors: order.service.ts 的 calculateDiscount 调用了 getDiscountRate;被 order.controller.ts:88 调用]检索数量 top_k 同样不宜设置太大。我实践中的经验值是 3-5 个块。很多人以为多返回几个块可以让 Agent 有更多选择,实际结果是它容易被不相关块带偏。top_k 的本质是“最高置信的少数几个证据”,而不是“尽可能多的候选”。一次组装上下文时,代码源信息能提供 1-3 个文件、各取核心片段,通常就足够支撑一轮高精度操作。
4.2 检索结果的排序:相关性和“新鲜度”要兼顾
在持续多轮的编码任务里,代码检索结果不是一次性用完就扔的。Agent 可能改完一个函数后还要去改它的调用方,这时候它需要重新检索,而这个“重新检索”最好能带上时态信息。
我这里说的是一个很容易踩的坑:代码库在 Agent 的工作过程中可能发生了变化(因为 Agent 自己正在改它)。如果你给它的检索结果是改动前的旧代码,它就会基于错误现状继续工作。解决方法是让检索系统维护一个“已修改文件列表”,当检索命中的文件在这个列表里时,返回结果里明确标注“该文件已被本次任务修改,以下为当前磁盘内容”。至少做到让 Agent 知道哪些代码是旧版本、哪些是它自己刚改过的。
更完整的方案是引入简单的文件版本指纹——比如对每个文件算一个 hash。在每次组装上下文时,对比指纹,如果有变化就说明 Agent 的笔记里关于该文件的结论可能与当前磁盘不一致,此时强制要求 Agent 重新读取而不是依赖旧摘要。这个做法能有效避免“自信地错”的情况。
4.3 窗口参数的现实选择:用多档位,不要迷信最大窗口
关于上下文窗口大小,市场上各家模型的参数不同,但我建议不要一味追求“最大窗口”。窗口越大,你以为能塞的内容越多,实际注意力稀释也越严重,而且成本和时间开销跟着涨。更合理的做法是设三个档位:日常档(比如 8k-16k token)、扩展档(32k-64k,用于涉及多文件的大重构)、极简档(4k 以下,用于单点小改动)。让 Agent 在任务开始前判断任务的复杂度,选对应的档位。这比“所有任务都挂个大窗口”要高效得多。
我自己实践时甚至会把“档位选择”的决定权交还给流程而不是模型:简单修个 bug 就是极简档,涉及跨模块接口变更就是扩展档。这样 Agent 的开销可控,注意力也不会因为无谓的长上下文而下降。
5. 常见问题与排查技巧实录
上下文工程不是配完就跑得通的事,我调这套东西的过程中踩了不少坑。下面挑几个最典型的,附上排查思路和解决经验。
5.1 Agent 忽然开始“胡言乱语”,行为前后矛盾
现象:任务跑到一半,Agent 突然提出一个明显与前面步骤矛盾的方案,比如它前面刚确认要删除某段逻辑,后面又在分析这段逻辑怎么优化。
排查思路:这通常是上下文里塞入了互相矛盾的内容。最常见的原因是笔记块与对话历史里某段早期结论冲突。这时去看注入的笔记是否已经更新、对话历史里是否有 Agent 自己后来修正的结论。如果笔记里写的是早期错误结论,而后面的对话其实已经修正了,那就是笔记同步机制出了问题。
我的解决方法是:在每轮操作结束时强制产生一条“本轮结论”,并让这条结论覆盖笔记里的同名条目,而不是简单追加。也就是说,笔记不是流水账,而是“当前最优事实集合”。举例来说,如果前面记了“接口参数为 id: string”,后来发现实际是“id: number”,笔记里该条目应该被替换而不是新增一行。
5.2 检索总是找不到真正要改的代码
现象:Agent 想找“订单过期自动关闭的逻辑”,但检索返回的全是订单创建的代码,每次都差那么一层。
排查思路:代码检索最怕“口头表达”和“代码命名”脱节。业务概念“过期关闭”在代码里可能叫 expireJob、closeExpiredOrders、或者 scheduler 里的一个 cron 任务。关键词检索引擎往往无法建立这种概念映射。解决方案是加强离线索引的“概念别名表”:在生成代码块索引时,把代码块邻近的注释、JSDoc、需求文档关键词一并抽取进来,作为该块的别名。更进一步的方案是人工维护一份“业务概念到代码入口的映射”放在仓库地图里,让 Agent 的搜索请求先经过地图做一次路由,再去书架检索。
我自己的经验是:加大检索结果里“入口文件”的权重。很多大型仓库里,真正能改的东西集中在少数几个 service/action 文件里,而工具函数只是被调用的。把入口文件的高层职责写清楚放到地图里,比让 Agent 靠检索直接命中深层实现要可靠得多。
5.3 摘要缓存过期,Agent 基于旧信息做决策
现象:Agent 引用某个函数时的说法还是旧版本行为,但代码其实早被改过(甚至是它自己改的),导致后续步骤基于过时信息。
排查思路:这是“笔记机制”与“文件变更”脱节的典型症状。当 Agent 的笔记缓存了某个文件的旧结论,而磁盘上的文件已经变化,它就会带着旧眼镜看新问题。我后来引入了一个强制规则:文件哈希变化时,笔记中对该文件的结论一律作废,并在下一轮上下文组装时标记“该文件已变化,需重新确认再继续”。这不需要复杂的 diff 分析,只要知道你“看过的是旧版本”就够了。
更底层的建议是:不要让笔记里保存太多“实现细节结论”,而是多保存“已验证的路径和结果”。比如“已运行测试:discount.spec.ts 3 个用例全过”是可靠的;“calculateDiscount 的逻辑是 A”是不可靠的,因为逻辑随时会变。笔记里多留操作记录、少留事实断言,能减少很多过期问题。
5.4 上下文还是不够用,怎么办
现象:一个超大任务,涉及十几个文件,再怎么裁剪,上下文还是快要爆了。
排查思路:这时候不要想着优化“压缩率”,而是想“拆分任务”。把一个大任务拆成几个阶段,每个阶段有独立的输入输出,前一个阶段的结论以摘要形式传到下一个阶段。这就是把“一个大上下文”变成“几个串联的小上下文”。我一般会在任务开始时让 Agent 先输出一个拆解计划,标记出哪些步骤可以单独成为一个可验证的里程碑。
另外,还要敢于“丢弃”上下文。有些 Agent 框架支持把某个时间点之前的历史压缩成一行摘要,而不是原样保留。这种“截断+摘要”的组合比“全量保留”更稳。记住:工作台不是保险柜,不需要存档所有历史,只需要保留能支撑下一步行动的信息。
6. 几个让我少走弯路的实用建议
最后分享几条我在实际项目中沉淀下来的小经验,它们不一定写在任何工具文档里,但真的能救命。
第一,尽量给 Agent 提供“执行环境反馈”,而不是只给静态代码。上下文工程不只是“往窗口里塞什么”,还包括“Agent 能不能快速看到自己改动后的结果”。同样是改一个函数,如果 Agent 每改完就能看到类型检查或单测结果,它就能立刻在上下文中完成“试错-修正”的闭环;如果它每次都要等十几秒才能得到反馈,那它就只能靠猜,猜的过程就会产生大量无效上下文占用。我强烈建议把“编译/测试/静态检查”的快速反馈路径纳入上下文设计的一环。
第二,对话历史的“第一页”值得特别设计。很多 Agent 在长对话里失忆,不是因为模型差,而是最初的关键信息被层层叠叠的中间过程冲掉了。我解决的办法是把最重要的系统约束写在系统提示词最前面,同时要求 Agent 每十轮左右主动“回顾初始任务描述”。你可以理解为给 Agent 一个“锚点重置”的习惯,防止它越走越偏。
第三,记录“失败路径”比记录“成功路径”更有价值。笔记里除了写“已完成什么”,还要写“试过什么方案但失败了、原因是什么”。这个信息能防止 Agent 在后续步骤里重新踩进同一个坑。我见过很多次 Agent 用三种不同方式攻击同一个问题,只因为它的笔记里没有记录前两次失败的教训。把失败原因压缩成一句话放进笔记,往往能省下几十轮无效操作。
第四,区块化的上下文设计要预留“接口位”。意思是,你每次组装上下文时,应该让每个区块都像函数一样有一个清晰的边界和入口。Agent 看到“当前目标文件”这个区块时,应该立即能判断这个区块内容是给它直接修改用的,还是仅供阅读用的。我在实际使用中发现,Agent 不擅长从混合区块里自动区分“可改”和“只读”,如果能在区块标题里直接写明“本文件可直接修改”或“本文件只读参考”,它的行为会稳健很多。
我调上下文工程这套方法,有一个很深的体会:编程 Agent 的能力边界,其实一开始就藏在上下文里。你给它的上下文是垃圾堆,它就给你垃圾堆一样的代码;你给它的上下文是干净整洁的工作台、书架和笔记本,它就能持续稳定地输出好东西。百万行仓库不可怕,可怕的是我们把所有重担都压到模型那扇狭窄的“注意力窗口”上。把上下文当成工程来做,很多“失忆”问题,其实在进入对话之前就已经能解决一大半了。