你是否有过这样的经历:Agent 在某次任务中明显犯了重复的错误,你明明在之前的 session 里记录过这个问题,但它这次还是犯了——不是因为 Agent 记不住,而是因为它根本找不到那条记忆

记忆系统的核心问题不只是「存什么」,更是「怎么找到」。

本文是我对 Agent 记忆检索系统的深度研究,核心解决两个问题:

  1. 如何设计检索机制,让记忆在正确的时间被找到?
  2. 如何让 Context Caching 在记忆系统中发挥实际价值?

一、为什么「存了」不等于「能找到」

大多数 Agent 记忆系统的设计思路是:

写:Session 结束时,把关键信息写入记忆文件
读:下次 Session 开始时,把记忆文件读入上下文

这套机制的问题在于:它是全量加载,而不是精准检索

1.1 全量加载的问题

当记忆文件积累到一定规模(比如半年的 daily notes + pattern records + strategy records),全量加载会产生:

问题 A:上下文爆炸
记忆文件总大小:500KB ~ 1MB
系统提示:50K tokens
工具定义:20K tokens
项目上下文:100K tokens
当前任务:5K tokens
---
总计:175K ~ 675K tokens(取决于记忆规模)
Token 成本:线性增长

问题 B:信噪比下降
Agent 读到了 100 条记忆,但当前任务只需要其中 3 条
剩下 97 条变成了「上下文噪音」,稀释了真正相关的信息
Agent 的表现反而可能下降(被无关记忆干扰)

问题 C:检索时机不对
即使记忆文件里有相关内容,如果 Agent 不知道什么时候该查,
它可能在一个任务执行到一半才突然想起来「好像之前遇到过」
这时的检索已经晚了,错误已经发生

1.2 从「记忆仓储」到「精准检索」的转变

真正的记忆系统应该是这样工作的:

不是:Session 开始 → 加载所有记忆 → 执行任务
而是:任务输入 → 精准检索记忆 → 注入相关记忆 → 执行任务

这个转变的核心是:记忆的激活应该由任务需求驱动,而不是由 Session 开始触发


二、Context Caching 对 Agent 记忆系统的实际价值

在深入检索机制之前,先解决一个基础设施问题:即使有了精准检索,每次都重新传输记忆内容也是有成本的。Context Caching 提供了解决方案。

2.1 记忆内容的分层缓存策略

把 Agent 的上下文分成「静态缓存」和「动态检索」两部分:

┌─────────────────────────────────────────────────────────────┐
│  静态缓存(Context Caching,一次加载,多次复用)             │
│  - System Prompt(~50K tokens):Agent 角色定义、核心原则     │
│  - Tools 定义(~20K tokens):工具能力描述、调用约束         │
│  - 项目结构(~30K tokens):目录树、核心模块、依赖关系       │
│  - 策略记忆(Strategic Memory):高层指导原则,数量有限       │
├─────────────────────────────────────────────────────────────┤
│  动态检索(每次任务按需加载)                                │
│  - 事件记忆(Episodic Memory):时间+类型过滤后精准检索       │
│  - 模式记忆(Pattern Memory):触发条件匹配后检索            │
│  - 当前任务上下文:Task description、约束、风险等级           │
└─────────────────────────────────────────────────────────────┘

缓存成本模型:
- 静态部分:100K tokens × 1次 = 100K tokens 初始化成本
- 动态部分:10K tokens × N次任务 = 显著降低每次成本
- 缓存命中率:越高,动态 token 消耗越少

主流 LLM API 的 Context Caching 已经支持这种模式:

2.2 记忆检索的 Token 预算分配

有了缓存机制后,每次任务的 Token 预算可以这样设计:

每次任务 Token 预算分配(约 60K tokens):

[缓存区 - 不占每次预算]
System Prompt + Tools + 项目结构 + 策略记忆
= ~100K tokens(初始化一次,之后缓存复用)

