
1. 为什么事后复盘这件事值得单独做成一个项目第一次看到 hindsight 这个词被拿来命名一个技术项目我脑子里蹦出来的不是词典释义而是每次线上事故复盘会上那种要是当时就知道就好了的懊恼。Hindsight 直译是后见之明但放在 agent memory 和 LLM 这个语境里它其实指向一个非常具体、非常痛的问题智能体在完成任务之后能不能把这次经历沉淀下来让下一次做得更好。我接触过不少做 agent 的团队大家一开始都把精力砸在 prompt 调优、工具编排、模型选型上等到系统跑起来、用户量上来才发现真正卡脖子的不是这一轮回答得好不好而是上一轮学到的东西有没有留下来。一个客服 agent 今天被用户纠正了某个退款政策的边界明天换个会话又犯同样的错一个代码 agent 这次踩过的依赖冲突坑下次遇到同样的报错还是从头试错。这就是典型的没有 hindsight——每次都在裸奔每次都在重复交学费。Hindsight 这个项目要解决的核心问题就是给 LLM-based agent 装上一套可检索、可演化、可防御的长期记忆系统。它不是一个简单的向量数据库封装而是把 agent 的 working memory工作记忆、episodic memory情景记忆和 semantic memory语义记忆串成一条闭环任务执行时写入任务结束后提炼下次任务开始前召回。配合 MCP 协议做工具层的标准化接入用 Docker 做环境隔离和快速部署整套东西是可以直接跑在本地或者小规模集群上的。这篇文章适合谁看如果你正在做 agent 应用被记忆这件事折磨过如果你听说过 MCP 但还没搞明白它跟 agent memory 到底怎么配合如果你想把 LLM wiki 那套知识库思路落地成一个能跑的系统——那这篇就是写给你的。我会从设计思路讲到实操部署把踩过的坑和参数选择的理由都摊开说尽量让你看完能直接抄作业。2. 整体设计思路hindsight 到底在架构上做了什么取舍2.1 从无状态调用到有状态沉淀的范式转变大部分人对 LLM 的直觉是无状态的你给它一段上下文它给你一个输出会话结束一切归零。这种模式在单轮问答里没问题但一旦进入多轮、多任务、跨会话的场景问题就暴露了。Agent 需要记住的不只是刚才聊了什么还包括我上次是怎么解决这类问题的、用户对哪类回答明显不满意、哪个工具在什么条件下会失败。Hindsight 的设计出发点就是把 agent 从无状态函数改造成有状态实体。它的记忆分层大致是这样的记忆类型存什么生命周期典型用途Working Memory当前任务的中间状态、临时变量单次任务任务内的上下文维持Episodic Memory具体某次任务的完整轨迹中长期复盘、相似任务召回Semantic Memory从多次经历中提炼的规律长期决策依据、策略优化Guard Memory被标记为危险/错误的模式长期主动防御、避免重蹈覆辙这个分层不是拍脑袋定的它对应的是认知科学里比较成熟的一套记忆模型。Working memory 容量小、易失负责现在正在干什么episodic memory 记录发生过什么semantic memory 沉淀我知道了什么。Hindsight 把这套模型工程化用不同的存储后端和检索策略来支撑。2.2 为什么选 MCP 作为工具接入层MCPModel Context Protocol这两年被讨论得很多但很多人对它的理解还停留在又一个协议。我的看法是MCP 真正的价值在于把 agent 和外部能力之间的接口标准化了。在没有 MCP 之前你要给 agent 接一个数据库、接一个浏览器、接一个代码执行环境每个都得写一套适配层工具描述格式、调用约定、错误处理全都不一样。MCP 把这些统一成服务端暴露能力客户端按协议调用的模式。Hindsight 用 MCP 做记忆系统的接入层好处很直接记忆的读写、检索、更新都可以封装成 MCP server 暴露的工具agent 侧不需要关心底层是向量库还是图数据库只需要按协议调用。这样一来你换存储后端、加新的记忆类型对 agent 都是透明的。而且 MCP 生态里已经有大量现成的 server比如 playwright mcp、burpsuite mcp 这类hindsight 可以和它们共存形成记忆 工具的完整能力矩阵。提示MCP 是软件协议层面的概念和硬件协议不是一回事。它的核心是定义了一套基于 JSON-RPC 的通信规范让模型侧和工具侧能解耦。2.3 Docker 化部署的考量把 hindsight 跑在 Docker 里不是为了赶时髦。Agent memory 系统涉及多个组件向量数据库、关系型数据库存元数据、缓存、MCP server、可能还有 embedding 服务。这些组件版本依赖复杂本地直接装很容易出现在我机器上能跑的尴尬。Docker Compose 把这些服务编排在一起网络、卷、环境变量一次配好换台机器docker compose up就能复现。另一个现实原因是隔离。记忆系统里存的是 agent 的历史轨迹可能包含敏感的业务数据。用容器做隔离至少在网络和文件系统层面有个边界。当然生产环境还需要更严格的权限控制但开发和小规模部署阶段Docker 是性价比最高的选择。3. 核心细节拆解记忆的写入、提炼与召回3.1 写入阶段什么该记什么不该记记忆系统最容易犯的错是什么都记。我见过一个团队把 agent 的每一轮对话原文全塞进向量库结果检索时噪声大到没法用召回的内容一半是寒暄一半是重复。Hindsight 在写入阶段做了几层过滤这个思路值得借鉴。第一层是显著性判断。不是所有交互都值得进长期记忆。一次成功的、符合预期的常规操作价值很低一次失败、一次用户纠正、一次工具报错价值很高。可以用一个轻量的打分机制来判断任务是否成功、用户是否有负面反馈、是否触发了重试、是否出现了新工具组合。分数超过阈值的才进入 episodic memory。第二层是结构化抽取。原始对话文本直接存进去检索效率低。Hindsight 会把一次任务轨迹抽成结构化字段任务目标、使用的工具序列、关键决策点、最终结果、失败原因如果有。这样检索时可以按字段过滤而不是全靠语义相似度。第三层是去重与合并。相似度超过阈值的记忆条目要合并否则同一个坑记十遍检索时全是冗余。这里要注意阈值不能设太高否则该区分的场景被合并了也不能太低否则去重形同虚设。我的经验是余弦相似度 0.92 到 0.95 之间比较合适具体要看 embedding 模型。3.2 提炼阶段从情景记忆到语义记忆Episodic memory 记录的是某一次发生了什么semantic memory 要回答的是一般来说应该怎么做。这个提炼过程是 hindsight 最有技术含量的部分也是最容易做砸的部分。常见的做法是定期跑一个反思任务把最近积累的一批 episodic memory 喂给 LLM让它总结出规律性的结论。比如当用户询问退款政策且订单状态为已发货时需要先确认物流状态再回答这种规则。这些结论写入 semantic memory作为后续决策的先验。这里有几个坑。一是过度泛化LLM 很容易从两三个案例里总结出过于宽泛的规则导致后续误用。解决办法是要求提炼时必须附带支撑案例的引用规则和案例绑定存储召回时一起返回让 agent 自己判断适用性。二是规则冲突新提炼的规则可能和旧规则矛盾。Hindsight 的处理方式是保留版本历史冲突时以更新的、支撑案例更多的规则优先但不删除旧规则留作审计。三是提炼频率。跑太勤浪费算力跑太懒记忆陈旧。我的建议是按记忆条数触发比如每积累 50 条新的 episodic memory 触发一次提炼而不是按固定时间。这样在低负载时不会空跑高负载时也不会积压。3.3 召回阶段token 预算下的取舍召回是记忆系统直接面向 agent 的环节也是最考验工程能力的地方。核心矛盾是记忆库很大但 LLM 的上下文窗口有限token 是要花钱的。你不可能把相关记忆全塞进去。Hindsight 的召回策略是多路召回 重排 预算裁剪。多路召回指同时用几种方式找候选语义相似度检索、关键词检索、按时间近因检索、按任务类型过滤。每路取 top-K合并成候选集。然后用一个重排模型可以是小型的 cross-encoder也可以直接用 LLM 打分对候选排序。最后按 token 预算从高到低填充填满为止。这里有个细节值得说召回的不只是成功经验还有失败教训。很多人做记忆系统只记成功案例结果 agent 反复踩同样的坑。Hindsight 把失败案例单独标记召回时如果当前任务和某个失败案例高度相似会优先把这条路走不通的警告推给 agent。这就是 a-memguard 那类主动防御框架的思路——不是等出错了再补救而是在决策前就提示风险。注意召回时给记忆条目标注来源和时间戳很重要。Agent 需要知道这条记忆是三天前的还是三个月前的是来自自己还是来自其他 agent这直接影响它的可信度判断。4. 实操部署从零把 hindsight 跑起来4.1 环境准备与 Docker 安装要点先说环境。Hindsight 依赖 Docker 做容器编排所以第一步是把 Docker 装好。Windows 用户装 Docker Desktop 时最常见的报错是 Virtualization support not detected这个基本是 BIOS 里虚拟化没开进 BIOS 把 Intel VT-x 或 AMD-V 打开就行。另一个常见问题是 WSL2 没装或版本太旧Docker Desktop 现在默认走 WSL2 后端建议先把 WSL2 更新到最新。Linux 用户相对简单用官方脚本或者包管理器装都行。装完记得把当前用户加进 docker 组否则每条命令都要 sudosudo usermod -aG docker $USER newgrp docker验证安装docker --version docker compose version docker run hello-worldhello-world能跑通说明 Docker 引擎和网络都没问题。如果卡在拉镜像检查一下镜像源配置国内环境建议配一个可用的 registry mirror。4.2 用 Docker Compose 编排记忆系统组件Hindsight 的核心组件我建议这样编排一个向量数据库Qdrant 或 Milvus 都行Qdrant 轻量些适合起步、一个 PostgreSQL 存结构化元数据、一个 Redis 做缓存和任务队列、一个 MCP server 容器暴露记忆接口。version: 3.9 services: qdrant: image: qdrant/qdrant:latest ports: - 6333:6333 volumes: - ./data/qdrant:/qdrant/storage restart: unless-stopped postgres: image: postgres:16 environment: POSTGRES_USER: hindsight POSTGRES_PASSWORD: change_me_please 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 restart: unless-stopped mcp-memory: build: ./mcp-memory depends_on: - qdrant - postgres - redis environment: QDRANT_URL: http://qdrant:6333 DATABASE_URL: postgresql://hindsight:change_me_pleasepostgres:5432/hindsight REDIS_URL: redis://redis:6379/0 ports: - 8080:8080 restart: unless-stopped几个参数选择的理由。Qdrant 的存储卷一定要挂出来否则容器重建记忆就没了。Postgres 密码别用默认的即使是本地环境养成习惯。Redis 用 alpine 版本体积小做缓存够用。MCP server 用 build 而不是现成镜像是因为你大概率要改配置本地构建方便迭代。启动docker compose up -d docker compose logs -f mcp-memory看到 MCP server 打印出监听端口和已连接的后端就说明起来了。4.3 MCP 连接配置与验证MCP server 起来之后要在 agent 侧配置连接。不同客户端的配置方式不一样但核心都是填 server 的地址和认证信息。以常见的配置格式为例{ mcpServers: { hindsight-memory: { url: http://localhost:8080/mcp, transport: http } } }有些客户端走 stdio 传输那就需要把 server 作为子进程启动配置里写 command 和 args。走 HTTP 的好处是可以远程连接多个 agent 共享同一套记忆。验证连接是否正常最直接的办法是调用一个记忆写入工具然后调用检索工具看能不能查回来。Hindsight 的 MCP server 一般会暴露这几个工具memory_write、memory_search、memory_reflect、memory_forget。写一条测试记忆{ tool: memory_write, arguments: { type: episodic, task: 测试记忆写入, content: 这是一条测试记忆用于验证 MCP 连接, tags: [test, setup] } }然后检索{ tool: memory_search, arguments: { query: 测试记忆, top_k: 3 } }能返回刚才写的那条链路就通了。如果返回空先查 MCP server 日志再看向量库有没有数据逐层排查。4.4 记忆写入与召回的完整代码示例光有 MCP 工具还不够agent 侧要有一套调用逻辑。下面是一个简化的 Python 示例展示任务执行前后怎么和记忆系统交互import httpx import json MCP_ENDPOINT http://localhost:8080/mcp def call_mcp(tool_name, arguments): payload { jsonrpc: 2.0, method: tools/call, params: {name: tool_name, arguments: arguments}, id: 1 } resp httpx.post(MCP_ENDPOINT, jsonpayload, timeout30) return resp.json() def recall_before_task(task_description): result call_mcp(memory_search, { query: task_description, top_k: 5, include_failures: True }) memories result.get(result, {}).get(memories, []) context \n.join([m[content] for m in memories]) return context def write_after_task(task, trajectory, success, failure_reasonNone): call_mcp(memory_write, { type: episodic, task: task, trajectory: trajectory, success: success, failure_reason: failure_reason, timestamp: auto }) task 帮用户查询订单退款状态 prior recall_before_task(task) print(召回的历史记忆, prior) trajectory [调用订单查询工具, 调用物流查询工具, 生成回答] write_after_task(task, trajectory, successTrue)这段代码的关键点在recall_before_task里带了include_failuresTrue确保失败教训也能被召回。write_after_task里记录了完整的工具调用轨迹而不只是最终答案这样后续提炼时才有足够信息。5. 常见问题与排查技巧实录5.1 记忆检索召回不准的排查路径召回不准是最常见的问题表现是明明记过就是查不出来或者查出来一堆不相关的。排查要按层次来。先看 embedding 模型。如果 embedding 模型和写入时用的不是同一个向量空间对不上检索必然失效。检查配置里 embedding 模型名称和维度是否一致。维度不匹配的话向量库通常会直接报错但模型换了维度没变的情况更隐蔽需要看模型版本。再看分块策略。如果一条记忆太长被切成多块检索时可能只召回其中一块上下文不完整。Hindsight 里建议单条记忆控制在 500 到 1000 token超长的任务轨迹要按决策点切分而不是按固定长度硬切。然后看过滤条件。如果检索时加了 tag 过滤或者时间范围过滤条件太严会把相关记忆挡在外面。调试时先把过滤条件全去掉确认基础检索没问题再逐步加回过滤。最后看相似度阈值。阈值设太高召回少设太低噪声多。建议先用 0.7 作为起点根据实际效果调整。不同 embedding 模型的相似度分布不一样没有万能值。现象可能原因排查方法完全查不到embedding 模型不一致对比写入和检索的模型配置召回不相关阈值过低或分块过粗提高阈值细化分块召回不完整分块切断上下文按语义边界重新分块时好时坏缓存不一致清 Redis 缓存重试延迟很高向量库索引未优化检查索引类型和参数5.2 Docker 网络不通的典型场景Docker 网络问题在记忆系统部署里特别常见因为涉及多个容器互相通信。最典型的是容器内用localhost访问另一个容器这必然失败——每个容器有自己的网络命名空间localhost指向容器自己。正确做法是用 Compose 里的服务名做主机名。比如 MCP server 连 Qdrant配置里写http://qdrant:6333而不是http://localhost:6333。Compose 会自动创建网络并把服务名解析到对应容器 IP。另一个坑是端口映射和容器内端口的混淆。ports: 6333:6333是把容器 6333 映射到宿主机 6333宿主机上的其他程序用localhost:6333访问没问题但容器之间通信不走这个映射直接走容器网络。如果容器之间 ping 不通检查是不是在同一个 network 里。Compose 默认会创建一个网络给所有服务但如果你手动指定了 network 或者用了network_mode: host就可能出问题。用docker network inspect看容器挂在哪个网络。5.3 记忆膨胀与性能衰减的应对系统跑一段时间后记忆库会越来越大检索变慢成本上升。这是必然的关键是怎么控制。第一招是分层存储。热记忆最近一周、高频访问放内存或 SSD 上的向量库冷记忆归档到对象存储需要时再加载。Hindsight 可以配置 TTL超过一定时间没被访问的记忆自动降级。第二招是主动遗忘。不是所有记忆都值得永久保留。低价值、重复、过时的记忆要定期清理。memory_forget工具就是干这个的可以按 tag、按时间、按访问频率批量清理。但清理要谨慎建议先标记为待清理观察一段时间确认没有影响再真删。第三招是摘要压缩。把多条相关的细粒度记忆合并成一条摘要记忆减少条目数。比如同一个任务的十次执行轨迹可以压缩成一条该任务的标准流程和常见变体。压缩会损失细节所以原始记忆可以归档而不是删除需要时能追溯。提示记忆清理一定要有审计日志。删了什么、什么时候删的、为什么删都要记下来。否则出了问题没法回溯。5.4 几个我踩过的坑第一个坑是embedding 服务不稳定导致写入失败。早期我没做重试embedding API 偶尔超时那条记忆就丢了而且 agent 不知道丢了。后来加了写入队列和重试机制失败的任务进死信队列定期人工检查。第二个坑是并发写入导致重复。多个 agent 同时写相似记忆去重逻辑在并发下失效产生重复条目。解决办法是在写入路径上加分布式锁或者用向量库的 upsert 语义按内容哈希去重。第三个坑是提炼任务把 LLM 配额吃光。反思提炼很耗 token如果和主业务共用同一个 API key高峰期会把配额抢光。建议提炼任务用独立的 key 或者独立的模型小模型做初筛大模型做精炼并且限制并发。第四个坑是时区问题。记忆的时间戳如果时区不统一按时间检索会错乱。统一用 UTC 存储展示时再转本地时区。6. 记忆系统的演进方向与扩展思路6.1 从单 agent 记忆到多 agent 共享记忆单 agent 的记忆系统跑通之后自然会想到多 agent 场景。多个 agent 共享一套记忆好处是经验可以复用——A agent 踩过的坑B agent 不用再踩。但挑战也大记忆的归属、权限、冲突怎么处理。Hindsight 的思路是给记忆打上来源标签检索时可以按来源过滤。共享记忆和私有记忆分开存储共享区的内容需要经过审核才能写入避免污染。冲突处理上不同 agent 对同一问题的不同结论都保留检索时按 agent 的可信度加权。6.2 结合知识库做混合检索纯向量检索有局限特别是涉及精确匹配、数值范围、结构化查询时。把 hindsight 和 LLM wiki 那类知识库结合做混合检索效果会好很多。知识库提供权威的、结构化的领域知识记忆系统提供经验性的、情境化的历史轨迹两者互补。实现上可以用一个路由层先判断查询类型事实性问题走知识库经验性问题走记忆库混合问题两路都查然后融合。融合时要注意去重和排序避免同一信息出现两次。6.3 主动防御能力的强化a-memguard 那类框架强调主动防御这个方向值得深挖。现在的记忆系统大多是被动召回agent 问了才查。主动防御是在 agent 决策前就介入根据历史失败模式提示风险。具体做法是在 agent 的规划阶段插入一个检查点拿到 agent 的下一步计划后先用记忆库检索类似计划是否失败过如果匹配到高风险模式就把警告注入上下文。这个检查点会增加延迟所以要用轻量模型或者缓存来加速。风险提示的措辞也很重要太强硬会干扰 agent 正常决策太弱又起不到作用需要反复调优。6.4 可观测性与评估记忆系统好不好不能靠感觉要有指标。我建议至少跟踪这几个召回命中率召回的记忆里有多少被 agent 实际使用、记忆利用率写入的记忆有多少被召回过、任务成功率的变化趋势、平均召回延迟、token 消耗。这些指标要能按时间、按任务类型、按 agent 维度下钻。没有可观测性优化就是盲人摸象。Hindsight 的 MCP server 可以暴露 metrics 接口接上 Prometheus 和 Grafana 就能看板化。评估记忆系统的效果最靠谱的还是 A/B 测试一组 agent 带记忆一组不带跑同样的任务集对比成功率和效率。这个测试要跑足够多的样本才有统计意义别跑十个任务就下结论。7. 一些实操心得部署 hindsight 这套东西我最大的体会是别一上来就追求完美。记忆系统的价值是随数据积累逐渐显现的前期数据少的时候检索效果可能还不如直接把最近几轮对话塞进上下文。所以起步阶段可以先用最简单的方案一个向量库加一个写入检索接口跑起来积累数据观察问题再逐步加提炼、加防御、加多 agent 共享。另一个体会是记忆的质量比数量重要得多。我见过团队为了显得记忆丰富把各种边角料都往里塞结果检索质量一塌糊涂。宁可少记记精。写入时的过滤和结构化比检索时的各种技巧更能决定系统上限。还有就是别忘了给记忆系统本身做监控。它是个基础设施挂了会影响所有 agent。健康检查、告警、降级策略都要有。记忆系统不可用时agent 应该能降级到无记忆模式继续工作而不是直接崩掉。最后分享一个小技巧调试记忆检索时把召回的记忆和 agent 的实际输出一起打日志。这样你能直观看到召回了什么和用了什么之间的差距快速定位是召回问题还是使用问题。这个日志在优化阶段比任何指标都管用。