ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Claude记忆增强:从零构建跨会话持久记忆层

Claude记忆增强:从零构建跨会话持久记忆层 1. 项目背景与实际痛点做 AI 应用开发的同行应该都有这种感觉Claude 的能力确实强但用起来总有那么一个地方让人抓狂——它不记事。不是说它没有上下文窗口而是每次对话结束它就把之前聊过的东西全忘干净了。你上午跟它确认过项目架构、敲定了 APIs 设计下午重新开一个会话它连你叫什么、在做什么项目、上次聊到哪里统统不记得。你只能把背景信息、需求文档、对话摘要一遍又一遍地粘贴进去。对话越长粘贴的内容越多真正用来干活的 context 反而被挤占了。这就像你招了一个能力很强的助手但这助手有失忆症每天早上上班都像第一天入职你得重新给他讲一遍公司业务、项目背景和你的习惯。时间长了你会发现大量精力消耗在同步信息上而不是真正推进项目。我做的这个 claude-mem 项目就是为了解决这个失忆问题。它的核心目标很明确给 Claude 加一层持久化记忆层。让 Claude 能够跨会话记住你的项目背景、技术偏好、用户偏好、重要决策和待办事项。简单说就是给 Claude 配一个外置大脑让它下次见面还能记得你是谁、在做什么、做到哪一步了。这篇文章会把 claude-mem 的完整设计思路、实现方案和踩坑经验整理出来。无论是做 AI 工具开发、用 Claude 自动化办公还是单纯想提升日常对话效率的读者都可以参考这套方案给自己的 AI 工作流加上记忆能力。我会先讲清楚为什么需要这层记忆层然后拆解核心设计最后给出可落地的完整实现。2. 记忆层设计的核心思路2.1 为什么不能直接依赖上下文窗口很多人第一反应是Claude 不是有超长上下文窗口吗把历史记录都塞进去不就行了理论上确实可以但实际用起来有几个绕不开的坎。首先是成本问题。模型输入是按 token 计费的把每一次对话的完整历史都传给模型意味着 token 消耗会随着对话轮次线性增长。你早上聊了 10 轮的背景讨论下午就要把这 10 轮内容原封不动再传一遍第二天再传一遍。大部分 token 都花在了重复的背景信息上真正的新问题只占一小部分。其次是有效性问题。上下文越长模型对早期信息的注意力会自然衰减——这就是所谓lost in the middle效应。你第一天定下的关键决策藏在 20 轮之前的历史里后面真正要用到这个决策时模型很可能选择性失明给出的回答和你当初的决策完全脱节。更麻烦的是多会话之间的信息无法共享。你在会话 A 里跟 Claude 确认了数据库用 PostgreSQL切换到会话 B 时它完全不知道这件事你只能重新解释一遍。所以结论很清晰上下文窗口适合做短期的、会话内的工作记忆不适合做长期的、跨会话的持久记忆。持久记忆需要独立于会话之外的存储体系。2.2 记忆层应该长什么样先说一个关键认知记忆并不是把整段对话历史原样存下来。如果只是原样存储那和把全部历史塞进上下文没有本质区别只是把问题从上下文太长变成了存储太大。我设计的记忆体系分三层第一层是事实型记忆。包括用户的身份信息、项目背景、技术栈、架构决策、偏好设置等客观事实。这类记忆的特点是结构清晰、长期稳定适合用结构化的方式存储。第二层是对话记忆。这是对过往对话的高度浓缩——每轮对话结束后抽取关键信息生成摘要。比如用户提到生产环境遇到 MySQL 连接数打满的问题怀疑是连接池配置不当决定优先排查 HikariCP 参数。这种摘要保留了信息的核心脉络又不会像流水账一样冗长。第三层是决策记忆。记录用户在对话中做出的重要决策和原因。比如用户决定不用 Redis 缓存用户会话理由是数据一致性要求高收益不显著。这类记忆的价值在于后续讨论如果偏离了之前的决策方向模型可以主动提醒帮助用户保持一致性。这三层记忆各司其职事实型记忆回答用户是谁、项目是什么对话记忆回答之前聊过什么决策记忆回答为什么做这个选择。2.3 记忆的写入和读取机制记忆层的信息流必须构建成一个闭环。写入侧在每轮对话结束后自动运行一个记忆提取器从当前对话中识别值得记住的信息更新到记忆存储中。读取侧在新会话开始时加载相关记忆注入到系统提示词里让模型带着记忆开始工作。这个闭环要解决两个问题第一什么值得记住第二什么时候加载哪些记忆什么值得记住我采用了一套分级判定规则涉及用户个人信息的事必记涉及项目技术选型和架构决策的事必记用户明确表达偏好或习惯的事必记纯寒暄类内容不记一次性的、不会再提及的细节不记。什么时候加载哪些记忆则要看场景。不是每次会话都要把所有记忆全部加载进来那样又回到了 context 爆满的老问题。合理的做法是按需加载——先加载用户基本信息和项目概要对话过程中再根据当前话题动态拉取相关历史记忆。3. 技术实现从零搭建 claude-mem3.1 整体架构选型claude-mem 的整体架构并不复杂核心由四个模块组成对话监听器负责在对话结束后触发记忆提取流程记忆提取器调用 Claude 对对话内容进行结构化分析输出记忆条目记忆存储层负责记忆的持久化我选择 SQLite 作为默认存储方案记忆注入器在新会话开始时检索并注入相关记忆为什么选择 SQLite考虑过 Redis、MongoDB、甚至是纯 JSON 文件方案最终选 SQLite 的原因很朴素单文件部署零运维成本适合个人项目和中小团队原生支持 JSON 字段可以直接存结构化的记忆数据检索能力足够配合全文索引可以满足记忆检索的需求相比 MongoDB 等重型方案对个人开发者更友好如果后续记忆规模增长到百万条级别再迁移到 PostgreSQL 也不是难事因为抽象层已经预留了切换空间。3.2 记忆的数据结构设计记忆条目的数据结构是我反复调整过的部分。最初版本非常简单——就标题 内容 时间戳三个字段但用下来发现三个严重问题无法区分记忆类型、无法判断记忆重要性、无法处理相似记忆的合并。最终采用了这样的核心结构{ id: 记忆唯一ID, type: fact | summary | decision, content: 记忆的具体内容描述, importance: 0.0~1.0, tags: [相关主题标签], created_at: 创建时间, last_accessed_at: 最后被引用时间, access_count: 0, metadata: {} }重点讲讲importance这个字段。它用来表示记忆的重要程度范围是 0 到 1。比如用户的项目名、核心架构决策这类信息importance就是 0.9 以上某次对话中提到的一次性细节importance只有 0.2 左右。这个字段有两个用途。一是决定记忆是否值得写入——提取器会把importance低于 0.3 的内容直接丢弃避免记忆库被噪声填满。二是决定记忆的加载优先级——新会话启动时按importance降序加载只取 Top N 条。这个设计让记忆层在记得全和不占context之间取得了平衡。access_count和last_accessed_at则是给记忆淘汰策略用的。长期不被引用的记忆会逐渐降权最终被归档。这套机制模仿了人脑的遗忘规律——经常被使用的东西印象深刻长期不用的逐渐淡化。3.3 记忆提取的 Prompt 设计记忆提取器是整个项目中最核心的模块它本质上是调用 Claude 对对话做一次阅读理解。这一步的 Prompt 写得是否合理直接决定了记忆的质量。我最初用的是非常开放的指令比如请提取这段对话中的关键信息但效果不稳定——有时候提取得太细碎有时候又漏掉重要的决策信息。多次调整之后形成了下面这套相对可靠的 Prompt 模板你是一个记忆提取引擎。请从以下对话中提取需要长期记住的信息。 提取要求 1. fact 类型用户明确提到的个人身份、项目信息、技术环境、工具偏好、团队信息等客观事实 2. summary 类型对话中讨论过的重要议题的高浓度摘要保留核心结论和关键细节 3. decision 类型用户做出的重要技术决策、方案选择或方向调整需包含决策原因 提取规则 - 寒暄、玩笑、无实质内容的发言不提取 - 如果信息已经存在于历史记忆中不要重复提取输出 DUPLICATE - 对于 summary 类型提取内容不得超过 50 个字 - 如果对话没有任何值得长期记忆的内容输出 NONE 输出格式为 JSON 数组 [{ type: fact, content: ..., importance: 0.9 }]几个关键设计点解释一下。如果信息已经存在于历史记忆中不要重复提取这一条避免了记忆库的膨胀。实际操作中为了让这条规则生效我会把已有记忆的摘要拼接到 Prompt 中作为参考让模型判断当前对话中是否有新增信息。summary 类型必须控制在 50 字以内这个约束很重要。模型天然倾向于把摘要写得面面俱到如果不管控长度存下来的摘要比原文短不了多少失去了摘要的意义。强制限长能让模型只保留最核心的信息。没有值得记忆的内容就输出 NONE这一条看似简单却能节省大量 token 和存储空间。很多对话其实是闲聊或者重复确认并不需要都沉淀到记忆里。3.4 持久化存储与检索实现存储层的核心代码不算复杂但有几个细节值得分享。首先是表结构设计我用了一个memories表加一个memory_tags表的展平设计。CREATE TABLE memories ( id INTEGER PRIMARY KEY AUTOINCREMENT, type TEXT NOT NULL CHECK(type IN (fact, summary, decision)), content TEXT NOT NULL, importance REAL NOT NULL DEFAULT 0.5, metadata TEXT NOT NULL DEFAULT {}, created_at TEXT NOT NULL DEFAULT (datetime(now)), last_accessed_at TEXT NOT NULL DEFAULT (datetime(now)), access_count INTEGER NOT NULL DEFAULT 0 ); CREATE TABLE memory_tags ( memory_id INTEGER NOT NULL REFERENCES memories(id) ON DELETE CASCADE, tag TEXT NOT NULL, PRIMARY KEY (memory_id, tag) ); CREATE INDEX idx_memories_type ON memories(type); CREATE INDEX idx_memories_importance ON memories(importance); CREATE INDEX idx_memory_tags_tag ON memory_tags(tag);存储层有两点值得强调。第一点是关于写入去重。为了防止同一信息被重复写入我在写入前会做一层相似度检查。做法是把新记忆和已有记忆中带相同 tag 的条目做语义相似度比较相似度超过 0.92 就认为是重复信息。语义相似度计算直接调用一个 text-embedding 接口虽然多一次网络调用但换来了记忆库的干净整洁。第二点是关于记忆检索。新会话启动时不直接按importance全量取 Top N而是采用基础记忆 主题记忆的组合策略。基础记忆是用户始终需要携带的全局信息比如用户身份和项目概要固定取 importance 最高的 5 条。主题记忆则是根据新对话的主题动态检索相关度最高的一批记忆。这个策略既保证了必要的背景信息又控制了 context 占用。3.5 记忆注入与系统提示词整合记忆层最终要发挥作用必须把检索到的记忆注入到 Claude 的对话中。这一步我在实际使用中踩过一个重要教训直接把记忆内容塞进用户消息里效果非常差。原因很简单——用户消息是模型以对话参与者身份处理的把记忆内容塞进去模型需要额外分辨哪些是用户真正说的、哪些是系统给我的背景注意力被分散了。更好的做法是放进 System Prompt。我最终的注入方案长这样你正在与用户进行对话。你可能需要参考以下关于用户的记忆信息 memory [用户基础信息] - 用户是一名全栈工程师后端以 Java/Spring Boot 为主 - 当前正在开发的项目是 claude-mem一个 Claude 记忆增强工具 [项目决策记录] - 使用 SQLite 作为默认存储原因是不想引入额外的运维负担 - 不使用 MongoDB认为对个人项目来说过于重型 [最近对话摘要] - 讨论了记忆提取的 Prompt 设计核心诉求是防止重复记忆 /memory 如果记忆信息与当前对话无关请忽略它们。 如果用户询问的内容与记忆中的事实冲突以用户最新表述为准。 请在适当的时候主动引用相关记忆帮助用户保持上下文连贯。最后这两个指令非常重要。第一句给了模型自由裁量权避免它因为记忆的存在而变得束手束脚。第二句解决了记忆过期的问题——用户可能会改变想法如果记忆和用户当前表述冲突应该相信用户当前的表述。实际测试下来这套注入方式能显著提升对话连贯性。有个很直接的体验变化跟 Claude 说继续上次的设计方案它能准确说出上次做到哪一步、卡在哪个问题上而不是一脸茫然地反问你哪个方案。4. 实操部署与接入工作流4.1 安装与初始化配置claude-mem 的使用方式分成两种一是作为命令行工具独立运行二是作为中间层嵌入你自己的 AI 应用。这里重点讲独立运行的场景因为它最容易上手。安装过程很直接项目是 Python 写的直接用 pip 安装pip install claude-mem安装完成后需要做一次初始化。初始化会做三件事创建 SQLite 数据库文件、生成默认配置文件、验证 Claude API 是否可用。claude-mem init执行完之后会在当前目录生成一个claude_mem_config.json配置文件。这里面有几项关键参数值得细说{ api_key_env: ANTHROPIC_API_KEY, db_path: ./claude_mem.db, min_importance_threshold: 0.3, max_memory_injection: 10, auto_summary: true, summary_interval: 5 }min_importance_threshold记忆写入的最低重要性阈值。低于这个值的内容会被过滤调太高容易漏掉细节调太低会被噪声淹没。实测 0.3 是一个比较均衡的值。max_memory_injection新会话时最多注入几条记忆。10 条左右不太占 context又能覆盖大部分场景。summary_interval每多少轮对话触发一次摘要提取。太频繁会浪费 API 调用太稀疏又可能遗漏关键转折点。默认 5 轮比较合适。4.2 命令行工作流的日常使用初始化完成后日常使用分为记录和回忆两个方向。当你和 Claude 完成一段对话想要把这段对话沉淀进记忆库时只需将对话内容传入提取命令claude-mem add EOF 用户确认数据库选型用 PostgreSQL原因是团队已有生产环境经验迁移成本可控。 原定的 MySQL 方案被否决。 EOF执行后claude-mem 会调用记忆提取器将这段文字分析为结构化的记忆条目写入 SQLite。你可以查看当前记忆库的所有内容claude-mem list输出是一个按重要性排序的表格包含记忆类型、内容摘要、重要性分数、创建时间等字段。更有用的命令是搜索claude-mem search 数据库选型它会做语义相关搜索把关于数据库选型的记忆条目带出来。当你准备开启新的 Claude 会话时用 session 命令一键生成注入提示词claude-mem session --prepare这条命令会把候选记忆按 importance 排序生成一段 System Prompt你可以直接粘贴到 Claude 的新会话中。这就是前面提到的基础记忆 主题记忆策略的落地形态。4.3 集成到 Python 应用如果你的项目本身就是一个 Python 写的 AI 应用claude-mem 可以作为一个库直接集成。这个用法对做产品原型和内部工具的场景非常有价值。from claude_mem import MemoryClient client MemoryClient() # 对话结束后将对话内容交给记忆层处理 client.add(用户表示希望后续所有命令输出都用中文) # 新会话开始时获取需要注入的上下文 memories client.get_injection_prompt( user_query继续昨天的方案设计, max_memories8 ) # 拼接 System Prompt system_prompt 你是一个AI助手。参考以下用户记忆保持对话连贯性 memories集成点总共就三个对话结束后调add写入记忆新会话开始时调get_injection_prompt拉取记忆注入口径。这两个接口覆盖了记忆层的大部分价值。4.4 实际效果一个完整的对话体验为了让你直观感受 claude-mem 带来的改变我模拟一次真实的使用场景。Session 1用户我在做一个 AI 写作助手后端用 Python FastAPI前端用 React。数据库暂时用 SQLite后续如果数据量上来会迁到 PostgreSQL。Claude好的了解了你的技术栈。后端 FastAPI 配前端 React存储先 SQLite 后迁移 PG。这段对话结束后claude-mem 会自动提取出几条记忆fact 类型记录用户的项目是 AI 写作助手端技术栈 FastAPI Reactdecision 类型记录数据库先 SQLite 后续迁移 PostgreSQL缓存及原因。这些记忆存进 SQLite。Session 2第二天用户继续帮我把数据库迁移到 PostgreSQL 的计划写一下。Claude带着记忆好的。根据上次的讨论你计划从 SQLite 迁移到 PostgreSQL。我先整理迁移计划...如果按照原生的 Claude 体验Session 2 里模型根本不知道上次讨论过要迁移到 PostgreSQL这件事你需要在 Session 2 里重新解释一遍。而有了记忆层它天然就知道项目背景甚至能直接跳进帮你写计划这个阶段。这个体验上的差异恰恰是记忆层最核心的价值Claude 从一个每次都要重新解释背景的工具变成了一个持续跟进你项目的伙伴。5. 常见问题与排查技巧实录5.1 记忆提取经常漏掉重要决策现象对话里明明确认了关键方案但记忆库里找不到对应条目。排查这类问题大多是 Prompt 中对决策的定义不够具体导致的。大模型对decision的理解存在偏差它倾向于把决策理解成一个重大问题的新选择而开发场景中很多决策其实是行就按你说的来这样轻描淡写的确认。解决在提取 Prompt 中明确补充决策的判定标准。我在实践中加了一句用户对某个方案表示认可、同意、确认即使表述简短也视为一次决策记录漏记率立刻下降了很多。5.2 记忆注入后模型表现反而变差现象Session 2 中模型变得偏执总是引用记忆库中无关的内容甚至分不清memory 和真实对话的边界。排查这是注入方式不对叠加注入量过大导致的。把记忆塞进用户消息会让模型混淆身份来源。同时注入过多的记忆条目会占用大量 context让模型抓不住重点。解决执行两步修正。第一步把所有记忆注入全部移到 System Prompt 中绝不要塞进用户消息。第二步精简注入条目数量——单个主题最多注入 5~8 条并检查是否有重复或矛盾的内容发现矛盾时优先保留最近写入的版本。5.3 数据库体积膨胀过快现象用了两周之后SQLite 文件超过了 100MB检索速度明显下降。排查查看列表发现大量低重要度的 summary 类型记忆很多是同一个话题反复产生的摘要。解决完善记忆合并机制。每当新摘要写入前检索同一 tag 下的旧摘要如果新摘要和旧摘要在关键信息上一致就用新摘要覆盖旧摘要而不是追加新记录。这样保证了每个话题只保留最新、浓缩程度最高的摘要数据库增长的趋势得到了有效遏制。5.4 记忆被重复写入现象同一个项目事实在记忆库中出现 3 次以上。排查多半是记忆去重环节失效了。如果语义相似度计算的阈值设得太高比如 0.98那么表述稍有差异就会判为不相似从而写入重复。解决把阈值降低到 0.92并在计算前先做一步归一化把记忆内容里的时间词、主观情绪词去掉再比较核心信息。经过这一步处理后误判概率明显下降重复写入的问题基本消失。5.5 长期使用需要关注的三个关键指标在实际跑了两三个月之后我摸索出了一个适合长期维护记忆库的检查清单记忆条目的贡献率随机抽查 100 条记忆看有多少条在后续对话中被实际引用过。如果引用率低于三成说明记忆提取策略过于宽松需要上调min_importance_threshold。重复和矛盾的比例如果重复率超过 10%说明去重机制还有改进空间。如果矛盾率高重点排查记忆合并逻辑看是否因为覆盖规则太激进而丢掉了关键旧信息。注入后模型回复质量对比同样的新问题有记忆和没有记忆两种模式下各问 10 次对比实质性回复的比例。如果差距不大说明当前记忆注入的边际价值在递减可以考虑降低注入条数节省 context 开支。6. 一段实战后的心得总结回到开头说的那个问题——Claude 不记事。用 claude-mem 解决这个问题之后我最大的体会是记忆层的价值不只是让 AI 记住信息更是让 AI 节省你的解释成本。以前用 Claude 做一件事尤其是在新会话里继续旧任务光是把背景写清楚就要花好几分钟写少了怕它理解不到位写多了又觉得是在重复劳动。现在有了记忆层之后这个成本几乎被缩减为零。我只需要说继续它就能接上之前的工作状态。这种体验转变很像从每次找新人接手项目都得重新讲一遍背景变成了项目交接得清清楚楚接手人进门就能干活。这套项目还有很多可以扩展的方向。我目前已经在尝试把记忆层和更多工作流串联比如接入定时任务自动汇总每周的对话记录、把记忆导出成 Markdown 和团队共享、让 Claude 根据记忆库自动生成周报。这些扩展并不复杂而且每一个都因为底层有了记忆这个基础能力而变得格外顺滑。最后再分享一个小建议不要追求记忆库记得越多越好。记忆的价值在于被使用不被使用的记忆只是一种存储负担。在设计与构建记忆层时时刻保持克制这一个原则比你精心设计任何技术细节都更重要。好的记忆系统是辅助思考而不是替代思考。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进