
1. 从“hindsight”这个词说起为什么记忆是 Agent 最被低估的能力“hindsight”这个词本身很有意思字面意思是“事后的洞察力”也就是我们常说的“后见之明”。放在 Agent 和 LLM 的语境里它指向一个非常具体、也非常要命的问题一个 Agent 在完成一轮任务之后能不能把这一轮里发生的事、踩过的坑、验证过的结论变成下一轮可以直接调用的经验。大多数人做 Agent 的时候注意力都放在“这一轮怎么把任务跑通”上——prompt 怎么写、工具怎么调、MCP 怎么接、Docker 怎么起。这些当然重要但真正决定一个 Agent 能不能从“玩具”变成“生产力”的是它在时间维度上的连续性。一个没有记忆的 Agent每次对话都是第一次见面你昨天教它的东西今天它忘得一干二净。这就是为什么agent memory、working memory、agent 存储这些词会反复出现在热词榜上。我先把结论摆在前面hindsight 这类项目的核心价值不是让 Agent 记住更多而是让 Agent 记住“对的东西”并且在正确的时机把它取出来。记住更多是存储问题记住对的、取对的是检索和建模问题。前者用 Docker 起个数据库就能解决后者才是真正拉开差距的地方。这篇文章适合三类人看第一类是自己动手搭过 Agent、被“失忆”问题折磨过的开发者第二类是在选型阶段想搞清楚 agent memory 到底该怎么设计的架构同学第三类是对 MCP、LLM 应用感兴趣想找一个具体项目把概念落地的人。我会围绕 hindsight 这个主题把记忆的存储、建模、检索、注入整条链路拆开讲中间穿插 Docker 部署、MCP 集成这些实操细节尽量做到看完能上手。需要先说明一点hindsight 这个标题本身给的信息很少正文和关键词都是空的所以我下面的内容是基于“agent memory LLM MCP Docker”这组热词所指向的典型场景做的合理推演和补全。凡是涉及具体实现的地方我都会说明这是基于常见工程实践的方案你可以根据自己的技术栈替换。2. Agent 记忆到底难在哪三个绕不过去的坎2.1 第一个坎什么该记什么该忘人脑的记忆不是录像机它是有选择性的。你今天早上吃了什么可能记不住但第一次被烫到的痛感能记一辈子。Agent 的记忆系统如果什么都记很快就会变成一个垃圾场——检索的时候噪声比信号还多反而拖累效果。我见过不少团队的做法是“全量存进向量库”对话历史一条不落全 embed 进去。刚开始效果还行因为数据量小检索出来的东西都相关。等到积累了几千条记录问题就来了用户问一个简单问题检索出来的十条里有八条是无关的寒暄。这时候模型要么被带偏要么直接忽略检索结果记忆系统形同虚设。所以 hindsight 要解决的第一个问题是记忆的筛选和压缩。哪些信息值得长期保留我的经验是看三个维度可复用性这个结论下次还会用到吗、不可推导性这个信息能不能从其他信息推出来、代价性重新获取这个信息的成本高不高。三个维度都高的比如“这个 API 的鉴权方式是 X踩过坑”必须记三个维度都低的比如“用户说了句你好”直接丢。2.2 第二个坎记忆怎么组织才能被“想起来”存进去容易取出来难。这里有个很反直觉的点记忆的检索质量很大程度上取决于写入时的组织方式而不是检索算法本身。你写入的时候如果只是把一段文本丢进向量库检索的时候也只能靠语义相似度硬匹配。但如果你写入的时候就把这段记忆结构化——它是什么类型、涉及哪些实体、发生在什么上下文、有效期到什么时候——检索的时候就能用多维度的条件去筛。这就是热词里llm ontology本体和llm 的 token 三个点 key/query/value指向的东西。本体论听起来很学术说白了就是给记忆定一套“分类和关系”的规则。比如一条记忆可以建模成(主体, 关系, 客体)的三元组或者更工程化一点建模成带类型标签的结构化记录。key 是“我是谁”这条记忆属于哪个 Agent、哪个用户、哪个会话query 是“我在找什么”当前任务的意图value 是“我能提供什么”这条记忆的实际内容。这三个点对齐了检索的命中率会明显不一样。2.3 第三个坎记忆怎么注入才不干扰当前推理检索出来一堆相关记忆怎么塞给模型也是个技术活。全塞进 system prompt 里token 消耗大不说还容易让模型分心。我的做法是分层注入最相关的几条作为“强提示”放在靠前的位置次相关的作为“背景知识”放在后面并且明确告诉模型“以下信息是历史经验供参考不一定适用于当前情况”。这个“不一定适用”的免责声明很重要否则模型会把历史记忆当成铁律遇到新情况也不知道变通。还有一个细节是记忆的时效性标注。一条三个月前的记忆和一条昨天的记忆可信度是不一样的。在注入的时候带上时间戳或者“新鲜度”标签模型在做判断时会更有分寸。这个技巧在agentpoison那类“记忆投毒”的安全场景里尤其关键——如果记忆可以被污染那么区分“可信记忆”和“待验证记忆”就是一道必要的防线。3. 用 Docker 把记忆服务跑起来从零到可用的完整路径3.1 为什么记忆服务要独立部署很多人一开始会把记忆逻辑写在 Agent 的主进程里用一个本地文件或者内存字典存着。小规模测试没问题但一旦要支持多 Agent、多会话、持久化就必须把记忆抽成一个独立的服务。原因有三个第一记忆的读写频率和 Agent 的推理频率不一样混在一起不好做资源隔离第二记忆服务需要独立的存储后端向量库 关系库跟 Agent 的运行环境解耦更灵活第三独立服务才能被多个 Agent 共享这也是tencentdb agent memory这类方案出现的背景——把记忆做成一个可复用的基础设施。用 Docker 部署的好处不用多说环境一致、启动快、迁移方便。下面我给一套基于常见实践的部署方案你可以照着改。3.2 一套可复用的 docker-compose 编排记忆服务通常需要两个存储一个存向量做语义检索一个存结构化数据做元数据过滤和关系查询。我用 PostgreSQL 加 pgvector 做向量存储用 Redis 做热数据的缓存和会话状态。这套组合的好处是运维成本低一个数据库把关系型和向量都管了。version: 3.9 services: memory-db: image: pgvector/pgvector:pg16 container_name: hindsight-db environment: POSTGRES_USER: hindsight POSTGRES_PASSWORD: hindsight_pass POSTGRES_DB: memory ports: - 5433:5432 volumes: - ./data/pg:/var/lib/postgresql/data healthcheck: test: [CMD-SHELL, pg_isready -U hindsight] interval: 10s timeout: 5s retries: 5 memory-cache: image: redis:7-alpine container_name: hindsight-cache ports: - 6380:6379 command: [redis-server, --appendonly, yes] volumes: - ./data/redis:/data memory-api: build: ./memory-api container_name: hindsight-api depends_on: memory-db: condition: service_healthy environment: DB_URL: postgresql://hindsight:hindsight_passmemory-db:5432/memory REDIS_URL: redis://memory-cache:6379/0 EMBED_MODEL: bge-m3 ports: - 8100:8000这里有几个我踩过坑的地方要提醒。端口映射我特意错开了默认端口5433、6380、8100因为开发机上很可能已经跑着别的 Postgres 和 Redis用默认端口会直接冲突报错信息还不明显排查半天。healthcheck 是必须的memory-api 依赖数据库就绪没有健康检查的话容器起来了但连不上库服务会一直重启。数据卷一定要挂出来否则容器一删记忆全没这在调试阶段是灾难。3.3 初始化向量扩展和表结构pgvector 装好之后第一件事是启用扩展然后建表。表结构的设计直接决定了后面检索的灵活性我建议至少包含这几个字段CREATE EXTENSION IF NOT EXISTS vector; CREATE TABLE memories ( id BIGSERIAL PRIMARY KEY, agent_id TEXT NOT NULL, session_id TEXT, mem_type TEXT NOT NULL, -- fact / preference / experience / tool_result content TEXT NOT NULL, embedding vector(1024), entities JSONB DEFAULT {}, -- 结构化实体用于精确过滤 importance REAL DEFAULT 0.5, -- 重要度写入时打分 created_at TIMESTAMPTZ DEFAULT now(), expires_at TIMESTAMPTZ, -- 可空表示永久有效 last_access TIMESTAMPTZ ); CREATE INDEX idx_mem_agent ON memories (agent_id, mem_type); CREATE INDEX idx_mem_embedding ON memories USING ivfflat (embedding vector_cosine_ops) WITH (lists 100);mem_type这个字段是我强烈建议加的。不同类型的记忆检索策略和注入方式都不一样。fact是客观事实检索时优先精确匹配preference是用户偏好注入时要放在显眼位置experience是踩坑经验适合在遇到相似任务时召回tool_result是工具调用结果通常有有效期过期就该清理。有了这个分类后面做检索的时候就能按类型加权而不是一锅乱炖。importance字段是写入时打的分可以用一个轻量的 LLM 调用或者规则来算。别小看这个分数它在检索排序时非常有用——同样语义相似度的两条记忆重要度高的应该优先返回。last_access用来做“冷热分离”长期没被访问的记忆可以降权甚至归档。3.4 启动顺序和常见报错处理按上面的编排正常启动顺序是 db → cache → api。如果你在 Windows 上跑 Docker Desktop有几个高频报错要提前知道。“virtualization support not detected”这个报错八成是 BIOS 里的虚拟化没开或者跟 Hyper-V/WSL2 冲突了需要在系统设置里确认虚拟化平台已启用。“docker network 不通”通常是容器间用了 localhost 而不是服务名记住在 compose 网络里容器之间要用 service name 通信比如memory-db而不是127.0.0.1。启动之后先验证数据库连通性docker exec -it hindsight-db psql -U hindsight -d memory -c \dx看到vector扩展在列表里说明 pgvector 装好了。如果没看到手动执行一次CREATE EXTENSION vector;。这一步没做的话后面建表会直接报type vector does not exist。4. 记忆的写入与检索把“后见之明”变成可调用的能力4.1 写入不是简单存文本而是一次结构化提炼前面说了检索质量取决于写入质量。所以写入这一步我建议不要直接把原始对话丢进去而是先过一遍“提炼”。提炼的逻辑可以是一个专门的 prompt让模型输出结构化的记忆条目。比如一轮任务结束后让模型回答几个问题这轮任务里有哪些结论是下次还能用的有哪些工具调用的参数是踩坑试出来的用户表达了哪些偏好提炼出来的每条记忆写入时带上mem_type、entities、importance。entities用 JSONB 存比如{tool: mysql, error: auth_failed}这样后面可以用entities {tool:mysql}做精确过滤比纯向量检索靠谱得多。这里有个经验写入时给记忆打“来源标签”。是用户明确说的还是模型推断的还是工具返回的来源不同可信度不同。用户明确说的偏好可信度最高模型推断的结论需要后续验证。这个标签在检索时可以作为一个过滤维度避免把模型的臆测当成事实反复使用。4.2 混合检索向量 关键词 元数据三路并行纯向量检索的问题是“语义相似但事实不符”。比如用户问“MySQL 怎么连”向量检索可能召回一条“PostgreSQL 连接失败”的记忆因为语义上都是“数据库连接”。这时候元数据过滤就派上用场了——先按entities里的tool字段过滤再在过滤后的子集里做向量检索。我的检索策略是三路并行然后融合检索路径适用场景权重建议向量语义检索模糊意图、经验类记忆0.5关键词/全文检索精确术语、工具名、错误码0.3元数据精确过滤指定 Agent、指定类型、时间范围0.2三路结果用 RRFReciprocal Rank Fusion融合比简单加权求和更稳因为它不依赖分数的绝对大小只看排名。融合之后再按importance和last_access做一次重排把重要且新鲜的记忆顶上来。4.3 检索结果怎么注入才不添乱检索出 top-k 之后注入方式我前面提了分层。具体实现上我会把记忆格式化成一段带标签的文本而不是直接拼原始内容[历史经验 | 可信度:高 | 3天前] MySQL 8.0 默认认证插件是 caching_sha2_password 旧客户端连接会报 auth 错误需要改用户认证方式。 [历史经验 | 可信度:中 | 15天前] 该用户偏好用 Docker Compose 管理服务不喜欢手动 docker run。这种格式的好处是模型能一眼看出每条记忆的“分量”。可信度高的直接采信可信度中的作为参考时间久的提醒自己可能过时了。实测下来这种带元信息的注入比裸文本注入模型对记忆的利用率明显更高也更少出现“被错误记忆带偏”的情况。还有一个技巧是控制注入的记忆条数。不是越多越好我一般控制在 3 到 5 条。超过 5 条模型的注意力会被稀释而且 token 成本上去了。如果检索出来很多条宁可只取最相关的几条也不要全塞。5. 接上 MCP让记忆成为 Agent 的“标准外设”5.1 MCP 到底解决了记忆集成的什么问题MCPModel Context Protocol这个词最近热度很高很多人搞不清楚它跟硬件协议的区别。简单说MCP 是一套软件层面的协议规定了“模型/Agent 怎么跟外部能力工具、数据源、服务对话”。它跟硬件协议比如 USB的类比关系是USB 规定了设备怎么插上电脑被识别MCP 规定了工具怎么被 Agent 发现和调用。对记忆系统来说MCP 的价值在于标准化。在没有 MCP 之前每个 Agent 框架接记忆服务都要写一套适配代码LangChain 一套、自研框架一套、IDE 插件又一套。有了 MCP记忆服务只要暴露成一个 MCP server任何支持 MCP 的客户端都能直接调用。这就是为什么热词里会出现codex 接入 mcp、dify 浏览器 mcp、hermes 接入 mcp这些组合——大家都在往这个标准上靠。5.2 把记忆服务包装成 MCP Server一个记忆 MCP server 通常暴露这么几个工具memory_write写入记忆、memory_search检索记忆、memory_forget删除/归档记忆。用 Python 的 MCP SDK 写起来不复杂核心是把前面那套写入和检索逻辑包一层。from mcp.server import Server from mcp.types import Tool, TextContent app Server(hindsight-memory) app.list_tools() async def list_tools(): return [ Tool( namememory_search, description检索历史记忆用于获取过往经验、用户偏好、工具调用结论, inputSchema{ type: object, properties: { query: {type: string}, agent_id: {type: string}, mem_type: {type: string}, top_k: {type: integer, default: 5} }, required: [query, agent_id] } ), Tool( namememory_write, description写入一条记忆需提供类型、内容和重要度, inputSchema{ type: object, properties: { agent_id: {type: string}, mem_type: {type: string}, content: {type: string}, importance: {type: number, default: 0.5} }, required: [agent_id, mem_type, content] } ) ]这里有个设计上的取舍要讲清楚工具描述description写得好不好直接决定模型会不会在正确的时机调用它。我见过很多 MCP server 的 description 写得含糊模型根本不知道什么时候该用。记忆检索的描述里我特意强调了“获取过往经验、用户偏好、工具调用结论”就是给模型明确的触发信号。实测下来描述越具体模型的调用时机越准。5.3 客户端接入时的授权和配置坑MCP 客户端接入的时候最常见的坑是授权配置。比如codex 接入 figma mcp 怎么授权、codex 无法找到 mcp这类问题本质都是配置没对齐。通用的排查思路是先确认 MCP server 进程起来了本地的话看端口远程的话看连通性再确认客户端的配置文件里 server 的启动命令或 URL 写对了最后确认授权 token 或环境变量传进去了。还有一个高频问题是工具名冲突。如果你同时接了多个 MCP server不同 server 暴露了同名工具客户端可能会混淆。解决办法是给工具名加前缀比如hindsight_memory_search避免跟别的 server 撞名。6. 记忆系统的运维清理、归档和防污染6.1 记忆不是只增不减定期清理是必须的一个长期运行的 Agent记忆库会越来越大。如果不做清理检索延迟会上升噪声也会变多。我的做法是设一个归档策略超过一定时间没被访问、且重要度低于阈值的记忆移到归档表或者直接删除。expires_at字段就是干这个的写入时如果知道这条记忆有有效期比如某个临时配置直接设过期时间到期自动清理。清理任务可以用一个定时脚本跑也可以做成数据库的定时任务。关键是清理要有日志删了什么、为什么删留个记录方便回溯。我有一次误删了一批重要记忆就是因为清理规则写得太激进把“重要度中等但访问频率低”的经验类记忆也清掉了。后来把规则改成“重要度低于 0.3 且 90 天未访问”才删就稳多了。6.2 防污染记忆系统也有安全边界agentpoison这类研究提醒我们记忆是可以被投毒的。如果攻击者能往记忆库里写入恶意内容Agent 后续的行为就可能被操纵。防御的思路有几层写入鉴权不是谁都能写、来源标记区分可信和不可信来源、内容审核写入前过一遍敏感内容检测、隔离不同信任级别的记忆分开存储检索时按级别过滤。对普通项目来说至少要做到“写入鉴权”和“来源标记”。记忆写入接口不能裸奔要有 token 或者会话校验。来源标记前面提过用户明确说的和模型推断的要分开检索时优先采信高可信来源。6.3 监控指标怎么知道记忆系统在正常工作记忆系统不像业务接口那样有明确的成功失败它的效果是“润物细无声”的。所以我建议盯几个指标检索命中率检索出来的记忆有多少被模型实际引用、记忆增长率每天新增多少条是否异常、检索延迟P95 延迟超过阈值要告警、冷记忆占比长期未访问的记忆比例太高说明写入质量有问题。其中“检索命中率”最难测但最有价值。我的做法是在注入记忆的时候让模型在回答里标注它用了哪几条记忆然后统计引用率。引用率低说明检索出来的东西不相关要回头优化写入和检索策略。7. 我在实际搭建中总结的几条经验第一先跑通最小闭环再优化检索。很多人一上来就纠结用哪个 embedding 模型、要不要上 rerank结果主流程都没通。我的建议是先用最简单的向量检索把“写入-检索-注入”跑通看到效果之后再逐步加元数据过滤、混合检索、重排。每一步优化都要有对比数据否则你不知道是变好了还是变差了。第二记忆的 value 不在“多”在“准”。我见过一个项目记忆库塞了几十万条检索出来一堆无关内容模型干脆忽略记忆自己瞎编。后来把写入策略收紧只保留高价值记忆库小了十倍效果反而好了。这跟做搜索是一个道理召回率重要但准确率更重要。第三给记忆加“保质期”思维。不是所有记忆都该永久保存。工具调用结果、临时配置、一次性结论这些都有时效性。写入时就判断好有效期比事后清理省事得多。第四MCP 是趋势但别为了 MCP 而 MCP。如果你的 Agent 是单体应用记忆逻辑直接内嵌也没问题不一定非要拆成 MCP server。MCP 的价值在多客户端复用和标准化如果你的场景不需要这些内嵌反而更简单。等技术栈需要扩展了再拆不迟。第五测试记忆系统要用“跨会话”的场景。单会话内的记忆测试不出问题因为上下文本来就在。真正考验记忆系统的是关掉会话重新开一个Agent 还能不能记得之前的关键信息。这个测试跑通了记忆系统才算及格。关于 hindsight 这个主题能展开的还有很多比如记忆的图结构建模、多 Agent 之间的记忆共享、记忆与 RAG 的边界划分。但核心思路是一致的让 Agent 拥有跨时间的连续性把每一次任务的“后见之明”沉淀成下一次的“先见之明”。这件事做扎实了Agent 才真正从“一次性工具”变成“越用越顺手的伙伴”。