自进化AI的记忆增强:拓展Openclaw记忆压缩的结构化知识抽取
我在之前那篇 《从 OpenClaw 记忆模块出发》 里,重点聊了会话治理机制——如何在 compaction 前把高价值信息沉淀下来。那篇文章解决的是「什么时候该记住」的问题。
在我近期的实验和测试中发现,如果只做到记忆压缩和写入,其实只是把「更多信息塞进上下文」,并没有真正解决「如何让记忆变得可检索、可复用」。这正是结构化知识抽取要补的课:把原始记录变成实体、关系、情节、倾向、模式,让记忆从「一团文本」变成「可索引的知识图谱」。
但是我又不希望吧可以索引的知识图谱做的很重,要安装图数据库之类的,正在尝试用轻量的方式来解决。
SkillLite 自进化引擎的记忆增强,就是沿着这个方向走的。
一、问题:记忆压缩后,信息依然难以复用
传统记忆方案的典型做法:
-
会话快满了 → 做 summary
-
把 summary 写入记忆文件
-
下次检索时把记忆文件召回
这套流程有个隐藏问题:summary 是自然语言文本,检索粒度粗,召回结果常常「相关但无用」。
比如任务记录里写了「某次重构任务用了 grep + read_file,先查文件再写回」,下次你想搜「有没有类似的路径错误经历」——这种语义级别的类比检索,纯靠关键词或简单 summary 很难命中。
这就引出一个需求:不仅要把信息记下来,还要把信息「拆解」成可独立检索的原子单元。
二、OpenClaw 没做的事:结构化知识抽取
OpenClaw 的记忆机制核心是「压缩前flush」——在上下文被压缩前,把有效信息写入持久层。它的贡献在于解决了时机问题(什么时候该记),但没有解决形态问题(记成什么样)。
oganic 的记忆方案更进一步,提出了实体-关系-情节的层级划分。但真正工程化落地的凤毛麟角。
SkillLite 在借鉴 OpenClaw 机制的基础上,做了一个独立的「记忆进化」模块:从任务执行记录中自动抽取结构化知识,写入知识库供后续检索和类比。
三、SkillLite 的五类知识结构
这套知识抽取定义了五种知识类型,各自承担不同的检索目的:
|
类型 |
检索目的 |
示例 |
|---|---|---|
|
实体 |
「出现过哪些工具/项目/技术」 |
grep、Rust、某配置文件路径 |
|
关系 |
「X 和 Y 什么关联」 |
「重构任务常用 grep + read_file」 |
|
情节 |
「有没有类似的经历」 |
「某次未确认工作目录导致失败 → 教训:改路径前先确认 cwd」 |
|
倾向 |
「Agent 习惯怎么做事」 |
「在改配置时,多次观察到先查文档再改」 |
|
模式 |
「这类任务的通用规律是什么」 |
「做查找并替换类任务时,通常先 grep 再 read_file 再写回」 |
这五种类型的区别很重要:
-
实体和关系是静态知识,回答「有什么」
-
情节是单次经历,回答「发生过什么」
-
倾向和模式是行为规律,回答「通常怎么做」
检索时可以根据需求精准召回,而不是拉出一大段 summary。
四、与规则、技能的区别:只输出「实然」
这是最容易混淆的地方。Knowledge Extraction 模块有一条明确的设计边界:
-
规则模块:抽取「何时做什么」类指令(应然)→ 指导下一步行动
-
技能模块:抽取可执行的工具组合(能力)→ 直接调用
-
记忆模块:抽取「出现了什么、发生了什么、观察到什么」(实然)→ 供检索和类比
一个具体的例子:
-
「先 grep 再 read_file」→ 如果是观察到的规律 → 记忆模块输出为模式
-
「改路径前应先确认 cwd」→ 如果是规定的检查项 → 规则模块输出为规则
这种边界划分保证了知识不重复、不冲突。
五、工程实现:从决策记录到知识条目
1. 数据来源:decisions 表
记忆进化的输入来自 SQLite decisions 表。每条记录包含:
-
task_description:任务描述
-
total_tools / failed_tools:工具调用统计
-
tools_detail:工具调用序列
-
task_completed:是否成功
-
elapsed_ms:耗时
这些字段组合起来,就是「一次任务执行」的结构化画像。
2. 抽取 Prompt:五类知识的提取指令
你是 SkillLite 进化引擎的记忆知识抽取模块。从任务执行记录中抽取**事实与经历**,写入知识库,供后续检索与类比。
## 知识类型(五类)
| 类型 | 含义 | 示例 |
|------|------|------|
| **实体** | 记录中反复或关键出现的概念、项目、工具、路径、技术等 | read_file、skillLite、Rust |
| **关系** | 两个实体或「任务类型↔工具/结果」之间的关联 | 「重构任务常用 grep+read_file」 |
| **情节** | 单次经历的摘要:发生了什么、成功/失败、一条可复用教训 | 「某次未确认工作目录导致失败 → 教训」 |
| **倾向** | 观察到的行为倾向或习惯(非规定性),带情境 | 「在改配置时,多次观察到先查文档再改」 |
| **模式** | 跨多次经历的重复规律(比单条情节更抽象) | 「做查找并替换类任务时,通常先 grep 再 read_file 再写回」 |
## 输出格式
{
"entities": [{ "name": "...", "type": "概念|项目|工具|路径|技术", "note": "..." }],
"relations": [{ "from": "...", "to": "...", "relation": "..." }],
"episodes": [{ "summary": "...", "outcome": "成功|失败", "lesson": "..." }],
"preferences": [{ "description": "...", "context": "..." }],
"patterns": [{ "description": "...", "evidence": "..." }],
"skip_reason": "若无可抽取内容则说明原因"
}
这个 prompt 设计了几个关键约束:
-
只输出实然:禁止出现「应该」「务必」等规定性表述
-
去重机制:把已有知识摘要作为参考,避免重复抽取
-
单次上限:每类知识有抽取上限(实体12条、关系10条、情节8条等),避免单次写入过长
3. 抽取结果写入
抽取完成后,结果以 Markdown 格式追加到 memory/evolution/knowledge.md:
## 2026-03-09 14:32
### 实体
- **grep** (工具) 在多次查找任务中使用
- **read_file** (工具) 常用于读取配置和代码
### 关系
- 重构任务 → grep + read_file: 常用组合
- 路径错误 → 失败: 常导致任务中断
### 情节
- [失败] 未确认工作目录导致文件写入错误 → 教训:改路径前先确认 cwd
- [成功] 使用 grep 定位后批量读取并修改 → 教训:先定位再处理是有效策略
### 倾向
- 在改配置时,先查文档再改(观察到 3 次)
### 模式
- 做「查找并替换」类任务时,通常先 grep 再 read_file 再写回
4. 检索链路
写入的知识可以直接被 memory_search 召回。检索时按关键词匹配对应字段,比如:
-
搜「路径错误」→ 命中 relations 和 episodes
-
搜「grep」→ 命中 entities 和 patterns
-
搜「有没有类似的失败经历」→ 召回 episodes 中 outcome=failed 的条目
六、与会话级记忆的关系:互补而非替代
这里有一个容易混淆的点:有了结构化知识抽取,还要不要原来的会话记忆?
答案是:两者是互补关系。
|
维度 |
会话级记忆(memory flush) |
进化知识库(knowledge extraction) |
|---|---|---|
|
触发时机 |
compaction 前 |
定期(最近7天决策记录) |
|
内容形态 |
原始文本 summary |
结构化实体/关系/情节/倾向/模式 |
|
检索粒度 |
粗(整段文本) |
细(字段级匹配) |
|
用途 |
补充上下文 |
类比检索、经验复用 |
会话级记忆解决「上下文不丢」的问题;进化知识库解决「经验可复用」的问题。两者走不同的检索路径,互不干扰。
七、工程细节:容错与安全
1. 解析失败处理
LLM 返回的 JSON 可能格式不规范。解析失败时记录 event 并跳过本次抽取,不会阻断主流程:
let parsed = match parse_knowledge_response(&content) {
Ok(p) => p,
Err(e) => {
tracing::warn!("Memory knowledge extraction parse failed: {}", e);
if let Ok(conn) = feedback::open_evolution_db(chat_root) {
let _ = crate::log_evolution_event(
&conn, chat_root, "memory_extraction_parse_failed", "", &format!("{}", e), ""
);
}
return Ok(Vec::new());
}
};
2. 内容安全校验
抽取结果经过 L3 内容审查(gatekeeper_l3_content),防止敏感信息写入知识库:
if let Err(e) = gatekeeper_l3_content(&full_content) {
tracing::warn!("Memory evolution L3 rejected content: {}", e);
return Ok(Vec::new());
}
3. 路径安全校验
知识库文件路径经过 L1 路径审查,防止路径逃逸:
if !gatekeeper_l1_path(chat_root, &knowledge_path, None) {
tracing::warn!("Memory evolution L1 path rejected: {}", knowledge_path.display());
return Ok(Vec::new());
}
八、效果验证
以一次「查找并批量替换」类任务为例:
原始决策记录(输入):
- 任务: 在 src 目录下查找所有包含 "TODO" 的文件并替换为 "FIXME"
- 完成: 是 | 工具: grep, read_file, write_file (失败: 0) | replan: 0 | 耗时: 3200ms
抽取后的知识条目:
### 实体
- **grep** (工具) 用于文本搜索
- **write_file** (工具) 用于写回修改
- **TODO/FIXME** (概念) 代码标记类型
### 关系
- 查找替换任务 → grep + read_file + write_file: 完整工具链
### 情节
- [成功] 使用 grep 定位 TODO 后批量读取并替换为 FIXME → 教训:先定位再批量处理是有效策略
### 模式
- 做「查找并替换」类任务时,通常先 grep 定位,再 read_file 读取上下文,最后 write_file 写回
下次遇到类似任务时,检索「查找替换」「grep」「批量处理」等关键词,都能命中这些结构化条目。
九、总结
SkillLite 尝试记忆增强方案,核心价值在于把「记了什么」变成「记了什么实体、什么关系、什么经历、什么规律」:
-
五类知识分工明确:实体关系回答「有什么」,情节回答「发生过什么」,倾向模式回答「通常怎么做」
-
与规则、技能明确边界,只输出实然,不与指令类知识重复
-
会话记忆(压缩前flush) + 结构化知识(进化抽取)双轨并行,各司其职
-
工程层面有容错、有审计、可追溯
回到最初的问题:超越记忆压缩的本质,是让记忆从「文本」变成「可拆解、可检索、可复用的知识单元」。SkillLite 的结构化知识抽取,就是这个方向的一次工程化尝试。
如果你在做一个会「积累经验」的 Agent,这套方案可以直接参考——关键不是记多少,而是记完之后能不能找得到、用得上。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)