最近几个月我一直在做AI编码代理相关的项目,要说最折磨人的,不是模型选型,不是prompt怎么写,而是上下文管理。一个再聪明的模型,上下文一旦爆掉,照样把你三小时前拍板的架构方案忘得一干二净,甚至能把刚改好的代码静默退回第一版。我前前后后折腾了不少方案,从最早的硬拼聊天记录,到给ChatMemory配滑动窗口,再到把Context-mode MCP接进协议层做上下文优化,整个链路跑通之后,效果是肉眼可见的改善。这篇我把整个上下文工程的实战经验完整摊开,包括窗口参数怎么定、摘要模板怎么设计、MCP模式怎么配、踩过哪些坑,都给整理出来。不管你是用Cursor、Codex还是其他编码代理,这套思路基本都能直接套。
1. 为什么上下文管理成了AI编码代理的"命门"
1.1 编码代理和单轮对话的本质差别
普通聊天机器人是问一答一,上下文丢了影响不大,用户重发一遍问题就能补救。编码代理完全不是这么回事,它是一个"多步骤、带状态、需要持续记忆"的系统:读代码、改文件、跑测试、修bug、再回归,每个环节都依赖前面环节留下的判断。你可以把它想象成一个刚入职的实习生,如果这位实习生每次离开工位就失忆,你上午交代的五件事他下午只能记住两件,那这个团队就别想推进了。模型能力再强,在"记忆不稳"的前提下也会频繁做出错误决策,而且因为它在代码里做的修改是有破坏性的,这种错误的影响比普通对话大得多。
上下文工程要解决的就是这个矛盾:在token预算有限、注意力随长度衰减的条件下,保证代理始终能把"当前最重要的信息"放在最容易取用的位置。这里的约束条件很具体:一是token长度上限是硬性的,到了上限要么截断要么报错;二是注意力质量并不均匀,同一个窗口里前面的内容经常会被后面冲刷掉;三是上下文切换存在时间开销,每次重新加载或摘要都要额外消耗token和计算时间。理解了这三个约束,你就能明白为什么"把历史全塞进去"这种朴素方案一定会翻车。
1.2 上下文失控的典型事故现场
我先列几个自己实际遇到过的事故,都很有代表性。第一个,让代理重构一个支付模块,前30轮它都记得要保留对外接口兼容性,结果第31轮我让它"顺手优化一下日志输出",它直接把接口签名改了,下游三个服务全部编译失败。第二个,项目里有两个配置常量名字很像,代理在第七轮之后开始混淆,把我指定的"生产环境配置"写到了"本地调试配置"的初始化逻辑里,线上环境差点出事。第三个,一次超长会话中,代理忘了最初的技术选型约束——明确说过不要引入重量级ORM,结果它在会话后期自作主张引入了,重构完之后整个项目的启动时间翻了三倍。
这些事故放在一起看,原因高度一致:关键信息没有在上下文里被"锚定",被后续大量对话冲刷掉了。不是模型变笨了,而是有效信息在上下文中的密度太低、位置太靠后。由此可以引出上下文工程真正要回答的四个问题:什么信息必须常驻?什么信息可以被压缩?什么信息可以淘汰?压缩和淘汰的时机怎么定?这四个问题,后面每一章都在围绕它们展开。
1.3 上下文工程的三个核心衡量维度
做了这么多轮实验之后,我习惯用三个维度去衡量一个上下文方案到底好不好。
- 保真度:用户明确说过的硬约束、代理自己做过的关键决策,在任意时刻能不能被准确回忆起来。这是最重要的维度,也是大多数方案最先丢分的维度。
- 密度:单位token内携带多少有效决策信息。同样的上下文预算,有些人塞进去的是"我们讨论了登录流程,决定后续再做权限优化"这种废话,有些人塞进去的是"登录流程已定稿,接口路径/auth/login,下一步实现RBAC"这种高密度记录,两者的效果天差地别。
- 新鲜度:当前文件内容、最近一次测试结果、最新反馈这些状态信息,能不能快速占据上下文中的有效位置。旧信息占着位置,新信息进不来,代理就会基于过期状态做判断。
滑动窗口解决的是"如何淘汰旧信息"的问题,ChatMemory在这个窗口里进一步解决"哪些信息值得保留、怎么压缩"的问题,Context-mode MCP则把上下文的分发与订阅下沉到协议层,让代理工具可以按需拉取权威信息。这三层不是竞争关系,而是层层递进的关系。
2. ChatMemory与滑动窗口:最朴素的上下文管理方案
2.1 滑动窗口:从网络协议借来的通用思想
滑动窗口这个词在不同领域有不同的具体含义。网络工程师熟悉TCP的滑动窗口,它控制发送速率和流量,重传协议里的窗口决定哪些数据包可以发、哪些要等待确认。做信号处理的知道滑动窗口滤波,拿一个固定尺寸的核在信号上平移取平均。这些方案共享同一个思想:数据流无限长,但我用一个固定大小的窗口,只对窗口里的有限数据做处理,处理完再整体平移。
LLM上下文里的滑动窗口也是这个思路:只保留最近N条消息或最近M个token,超出范围的历史直接丢弃或者压缩。它的实现非常朴素,甚至可以简单到"截断字符串",但在很多场景下非常有效。ChatMemory可以看作是在滑动窗口这个框架上做了一层记忆分层管理,通常分为三个区域。
短期记忆区保留最近几轮对话的原始内容,保真度最高;中期记忆区存放已经折叠的阶段性摘要,由代理在某些触发点自己生成;长期记忆区存放跨会话的偏好、架构决策、项目约束,一般以结构化文档或索引库的形式存在,按需拉取而不是常驻窗口。这个分层的直观类比是人的记忆:你不可能记住和同事说的每一句话,但你会记住"项目截止日期"和"对方讨厌被临时改需求"这两件重要的事。
2.2 ChatMemory设计核心:先记什么、忘什么、怎么压缩
我在配置ChatMemory时,踩过最深的坑是:一上来就调窗口大小,结果窗口开得再大,关键信息该丢还是丢。后来我把顺序反过来,先定义"什么信息绝对不能丢",再决定窗口和压缩策略。我的必保留清单是这样的:
- 项目根目录结构,尤其是模块划分和依赖关系。
- 当前正在修改的文件全貌,注意是全貌,不是diff摘要。
- 用户明确说过的硬约束,比如技术栈、兼容性要求、代码风格。
- 最近一次测试或构建的结果,包括错误信息全文。
- 代理自己立下的行动flag,比如"接下来我要先处理数据库连接问题"。
优先级确定之后,再按这个顺序处理窗口内的数据:最近K轮原始消息完整保留;K轮之前但仍在窗口内的内容按"决策链"压缩成要点;窗口外且没有进入长期记忆的内容直接淘汰。压缩这一步是ChatMemory质量的分水岭。我试过让模型自由发挥来写摘要,结果它写出来的是"我们讨论了项目结构,决定进行重构"这种毫无营养的空话。后来我改用强制模板,摘要里必须包含当前目标、已完成、进行中、阻塞项、关键决策、下一条行动这六个字段,质量立刻上来了。
注意:摘要模板里的每个字段都要写清楚"允许为空"还是"必须填充"。比如"阻塞项"如果没有就填"无",不要让模型为了填满字段而编造内容,"编造阻塞项"比"没有阻塞项"对代理的误导更大。
2.3 滑动窗口的三个关键参数怎么配
这里我给出一组我自己用过且效果稳定的参数基线,但你要清楚这只是一个起点,具体情况要按模型和项目规模调整。
- 窗口大小(token数):我建议设为模型上下文上限的60%-70%。模型声明128K,窗口就设在80K到96K之间,留出余量给工具返回结果、临时推导和MCP增量数据。如果按满上限来设窗口,一旦出现突发的大查询,整个会话直接报错。
- 保留完整轮数K:通常是6-10轮。太少,代理记不住刚交代的细节;太多,原始消息会占据大量token,压缩的意义就没了一半。
- 摘要触发阈值:当原始消息占用窗口的50%时,触发第一轮摘要,把排名靠后的中期记忆区内容压缩;占用70%时,触发第二轮摘要并强制淘汰窗口外内容。这个双阈值机制比单阈值稳定得多,因为它给了系统"缓压缩"和"强压缩"两档操作,避免在边界上来回抖动。
参数配置完之后,我建议你做的第一件事不是直接投入项目,而是跑一轮"记忆回放测试":让代理试着复述项目最初的三个硬约束,再往里塞50轮无关对话,再问一次同样的问题。如果第二次复述丢内容,说明窗口参数或者摘要模板还有问题,提前修好,别等到真实项目里翻车。
2.4 单纯滑动窗口方案的三个短板
滑动窗口虽然简单有效,但我用了两个月后发现它有三个补不上的短板。
第一个短板,窗口外的硬约束照样丢。如果一条关键需求在第20轮被挤出窗口,即使它是用户反复强调的,代理也会在窗口外把它忘干净。窗口机制天然是"最近优先"的,它不区分"刚说的闲话"和"三小时前的关键决策"。
第二个短板,摘要质量不可控。摘要生成时机不同、生成时模型的注意力分布不同,会导致同一个项目在不同会话里"记忆"质量差异很大,有时候连项目的核心模块名都会记错。
第三个短板是结构性的:应用层各管各的,聊天窗口占一块上下文,文件加载占一块,工具返回占一块,没有统一的调度机制,多个来源争抢同一个有限的上下文空间,最后谁也保不住。
这三个短板指向同一个结论:上下文管理不能只靠应用层的"窗口",还需要一个更底层的机制来统一分发、按需加载、增量同步。这正好是Context-mode MCP想补上的缺口。
3. Context-mode MCP:把上下文管理下沉到协议层
3.1 先搞清楚MCP到底是什么协议
MCP全称是Model Context Protocol,直译过来就是"模型上下文协议",它是一个软件协议,不是硬件协议。这个区别很关键:硬件协议解决的是物理设备之间怎么通信,比如USB、HDMI;软件协议解决的是程序之间怎么约定通信格式,比如HTTP、JSON-RPC。MCP属于后者,它定义的是LLM应用与外部工具、数据源之间如何交换上下文和调用能力。
打个比方就清楚了:HTTP是浏览器和Web服务器之间的通用语言,没有HTTP,每个网站都需要给浏览器写专属插件。MCP就是AI应用和工具之间的通用语言,有了它,Cursor、Codex、Trae IDE这类客户端就不用为每个工具写一套专属对接逻辑,工具开发者也不用为每个客户端各做一个SDK。在MCP模型里,最典型的存在形式是一个MCP server,它对外暴露工具、资源和上下文模式,编码代理通过MCP协议去调用。
3.2 Context-mode的理念:从"填鸭"到"订阅"
先看普通MCP的工作方式:客户端发起一次调用,服务端返回结果,一次一清。这对单个工具调用来说没问题,但对上下文工程来说不够。原因是代理在整个项目生命周期里的需求是动态变化的:启动阶段需要项目骨架和依赖清单,改代码阶段需要目标文件和引用关系,调试阶段需要测试日志和错误栈,而这三类信息如果全部常驻上下文,token预算早就爆了。
Context-mode的思路是把"上下文获取"从一次性请求变成"模式订阅":代理先告诉MCP服务端"我现在处于什么阶段、需要什么上下文",服务端按模式打包返回,并在内容变更时主动推送增量。实际落地时,我通常会把模式分成四类:
- 项目骨架模式:返回目录树、模块清单、依赖关系图。
- 文件编辑模式:聚焦当前文件、相关引用和最近变更的diff。
- 构建调试模式:加载最近的测试输出、错误日志、构建产物状态。
- 架构决策模式:加载历史决策记录和技术约束列表。
这个设计的价值在于上下文从"被动填鸭"变成"主动订阅",代理知道自己当前在做什么阶段,就去拉取对应的权威信息,而不是把所有东西一股脑塞进对话。就像开车时你不会全程盯着发动机转速表,而是在切换到运动模式时才关注换挡逻辑。每一次模式切换,都是把有限的注意力预算花在刀刃上。
3.3 Context-mode核心能力拆解
Context-mode MCP落到实现层面,我理解其实包含四个核心机制,任何一个环节做不好,效果都会打折扣。
- 上下文注册中心:由MCP服务端登记可用的上下文模式,标注名称、内容范围、更新频率和优先级。代理发起订阅时以注册中心为准,而不是靠prompt里写死的路径。
- 模式路由:代理根据当前任务状态选择激活哪些模式。这一步需要客户端提供任务状态的判断逻辑,最简单的是让代理显式声明"我现在要改文件了",也可以由工具自动识别。
- 增量传输:只传输与当前模式相关的增量内容。比如文件编辑模式下,只推送变更部分而不是整个文件,服务端维护一份文件状态快照,客户端每次拿diff。
- 缓存与失效:模式内容可以缓存,文件变更时按路径失效重载。增量传输的前提是缓存可靠,如果失效机制做得不好,代理会用上同一份文件的旧内容,比不缓存还危险。
这四个机制恰好能补上滑动窗口的三大短板:硬约束可以被固化成"架构决策模式",不会因为对话轮次被挤出窗口;摘要质量可以被"模式描述"约束,摘要里的关键信息永远引用权威来源;不同来源的上下文统一到协议层调度,聊天记录、文件内容、工具返回不再争抢无序。
3.4 与ChatMemory滑动窗口的协同策略
我自己的观点很明确:不要用Context-mode MCP去替代ChatMemory,两者本来就不在同一层。ChatMemory负责在会话内部管理记忆的存留、排序和淘汰,就像一个项目的资料员,决定哪些文件放桌上、哪些放抽屉、哪些粉碎。Context-mode MCP负责在会话外部管理上下文的注册、分发和订阅,就像一个知识库的借阅系统,决定谁能借走什么、按什么格式借。
两者协同的流程我按这个顺序执行:
- 会话启动时,先通过MCP拉取"项目骨架模式"和"架构决策模式",把长期约束灌进ChatMemory的长期记忆区。
- 会话过程中,ChatMemory负责日常轮次管理,窗口内自由流动,窗口外淘汰。
- 代理开始改文件时,切换到"文件编辑模式",MCP把目标文件的最新增量推入窗口,同时把过期版本标记失效。
- 窗口压力达到阈值时,ChatMemory做摘要,摘要模板里的"关键决策"字段引用MCP模式返回的权威内容,而不是依赖模型自己的记忆。
这套协同执行了一个多月,最直观的感受是:代理在长会话里"改着改着就改回老版本"的毛病几乎绝迹了。因为关键约束在滑动窗口里被淘汰之前,早就被MCP模式固化了,摘要又总是指向权威来源。当然,这种协同也引入了新的复杂度,比如模式路由判断不准时,代理会切错模式,这一点我在后文问题排查里会专门展开。
4. 实战落地:在主流编码代理工具里配置上下文工程
4.1 准备工作
进入实操之前,先确认你手上的基础条件。第一,你的编码代理工具要支持MCP客户端配置。目前主流的几款编辑器/IDE都已经支持,一般通过命令行启动MCP server,或者在配置界面里指定server的启动命令。第二,你要有一个可以承载Context-mode模式的MCP服务端。现在有很多现成实现,但如果你项目特殊,需要自己控制模式注册逻辑,建议直接用Python或TypeScript的MCP SDK搭一个轻量服务端,路由逻辑自己写。第三,给ChatMemory准备一个可持久化的目录,用来存放历史会话的摘要文件、索引文件和长期记忆文档。
推荐的目录结构是这样的:
memory/ ├── sessions/ # 按会话ID存放原始消息和窗口快照 ├── summaries/ # 每轮摘要,按会话ID和时间戳命名 ├── longterm/ # 跨会话的约束、偏好、架构决策文档 └── index.json # 会话索引和当前活跃会话的状态我建议从一开始就按项目分目录,不要所有项目共用一个memory目录,否则长期记忆区会发生严重的信息串台——A项目的技术选型约束被B项目的代理误当成了自己的决策依据。这个坑我亲身踩过,排查起来极其痛苦,因为表面上看会话正常,实际上一连串决策都建立在别人的约束上。
4.2 配置ChatMemory滑动窗口
下面是一份我实际用过的ChatMemory配置示例,关键字段我都加了注释说明。不同工具的配置字段名可能不太一样,但对应的语义是通用的。
{ "chat_memory": { "window_tokens": 96000, "keep_full_rounds": 8, "summary_trigger_ratio": 0.5, "summary_force_ratio": 0.7, "summary_template": "当前目标: {goal}\n已完成: {done}\n进行中: {wip}\n阻塞项: {blockers}\n关键决策: {decisions}\n下一条行动: {next_action}", "longterm_dir": "memory/longterm", "session_dir": "memory/sessions" } }window_tokens设为96000,这是按128K模型留出25%余量算的。keep_full_rounds设为8,在"保留关键细节"和"节省token"之间取了一个折中。summary_trigger_ratio和summary_force_ratio分别对应缓压缩和强压缩的阈值。summary_template是我前文说的六段式强制模板,这里直接把字段写死在配置里,避免模型自由发挥。
配好之后建议跑一次"记忆回放测试"。具体方法:新建一个会话,喂给它三条约定的硬约束,比如"不要引入ORM""支付模块接口必须兼容v1""日志统一走loguru",然后连续塞50轮不相关的开发对话,最后问它"最初的三条约束是什么"。如果它一条不落答出来,说明配置合格;如果丢了,先检查keep_full_rounds是不是太小,再检查summary_template是否被模型忽略了。
4.3 配置Context-mode MCP服务端
MCP server的搭建我以一个最小实现为例,重点说清楚模式注册和增量传输的逻辑。下面是一个简化版的配置和路由代码片段,基于常见的MCP SDK,但把业务逻辑抽象掉了,你可以直接对照改成自己的实现。
from mcp import Server, Tool, Resource server = Server("project_ctx") @server.mode("project_skel") def project_skel_mode(): # 返回目录树、模块清单、依赖关系 return load_project_skeleton() @server.mode("file_edit") def file_edit_mode(path: str): # 返回目标文件最新快照,以及自上次快照以来的diff snapshot = load_snapshot(path) current = read_file(path) diff = compute_diff(snapshot, current) save_snapshot(path, current) return {"current": current, "diff": diff} @server.mode("arch_decision") def arch_decision_mode(): # 返回历史架构决策和技术约束 return load_arch_decision_records() server.register_modes(["project_skel", "file_edit", "build_debug", "arch_decision"]) server.run("ws://localhost:8765/mcp")客户端的核心配置是把这些模式注册进编码代理的可用工具列表,同时声明好每个模式的缓存规则。这里我特别想提醒一个坑:file_edit模式里的快照保存时机。如果每次文件读写都保存快照,diff计算本身会消耗token和算力;如果只在显式调用时保存,又会漏掉外部编辑器对文件的修改。我的做法是订阅文件系统watch事件,外部变更触发快照更新,业务代码只负责读最新快照。
4.4 参数计算示例:从128K模型倒推开
很多人在这一步卡住,不知道怎么定具体参数。我给一个完整的计算链路。假设你的模型上下文上限是128K token:
- 先划出"不可占用区":工具返回、临时推导、系统prompt大约占20%,约25K token。这是保底,不能省。
- 剩下约100K给业务上下文。窗口大小取其70%到80%,我取76K到80K,留一部分给MCP增量推送的突发流量。
- 在窗口内,原始消息完整保留区域控制在窗口的30%,也就是24K左右。按平均每轮消息2K-3K token估算,大约能保留8-10轮,所以keep_full_rounds设8是合理的。
- 剩下的窗口部分作为压缩区,存放各阶段摘要和当前文件快照。当原始消息达到窗口的50%(约40K)时,触发第一轮摘要;达到70%(约56K)时,触发第二轮。
这套计算的核心逻辑是"先留余量,再分区域"。很多人犯的错是先把窗口占满,结果模型真正干活时没有思考空间,token被截断后只能基于残缺上下文硬猜。记住一句话:上下文管理不是把窗口塞满,而是让窗口永远保留处理突发情况的弹性。
5. 实测对比与参数调优记录
5.1 三组对照实验
参数配好之后,我用一个真实的中型项目跑了三组对比实验。项目规模是约120个文件、4个模块,任务统一是"新增一个用户反馈导出功能,需要兼容反馈分页查询接口,并复用已有权限体系"。三组方案分别是:
- 方案A:纯滑动窗口,窗口64K,无摘要模板,无MCP。
- 方案B:滑动窗口+ChatMemory,窗口96K,六段式摘要模板,无MCP。
- 方案C:滑动窗口+ChatMemory+Context-mode MCP,完整方案。
每组都让代理连续工作60轮,然后统计几个指标:硬约束保持率(是否始终记得复用权限体系)、最终代码可编译率、单轮平均耗时。结果如下:
| 指标 | 方案A | 方案B | 方案C |
|---|---|---|---|
| 硬约束保持率 | 41% | 78% | 95% |
| 最终代码可编译率 | 68% | 86% | 97% |
| 单轮平均耗时 | 4.2s | 5.1s | 4.8s |
| 60轮有效产出轮次 | 22 | 37 | 51 |
方案C在硬约束保持率上明显领先,而且单轮耗时并没有因为多了一层MCP调用而变慢——这是因为增量传输之后,模型需要重新推导的次数反而减少了。方案A的问题在于硬约束被当作普通历史消息,窗口一滚动就被丢;方案B改善了摘要质量,但架构决策这类长期约束仍然依赖模型记忆的偶然性;方案C把约束固化成模式内容,每次需要时都从权威来源拉取,稳定性自然上来了。
5.2 调参经验:窗口、阈值、摘要轮次怎么联动
有了实测数据,我再分享几条具体的调参经验。
第一条,窗口大小和摘要轮次是联动关系,不是独立的。窗口加大之后,如果摘要触发阈值不跟着上调,系统会频繁进入"压缩-恢复-再压缩"的循环,每轮都要额外花token重写摘要,实测下来单轮耗时会增加15%以上。正确做法是:窗口调大的同时,把summary_force_ratio从0.7上调到0.75,给原始消息更多回旋空间,减少无效压缩。
第二条,keep_full_rounds不能设太大。我试过设到16,结果原始消息占据了窗口近一半,中期记忆区的摘要被严重挤压,代理反而丢了更多阶段性上下文。对于大多数编码任务,8轮左右是甜点位,既覆盖了"最近两三次修改-验证-修正"的完整闭环,又没牺牲摘要空间。
第三条,Context-mode的缓存失效要舍得"过度失效"。宁可让某个模式的缓存作废频率高一点,也不要让代理用到过期数据。我在file_edit模式上吃过亏:缓存失效粒度太粗,代理根据旧快照做了重命名,结果新文件还是旧名字,最后编译直接失败。后来我把文件路径的哈希也放进缓存键,写入时强制比对最新mtime,这个问题才消失。
6. 常见问题与排查技巧实录
6.1 高频问题速查表
我把大家最容易遇到的问题整理成了一个速查表,按"症状-可能原因-排查思路-解决方法"的格式来写,你在实战里可以直接查。排查这类问题最关键的是不要上来就怀疑模型能力,而是要一步步确认:上下文里到底有没有那条信息、有没有被压缩掉、有没有被旧版本覆盖。
| 症状 | 可能原因 | 排查思路 | 解决方法 |
|---|---|---|---|
| 上下文超限报错频繁 | 窗口大小设得接近模型上限 | 查看窗口日志,统计峰值占用 | 窗口下调到模型上限的70%以内,同时给MCP增量推送设独立缓冲 |
| 代理反复跳回旧方案 | 硬约束不在窗口内 | 检查到底哪一轮开始丢失约束 | 把约束固化为架构决策模式,进入会话时强制订阅 |
| 摘要内容空洞无物 | 摘要模板缺失或字段太泛 | 查看最近三轮摘要原文 | 改用六段式模板,强制填写决策和下一条行动 |
| 配置了MCP但代理不调用 | 工具列表里没有注册模式名 | 查看客户端日志里的工具注册记录 | 显式把模式名放进代理的工具白名单 |
| 同一文件内容前后矛盾 | file_edit增量缓存失效不及时 | 检查快照mtime和watch事件 | 改用文件路径哈希+最新mtime的失效策略 |
| 多项目共用memory目录导致信息串台 | 长期记忆区跨项目污染 | 查看longterm目录下的文档归属 | 按项目分离memory目录,index.json里强制带项目ID |
| 代理切错模式、拉取无关上下文 | 模式路由判断条件太宽松 | 记录每次模式切换时的任务描述 | 为每个模式设定明确的触发关键词和前置条件 |
6.2 三条独家避坑技巧
除了速查表,我还有三条平时不会写进文档里的经验,算是压箱底的。
第一条,MCP模式名尽量用"动词+对象"的命名风格,比如load_project_skeleton、get_file_changes。别用project_info、data这种太泛的名字。原因很实在:编码代理的工具路由逻辑往往依赖模型对工具名的理解,名字越具体,模型越容易在正确时机调起正确模式。我最早用"context"当模式名,结果代理在毫无需要的时候频繁调用,浪费了大量token,后来改成细粒度命名才恢复正常。
第二条,在摘要模板里加入"上次摘要时间戳"字段。这个字段本身没有业务价值,但能帮代理判断某条摘要的新鲜度。真实场景里,代理经常面临两个相互矛盾的摘要:一份是20分钟前生成的,一份是4小时前生成的。如果没有时间戳,它会随机取一份来用;加上时间戳之后,它至少能做出"取较新的那份"这种合理判断。
第三条,给滑动窗口的淘汰操作加一个"日志旁路"。窗口淘汰不是立刻从存储里删除,而是写一条淘汰记录到session日志里。这样当代理突然问"我们最初是不是讨论过XXX"时,你还能从日志里找回被淘汰的原始内容,而不是只能让模型重新猜。这个设计几乎不增加成本,但关键时刻能救回一整轮已经被遗忘的关键决策。
最后再分享一个我个人的体会。上下文工程这件事,本质上是把"模型记忆不可靠"这个现实问题,通过工程手段去对冲。不要指望某一款工具、某一个协议能一键解决所有问题,也不要觉得调好一次参数就一劳永逸。我现在每次开新项目,都会先花半小时重新过一遍这套配置,因为项目规模、文件组织方式、团队协作习惯都会影响上下文的分层策略。真正做到"让该记的记得住、该忘的忘得掉、该查的查得到",编码代理的稳定性才会有质的提升。