第 3 章:记忆与 Skill 进化——让经验留下,但不能自行上线
← 上一章:百行 Agent Loop · 下一章:任务规划
开场:老员工越用越顺,靠的是什么?
上一章你让 D 类客服真的办成了事。现在设想:你挂断电话,隔天又打过去。他认得出你吗?如果你报完名字,他立刻接上「您上次那笔退款,我已经放到加急队列了」,你会觉得他贴心。如果他什么都记不住,让你把地址、偏好、上次进度重新报一遍,你会觉得他「笨」。
可反过来也有一种恐怖:他把什么都记住,甚至把你随口一句「我希望七天以后也能退」直接写进了公司政策手册。那不是贴心,是事故——你一个人说的话,从此变成了所有顾客都得遵守的规则。
于是这一章的问题来了:为什么「记得住」很好,但「记得住 + 能自己改规则」很危险? 答案是记忆必须分层:有些话只属于你的用户档案,有些事只属于运行日志,有些规则只能待在官方手册里,而且手册只能由一套批准流程修改。用户事实不是工作方法,运行经验也不是已发布 Skill——这一章就讲这条分界线画在哪、怎么守住。
D 类客服把一位用户随口说的「我希望七天以后也能退」直接写进公司政策手册,问题出在哪?
- A:他记性太好,把不该记的也记住了
- B:他把「用户偏好」写进了「企业政策」的位置,两类东西混在一起
- C:他根本不该记住任何关于用户的事
- D:手册字数变多了,不好读
先把「记住」分成五本不同的本子
一个 Agent 用久了会留下很多东西。最危险的设计,是把它们统称「记忆」后一起塞进一个文件。正确的做法是先分五本本子——它们回答不同的问题,来源不同,写入条件也不同:
| 本子 | 回答的问题 | 来源 | 写入条件 | 不能替代 |
|---|---|---|---|---|
| 对话记录 History | 这次会话说过什么? | 原始 User / Assistant / Tool 消息 | 每轮追加 | 长期用户事实 |
| 用户记忆 User Memory | 这个用户是谁、偏好什么? | 用户明确表达或受控抽取 | 授权、可更正、可过期 | 企业政策 |
| 知识库 Knowledge Base | 组织认可的事实是什么? | 审核文档与业务系统 | 版本化索引 | 用户偏好 |
| 运行轨迹 Trajectory | 这次任务怎样成败? | AgentEvent、Tool Result、反馈 | 脱敏、按保留期 | 已验证工作方法 |
| Skill | 这类任务怎样做? | 人工编写或多轨迹抽象 | 评估、安全、人审 | 执行权限 |
注意中间那几行:用户记忆记录的是「这个用户」,知识库存的是「组织认可的事实」。 一次退款任务因为订单号输错而失败,这条记录属于运行轨迹——它告诉你「这次是怎么失败的」,而不是「这个用户总输错订单号」。把暂时事件固化成用户画像,是失真的开始。
这五本本子,每本都只能回答自己那一列的问题。后面的章节里你反复要做的判断只有一个:这条信息,该进哪本本子?
一次退款任务因为订单号输错而失败。这条记录最该归进哪本「本子」?
- A:用户记忆——记下这个用户总输错订单号
- B:企业政策——改一条退款规则
- C:运行轨迹——任务怎样成败的原始记录
- D:不保存,忘了最省事
三层记忆:是访问模式,不是三个数据库
除了五本本子,你还常听到「工作记忆、情景记忆、核心记忆」的说法。这里有个特别容易踩的误区:这三层是访问模式,不是三个数据库。
- 工作记忆:当前 Context 里正在用的近期消息和状态——这次任务现在进行到哪了;
- 情景记忆:按任务或日期保存的「当时发生了什么」的经历摘要;
- 核心记忆:稳定的目标、用户事实或高层结论。
「语义记忆」描述的是内容类型(稳定的、可检索的事实),向量数据库只是实现它的方式之一。不要把「语义」和「向量检索」划等号。
再补一条工程上的警告:压缩模型的输出仍是不可信的摘要。 用摘要顶替原始流水时,必须留着原始轨迹做对照,并用回归集检查「事实有没有被压缩丢」。摘要不是事实的权威来源,原始记录才是。
一条经验,怎么长成 Skill
五本本子里,唯一「越用越聪明」的是 Skill——但不是靠 Agent 自己偷偷改,而是走一条受控的流水线。
一条真实经验长成 Skill 的过程是这样的:
运行轨迹 → 脱敏 → 按任务聚类 → 生成候选 SKILL.md
→ 确定性测试 + 训练集/保留集评估 + 安全回归
→ 人工比对批准 → 灰度激活 / 发布 → 监控
→ 晋级 / 隔离 / 回滚从轨迹生成候选这一步,Mini Emperor 的 SkillOptimizer.propose() 做得很克制:它接收基础指令和一批轨迹,对教训逐条脱敏(把疑似密钥替换成 [REDACTED]),去重后追加到 ## Candidate lessons 里,然后只返回一个 SkillCandidate——不发布、不激活、不扩权。
为什么候选不能直接上线?因为它只是「有一次成功的做法」。一次成功可能是碰巧:那次搜索恰好撞对了关键词,新方法放到别的任务上可能退化;更危险的是,轨迹里可能残留密钥或「忽略平台规则」这类注入文字。脱敏能去掉明文密钥,但候选里就算残留恶意文字,也绝不能因为文字本身而扩大权限——Skill 加载的是指令,不是执行许可。
Skill 本身也有一个标准形态。它至少是一个目录:
customer-support/
├── SKILL.md # 必需:frontmatter + 指令
├── scripts/ # 可选
├── references/ # 可选
└── assets/ # 可选渐进式披露是它的核心设计:启动时只把 name 和 description 放进 Context——让 Agent 知道「有这么个本事、什么时候用」;命中了任务再加载完整的 SKILL.md;还需要时再读 references/ 或执行受批准的脚本。这样日常 Context 不会被所有 Skill 的全文撑爆。注意一个边界:加载 Skill 不等于批准脚本执行,脚本仍要过 Harness 的 CapabilityPolicy。
Agent 启动时,一个 Skill 的哪部分应该放进 Context?
- A:整个 SKILL.md 全文
- B:只有 name 和 description,命中任务后再加载正文
- C:所有 references/ 和 scripts/
- D:用户资料的完整副本
进化必须有闸:候选只是草稿
现在到了这一章的核心:把「候选」变成「已发布」,中间必须过几道闸。Mini Emperor 的 SkillRegistry 把这条路强制成一条状态机:
add → record_evaluation → approve → publishadd:候选登记为draft;record_evaluation:跑评估,通过才升为candidate,否则留在evaluating;approve:只有评估通过的版本才能被批准,批准人必须真实存在;publish:只有已评估且已批准的版本才能发布,发布时旧的已发布版本自动降为deprecated。
「进化像越用越聪明」这个说法之所以危险,是因为它把一件本该严谨的事说得很随意。实际上这是一条软件发布链:候选只是草稿,测试通过也不等于自动获得生产权限。三个铁律:
铁律一:安全不能拿平均分抵消。 一个候选成功率从 70% 升到 95%、成本降 30%,看起来很漂亮——但只要安全用例回退 1 个,SkillEvaluator.decide() 就立刻拒绝。安全不是可以被成功率平均掉的一项指标。
铁律二:训练集和保留集不能共用。 训练集用于生成和调试,保留集在候选冻结后一次性评估。如果根据保留集的失败反复手调,再把它叫「保留集」,那测的就是你已经偷看过答案的卷子——这不是进化,是过拟合。
铁律三:发布和回滚是状态切换,不是覆盖文件。 已发布版本不可被覆盖;显式版本只能解析已发布或已弃用的版本;回滚目标必须有历史批准。灰度中发现异常时,正确做法是切回上一批准版本,把异常版本和轨迹留下来隔离观察——而不是删掉新文件、把旧文件复制回来。版本号、批准人和状态,都要可审计。
一个新候选成功率从 70% 升到 95%、成本降 30%,但安全用例回退 1 个。评估结果应该是?
- A:晋级,因为成功率涨幅很大
- B:拒绝,安全不是能被平均分抵消的指标
- C:直接发布,先上线再观察
- D:只发一半流量试试
灰度中发现异常要回滚,正确的做法是?
- A:删掉新版本文件,把旧文件复制回来
- B:把旧版本重新评估一遍再审批
- C:切换默认版本指针,保留新旧版本与运行轨迹
- D:直接改已发布版本的正文
完整的受控进化流程长这样:
动手:让两个失败发生,再用一个测试拦住
理论和手感之间隔着一道墙,亲手撞一次才会留下印象。下面两段代码都是故意写错的。
失败一:一次成功,直接发布。
# 只凭一条成功轨迹就发布——跳过评估、跳过批准
candidate = optimizer.propose(base_instructions=..., trajectories=[one_success])
registry.publish(candidate_version_id) # 版本还是 draft,approved_by 为空SkillRegistry.publish() 会抛错:版本必须已评估且已批准才能发布。 候选生成成功与发布资格是两回事——这正是「一次成功 ≠ 一个 Skill」的代码体现。
跑完用现成测试验证这道门禁:
cd "$(git rev-parse --show-toplevel)"
uv run --python 3.12 --extra dev pytest \
mini-emperor/backend/tests/test_skills.py::test_registry_requires_evaluation_and_approval_before_publish \
-v失败二:安全回退混进晋级评估。
decision = SkillEvaluator().decide(
success_rate=0.95, # 成功率大涨
baseline_success_rate=0.70,
cost_reduction=0.30, # 成本大降
safety_regressions=1, # 但安全用例回退 1 个
deterministic_tests_passed=True,
)
# decision.eligible 必须是 False数字再漂亮,只要 safety_regressions 不为 0,评估就拒绝。安全不是可以用涨幅抵掉的指标。
如果失败一跑不通,按这个顺序排查:
- 是哪个版本被 publish →
SkillVersion.status和approved_by的状态; - 评估没有记录 →
record_evaluation没有调用; - 批准没有被记 →
approve的approved_by参数是不是空。
门禁·代码:跑通上面第一条测试,把输出保存为证据。
本章小结
这一章讲了两件事,其实是同一件事的两面:留下经验,和管住经验。
记忆要分层,不要一口一个「记忆」。 对话记录、用户记忆、知识库、运行轨迹、Skill,五本本子各回答各的问题、各有各的写入条件。判断一切信息的第一个问题都是:这该进哪本本子?一次失败不是用户事实,一次成功也不是工作方法。
Skill 进化是一条发布链,不是自动成长。 候选只是草稿:脱敏 → 评估(训练集 + 保留集 + 安全回归)→ 人工批准 → 灰度 → 发布 → 监控 → 回滚。任何一步都不该由 Agent 自己跳过,因为它没有权限扩大自己的权限。
三句铁律收住这一章:
- 安全不能拿平均分抵消;
- 训练集和保留集不能共用;
- 发布和回滚是状态切换,不是覆盖文件。
一句话记住这一章:
记忆让 Agent 记得住,治理让记忆进得对、改不了不该改的。用户事实不是工作方法,运行经验也不是已发布 Skill。
如果上面这些话你自己也能说得出来(不看资料),就说明这一章的知识已经连成一片。
复述与证据
合上资料,把五本本子挨个讲一遍:各自回答什么问题、从哪里来、什么条件下才能写。然后画出「一条运行经验变成已发布 Skill」的状态机,标出每一步谁有权放行。
再追问三个问题:
- 为什么「一次失败」不该被写成「这个用户总输错订单号」?
- 为什么候选里残留了恶意文字,也不能因为文字要求而扩大权限?
- 回滚为什么是「切换默认版本」,而不是「把旧文件复制回来」?
门禁·证据:提交一张五种持久产物分类表,和一张「draft → candidate → published」的状态切换记录(含批准人和时间)。
24 小时后:不看资料,画出 Skill 进化的完整状态机,并标出安全回归、人工批准、回滚分别插在哪。
有兴趣可以继续看
以下资料按优先级排。至少完成一项 P0,并保存完成证据。
视频P035 分钟 · 视频 03 全片;视频 02 [00:06:04–00:08:27];视频 07 [00:02:53–00:04:15]
- 预计
- 35 分钟
- 位置
视频 03 全片;视频 02 [00:06:04–00:08:27];视频 07 [00:02:53–00:04:15]- 阅读任务
- 分别提取记忆三层与 Skill 三层,不把二者合并。
- 完成证据
- 提交五种持久产物分类表。
教学仓库P070 分钟 · Step 06、07 与同名 doc
- 预计
- 70 分钟
- 位置
Step 06、07 与同名 doc- 阅读任务
- 跟踪 Skill 摘要/正文加载,再触发一次压缩并查看四类文件。
- 完成证据
- 保存压缩前后消息数和文件 Diff,标出信息损失。
书与实验P0120 分钟 · ai-agent-book 第 2、3、8 章;必做实验 context-compression、user-memory、self-modifying-agent
- 预计
- 120 分钟
- 位置
ai-agent-book 第 2、3、8 章;必做实验 context-compression、user-memory、self-modifying-agent- 阅读任务
- 依次做上下文压缩、用户记忆、自修改 Agent 三个实验,记录事实有没有被压缩丢。
- 完成证据
- 提交一份候选 Skill 晋级报告,必须含保留集与安全回归。
官方文档P035 分钟 · Agent Skills Specification
- 预计
- 35 分钟
- 位置
Agent Skills Specification- 阅读任务
- 读目录、Frontmatter、Progressive Disclosure 和 validate。
- 完成证据
- 一个可校验的标准 Skill;说明 skill.web.json 不属于通用标准。
论文P0110 分钟 · Reflexion + Generative Agents
- 预计
- 110 分钟
- 位置
Reflexion + Generative Agents- 阅读任务
- 比较 episodic memory、reflection 与候选 Skill;找出生产门禁缺口。
- 完成证据
- 一张三者对照表,并说明为什么反思不会自动发布。
论文P145 分钟 · LLMLingua
- 预计
- 45 分钟
- 位置
LLMLingua- 阅读任务
- 关注预算控制和压缩质量,不只看压缩率。
- 完成证据
- 比较原文、摘要、截断的 Token、延迟和约束保持率。
本文提到的代码与出处(有兴趣逐条核对时用):
claude-agent-examples@54a18980334541773f13940aa0c0475728d30ee0:build-agent-example/code/step06_skills.pyclaude-agent-examples@54a18980334541773f13940aa0c0475728d30ee0:build-agent-example/code/step07_memory_system.pyai-agent-book@1c18370279f8f0457bf2c44dfa585d08d4d5f281:book/chapter2.mdai-agent-book@1c18370279f8f0457bf2c44dfa585d08d4d5f281:book/chapter3.mdai-agent-book@1c18370279f8f0457bf2c44dfa585d08d4d5f281:book/chapter8.mdai-agent-book@1c18370279f8f0457bf2c44dfa585d08d4d5f281:chapter8/self-modifying-agent/README.mdmini-emperor/backend/src/mini_emperor/skills.pymini-emperor/backend/src/mini_emperor/evolution.pymini-emperor/backend/tests/test_skills.pymini-emperor/backend/tests/test_evolution.py