从想重造 Agent Harness,到把记忆系统收缩为一层可验证的经验蒸馏机制。
最初接触 Agent Harness 时,我的想法很简单:亲手写一套上下文管理系统,甚至指望它能长期替代 Pi。这个念头本身合情合理——Agent 的实力不只看模型,还涉及上下文拼装、工具调用、记忆、压缩、状态管理和评测。把这些零件都攥在自己手里,似乎才算真正理解 Agent 的运作。
几周后,我不得不承认:问题一开始就提错了方向。不是写不出一个 Agent loop,而是没先问清楚:宿主已经提供了什么,真正缺失的又是什么?
读完 Pi 的 loop、工具派发、session 持久化和 compaction 代码后,我发现它本身已经相当完整、可读、可扩展——不是等着我重造的半成品。真正的缺口在别处:跨会话状态如何保存,哪些信息值得被召回,旧记忆如何退出,记忆如何不污染当前任务,以及怎样验证一次召回真的帮助了后续决策。
于是,项目在 2026 年 7 月 19 日彻底转向:
不再另造 Agent Harness,而是为 Pi 设计一个外挂记忆系统,不碰核心代码。
这次转向,后来重塑了我对记忆、上下文、Agent 协作乃至模型路由的全部看法。
一、先读懂宿主,再决定补什么
过去我总觉得“亲手做一遍”等于“真正理解”。但在 Agent 工程里,重写之前还有一个更关键的步骤:摸清已有系统的边界。
Pi 的 session 采用 JSONL 格式,原始对话、工具调用和工具结果全被保存下来。Compaction 并不物理删除历史,而是持久化一个压缩条目,再用新的逻辑视图重建当前上下文。也就是说,Pi 早已承担了“记录发生了什么”这一职责。
这就改变了问题的性质。
既然原始过程已经存在,另建系统复制全部对话历史就没多大意义。更有价值的问题变成了:
- 原始 session 里哪些东西值得从噪声中剥离出来?
- 哪些信息日后需要主动提醒?
- 哪些旧判断已经被新判断取代?
- 怎样只在相关的决策点动用记忆?
- 怎样确认记忆没有误导 Agent?
这也是我始终坚持“不修改 Pi 核心”的缘由。Pi 的 loop、session、compaction 和工具运行时无需替换;记忆系统只需做成一个干净的小扩展,利用 Pi 已有的扩展口和事件挂点。
这不是把目标做小了,而是把问题从“重造一套系统”收缩到真正值得发力的缺口上。
二、记忆真正的难题:找对和过期,而非存储
很多关于记忆系统的讨论,总从存储开始:用什么数据库、要不要向量库、如何做 embedding、是否建知识图谱。
但在实际使用中,我越来越觉得,记忆最棘手的不是“存不下来”,而是另外两个问题:
- 搜不到真正需要的那一条。
- 旧记忆会污染当前判断。
第一个是召回精度。相似内容一多,系统能翻出一堆“貌似相关”的资料,却未必命中当前真正需要的那条。第二个涉及时间演化——某个项目入口、工具选择或设计决策曾经正确,但后来已被取代。如果旧记忆和新记忆平起平坐,它就从帮助变成干扰。
我的第一版记忆系统因此没有继续堆叠层次,而是逐步精简到三层:
log.jsonl:结构化事实和指针;CURRENT.md:从事实派生出的当前快照;embeddings.jsonl:可重建的召回缓存。
这里有个后来被反复验证的重要区分:
- Pi session 原文是外部原始真相,不改;
log.jsonl是本系统的抽象层,可以修正;CURRENT.md和 embedding 是派生视图,可以重建。
我曾错误地把 Pi session 的 append-only 哲学照搬到自己 log.jsonl 上,以为所有层都不可变,只能追加 correction。后来我重新审视,才分清了三层的性质。这个纠偏很关键:不能因为上游某一层不可变,就机械地套用到自己所有产物上。
记忆系统还必须允许演化。现在的做法是:新判断可以提议取代旧判断,但真正使旧记录失效需要经过批准门。这样既保留了变化关系,又避免一次错误的自动判断悄悄改写历史。
另一个重要决定:默认不使用记忆。只有在我明确要求“继续上次”“找之前那个”“回忆这个项目”之类的场景时,才触发召回。
这听上去不像很多产品鼓吹的“自动记住一切”,但我越来越确信,默认关闭是一种保护——它防止旧记忆悄悄影响当前任务,也防止 Agent 自行判断“该搜一下记忆”而把无关内容拖进上下文。
三、正确的历史不等于已经学会
这次学习中,我逐渐认识到一个深刻的观点:
显性知识不沉淀,隐性教训才沉淀。
Pi 已经保存了正确的历史。一个 Agent 以前在哪里改过文件、用过什么命令、做过什么决定,理论上都能从 session 查到。把这些内容再完整复制一份,不是学习,只是重复存档。
但错误不同。
很多错误淹没在一长串成功的操作里。Agent 起初选错工具,失败后自我纠正,最终完成任务。Session 记录下来的结果是“任务完成”,而不是“第一次决策错了”。下一次遇到同类任务,Agent 仍可能重蹈覆辙,再纠正一遍。
所以:
历史是档案,不是能力。
历史能证明过去发生过什么,却无法自动优化下一次决策。
这也是为什么我现在认为,沉淀错误经验比积累技能更有价值。一个完整的 skill 往往偏重,而一条简单的工具选择教训,却值得在下一次提前想起。
例如,下面这条不值得作为 lesson 保留:
某次修改文件时 edit 调用失败。
它只是一个事件,可能是参数错误,也可能是环境问题。
更像 lesson 的形式是:
场景:修改已有文件中的局部内容
错误模式:使用全量覆盖工具,增加误改风险
正确做法:优先使用精确替换;匹配不唯一时先重新读取定位
它保存的不是某次失败,而是一个可以迁移到后续相似场景的决策模式。
四、不是所有错误都值得记住
如果把每次报错都写进记忆,系统很快会被噪声淹没。
文件被删除、服务没启动、网络临时中断、依赖版本变化——这些通常是环境性错误,当下处理即可,未必需要变成长期经验。
真正值得沉淀的是认知性错误:
- 用了不适合当前任务的 skill;
- 工具选择逻辑不合理;
- 没核实事实就下结论;
- 把一层的规则错误套到另一层;
- 已被纠正过,却在相似场景中继续重复同一决策。
我最后采用了一个很简单的判定标准:
换一个相似场景,这个错误还会再犯吗?
如果答案是否定的,它大概率只是环境事件。如果答案是肯定的,它才可能成为值得蒸馏的 lesson。
这也解释了“错误—纠正对”的独特价值:它同时包含错误动作和正确动作,并且说明 Agent 已经在真实任务中完成了一次自我修正。问题在于,任务成功后 Agent 并不会自发回头总结自己当初为何出错。所以,错误经验的捕获不能指望模型自觉写记忆,而要靠机制去观察工具轨迹、手动纠正和结构性自纠。
自动机制最多生成 candidate,不能直接把每个失败提升为 active lesson。
五、Lesson 介于 System Prompt 和 Skill 之间
过去我习惯把知识分成两层:要么写进 system prompt,要么做成 skill。
但错误经验暴露出一个中间地带:
| 层 | 适合保存的内容 |
|---|---|
| System prompt / 项目规范 | 全局、静态、每次都必须遵守的规则 |
| Lesson / instinct | 原子、动态、只在相关决策点激活的经验 |
| Skill | 多步骤、完整、需要资料和验证的能力包 |
“编辑已有文件时优先精确替换”不值得每次塞进 system prompt,也不值得为它写一整篇 skill。它更像人的经验:平时不必一直背着,但再次遇到类似场景时,应该在做决定前想起来。
因此,我把 lesson 的目标定义为:
场景 → 错误/决策模式 → 正确做法 → 适用边界 → 验证条件
它不是常驻的第二个 system prompt,而是在执行期、决策点附近临时插入的提示。它也不是自动增长的技能库,而是经过筛选、可回指原始证据、能被后续结果反证的轻量规则。
六、从 Session 到经验蒸馏
后来我意识到,从 Session 历史中抽出决策和错误经验,本质上是一种蒸馏。
不是模型蒸馏(不涉及训练 student model),也不是把整段上下文压缩成摘要。更准确的说法是:
从 Session 轨迹进行无训练的经验蒸馏或策略蒸馏。
一条 Session 可以看作一条原始 teacher trajectory:
任务上下文
→ Agent 决策
→ tool call
→ 环境反馈
→ 错误
→ 自我纠正或手动纠正
→ 最终结果
从中提取的不同产物,性质各异:
| 产物 | 性质 |
|---|---|
| 会话摘要 | 压缩 |
| 路径、命令、事实 | 结构化提取 |
| 项目最终决定 | 决策提炼 |
| 错误到纠正的轨迹 | 经验蒸馏 |
| 多次重复出现的行为模式 | 策略蒸馏 |
| 把全部历史重新塞回上下文 | 记忆堆积 |
真正的蒸馏必须完成一次抽象跃迁:从“这次发生了什么”,变成“以后遇到什么条件,应该如何决策”。
我设想的完整流水线:
Pi session 原始轨迹
→ 识别决策点、手动纠正、结构性自纠、错误-纠正对
→ 过滤环境性失败和一次性事件
→ 抽象 candidate lesson / decision rule
→ 去重、冲突检查、证据回指、人工批准
→ 写入外挂记忆层
→ 在相似决策点临时激活
→ 验证下一次是否第一次做对
每一层的职责应当清晰:
- Session 是原始证据;
log.jsonl是结构化导航和指针;- lesson 是经验蒸馏产物;
- context hook 是运行时激活位置;
- verifier 判断这条经验是否真正改变了后续行为。
从这个角度看,我想做的已不只是一个“长期记忆数据库”,而是一个从 Agent 轨迹中提炼行为经验的系统。
七、学习过程中改变我观点的几个错误
这段学习最有价值的,不是一次想出了正确架构,而是许多判断在现实和反复审视中被纠正。
1. 误判了 Pi 的能力缺口
我曾以为 Pi 的 session 搜索能力弱,但读源码发现它实际会搜索 session 的完整文本。真正的问题不是“没有搜索”,而是原始 session 的结构不适合高精度召回——内容扁平、操作噪声多、事实和过程混在一起。
这让我把记忆系统的定位从“补搜索”改成了“补结构化召回和演化处理”。
2. 把所有层都当成不可变历史
这是个类比错误。看到 Pi 的 JSONL 事件日志和 append-only 设计,就下意识认为自己的 log.jsonl 也必须不可修改。实际上,Pi session 是我不拥有的原始真相,而自己的 log 是可以重新提炼的抽象层。
正确的设计不是迷信某个架构词,而是先判断每一层到底是什么性质。
3. 测试脚本自己制造了错误结论
有次 embedding 测试把空的 error 内容送入模型,空字符串产生固定向量,结果看起来合理,却完全误导了判断。我一度以为 error 记录会毒害 embedding,修正过滤逻辑后才发现结论相反。
这件事让我意识到:
测试脚本也可能制造“看似证据”的错误。
所以测试结果不能只看最终数字,还要检查输入数据、过滤条件和失败样本。
4. 设计出来的治理层并没有真正运行
生产合并重构时,我审视了旧治理层的真实数据,发现图、fuzzy、profile 和 evidence 等许多结构虽然设计完整,但几乎未被使用;真正持续运行的是更简单的 observability 主干。
这给了我一个很直接的判断标准:
真实数据是过度设计最好的试金石。
一个设计如果长期没有真实输入、真实读取、真实反馈,就不能因为文档写得完整而被视为系统能力。
八、为什么我不再迷信开放式 Agent Team
学习过程中,我也重新审视了多 Agent 和 Agent Team。
多 Agent 协作的常见画面是:一个 Agent 负责规划,几个 Agent 并行研究,再由一个汇总。问题在于,任务交互必然伴随上下文交接——前一个 Agent 的上下文要传给后一个,后一个又会加入自己的解释、摘要和中间结论。
于是出现一条很难避免的链条:
多 Agent 交接
→ 重复上下文复制
→ 上下文爆炸
→ 必须压缩交接内容
→ 丢失来源、反方证据和因果关系
→ 记忆腐烂
→ Agent 越多越难定位腐烂发生在哪一层
这不是说任何并行 subagent 都没用。我的结论更窄:
开放式 Agent Team 不应作为默认运行时架构;在个别情况下,开 subagent 做独立并行任务就足够了。
使用 subagent 时,最好满足四个条件:
- 子任务彼此独立;
- 每个子任务接收局部上下文;
- 输出有界,且是结构化证据而非完整思考历史;
- 不自动共享长期记忆,由主 Agent 统一判断和写回。
也就是说,subagent 可以是并行加速器,但不该成为不断互相转发记忆的组织结构。
多 Agent 的一致性,应来自共享的项目文档、代码和精选知识库,而非多个 Agent 压缩后的聊天记忆。
九、为什么我也不再把多模型路由当成默认答案
类似的状态连续性问题,也出现在多模型路由上。
在一个长上下文、连续多轮的任务中,如果每轮都根据表面 prompt 将请求路由给不同模型,系统可能丢失模型特定的 KV 或 prefix cache,不得不重复处理长前缀;同时模型切换还会改变工具选择、响应风格和行为习惯。
这并非说模型路由永远没用——短请求、明确的任务边界、成本分层、隐私隔离和故障转移,都让路由合理。但在默认的连续工作流中,模型、上下文和行为状态的连续性,往往比每一轮的局部模型最优更重要。
Manifest 的生产复盘强化了我的判断:《Everyone is building LLM routers, we deprecated ours》。他们自述路由器运行约四个月、覆盖约 7000 名云用户,最终选择废弃。文章提到:
- prompt 本身不足以判断 Agent 任务的完整复杂度,真实复杂度往往在工具调用和搜索之后才显现;
- prefix cache 对 system prompt 和会话历史很重要,cache-aware router 最后只能靠“粘住原模型”来保留缓存;
- 同一会话中切换模型会破坏行为一致性;
- 路由层引入的评测、system prompt、fallback、observability 和维护成本,可能抵消节省的推理费用。
这篇文章是生产复盘,不是普遍定理;其中的成本数字也不能外推到所有模型和供应商。但它和我的学习过程指向同一个原则:
增加组件之前,先算清楚它破坏状态局部性带来的成本。
十、我现在形成的几条观点
经过这段学习,我暂时形成了以下判断。
1. 记忆不是越多越好,而是越可控越好
记忆系统的价值不在于保存一切,而在于知道什么不该保存、什么时候不该召回、旧判断什么时候该退出。
2. 原始历史和经验能力是两种东西
Session 负责保存发生过什么;lesson 负责改变未来怎么做。前者是档案,后者才接近能力。
3. 经验蒸馏比历史摘要更有价值
摘要压缩过去,蒸馏改变未来。只有当具体事件被抽象成可迁移规则时,才真正产生了新的能力层。
4. 共享事实优于共享记忆
多个 Agent 或多个工具之间共享的应是项目文档、代码和来源经过筛选的知识,而非彼此生成的压缩聊天历史。
5. 记忆必须保留来源和演化关系
没有来源的记忆只是未经验证的判断。被替代的事实不能继续以当前事实的身份被召回。
6. Mechanism 必须承担确定性责任
模型可以参与提炼 lesson,但不能独自决定所有记忆写入、权限变更和历史替代。候选生成可交给模型,检测、过滤、批准门和 verifier 必须由机制承担。
7. 默认简单,按需增加复杂度
我曾不断增加 manifest、sessions、图关系、治理层、路由和自动化规则,后来又逐一砍掉一部分。现在更相信:未被真实工作流证明有用的结构,都是潜在的上下文成本和维护债务。
十一、下一步:验证记忆是否真的改变了决策
下一阶段不是继续堆更多记忆功能,而是做 Stage 3 verifier。
我想验证的不是“有没有成功写入”,也不是“搜索有没有返回结果”,而是:
在一个新的相似任务中,Agent 是否第一次就做出了正确决策?
为此,至少需要观察:
- 记忆写入污染率;
- 环境错误被误提炼成 lesson 的比例;
- 无关 lesson 的注入率;
- 过期和冲突记忆同时出现的比例;
- 记忆带来的额外 token 和延迟;
- lesson 是否能回指原始 session 和证据;
- 在我给出纠正后,Agent 是否真正遵循了新判断;
- 后续相似任务的首次工具选择和首次决策正确率。
这也会决定“Session 经验蒸馏”是否只是一个漂亮的概念,还是真能改善日常工作流的系统。
结语:不要替 Agent 记住一切,要帮它少犯可迁移的错
现在我对 Agent 记忆的理解,已经和开始时大不相同。
我不再想造一个把所有历史、摘要、工具和 Agent 都组织起来的大系统,也不再把更多 Agent、更多模型和更多自动路由视为天然的进步。
我更愿意把问题收缩成一个小得多、但更难验证的目标:
保留原始历史
→ 识别真正值得学习的错误
→ 把错误抽象成可迁移经验
→ 只在相关决策点激活
→ 用下一次是否做对来验证
这套系统不需要替 Pi 运行,也不需要把所有会话重新复制一遍。它只需在历史和未来之间增加一个足够轻、足够可追溯、足够可验证的中间层。
如果要用一句话概括这段学习,我会写成:
记忆不是替 Agent 保存更多过去,而是从过去提炼出未来不必重复犯的错误。
参考资料
- Pi — earendil-works/pi
- Natural Language Agent Harnesses (NLAH)
- LinguaClaw
- Context Engineering for AI Agents: Memory, Tools, and RAG
- Everyone is building LLM routers, we deprecated ours
本文记录的是一个仍在进行中的个人学习项目。文中的实验数字和工程判断只对相应的本地实现、数据集和工作流负责,不把单次结果包装成普遍定理。