1. 从 "hindsight" 这个词说起:为什么记忆是 Agent 最被低估的能力
"hindsight" 这个词本身很有意思,字面意思是"后见之明",也就是事后回头看才能看清的东西。把它作为项目标题,指向的其实是 LLM Agent 领域一个越来越被重视的问题:Agent 的记忆机制。一个 Agent 如果只有当前对话窗口里的上下文,那它本质上就是个"金鱼"——每轮对话都从零开始,用户上次说过的偏好、上次任务踩过的坑、上次确认过的参数,全都记不住。hindsight 要解决的,就是让 Agent 具备"回头看"的能力,把过去的交互沉淀成可复用的记忆,在后续任务里主动调用。
我接触 Agent 记忆这块大概是从做多轮工具调用开始的。当时遇到一个特别典型的问题:用户第一轮说"帮我查一下北京明天的天气",第二轮说"那后天呢",第三轮说"上海呢"。如果 Agent 没有记忆,第三轮它根本不知道"上海呢"指的是天气,更不知道要延续"明天/后天"这个时间维度。这就是 working memory(工作记忆)缺失的典型症状。后来我开始系统性地研究 Agent 记忆的架构,从最简单的对话历史拼接,到向量检索式的长期记忆,再到 hindsight 这类带反思和回溯机制的记忆框架,踩了不少坑,也积累了一些可以直接复用的经验。
这篇文章面向的是正在做 LLM Agent 开发、或者准备给自己的应用加上记忆能力的同学。不管你是刚接触 Agent 概念的新手,还是已经在用 MCP 协议搭工具链的老手,我都会从架构设计、核心机制、实操落地、问题排查几个维度把 hindsight 这类记忆系统讲透。涉及到的技术栈包括 LLM 基础、Agent 存储、MCP 协议、Docker 部署环境,以及记忆检索里最关键的 key-query-value 三元组设计。全文基于我在实际项目中的实践总结,代码和配置都可以直接抄作业。
2. Agent 记忆到底难在哪:核心设计与思路拆解
2.1 为什么"把历史对话全塞进 prompt"是死路一条
很多人做 Agent 记忆的第一反应是:把历史对话拼成一个长字符串,每次请求都带上。这个方案在对话轮次少的时候能用,但很快就会撞墙。原因有三个,而且每一个都是硬约束。
第一是token 成本。LLM 的上下文窗口虽然从 4K 涨到了 128K 甚至 1M,但 token 是要花钱的。假设每轮对话平均 500 token,50 轮就是 25000 token,每次请求都带这么多,成本会线性膨胀。更别说很多模型的定价是按输入 token 计费的,长上下文意味着每次调用都在为重复的历史付费。
第二是注意力稀释。这是很多人忽略的问题。上下文越长,模型对关键信息的注意力越分散。我实测过一个场景:把 30 轮对话全塞进去,问一个第 3 轮提到过的细节,模型经常答错或者答得含糊。但如果只把相关的那 2-3 轮检索出来塞进去,准确率反而高得多。这就是所谓的"lost in the middle"现象——模型对上下文中间部分的信息召回率明显低于开头和结尾。
第三是无法跨会话。对话历史拼接只在单次会话内有效,用户关掉页面再回来,历史就没了。而真正的记忆应该是跨会话、跨任务的,用户上周配置过的参数,这周还应该能调用。
所以 hindsight 这类框架的核心思路,是把记忆从"上下文拼接"升级为"结构化存储 + 按需检索"。记忆不再是流水账,而是被拆解、索引、压缩过的知识单元。
2.2 记忆的三层结构:working memory、episodic memory、semantic memory
参考认知科学的分类,Agent 记忆通常分三层,hindsight 的设计也基本遵循这个框架。
Working memory(工作记忆)是当前任务正在用的那部分信息,生命周期最短,通常就是当前对话轮次加上最近几轮的上下文。它的特点是容量小、访问快、随时更新。在实现上,working memory 一般就是 prompt 里直接拼接的那部分,但需要做滑动窗口或者摘要压缩,避免无限增长。
Episodic memory(情景记忆)是具体发生过的事件记录,比如"用户在 3 月 5 日让我查过北京天气,当时用的是摄氏度"。它带时间戳、带上下文,是原始经历的存储。这部分通常用向量数据库存,因为需要按语义相似度检索。
Semantic memory(语义记忆)是从多次经历中抽象出来的规律和事实,比如"这个用户偏好摄氏度而不是华氏度"、"这个项目的代码风格要求用 4 空格缩进"。它是压缩过的、去掉了具体情境的通用知识。这部分可以用结构化的 key-value 存储,也可以用知识图谱。
hindsight 的价值在于,它不只是被动地存这三层,而是有一套反思机制:定期回看 episodic memory,把重复出现的模式提炼成 semantic memory。这就是"hindsight"这个名字的由来——事后回看,提炼规律。
2.3 为什么选 MCP 作为记忆的接入协议
MCP(Model Context Protocol)是这两年 Agent 工具链里最值得关注的一个协议。它的定位是标准化 LLM 和外部工具/数据源之间的交互。把记忆系统做成一个 MCP server,好处非常直接。
首先是解耦。记忆逻辑独立成一个服务,Agent 主程序通过 MCP 协议调用,换模型、换框架都不用动记忆层。其次是复用。同一个记忆服务可以给多个 Agent 用,比如一个做客服的 Agent 和一个做代码助手的 Agent 可以共享用户偏好这类 semantic memory。第三是可观测。MCP 有标准的请求响应格式,记忆的读写都能被日志记录和监控,排查问题方便很多。
我在实际项目里把记忆服务做成 MCP server 之后,最大的感受是调试变简单了。以前记忆逻辑和 Agent 逻辑混在一起,出问题不知道是检索错了还是 prompt 拼错了。拆开之后,可以直接用 MCP 的调试工具单独测记忆的读写,定位问题快了一个数量级。
2.4 Docker 化部署:为什么记忆服务必须容器化
记忆服务通常依赖向量数据库、关系数据库、缓存这几样东西。本地裸装的话,版本冲突、端口占用、环境变量污染这些问题会让人崩溃。Docker 化之后,整个记忆服务加上它的依赖可以一键起停,迁移和扩容也方便。
具体来说,一个典型的 hindsight 记忆服务栈包括:向量数据库(存 episodic memory 的 embedding)、关系数据库(存 semantic memory 的结构化数据)、Redis(做 working memory 的缓存和会话状态)、以及记忆服务本体。用 docker-compose 编排,一条命令全起来。后面我会给出完整的 compose 配置。
3. 核心机制拆解:key-query-value 三元组与记忆检索
3.1 记忆的 key-query-value 三元组设计
这是整个记忆系统里最关键的设计。热词里提到的"key 我是谁、query 我在找什么、value 我能提供什么",其实是对记忆检索三元组的一个通俗概括。我展开讲一下。
在记忆检索里,key是记忆的索引标识,回答"这条记忆是关于什么的"。它可以是实体名、主题标签、时间戳,或者它们的组合。query是检索时的查询条件,回答"我现在需要什么"。value是记忆的实际内容,回答"这条记忆能提供什么信息"。
举个具体例子。用户说"我习惯用 Python 写脚本"。这条记忆的 key 可以是user_preference:language,value 是Python。当 Agent 遇到"帮我写个脚本处理 CSV"这个 query 时,检索逻辑会去匹配 key 里包含user_preference且语义接近"编程语言"的记忆,命中后把 value 注入 prompt。
这里有个容易踩的坑:key 的设计粒度。如果 key 太粗,比如统一用user_preference,那所有偏好都挤在一起,检索时容易召回不相关的。如果 key 太细,比如user_preference:language:python:version,那维护成本高,而且很多记忆根本填不满这么细的字段。我的经验是,key 用"领域 + 属性"两级结构比较合适,比如preference.language、fact.project_stack、event.meeting。既保证了检索精度,又不至于过度设计。
3.2 向量检索和关键词检索怎么配合
记忆检索不能只靠一种方式。纯向量检索擅长语义匹配,但对精确匹配(比如用户 ID、项目名)不敏感。纯关键词检索精确,但没法处理"意思相近但用词不同"的情况。
我的做法是混合检索:先用关键词做一轮粗筛,把候选集缩小到几十条,再用向量相似度做精排。这样既保证了召回率,又控制了计算量。具体实现上,可以用 Elasticsearch 做关键词层,用 Milvus 或 Qdrant 做向量层,中间用一个融合排序算法(比如 RRF,Reciprocal Rank Fusion)合并结果。
RRF 的公式很简单:对每个文档,得分 = Σ 1/(k + rank_i),其中 rank_i 是它在第 i 个检索器里的排名,k 通常取 60。这个算法不需要调权重,对异构检索器的融合效果很稳。我实测下来,混合检索比单用向量检索的召回率能提升 15-20 个百分点,尤其是在记忆条目多、语义分散的场景下。
3.3 记忆的写入时机:什么时候该记,什么时候不该记
这是实操里最容易做错的地方。很多人的 Agent 记忆系统要么记太多(把每句话都存下来,检索时全是噪音),要么记太少(关键信息漏掉)。
我的判断标准是三条:可复用性、稳定性、非显然性。一条信息如果满足这三条,就值得写入长期记忆。
可复用性指的是这条信息在未来的任务里可能被用到。比如用户的时区偏好,几乎每个涉及时间的任务都要用,可复用性高。稳定性指的是这条信息不会频繁变化。用户今天说喜欢蓝色,明天说喜欢红色,这种就不适合存长期记忆,存了反而会误导。非显然性指的是这条信息不能从其他信息推导出来。比如"用户在北京"和"用户用北京时间"是等价的,存一个就够了。
具体到实现,我会在 Agent 的每轮交互后跑一个轻量的判断逻辑(可以用小模型,也可以用规则),决定这轮要不要触发记忆写入。写入时还要做去重和冲突检测:如果新记忆和已有记忆矛盾,要么更新旧的,要么标记冲突让人工介入。
3.4 记忆的遗忘机制:不是所有记忆都该永久保留
记忆系统必须有遗忘机制,否则会越用越慢、越用越乱。遗忘分两种:时间衰减和重要性淘汰。
时间衰减是指记忆的权重随时间降低。比如一条三个月前的临时偏好,权重应该比昨天的低。实现上可以给每条记忆加一个last_accessed和access_count字段,检索时按weight = base_weight * decay(now - last_accessed) * log(access_count + 1)排序。这样经常被访问的记忆权重高,长期不用的自然沉底。
重要性淘汰是指定期清理低价值记忆。可以设一个阈值,权重低于阈值的记忆归档或删除。但要注意,有些记忆虽然不常访问,但一旦需要就非常关键(比如用户的紧急联系人),这类要打上pinned标记,不参与淘汰。
我踩过的一个坑是:早期没做遗忘机制,跑了两个月后记忆库里有几万条记录,检索延迟从 50ms 涨到了 800ms,而且召回质量明显下降。加上衰减和淘汰之后,记忆库稳定在几千条,延迟回到 60ms 左右。
4. 实操落地:从零搭一套 hindsight 记忆服务
4.1 环境准备:Docker 与依赖组件
先把基础环境搭起来。假设你在 Ubuntu 或者 macOS 上,Windows 用户建议用 WSL2,因为 Docker Desktop 在 Windows 上的文件系统性能会拖慢向量数据库的读写。
安装 Docker 的步骤不复杂,但有几个点要注意。Ubuntu 上建议用官方脚本装,不要用 apt 里的老版本:
curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh sudo usermod -aG docker $USER最后一行是把当前用户加入 docker 组,避免每次都要 sudo。执行完要重新登录才生效。验证安装:
docker --version docker compose version如果docker compose version报错,说明 compose 插件没装,单独装一下:
sudo apt-get install docker-compose-pluginWindows 用户如果用 Docker Desktop,可能会遇到 "Virtualization support not detected" 这个报错。这通常是 BIOS 里的虚拟化没开,进 BIOS 打开 VT-x 或 AMD-V 就行。另一个常见问题是 WSL2 没装,Docker Desktop 启动时会提示,按提示装完重启即可。
4.2 docker-compose 编排:一次起停整个记忆栈
下面是我在项目里用的 compose 配置,包含向量数据库 Qdrant、关系数据库 Postgres、缓存 Redis,以及记忆服务本体。你可以直接拿去改。
version: "3.9" services: qdrant: image: qdrant/qdrant:latest ports: - "6333:6333" - "6334:6334" volumes: - ./data/qdrant:/qdrant/storage restart: unless-stopped postgres: image: postgres:16 environment: POSTGRES_USER: memory POSTGRES_PASSWORD: memory_pass POSTGRES_DB: hindsight ports: - "5432:5432" volumes: - ./data/postgres:/var/lib/postgresql/data restart: unless-stopped redis: image: redis:7-alpine ports: - "6379:6379" volumes: - ./data/redis:/data command: redis-server --appendonly yes restart: unless-stopped memory-service: build: ./memory-service ports: - "8080:8080" environment: QDRANT_URL: http://qdrant:6333 POSTGRES_DSN: postgresql://memory:memory_pass@postgres:5432/hindsight REDIS_URL: redis://redis:6379/0 EMBEDDING_MODEL: text-embedding-3-small depends_on: - qdrant - postgres - redis restart: unless-stopped几个配置上的经验。Qdrant 的 6333 是 HTTP 端口,6334 是 gRPC 端口,两个都映射出来方便调试。Postgres 的密码别用默认的,生产环境一定要改。Redis 开了 appendonly,保证重启后数据不丢。memory-service 用 build 而不是 image,是因为记忆服务的逻辑通常要自己写,后面会讲。
启动命令:
docker compose up -d docker compose logs -f memory-service如果某个服务起不来,先看日志。最常见的是端口冲突,改一下映射端口就行。
4.3 记忆服务的核心代码:写入与检索
记忆服务本体我用 Python 写,框架用 FastAPI,因为它轻量、异步支持好。核心是两个接口:/memory/write和/memory/query。
先看写入逻辑。写入时要做的处理包括:内容分块、embedding 生成、key 提取、去重检测。
from fastapi import FastAPI from pydantic import BaseModel from qdrant_client import QdrantClient from qdrant_client.models import PointStruct, VectorParams, Distance import uuid, hashlib app = FastAPI() qdrant = QdrantClient(url="http://qdrant:6333") COLLECTION = "episodic_memory" # 初始化 collection if not qdrant.collection_exists(COLLECTION): qdrant.create_collection( collection_name=COLLECTION, vectors_config=VectorParams(size=1536, distance=Distance.COSINE), ) class MemoryWrite(BaseModel): content: str key: str session_id: str importance: float = 1.0 @app.post("/memory/write") async def write_memory(req: MemoryWrite): # 1. 生成 embedding vector = await embed(req.content) # 2. 内容哈希做去重 content_hash = hashlib.sha256(req.content.encode()).hexdigest() # 3. 写入 Qdrant point_id = str(uuid.uuid4()) qdrant.upsert( collection_name=COLLECTION, points=[PointStruct( id=point_id, vector=vector, payload={ "content": req.content, "key": req.key, "session_id": req.session_id, "importance": req.importance, "content_hash": content_hash, } )] ) return {"id": point_id, "status": "ok"}检索逻辑要复杂一些,因为要做混合检索和权重排序:
class MemoryQuery(BaseModel): query: str session_id: str top_k: int = 5 @app.post("/memory/query") async def query_memory(req: MemoryQuery): query_vector = await embed(req.query) # 向量检索 hits = qdrant.search( collection_name=COLLECTION, query_vector=query_vector, limit=req.top_k * 3, query_filter={ "must": [{"key": "session_id", "match": {"value": req.session_id}}] } ) # 权重排序:相似度 * 重要性 * 时间衰减 import math, time scored = [] for h in hits: age_days = (time.time() - h.payload.get("created_at", time.time())) / 86400 decay = math.exp(-age_days / 30) # 30天半衰期 score = h.score * h.payload.get("importance", 1.0) * decay scored.append((score, h.payload["content"])) scored.sort(reverse=True) return {"memories": [c for _, c in scored[:req.top_k]]}这段代码里几个关键点。top_k * 3是先多召回一些,再精排,避免精排后数量不够。session_id过滤保证只检索当前会话相关的记忆,跨会话的 semantic memory 走另一个 collection。时间衰减用指数函数,30 天半衰期是我调出来的经验值,太短会导致老记忆快速失效,太长又起不到淘汰作用。
4.4 接入 MCP:让 Agent 通过标准协议调用记忆
记忆服务跑起来之后,要把它暴露成 MCP server,这样任何支持 MCP 的 Agent 都能调用。MCP server 的实现可以用官方 SDK,Python 版是mcp包。
from mcp.server import Server from mcp.server.stdio import stdio_server from mcp.types import Tool, TextContent import httpx server = Server("hindsight-memory") MEMORY_API = "http://localhost:8080" @server.list_tools() async def list_tools(): return [ Tool( name="write_memory", description="写入一条长期记忆", inputSchema={ "type": "object", "properties": { "content": {"type": "string"}, "key": {"type": "string"}, "session_id": {"type": "string"}, }, "required": ["content", "key", "session_id"], }, ), Tool( name="query_memory", description="检索相关记忆", inputSchema={ "type": "object", "properties": { "query": {"type": "string"}, "session_id": {"type": "string"}, }, "required": ["query", "session_id"], }, ), ] @server.call_tool() async def call_tool(name: str, arguments: dict): async with httpx.AsyncClient() as client: if name == "write_memory": r = await client.post(f"{MEMORY_API}/memory/write", json=arguments) elif name == "query_memory": r = await client.post(f"{MEMORY_API}/memory/query", json=arguments) return [TextContent(type="text", text=r.text)] async def main(): async with stdio_server() as (read, write): await server.run(read, write, server.create_initialization_options()) if __name__ == "__main__": import asyncio asyncio.run(main())这个 MCP server 用 stdio 传输,适合本地 Agent 调用。如果要给远程 Agent 用,可以改成 SSE 或 streamable HTTP 传输。配置到 Agent 里的时候,在 MCP 配置文件里加上:
{ "mcpServers": { "hindsight": { "command": "python", "args": ["/path/to/memory_mcp_server.py"] } } }Agent 启动后就能看到write_memory和query_memory两个工具,在需要的时候自动调用。
4.5 记忆写入的触发策略:规则 + 模型双保险
前面讲了记忆服务的接口,但"什么时候调用写入"这个决策要在 Agent 侧做。我的方案是规则和模型结合。
规则层负责明显该记的情况:用户明确说"记住"、"以后都这样"、"我的偏好是"这类触发词,直接写入。会话结束时,把整段对话做一次摘要,摘要结果写入 episodic memory。
模型层负责模糊情况:每轮对话后,用一个轻量模型(比如小参数量的开源模型)判断这轮是否包含值得长期记忆的信息。判断的 prompt 大概是:
判断以下对话是否包含值得长期记忆的用户信息。 值得记忆的标准:可复用、稳定、非显然。 输出 JSON:{"should_remember": true/false, "key": "...", "content": "..."} 对话内容:{conversation}这个判断逻辑用规则也能做,但模型判断的准确率更高,尤其是处理隐含信息的时候。我实测下来,模型判断的准确率在 85% 左右,加上规则兜底,整体能到 95% 以上。
5. 常见问题与排查技巧实录
5.1 记忆检索召回不准的排查思路
召回不准是最常见的问题,表现是 Agent 明明该记得的东西却想不起来。排查按这个顺序走。
先看记忆有没有写进去。直接查 Qdrant 的 collection,看记录数对不对。如果写入接口返回 ok 但 collection 里没有,多半是 collection 名写错了或者 embedding 维度不匹配。
再看检索的过滤条件。session_id过滤是最容易出问题的,如果写入时用的 session_id 和检索时不一致,就永远查不到。我踩过一次坑:写入用的是用户 ID,检索用的是会话 ID,结果跨会话的记忆全查不到。统一用会话 ID 之后就好了。
然后看embedding 模型是否一致。写入和检索必须用同一个 embedding 模型,否则向量空间不对齐,相似度计算全是乱的。这个错误很隐蔽,因为不会报错,只是结果不准。
最后看权重排序。如果相似度高的记忆被重要性或时间衰减压下去了,也会表现为召回不准。可以临时把权重都设成 1.0 测试,确认是排序问题还是检索问题。
5.2 Docker 环境下的典型故障速查
| 现象 | 可能原因 | 排查方法 | 解决 |
|---|---|---|---|
| 容器起不来,日志报端口占用 | 宿主机端口被占 | lsof -i:6333 | 改映射端口或停掉占用进程 |
| 容器间网络不通 | 不在同一 network | docker network inspect | 用 compose 默认网络或手动指定 |
| Qdrant 数据丢失 | volume 没挂载 | docker inspect看 Mounts | 补上 volume 配置 |
| Postgres 连接超时 | 密码或库名不对 | 进容器psql手动连 | 核对环境变量 |
| 内存服务 OOM | 向量检索内存占用高 | docker stats | 限制 top_k,加内存或换量化索引 |
| Windows 上文件读写慢 | WSL2 跨文件系统 | 看 IO 延迟 | 数据卷放在 WSL2 内部路径 |
这张表是我实际遇到过的故障汇总,基本覆盖了 90% 的部署问题。其中"容器间网络不通"最容易被忽略,因为 compose 默认会创建一个 network,所有服务都在里面,但如果手动docker run起某个服务,就会掉到默认 bridge 网络里,跟 compose 的服务不通。
5.3 记忆冲突和过期的处理
记忆冲突是指新记忆和旧记忆矛盾。比如用户先说"我用 Python",后来说"我改用 Go 了"。如果不处理,检索时可能同时召回两条,Agent 就懵了。
我的处理方式是版本化 + 软删除。每条记忆带一个version和superseded_by字段。写入新记忆时,先检索是否有同 key 的旧记忆,如果有,把旧记忆标记superseded_by = 新记忆 ID,检索时过滤掉被 supersede 的。这样历史记录还在,但不会干扰当前决策。
过期处理类似,给记忆加expires_at字段,检索时过滤掉已过期的。对于没有明确过期时间的记忆,用前面讲的时间衰减来软性淘汰。
5.4 性能优化的几个实操技巧
记忆服务跑久了会变慢,优化有几个方向。
索引优化。Qdrant 默认用 HNSW 索引,参数m和ef_construct影响检索速度和召回率。m越大召回越好但内存占用越高,我一般设 16。ef_construct设 100 左右。检索时的hnsw_ef参数控制精度和速度的权衡,设 64 是个不错的平衡点。
批量写入。如果记忆写入频繁,单条 upsert 会很慢。改成批量,一次写 100 条,吞吐能提升 5-10 倍。
缓存热点记忆。经常被检索的记忆放 Redis 缓存,命中缓存直接返回,不走向量检索。缓存 key 用 query 的哈希,TTL 设 5 分钟。我实测下来,缓存命中率在 30% 左右,整体延迟降低 40%。
异步写入。记忆写入不需要同步等待,可以丢到消息队列里异步处理。这样 Agent 的响应速度不受写入影响。用 Redis 的 list 做简单队列就行,不用上 Kafka 那么重。
5.5 安全与隐私的注意事项
记忆系统存的是用户数据,隐私问题必须重视。几个基本要求。
敏感信息过滤。写入前要过一遍敏感信息检测,密码、身份证号、银行卡号这类绝对不能存。可以用正则加模型双重检测。
加密存储。Qdrant 和 Postgres 的数据落盘要加密,至少用磁盘级加密。传输层用 TLS。
访问控制。记忆服务的接口要有鉴权,不能裸奔。MCP server 的 token 要定期轮换。
数据删除。用户要求删除数据时,要能彻底删干净,包括向量库、关系库、缓存里的所有副本。这个要在设计时就考虑,别等用户投诉了才补。
6. 记忆系统的扩展方向与个人实践体会
hindsight 这类记忆框架搭起来之后,能扩展的方向其实很多。我自己试过几个,分享两个比较有价值的。
一个是记忆的可视化。把记忆库里的条目按 key 分类、按时间轴展示,能直观看到 Agent 记住了什么、哪些记忆被频繁调用。这个对调试和优化特别有用,我做了个简单的 Web 界面,用 Qdrant 的 scroll API 拉数据,前端用表格加时间线展示,半天就能做出来。
另一个是记忆的迁移和共享。同一个用户在不同 Agent 之间的记忆如果能共享,体验会好很多。比如用户在客服 Agent 里说过偏好中文,在代码助手 Agent 里也应该默认中文。实现上就是让多个 Agent 共用同一个记忆服务,用 user_id 而不是 session_id 做跨会话检索的 key。
我个人在实际操作中的体会是,记忆系统的难点不在技术,而在产品判断:什么该记、什么该忘、什么时候该主动回忆。这些决策没有标准答案,要靠实际使用中不断调整。我建议刚开始做的时候,宁可记少一点,把召回准确率做上去,再逐步扩大记忆范围。记忆太多太杂,比没有记忆更糟糕,因为错误的记忆会误导 Agent 做出错误决策。
最后分享一个小技巧:给记忆加一个source字段,记录这条记忆是从哪轮对话、哪个任务来的。排查问题时能快速定位到原始上下文,比只看记忆内容高效得多。这个字段在早期设计时加上,后期能省很多事。