AI Agent记忆系统:从热内存到知识库的闭环设计

AI Agent记忆系统:从热内存到知识库的闭环设计
当 AI Agent 需要处理持续数小时甚至数天的复杂任务时,记忆系统就不再是一个可选项,而是一个刚性基础设施。热内存(Hot Memory)只缓存最核心的骨架信息,详细内容被分层迁移到持久文件中——这种设计既能保证 LLM 上下文窗口不被塞满,又能让 Agent 的"回忆"跨越多个会话。
本文围绕一个完整的记忆全链路闭环,介绍核心守护组件、定时同步机制、查重归档策略,以及如何用极简的代码落地这套架构。目标读者是系统架构师和 AI Agent 开发者。
一、全链路架构总览
整个记忆系统分为三层:
- 热内存层(Hot Memory) — 运行在 Agent 进程内,存储高频访问的短期上下文和摘要骨架
- 文件持久层(File Store) — 本地文件系统的结构化存档,按时间分片
- 知识库层(Wiki / Knowledge Base) — 对外暴露的检索式知识库,供多 Agent 共享
│ AI Agent Memory System Pipeline │
├─────────────────────────────────────────────────────┤
│ │
│ Hot Memory (进程内) │
│ ┌──────────────┐ ┌───────────────────────┐ │
│ │ 核心骨架缓存 │ │ LLM Context Window │ │
│ │ (摘要/指针) │◄──►│ (实时会话上下文) │ │
│ └──────┬───────┘ └───────────────────────┘ │
│ │ │
│ ┌──────▼─────────────────────────────────────┐ │
│ │ memory_sync timer (每6h) │ │
│ │ 热内存 → 结构化文件 (子文件存储详细信息) │ │
│ └─────────────────────────────────────────────┘ │
│ │ │
│ ┌──────▼─────────────────────────────────────┐ │
│ │ dream auto-cleanup (每天 03:02) │ │
│ │ 去重合并 / 过期删除 / 摘要重生成 │ │
│ └─────────────────────────────────────────────┘ │
│ │ │
│ ┌──────▼─────────────────────────────────────┐ │
│ │ lobster_linker (每6h) │ │
│ │ 本地文件 → Wiki / 知识库 同步 │ │
│ └─────────────────────────────────────────────┘ │
│ │ │
│ ┌──────▼─────────────────────────────────────┐ │
│ │ 发布前查重 → 发布后自动存档 │ │
│ │ (dedup check) (auto archive) │ │
│ └─────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────┘
核心思想很简单:热内存只存你此刻必须知道的;其余的都下沉到文件中,只在需要时才按指针加载。
二、热内存设计:只存核心骨架
传统做法是把 Agent 的整个对话历史塞进热内存,导致上下文窗口被快速填满,token 成本飙升。我们的做法是:
- 骨架(Skeleton) — 每条记忆包含:时间戳、摘要(
summary)、标签列表(tags)、子文件指针(file_id)、以及一个可选的priority权重 - 内容(Content) — 完整的对话记录、思考链、代码产出等,写入子文件中,热内存中只保留一个
content_ptr
// HotMemory 最小数据结构示例
{
"memory_id": "mem_20260510_a3f2",
"timestamp": 1715356800000,
"summary": "用户讨论了微服务熔断降级方案,最终选择了 Sentinel + 自定义熔断器",
"tags": ["微服务", "熔断", "Sentinel"],
"priority": 7,
"content_ptr": {
"file": "/memory/2026/05/10/mem_20260510_a3f2.json",
"offset": 0,
"size": 2840
},
"ttl": 86400000 // 48小时自动降级
}
在 Python 实现中,热内存通常是一个 OrderedDict + LRU 淘汰策略,当条目数超过阈值(如 200 条),自动将低优先级内容压缩摘要并写入文件。
三、核心守护组件
? memory_sync timer · 每 6 小时同步
dirty=True),避免全量写入。
同步完成后清空脏标记,同时将文件的元信息回写至热内存中的 content_ptr。
? dream auto-cleanup · 每天 03:02
? lobster_linker · 每 6 小时同步到 Wiki
? 发布前查重 · 发布后自动存档
自动存档(Auto Archive):发布成功后,将原始文件从
/memory/hot/ 移动到 /memory/archive/,并添加发布元数据(publish_at、wiki_url、version)。热内存中的对应条目标记为 archived: true,但仍可通过指针追溯。
四、定时器调度实现
在 Python 中,推荐使用 apscheduler 管理 cron 任务。下面是一个完整的调度配置示例:
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.triggers.cron import CronTrigger
scheduler = AsyncIOScheduler()
# 1. memory_sync: 每 6 小时同步热内存到文件
scheduler.add_job(
memory_sync_job,
trigger=CronTrigger(hour="0,6,12,18", minute=0),
id="memory_sync",
replace_existing=True,
)
# 2. dream auto-cleanup: 每天 03:02
scheduler.add_job(
dream_cleanup_job,
trigger=CronTrigger(hour=3, minute=2),
id="dream_cleanup",
replace_existing=True,
)
# 3. lobster_linker: 每 6 小时同步到 Wiki
# 与 memory_sync 错开 30 分钟,让文件先同步完成
scheduler.add_job(
lobster_link_job,
trigger=CronTrigger(hour="5,11,17,23", minute=30),
id="lobster_linker",
replace_existing=True,
)
scheduler.start()
为什么 lobster_linker 要错开 30 分钟?因为 memory_sync 先写文件,lobster_linker 读文件——如果同时运行,可能读到不完整的半写数据。6 小时的同步周期本身容忍少量的延迟,错开调度是合理的防御性设计。
五、子文件存储策略
热内存中的每条详细内容被写成独立的 JSON 文件,按日期分层存储:
/memory/
├── hot/ # 热内存持久化快照
│ ├── 2026/
│ │ ├── 05/
│ │ │ ├── 10/
│ │ │ │ ├── session_a1b2.json
│ │ │ │ ├── session_c3d4.json
│ │ │ │ └── ...
│ │ │ └── ...
│ │ └── ...
│ └── index.json # 全局索引(热内存中的指针指向此索引)
├── archive/ # 已发布的归档
│ └── ...
└── wiki_mirror/ # lobster_linker 输出的 Wiki 同步镜像
└── ...
每个子文件的结构如下:
// /memory/hot/2026/05/10/session_a1b2.json
{
"meta": {
"memory_id": "mem_20260510_a1b2",
"session_id": "sess_89f2",
"created_at": 1715356800000,
"version": 3
},
"summary": "讨论了 API 网关限流方案,最终采用令牌桶 + 分布式计数器",
"tags": ["API网关", "限流", "令牌桶"],
"content": [
{
"role": "user",
"msg": "我们的 API 网关在高并发下需要限流,有什么推荐方案?",
"ts": 1715356801000
},
{
"role": "assistant",
"msg": "推荐使用令牌桶算法,结合 Redis 分布式计数器……",
"ts": 1715356805000,
"thinking": "用户场景是 API 网关,高并发…令牌桶适合突发流量…需要分布式…"
}
],
"decisions": [
"采用令牌桶算法(burst 支持)",
"使用 Redis + Lua 脚本保证原子性",
"熔断阈值设为 500 QPS"
],
"references": [
"docs/rate-limiting.md",
"RFC 001: API Gateway Architecture"
]
}
设计原则:热内存中的每条骨架记录,在持久化后都对应一个独立的子文件。子文件可以继续拆分为更细粒度的内容块(如按 decisions、references 分字段),但核心是保持 1:1 映射——一条记忆 = 一个骨架记录 + 一个子文件。这样查重和归档都变得简单、可预测。
六、查重与归档流程详解
发布前查重
查重不是简单的字符串匹配。我们采用双层过滤:
- 快速层(Level 1) — 计算当前条目的
summary和tags的 MD5 指纹,与知识库中已有的条目指纹做集合碰撞检测。O(1) 复杂度,秒过。 - 语义层(Level 2) — 对未命中指纹的条目,计算其
summary与知识库中同标签条目的 cosine 相似度。使用sentence-transformers生成 embedding。
def pre_publish_dedup(memory_entry, knowledge_base):
# Level 1: MD5 指纹碰撞
fp = md5_fingerprint(memory_entry["summary"], memory_entry["tags"])
if fp in knowledge_base.fingerprint_set:
return DedupResult(is_duplicate=True, method="fingerprint", score=1.0)
# Level 2: 语义相似度
candidates = knowledge_base.query_by_tags(memory_entry["tags"])
if not candidates:
return DedupResult(is_duplicate=False)
emb = embedder.encode(memory_entry["summary"])
for cand in candidates:
cand_emb = embedder.encode(cand["summary"])
sim = cosine_similarity(emb, cand_emb)
if sim > 0.90:
return DedupResult(is_duplicate=True, method="semantic", score=sim)
return DedupResult(is_duplicate=False)
发布后自动存档
查重通过后,执行发布流程:
- 将子文件从
/memory/hot/移动到/memory/archive/,保留原始目录日期结构 - 在归档文件中添加
publish_meta字段:publish_at、wiki_url、version - 更新全局索引
index.json,将条目的status从active改为archived - 热内存中的骨架标记
archived: true,但保留指针,便于追溯
七、完整闭环时序
以下是 24 小时内一条记忆的完整生命周期:
| 时间 | 事件 | 说明 |
|---|---|---|
| T+0h | Agent 对话产生记忆 | 写入热内存(骨架 + 指针),子文件暂存 |
| T+1h | 热内存持续累积 | LRU 淘汰低优先级条目,提前触发同步 |
| T+6h | memory_sync 执行 | 增量同步脏条目到 /memory/hot/,更新索引 |
| T+6.5h | lobster_linker 执行 | 将新文件同步到 Wiki,生成 embedding |
| T+12h | memory_sync 执行 | 再次同步 |
| T+12.5h | lobster_linker 执行 | 再次同步 |
| T+18h | 同上 | — |
| T+24h / 03:02 | dream_cleanup 执行 | 去重合并、摘要重生成 |
| T+24h+ | 查重通过 → 发布 | 执行 pre_publish_dedup,通过后自动归档 |
注意:dream_cleanup 在每天凌晨执行一次,它整理的是前 24 小时内产生的所有记忆。memory_sync 和 lobster_linker 则保持 6 小时的稳定脉冲。发布操作由上层业务触发(例如用户主动要求保存或 Agent 完成任务后自动触发),但发布前必须完成至少一次 lobster_linker 同步,确保知识库是最新状态。
八、工程实践建议
1. 热内存不要超过 500 条。 即使只存骨架,过多的条目也会让检索变慢。建议设置上限,超限后使用 LLM 对低优先级条目做"深度摘要压缩",生成更高级别的抽象摘要。
2. 定时任务要有幂等性保证。 无论是 memory_sync、dream_cleanup 还是 lobster_linker,都要设计成可重入的:重复执行不会产生副作用。用 version 字段 + 乐观锁(或文件级的时间戳比较)来防止重复写入。
3. 查重阈值不要设得太死。 0.90 的 cosine 阈值在实践中需要根据你的 embedding 模型微调。建议用一个测试集定期评估误报率(false positive)和漏报率(false negative),动态调整阈值。
4. 子文件名使用 UUID 或 content-hash。 避免用自增 ID——在分布式场景下会碰撞。使用 hash(content) + timestamp 作为文件名,天然防冲突,且便于内容寻址去重。
九、总结
这套记忆系统闭环的核心逻辑可以概括为三句话:
- 写少读精 — 热内存只存骨架,详细内容下沉到文件,按需加载
- 定时沉淀 — 6 小时的同步脉冲 + 每日整理的
dream_cleanup,让记忆从瞬时变为持久 - 发布闭环 — 查重 + 归档 + Wiki 同步,让记忆最终沉淀为可被多 Agent 共享的知识资产
这套架构已经在我们的生产环境中稳定运行超过 3 个月,支撑了包含 200+ Agent 实例的复杂任务编排系统。如果你正在设计类似的记忆基础设施,希望这份实践总结能帮你少走一些弯路。