
1. 从零认识 claude-mem它到底解决什么问题第一次看到claude-mem这个名字我的直觉是这应该是一个给 Claude 做“记忆管理”的工具。事实也确实如此。简单说claude-mem 是一套为 Claude 这类大语言模型提供持久化记忆能力的方案它让模型在多次对话之间不再“失忆”能够记住你之前告诉它的偏好、项目背景、历史决策甚至是一些琐碎但重要的细节。在没有记忆机制的情况下每次新开一个对话窗口模型就像一张白纸。你得反复交代“我用的是 Python 3.11”“我的项目叫 xxx”“上次我们决定用 PostgreSQL 而不是 MySQL”。这种重复劳动在短对话里还能忍一旦进入长期项目协作效率损耗就非常明显。claude-mem 要解决的就是这个痛点把对话中产生的关键信息抽取出来存到一个可检索、可更新的记忆库里在后续对话中按需注入。它适合谁我认为有三类人最需要它。第一类是长期用 Claude 做开发辅助的工程师比如持续几周甚至几个月维护同一个代码库模型需要记住架构决策和命名约定。第二类是内容创作者和研究者需要模型记住自己的写作风格、研究方向和已完成的章节。第三类是把 Claude 接入自动化流程的开发者希望模型在多轮任务中保持上下文一致性。需要提前说明的是claude-mem 并不是官方内置功能而是社区围绕 Claude 的 API 和工具链构建的增强方案。它的核心思路可以概括为抽取、存储、检索、注入。这四个环节环环相扣任何一个环节设计不好记忆就会变成噪音。接下来我会把这套思路拆开讲清楚每个环节的取舍和实操细节。2. 记忆系统的整体设计与核心思路拆解2.1 为什么不能直接把全部历史对话塞进上下文很多人第一反应是既然模型有上下文窗口那把历史对话全部拼进去不就行了这个方案在小规模下可行但很快会撞墙。原因有三个。首先是成本。上下文越长每次请求消耗的 token 越多费用呈线性甚至超线性增长。一个持续一个月的项目历史对话可能几十万 token每次都全量注入账单会非常难看。其次是注意力稀释。模型在超长上下文里检索关键信息的能力会下降重要的细节容易被淹没在大量无关对话中。你可能遇到过这种情况明明之前说过的要求模型在长对话后期就“忘了”其实不是忘了是被淹没了。第三是冲突与过期。历史对话里可能包含已经被推翻的决策比如“先用 SQLite 试试”后来改成了“确定用 PostgreSQL”。如果全量注入模型可能同时看到两个矛盾的信息输出就会摇摆。所以 claude-mem 的核心设计原则是不存原始对话存结构化记忆。把对话压缩成一条条独立的、可管理的记忆条目每条都有明确的类型、内容和时间戳。2.2 记忆的四种类型划分在实际设计中我习惯把记忆分成四类这个分类直接决定了后续的存储和检索策略。记忆类型说明示例更新频率事实型客观不变的信息项目名、技术栈、文件路径低偏好型用户的习惯和喜好代码风格、回复语言、详细程度中决策型已确定的选择及理由选 PostgreSQL 因为需要事务中临时型当前任务的短期状态正在调试的 bug、待办事项高这个分类的价值在于不同类型的记忆检索优先级和过期策略完全不同。事实型和偏好型几乎永久有效决策型需要保留理由以便后续推翻时有据可查临时型则应该在任务结束后清理掉否则会污染后续对话。提示如果你刚开始搭建不要一上来就追求四类齐全。先把事实型和偏好型做好这两类带来的收益最直接实现也最简单。2.3 存储方案选型为什么我最终选了 SQLite 加向量索引存储层是 claude-mem 的骨架。我试过三种方案这里把对比摊开讲。第一种是纯 JSON 文件。优点是零依赖、易调试直接打开就能看。缺点是检索能力弱只能靠关键词匹配而且并发写入容易出问题。适合原型验证阶段。第二种是纯向量数据库比如把每条记忆做 embedding 存进去。优点是语义检索强问“我之前说的数据库选型”能召回相关记忆。缺点是精确匹配差比如你想查“项目名到底是什么”向量检索可能召回一堆相关但不精确的内容。第三种是我最终采用的SQLite 加向量索引的混合方案。SQLite 存结构化字段类型、时间、标签、原文同时把 embedding 存成 BLOB 或者配合一个轻量向量索引。检索时先用 SQL 做元数据过滤再用向量做语义排序。这样既有精确性又有语义能力而且 SQLite 单文件部署备份和迁移都方便。选它的核心理由是可控。记忆系统最怕的是“黑盒召回”你不知道为什么某条记忆被选中了。混合方案里元数据过滤这一步是完全透明的你能清楚看到哪些记忆进入了候选集调试起来心里有底。2.4 检索策略什么时候该召回记忆检索时机的设计比检索算法本身更容易被忽视。我的经验是分两种模式。主动召回每次对话开始前根据当前用户输入检索 top-k 条相关记忆注入系统提示。这是默认模式适合大多数场景。被动召回不预先注入而是给模型一个“查询记忆”的工具让它自己决定什么时候查。这种方式更省 token但对模型的工具调用能力有要求而且可能漏查。我一般用主动召回打底同时保留被动召回作为补充。具体做法是主动注入 5 到 8 条高相关记忆同时在工具列表里放一个search_memory函数模型觉得信息不够时可以自己再查。这样兼顾了效率和完整性。3. 核心细节解析与实操要点3.1 记忆抽取怎么让模型吐出结构化条目抽取是整条链路里最考验 prompt 设计的环节。你不能直接说“把重要信息提取出来”模型会给你一堆模糊的总结。我的做法是用固定的 JSON schema 约束输出。一个可用的抽取 prompt 大致长这样你是一个记忆抽取器。阅读以下对话片段提取值得长期记住的信息。 只输出 JSON 数组每个元素包含字段 - type: fact | preference | decision | temporary - content: 一句话描述不超过 50 字 - tags: 字符串数组2 到 4 个关键词 - confidence: 0 到 1 的浮点数表示你有多确定这条值得记住 如果没有值得记住的内容输出空数组 []。 不要输出任何解释文字。这里有几个细节值得展开。content 限制在 50 字以内是为了强制模型做压缩避免把整段对话搬过来。confidence 字段很关键它让后续检索可以按置信度排序低置信度的记忆可以延迟注入或者干脆丢弃。tags 要求 2 到 4 个太少区分度不够太多会稀释检索信号。实测下来这个 schema 的抽取准确率比自由文本高很多。踩过的坑是早期我没限制输出格式模型经常返回带 markdown 代码块的 JSON解析时频繁报错。后来在 prompt 里明确“只输出 JSON 数组”并在解析前做一次清洗问题就解决了。3.2 去重与冲突处理记忆库最容易被忽视的环节记忆库用久了必然出现重复和冲突。同一个事实被抽了三次或者旧决策和新决策打架。如果不处理检索时会召回一堆冗余信息浪费 token 还干扰模型。我的去重策略分两层。第一层是精确去重对 content 做归一化去空格、转小写后算哈希完全相同的直接丢弃。第二层是语义去重对新记忆做 embedding和已有记忆算余弦相似度超过 0.92 的视为重复保留 confidence 更高的那条。冲突处理更微妙。我的做法是不删除旧记忆而是标记为 superseded并记录它被哪条新记忆取代。这样做的理由是有时候用户会反悔想回到之前的决策保留历史能让你快速恢复。检索时默认只返回未被取代的记忆需要时可以显式查询历史。注意冲突检测不要做得太激进。我一开始把相似度阈值设到 0.85结果把“用 PostgreSQL”和“用 PostgreSQL 15”这种正常演进也判成冲突了。阈值调到 0.92 以上并且只在 type 相同的情况下比较误判就少多了。3.3 注入格式记忆怎么放进系统提示才有效检索出来的记忆怎么组织进 prompt 也有讲究。我试过几种格式最终固定为下面这种以下是关于当前用户的长期记忆按相关性排序 [事实] 项目名为 claude-mem使用 Python 3.11 开发 [偏好] 用户偏好简洁回复代码示例用 Python [决策] 数据库选 PostgreSQL理由是需要事务支持 请在回复中自然运用这些信息不要逐条复述。关键点是最后那句“不要逐条复述”。不加这句模型经常会在回复开头来一段“根据我的记忆你之前提到……”非常啰嗦。加上之后记忆就变成了背景知识自然融入回答。另外按类型分组比按时间排序效果好。模型对分类信息的使用效率更高能快速定位到“偏好”部分调整语气定位到“决策”部分保持一致性。3.4 记忆的生命周期管理记忆不是存进去就完事了需要定期维护。我设计了一套简单的生命周期规则。临时型记忆默认 7 天后过期除非被显式提升为决策型。决策型记忆保留 90 天之后降级为事实型或归档。事实型和偏好型长期保留但每季度做一次人工审查。低置信度记忆confidence 0.530 天未被检索到就自动清理。这套规则不是拍脑袋定的是根据实际使用数据调的。我发现临时型记忆超过一周基本就没用了留着只会增加检索噪音。而决策型记忆的“保鲜期”大概三个月之后要么已经固化进代码要么已经失效。4. 实操过程与核心环节实现4.1 环境准备与依赖安装先把基础环境搭起来。我用的技术栈是 Python 3.11 加 SQLite向量部分用sentence-transformers做本地 embedding避免依赖外部服务。python -m venv venv source venv/bin/activate pip install anthropic sentence-transformers numpy选本地 embedding 而不是调用 API理由是成本和隐私。记忆内容往往包含项目细节走外部 API 有泄露风险而且高频调用费用不低。sentence-transformers的all-MiniLM-L6-v2模型只有 80MB推理速度快语义效果对记忆检索这个场景完全够用。数据库初始化脚本如下import sqlite3 def init_db(pathmemory.db): conn sqlite3.connect(path) conn.execute( CREATE TABLE IF NOT EXISTS memories ( id INTEGER PRIMARY KEY AUTOINCREMENT, type TEXT NOT NULL, content TEXT NOT NULL, tags TEXT, confidence REAL DEFAULT 1.0, embedding BLOB, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, superseded_by INTEGER, last_accessed TIMESTAMP ) ) conn.execute(CREATE INDEX IF NOT EXISTS idx_type ON memories(type)) conn.execute(CREATE INDEX IF NOT EXISTS idx_superseded ON memories(superseded_by)) conn.commit() return conn这里superseded_by字段是冲突处理的关键last_accessed用于生命周期管理。索引建在 type 和 superseded_by 上因为这两个字段在检索过滤时用得最频繁。4.2 抽取流程的完整实现抽取函数接收一段对话文本调用 Claude 返回结构化记忆然后写入数据库。import json import anthropic client anthropic.Anthropic() EXTRACT_PROMPT 你是一个记忆抽取器。阅读以下对话片段提取值得长期记住的信息。 只输出 JSON 数组每个元素包含字段 - type: fact | preference | decision | temporary - content: 一句话描述不超过 50 字 - tags: 字符串数组2 到 4 个关键词 - confidence: 0 到 1 的浮点数 如果没有值得记住的内容输出空数组 []。 不要输出任何解释文字。 对话内容 {dialogue} def extract_memories(dialogue): resp client.messages.create( modelclaude-sonnet-4-20250514, max_tokens1024, messages[{role: user, content: EXTRACT_PROMPT.format(dialoguedialogue)}] ) text resp.content[0].text.strip() if text.startswith(): text text.split()[1] if text.startswith(json): text text[4:] try: return json.loads(text) except json.JSONDecodeError: return []解析前那段清洗逻辑是踩坑换来的。即使 prompt 里说了“只输出 JSON”模型偶尔还是会包一层代码块。清洗逻辑要能处理json和两种情况。4.3 向量检索与混合排序检索环节把 SQL 过滤和向量相似度结合起来。先按类型和 superseded 状态过滤再算余弦相似度排序。import numpy as np from sentence_transformers import SentenceTransformer model SentenceTransformer(all-MiniLM-L6-v2) def embed(text): return model.encode(text, normalize_embeddingsTrue) def search_memories(conn, query, top_k8, typesNone): q_vec embed(query) sql SELECT id, type, content, tags, confidence, embedding FROM memories WHERE superseded_by IS NULL params [] if types: placeholders ,.join(? * len(types)) sql f AND type IN ({placeholders}) params.extend(types) rows conn.execute(sql, params).fetchall() scored [] for row in rows: mem_vec np.frombuffer(row[5], dtypenp.float32) sim float(np.dot(q_vec, mem_vec)) score sim * 0.7 row[4] * 0.3 scored.append((score, row)) scored.sort(keylambda x: x[0], reverseTrue) return [r for _, r in scored[:top_k]]排序公式sim * 0.7 confidence * 0.3里的权重是调出来的。纯按相似度排低置信度的噪音记忆会挤进来纯按置信度排又和当前话题不相关。7:3 这个比例在我自己的使用场景里效果最稳你可以根据自己的数据微调。4.4 注入与对话主循环最后把检索结果拼进系统提示跑通完整对话。def build_system_prompt(memories): if not memories: return 你是一个有帮助的助手。 lines [以下是关于当前用户的长期记忆按相关性排序, ] for m in memories: lines.append(f[{m[1]}] {m[2]}) lines.append() lines.append(请在回复中自然运用这些信息不要逐条复述。) return \n.join(lines) def chat(conn, user_input, history): memories search_memories(conn, user_input) system build_system_prompt(memories) messages history [{role: user, content: user_input}] resp client.messages.create( modelclaude-sonnet-4-20250514, max_tokens2048, systemsystem, messagesmessages ) reply resp.content[0].text history.append({role: user, content: user_input}) history.append({role: assistant, content: reply}) return reply, history主循环里每轮对话结束后异步调用extract_memories抽取新记忆并入库。异步很重要否则抽取的延迟会拖慢对话响应。我用一个简单的后台线程池处理实测对响应速度几乎无影响。5. 常见问题与排查技巧实录5.1 记忆召回不准的排查路径召回不准是最常见的问题表现是“明明存过但模型没用上”。排查按下面顺序走。第一步确认记忆是否真的入库了。直接查数据库SELECT * FROM memories WHERE content LIKE %关键词%。如果查不到问题在抽取环节可能是 prompt 没让模型识别出这条信息。第二步确认检索是否召回了。手动调用search_memories看返回结果。如果没召回看是 SQL 过滤把它排除了比如 type 不匹配还是相似度太低。相似度低通常是 embedding 模型对领域词汇不敏感可以换更大的模型或者加关键词匹配兜底。第三步确认注入是否生效。打印build_system_prompt的结果看记忆有没有拼进去。有时候是 top_k 太小相关记忆被挤掉了调大 top_k 试试。5.2 常见问题速查表现象可能原因解决方法模型复述记忆内容注入提示缺少约束加“不要逐条复述”指令记忆库膨胀过快抽取过于宽松提高 confidence 阈值收紧 prompt检索结果重复去重未生效检查语义去重阈值和归一化逻辑旧决策干扰新决策冲突未标记启用 superseded_by 机制响应变慢抽取同步执行改为异步后台处理embedding 报错维度不一致统一模型重建已有记忆的向量5.3 几个踩坑换来的经验经验一不要存对话原文。我早期图省事把整段对话存进去当记忆结果检索时召回的都是大段文本token 消耗暴涨而且模型很难从中提取要点。改成结构化条目后同样信息量的 token 消耗降了大概七成。经验二confidence 阈值宁高勿低。低置信度记忆带来的干扰远大于它的价值。我现在默认只注入 confidence 大于 0.6 的记忆低于这个值的留在库里但不参与检索效果明显更干净。经验三定期做记忆审查。我每个月会花十分钟导出记忆库人工扫一遍删掉过期的、合并重复的、修正错误的。这十分钟能省下后面大量的调试时间。自动化再好也替代不了人对关键信息的判断。经验四给记忆加来源标记。后来我在表里加了一个source字段记录这条记忆来自哪次对话。当发现某条记忆有问题时能快速回溯到原始上下文判断是抽取错了还是用户当时就说错了。这个字段在排查问题时非常有用。6. 记忆系统的扩展方向与个人体会claude-mem 这套方案跑通之后我陆续做了几个扩展效果不错这里分享给有需要的人。第一个扩展是记忆的层级化。把记忆分成全局记忆和项目记忆两层全局记忆跨项目共享比如用户偏好项目记忆只在特定项目里生效。检索时先查项目记忆不够再查全局。这样避免了不同项目的记忆互相污染。第二个扩展是记忆的自动摘要。当某个标签下的记忆超过 20 条时触发一次摘要把零散记忆合并成一条高层总结。比如关于“代码风格”的十几条偏好可以合并成一条综合描述。这能有效控制记忆库规模。第三个扩展是多用户隔离。在表里加user_id字段检索时强制过滤。如果你要把这套系统分享给团队用这一步是必须的否则记忆会串。我个人在实际操作中的体会是记忆系统的价值不在于存了多少而在于检索时能不能精准命中。我见过太多人把记忆库堆得很大但检索质量一塌糊涂结果模型反而被误导。与其追求记忆的数量不如把抽取质量、去重逻辑、检索排序这三件事做扎实。这三块做好了哪怕只存几百条记忆效果也比存几万条但乱七八糟强得多。最后再分享一个小技巧给检索加一个“最近使用”加权。在排序公式里加一项last_accessed的时间衰减因子最近被用过的记忆稍微提权。这符合人的记忆规律常用的信息更容易被想起。我加了这一项之后模型对当前任务上下文的把握明显更连贯了。