第 21 章:记忆的宫殿

个人公众号
1
2
卷四:纵深
[20] 配置的秘密 -> [21] 记忆的宫殿 <- you are here

Agent 怎么记住你之前说过的话?对话历史存在哪里?上下文太长了怎么办?这一章深入记忆系统——SafeJSONSession 的原子写入、MemoryCompactionHook 的三层压缩、ReMeLight 的语义搜索。


记忆的三层结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
短期记忆(InMemoryMemory)
当前对话的消息列表
存在内存中,每次请求加载
用 Session 持久化到磁盘

长期记忆(ReMeLight)
跨对话的知识库
向量搜索 + 全文搜索
用嵌入模型索引

压缩记忆(Compressed Summary)
对话历史的摘要
当上下文超过阈值时生成
替代原始消息,释放上下文空间

SafeJSONSession——原子写入的会话存储

SafeJSONSession 把对话历史存为 JSON 文件:

1
<workspace>/sessions/<user_id>_<session_id>.json

关键设计:

  • 文件名消毒:Windows 不安全字符被替换为 --
  • 损坏恢复json.loads() 失败后尝试 raw_decode 部分解析
  • 异步读取 + 同步写入:读取用 aiofiles,写入用普通 open()

每次请求结束后,save_session_state() 把 Agent 的完整记忆(所有消息)序列化。下次请求 load_session_state() 恢复。

MemoryCompactionHook——三层压缩

MemoryCompactionHook 注册为 pre_reasoning 钩子——每次 LLM 调用前检查是否需要压缩。

什么时候压缩?

1
2
3
4
5
6
# 触发条件
memory_compact_threshold = max_input_length * memory_compact_ratio
# 默认:131072 * 0.75 = 98304 tokens

left_compact_threshold = threshold - (system_prompt + compressed_summary)
# 可用空间 = 阈值 - 已占用的系统提示词和已有摘要

当消息的 Token 数超过 left_compact_threshold 时,压缩启动。

第一层:工具结果截断

1
compact_tool_result(recent_n=2, old_max_bytes=3000, recent_max_bytes=50000)

旧的工具输出截断到 3KB,最近 2 条保留到 50KB。工具输出通常比对话文本大得多(文件内容、命令输出),是上下文膨胀的主要来源。

第二层:对话压缩

1
compact_memory(messages, previous_summary, compact_ratio=0.75)

把超出阈值的对话历史发给 LLM,生成一个压缩摘要。摘要保留关键信息(用户意图、操作结果、决策过程),丢弃详细的工具输出和中间推理。

1
2
3
4
5
6
7
8
9
10
11
12
压缩前:
用户: "帮我读 log.txt"
助手: [调用 read_file]
工具: [500 行日志内容]
助手: "日志显示错误 X..."
用户: "修复它"
助手: [调用 edit_file]
...

压缩后:
"用户请求分析 log.txt,发现错误 X 在第 23 行。
助手修复了错误。当前状态:修复已完成。"

第三层:异步摘要

1
add_async_summary_task()  # 后台 asyncio.Task

在压缩的同时,后台任务生成更全面的摘要写入 MEMORY.md。这是”长期记忆”的一部分——跨对话持久化的知识。

ReMeLight——语义搜索

ReMeLightMemoryManager 封装了 reme-ai 库,提供语义搜索:

1
2
3
4
async def memory_search(query, max_results=5, min_score=0.1):
# 向量搜索 + 全文搜索
# 后端:chroma(默认)或 local
results = await self._reme.memory_search(query)

搜索结果在 reply() 中注入到记忆的 _long_term_memory——LLM 能看到与当前对话相关的历史知识。

Dream——夜间记忆优化

dream_memory() 是一个定时任务(默认每晚 11 点):

1
2
3
4
1. 创建临时 "DreamOptimizer" Agent
2. 读取 MEMORY.md 和今天的日志
3. 执行净化协议:去重、替换、合并
4. 写回 MEMORY.md(修改前备份)

这是 Agent 的”睡眠整理”——在用户不活跃时优化记忆质量。

工程权衡

为什么用 JSON 文件而非数据库?

QwenPaw 是单机部署的个人助手。JSON 文件简单、可读、可手动编辑、不需要额外依赖。SafeJSONSession 的损坏恢复机制保证了数据安全。

为什么压缩是三层而非一次性?

渐进式压缩减少信息损失。工具结果截断保留了对话结构,只是缩小了输出。对话压缩保留了关键信息。异步摘要生成了长期知识。如果一次性全部压缩,可能丢失重要的中间决策过程。

自检

  • 知道记忆分三层:短期(消息列表)、长期(向量搜索)、压缩(摘要)
  • 知道 MemoryCompactionHook 在 pre_reasoning 时触发
  • 知道压缩的触发阈值是 max_input_length * memory_compact_ratio
  • 知道 Dream 是夜间自动运行的记忆优化

下一章我们看自治任务——Cron 调度、心跳机制、Mission 系统。