[动态区 - 每次任务消耗]
任务输入:5K tokens
├─ 事件记忆检索(Top-5):3K tokens
├─ 模式记忆检索(Top-3):2K tokens
└─ 推理 + 输出:40K tokens

总计:每次任务 ~50K tokens(相比无缓存的 ~160K,降低 70%)

这个预算模型的关键洞察:把记忆的存储成本(缓存)和记忆的检索成本(动态)分开管理,才能真正优化 Token 消耗


三、三层记忆的差异化检索策略

三层记忆模型(事件/模式/策略)不是简单的分类,而是三种完全不同性质的记录,需要三种完全不同的检索策略。

3.1 事件记忆(Episodic Memory):时间 + 任务类型 + 语义三重过滤

事件记忆记录的是「具体发生了什么」,核心检索维度:

检索条件:
1. 时间范围:只检索最近 30 天的相关事件(老事件价值衰减)
2. 任务类型:task_type 必须与当前任务相关
3. 语义相似度:Top-5 结果(超过 5 条会稀释上下文)

检索 Prompt 示例:

当前任务类型:{task_type} 当前任务描述:{task_description} 时间范围:最近 30 天

请从事件记忆中检索与当前任务相关的记录:

  1. 任务类型匹配度高的事件优先
  2. 涉及失败或异常的事件优先(从中学习避免策略)
  3. 涉及成功决策的事件次优先(作为参考)

返回格式:

为什么事件记忆需要时间过滤?

三个月前的重构失败记录,代码库可能已经完全变了,那次失败的具体原因可能不再适用。但「避免策略」(如「重构前先写测试」)是通用的、可以保留。这就是时间衰减的意义:具体事件随时间失效,但抽象出的教训不过期

3.2 模式记忆(Pattern Memory):触发条件匹配 + 置信度排序

模式记忆记录的是「一类问题的通用规律」,检索逻辑完全不同:

检索条件:
1. 触发条件匹配:当前上下文是否匹配某个 Pattern 的触发条件?
2. 置信度排序:优先返回被验证多次的 Pattern(3次以上为高置信度)
3. 排除已失效:检查 Pattern 是否被后续实践推翻

Pattern 的典型结构:
```json
{
  "pattern_id": "Pattern-2026-04-15-RefactorWithoutTest",
  "name": "大规模重构前未建立测试保护",
  "trigger_condition": {
    "task_type": "code_refactor",
    "scope": "multi_file (>5 files)",
    "has_existing_tests": false
  },
  "failure_manifestation": "重构后引入新 bug,用户报告异常",
  "root_cause": "Agent 没有先验证原有行为就开始修改",
  "prevention_strategy": "重构前强制执行:1) 创建 git 快照 2) 运行现有测试 3) 每次改一个文件后跑相关测试",
  "confidence": 3,
  "last_validated": "2026-04-15",
  "superseded_by": null
}

触发条件匹配的实战逻辑

当 Agent 收到一个任务时,首先提取任务的「触发特征」:

特征提取:
  - task_type: "code_refactor"
  - files_affected: 8 files
  - has_existing_tests: false
  - is_production: true

匹配结果:
  - Pattern-A(重构前未建立测试保护):trigger 匹配 ✓,confidence: 3
  - Pattern-B(生产环境修改前未做变更评估):trigger 匹配 ✓,confidence: 5
  - Pattern-C(不熟悉的代码库直接修改):trigger 匹配 ✓,confidence: 2

注入记忆:
  ⚠️ 高置信度 Pattern 警告:
  - Pattern-B(置信度 5):当前为生产环境修改,请先执行变更评估清单
  - Pattern-A(置信度 3):当前没有现有测试,请先建立基线测试

3.3 策略记忆(Strategic Memory):直接映射,不需要语义检索

策略记忆是高层指导原则,检索最简单:

策略记忆的结构:
{
  "strategy_id": "Strategy-001",
  "description": "当任务涉及多文件重构时,先画依赖图再动手",
  "applies_to": ["code_refactor", "architecture_change"],
  "priority": "high",
  "source": "从 3 次重构失败中提炼,2026-04-10"
}

检索方式:
直接映射,不是语义检索
task_type → relevant_strategies 查表即可

注入格式:
💡 执行策略(从历史经验中提炼):
1. [高优先级] 重构任务 → 先画依赖图,识别「不能动的边界」
2. [高优先级] 生产环境修改 → 先做变更影响评估,准备回滚方案
3. [中优先级] 不熟悉的代码库 → 先写探索性测试,理解现有行为

四、检索注入机制:让记忆在正确时机进入上下文

检索到的记忆如何注入 Agent 上下文?这里有三个关键设计原则。

4.1 注入时机:任务规划阶段(Phase 0),执行之前

任务流程优化:

优化前:
  任务输入 → 直接执行 → 结果输出
  (没有利用历史经验的机会)

优化后:
  任务输入 → Phase 0:记忆检索 → 注入上下文 → 任务规划 → 执行
                                                  ↑
                                    这里用到了历史经验!

Phase 0 的检索注入示例:

[相关记忆上下文 - 从您的历史经验中检索]

📋 事件记忆(2条):
• 2026-04-15 | 重构 | 8文件重构引入新bug
  教训:重构前未建立测试基线,边界条件遗漏
  避免策略:先运行现有测试套件,确认基线后再改

• 2026-04-10 | 调试 | 复杂Bug定位耗时2小时
  教训:未充分利用 git blame 和历史测试用例
  避免策略:调试前先查 git history 和相关测试覆盖

🧠 模式记忆(1条):
• Pattern-RefactorWithoutTestGuard(置信度 3/5)
  触发条件:多文件重构 + 无现有测试
  预防:重构前强制建立基线测试

💡 策略记忆(2条):
• 「多文件重构前,先画依赖图识别安全边界」
• 「生产环境修改,准备回滚方案是标准流程」

[/相关记忆上下文]

4.2 注入优先级:高风险任务需要更密集的记忆注入

风险分级与记忆注入密度:

低风险任务(代码格式化、注释添加、重命名变量)
→ 只注入策略记忆(2-3条)
→ Token 消耗:~1K tokens

中风险任务(单文件修改、功能实现)
→ 注入策略记忆 + 模式记忆(3-5条)
→ Token 消耗:~3K tokens

高风险任务(生产环境修改、认证逻辑、数据库 schema 变更)
→ 全量注入:策略 + 模式 + 事件记忆中的失败记录(5-8条)
→ Token 消耗:~8K tokens
→ 额外:可能需要人类确认(Human-in-the-Loop)

触发高风险的标准:
- 涉及生产环境(production, prod)
- 涉及认证/授权逻辑(auth, permission, role)
- 涉及数据变更(schema, migration, delete)
- 影响范围超过 5 个文件

4.3 注入形式:结构化 + 可操作,不是摘要罗列

错误的形式(堆砌摘要):

相关记忆:
- 2026-04-15:重构失败教训
- 2026-04-10:调试经验
- 2026-03-28:架构决策参考

正确的形式(结构化 + 可操作):

⚠️ 当前任务涉及多文件重构(8个文件),请应用以下策略:

📋 重构前检查清单(来自 Pattern-RefactorWithoutTestGuard):
□ 运行现有测试套件,确认基线通过
□ 创建 git 快照(可回滚)
□ 识别「不能动的边界」(依赖图分析)
□ 每次修改一个文件后运行相关测试

💡 本次任务参考教训:
• 2026-04-15 的重构失败是因为边界条件遗漏,
  本次请特别注意边界条件的测试覆盖

五、遗忘机制:让记忆系统自我进化

记忆系统不能只进不出。三个遗忘/降权机制:

5.1 时间衰减(事件记忆)

事件记忆衰减规则:
- 7天内:100% 权重
- 7-30天:70% 权重(具体事件开始失效,但教训保留)
- 30-90天:40% 权重(需要显式验证才能恢复权重)
- 90天以上:自动归档到「历史记录」,不在常规检索中出现

例外:
- 被模式记忆引用的事件:保持原有权重
- 重大故障(Severity 1):无限期保留

5.2 验证反馈(模式记忆)

模式记忆的置信度更新:

应用结果 → 更新置信度 → 决定去留

应用成功(策略有效):
  置信度 +1(最高上限 5)

应用失败(策略不适用):
  置信度 -2
  标记「需要重新分析」
  触发根因分析:这个 Pattern 为什么失效?

置信度低于阈值(≤ 0):
  从模式记忆中移除
  触发「重新学习流程」:在新事件中寻找新的 Pattern

5.3 冲突解决

当同一触发条件有多个冲突的 Pattern 时:

场景:Pattern-A 说「应该用渐进式重构」,Pattern-B 说「应该用复制增量式」

解决流程:
1. 置信度高的 Pattern 优先
2. 置信度接近时,保留最近验证的
3. 触发「冲突分析」流程:
   - 两次验证的场景有什么不同?
   - 可能的原因是「环境变了」或「任务规模不同」
   - 尝试将 Pattern 细化为「在 X 条件下用 A,在 Y 条件下用 B」

六、实战:实现一个最小化的记忆检索系统

项目结构:
memory/
├── episodic/          # 事件记忆(每任务一条)
│   ├── 2026-04-20-session-xxx.md
│   └── ...
├── patterns/          # 模式记忆(每个 Pattern 一条)
│   ├── Pattern-2026-04-15-RefactorWithoutTest.md
│   └── ...
├── strategies/        # 策略记忆(通用原则)
│   └── strategic-memory.md
└── retrieval_config.json  # 检索配置

核心检索逻辑(伪代码):

function retrieve_memories(task_input, risk_level):
    results = []
    
    # 1. 策略记忆 - 直接映射
    strategies = load_strategies(task_input.task_type)
    results.extend(strategies)
    
    # 2. 模式记忆 - 触发条件匹配
    patterns = load_patterns()
    matched = [p for p in patterns if matches_trigger(p, task_input)]
    matched.sort(key=lambda p: p.confidence, reverse=True)
    results.extend(matched[:3])
    
    # 3. 事件记忆 - 时间 + 任务类型过滤(仅高风险任务)
    if risk_level == "high":
        events = load_events(days=30, task_type=task_input.task_type)
        results.extend(events[:5])
    
    return results

function inject_memories(retrieved, context):
    # 格式化为可注入的上下文片段
    return format_as_context_block(retrieved)

七、总结:从「能记住」到「记得住、找得到、用得上」

记忆系统的三个层次:

「能记住」—— 记忆的存储(Claude Code Memory Architecture)
「记得住」—— 记忆的分类(三层记忆模型:事件/模式/策略)
「找得到」—— 记忆的检索(RAG + Context Caching)← 本文重点
「用得上」—— 记忆的注入(Phase 0 精准注入 + 风险分级)

Context Caching 解决了「记忆传输的成本问题」,三层检索策略解决了「记忆找到的精度问题」,遗忘机制解决了「记忆系统的自我进化问题」。

三者组合,才是完整的 Agent 记忆工程化方案。

核心设计原则

  1. 记忆的激活由任务需求驱动,不是由 Session 开始触发
  2. 三层记忆(事件/模式/策略)需要三种完全不同的检索策略
  3. Context Caching 让记忆检索不再是 Token 成本负担
  4. 遗忘机制让记忆系统能够进化,而不是无限膨胀

当这套系统就位后,你会发现 Agent 不再重复犯同样的错误——不是因为它「记住了」,而是因为它终于「找到了、也用上了」。