1. 为什么需要 claude-mem一个上下文失忆症患者的自救先说个尴尬的场景。我用 Claude CLI 做代码重构的时候经常会在一次长会话里连续处理十几个文件聊到第五六个文件时前面的设计决策、命名约定、排除方案早就被上下文窗口挤出去了模型开始一本正经地重复已经否决过的方案。最崩溃的是第二天继续做同一个任务时新开一个会话它完全不记得昨天说过什么我只能重新把背景讲一遍甚至把关键结论再喂回去。这种“上下文失忆”不是个别现象而是所有对话式模型都存在的结构性短板上下文窗口再大也有上限会话一关记忆清零。我试过把重要内容手动复制到笔记文件里也试过开多个终端窗口分屏硬扛但都撑不过“任务一多、思路一乱”就原形毕露。于是我自己写了一个叫 claude-mem 的小工具专门解决“模型用完就忘、我自己也跟着忘”的问题。claude-mem 的定位很简单一个跑在本地的、给 Claude 会话加“长期记忆”的命令行工具。它做的事情可以概括成三条自动记录每次会话的内容、把重要结论提炼成可检索的记忆卡片、下次开会话时支持把相关记忆重新注入上下文。听起来不复杂但真正落地时牵扯到数据格式、存储结构、检索策略、触发时机、上下文注入位置等一系列细节每一处都有不少坑。如果你也在用 Claude CLI 处理较长周期的任务或者你经常在多个终端会话之间切换、需要模型保持对某个项目的理解一致性那么 claude-mem 这种“外挂记忆”的思路会很适合你。我写的这个版本是用 Python 做的依赖很少核心就三样读取会话日志、调用模型做摘要、把结果存成结构化文档。下面我会把整个设计和实现过程完整拆开讲包括我踩过的坑。2. 记忆不能只堆在聊天记录里数据模型与存储结构2.1 短期记忆JSONL 会话日志claude-mem 的第一步是“先有记录”。我把获取 Claude CLI 输入输出的过程包了一层每次运行 Claude 时它会同时把用户输入、模型回复、时间戳、工作目录、用了哪些工具调用等元信息以 JSONL 格式追加到统一的会话日志文件里。选 JSONL 而不是单个大 JSON是因为追加写入太方便了一条消息一行写坏了一行不影响其他行文件也不会因为格式化而反复重写整个内容。每个会话按时间命名例如session_20250214_153012.jsonl每次调用就是一个新文件避免多个会话互相污染。文件里不存二进制和无关噪音只存纯文本内容后面做摘要和检索都很干净。为什么不做数据库最初我确实用过 SQLite但后来发现对于“每次会话追加记录”这种场景文件系统天然够用而且用户可以直接用cat、grep、tail去检查记忆内容对技术用户来说透明性比强一致更重要。量级上一个会话大概几百 KB 文本一年也就几个 GB文件完全扛得住。2.2 长期记忆Markdown 知识卡片JSONL 是原始素材但直接拿它做长期记忆是有问题的内容太杂包含寒暄、试错过程、冗余输出而且会话一多检索时容易把过时信息也翻出来。所以 claude-mem 在会话结束或达到阈值时会对当前会话做一次“提炼”产物是一张 Markdown 记忆卡片。卡片的核心字段包括任务主题、关键决策、代码变更清单、待办事项、技术约束、相关文件路径、标签。并不是每次会话都会生成卡片claude-mem 会先判断会话“有没有值得记住的价值”判断依据很简单——会话中是否包含代码修改、架构讨论、明确结论或者错误排查记录。纯闲聊和重复问答直接跳过这能有效防止记忆库被低质量信息淹没。Markdown 是纯文本格式意味着你可以手动编辑、批量处理、放进 Git 仓库做版本管理。这个设计是刻意为之的记忆必须是用户能掌控的资产而不是锁在某个私有格式里的黑盒。你可以用vim改一张卡片也可以写脚本批量给卡片加标签甚至可以把这个目录同步到任意同步盘里做备份。2.3 标签系统和自动归纳如果记忆卡片只有标题和正文检索基本靠全文搜索效果会很粗糙。所以 claude-mem 引入了标签机制分为自动标签和手动标签两种。自动标签由大模型在提炼卡片时生成一般 3 到 5 个比如“数据库迁移”“性能优化”“API 设计”“Bug 修复”。手动标签则来自文件系统分类每张卡片存放在memory/项目名/对应的子目录里。标签系统还有一个重要的用途上下文注入时的“范围过滤”。当你在某个目录下启动一个新的 Claude 会话时claude-mem 会优先检索当前项目子目录下的卡片而不是全局记忆。这样能显著减少不相关内容被注入的机会。比如在~/workspace/order-service下工作时它检索到的记忆大概率都是这个微服务的而不是你在另一个项目里写的部署脚本。2.4 配置文件与存储目录规范默认情况下claude-mem 的存储根目录是~/.claude-mem/里面分三个子目录logs/JSONL 原始会话日志memory/Markdown 记忆卡片按项目分子目录index/检索用的向量索引和关键词索引配置文件在~/.claude-mem/config.yaml我给出一个最小可用的配置示例storage_root: ~/.claude-mem model: claude-3-5-sonnet-latest summary_max_length: 300 auto_tag: true compact_window: 0.8 recall_count: 5 recall_threshold: 0.55compact_window是会话压缩触发的阈值意思是当当前会话累计 token 数超过模型最大上下文的 80% 时自动对前文做摘要压缩。recall_count是每次注入几条记忆卡片recall_threshold是相关性得分的最低门槛低于这个分数的一律不注入。3. 核心链路拆解采集、提炼、检索、注入3.1 采集层给 CLI 戴上“记录仪”实现上claude-mem 并没有去改 Claude 的内部代码而是用了一层轻量包装。核心思路是“代理执行”即在运行时把原始命令替换成自己的入口然后透过伪终端PTY或者 stdin/stdout 的管道捕获真实交互。我用的是 Python 的pty模块创建子进程跑原生命令同时持续读取输出流按行切分并写入 JSONL 文件。伪终端的好处是保留了完整的交互行为包括模型流式输出、状态提示符、甚至一些特殊控制字符。坏处是它解析起来有些脏偶尔会遇到半行残片和 ANSI 转义码需要做一次清洗再落盘。为了让这套方案真正可靠采集层还做了一件事每当检测到模型输出结束出现特定提示符时就把这一轮完整的用户输入和模型输出合并成一条记录附上耗时、token 估算、工作目录。这样后续对会话做摘要时输入输出是配对的模型能看清完整的上下文逻辑而不是碎片。3.2 提炼层从流水账变成记忆卡片会话结束之后claude-mem 会进入提炼流程。流程分四步先按对话轮次分段然后提取关键事件接着生成标签和摘要最后写入 Markdown 卡片。我用了“三段式提示词”来驱动提炼模型第一段交代任务从会话日志中提取持久的、对后续工作有价值的信息第二段给约束忽略寒暄、忽略过程性试错、保留结论和原因、每条结论注明来源会话第三段给格式要求按“决策/变更/待办/约束”四类输出每类用列表呈现。这里有个经验直接让模型“总结一下这个会话”效果很差它会把“用户先问了 A又问了 B最后修改了 C”这种流水账写进去。限制输出类别之后生成的卡片质量明显提升因为它逼着模型站在“未来需要什么信息”的角度去做筛选而不是回顾“刚才发生了什么”。提炼的触发时机也做了策略区分。会话正常退出时立即提炼如果会话异常中断则保留原始 JSONL但标记为unprocessed等下次启动 claude-mem 时补提炼避免因 crash 丢掉原始资料。3.3 检索层关键词与语义两手抓记忆卡片多起来之后怎么找到“对的那一张”就成了关键。claude-mem 用了两层检索关键词检索和语义检索。关键词检索基于 SQLite 的 FTS5把卡片的标题、摘要、标签、正文全部建立倒排索引支持sqlite原生的 BM25 排序。这个层速度极快适合精确定位比如搜“事务隔离级别”“Redis 连接池”这种专有名词。语义检索用的是本地 embedding 模型把查询语句和所有卡片向量化然后做余弦相似度排序。为了轻量我没有引入重的向量数据库而是直接在内存里算卡片总量通常几百上千向量维度几百维暴力扫描一次也就几十毫秒完全够用。最终得分是关键词得分和语义得分的加权求和权重默认分别是 0.4 和 0.6。实际跑下来比单纯用关键词搜索精准不少尤其是当你不知道卡片里确切的用词只记得“大概是在聊缓存穿透”时语义检索能找到对应卡片关键词方案则毫无办法。3.4 注入层怎么把记忆塞回上下文检索到记忆只是第一步怎么“塞回去”才真正决定效果。claude-mem 的注入策略是把最相关的记忆卡片以“参考信息”区块的形式附加在用户输入前面。格式长这样[记忆] 项目order-service [记忆] 标签数据库、索引、性能 [记忆] 2025-02-10 决定订单列表查询改为覆盖索引 idx_order_user_status因为原查询回表导致慢查询 [记忆] 2025-02-11 待办迁移脚本还没写注意保留 status 字段的枚举兼容注入时机有两种。一种是在新会话启动时claude-mem 自动检索当前项目下最相关的若干条记忆注入到第一次用户输入之前给模型一个“预热”。另一种是会话进行中当用户提到的关键词与记忆卡片高度匹配时实时补充进输入这种方式适合“聊到某个旧方案时自动找回上下文”。这里必须控制注入量否则记忆本身就会把上下文窗口挤爆。我设的默认值是 5 条卡片每条摘要不超过 100 字实测在多数场景下模型回复的准确率能明显提升而 token 开销只有几百可以忽略。3.5 CLI 交互设计把常用功能都做成简单命令claude-mem 的命令行设计遵循“高频操作一定短”的原则。我自己高频用的命令就 7 个claude-mem init # 初始化存储目录和配置 claude-mem run 你的问题 # 带记忆注入的增强会话 claude-mem search 关键词 # 全局搜索记忆卡片 claude-mem recall --tag 数据库 # 按标签召回记忆 claude-mem stats # 查看记忆库统计信息 claude-mem compact # 手工触发当前会话压缩 claude-mem forget --before 2025-01-1 # 清理早期记忆claude-mem run和原生claude命令的体验几乎完全一致唯一的区别是它会在后台完成“检索记忆、拼接上下文、记录交互、会话结束提炼”这一整条链路。输出上也刻意保留了 Claude CLI 的颜色和格式不搞多余的状态栏。3.6 一键集成到常用工作流实际使用中我发现手动输入claude-mem run还是太麻烦人一旦进入专注状态根本想不起前缀。于是我在 shell 配置里加了一层 alias把claude直接映射到claude-mem run日常工作流无缝切换记忆功能默认开启。如果你想在 tmux 里用也做了适配claude-mem 会读取当前 tmux session name自动把session_name作为项目标记写入记忆卡片这样你每个 tmux 窗口天然对应一个项目记忆隔离不串味。这算是我在真实环境里被逼出来的一项改进最初根本没想过还有这种需求。4. 实战演示从零搭建属于自己的 claude-mem4.1 环境准备与安装claude-mem 目前以 Python 包的形式分发需要 Python 3.10 以上。安装只依赖一个核心库模型调用走的是 Claude 官方 SDK文本处理用标准库就能搞定。pip install claude-mem claude-mem initinit会在~/.claude-mem/下生成默认配置和目录结构。如果你要接入自己的 API Key在环境变量里配置好即可。初始化完成后可以用claude-mem stats看一眼初始状态正常应该是sessions: 0, memories: 0。我建议把一个终端窗口分成左右两半左边跑 claude-mem右边用tail -f ~/.claude-mem/logs/时刻观察日志写入情况。第一次运行时亲眼看到交互逐行落盘你才会对这套机制有底。4.2 记录一次完整任务的全过程下面用一个真实任务演示在某 Go 项目里排查订单服务的内存暴涨问题。第一次会话claude-mem run 帮我看看 order-service 内存一直在涨可能是什么原因模型给出的回答里提到了 goroutine 泄漏、大对象分配、定时器未释放等方向。期间我追问了“怎么定位 goroutine 泄漏”“pprof 怎么抓堆快照”并最终定位到time.AfterFunc在循环里创建了大量未清理的计时器。会话结束后claude-mem 自动提炼。生成的记忆卡片存到memory/order-service/20250214_memory_leak.md内容大致是# 内存泄漏排查time.AfterFunc 未清理 标签Go, order-service, 内存, goroutine ## 结论 - 订单服务内存上涨源于 time.AfterFunc 创建的计时器未在任务完成后调用 Stop - 修复思路改用 context 取消 time.NewTimer 手动回收 ## 变更 - 计划在 order_processor.go 中新增 ctx 监听逻辑 ## 待办 - 验证修复后 heap 是否回落 - 补充 Timer 清理相关的单元测试 ## 来源 - session_20250214_153012第二天新开会话cd ~/workspace/order-service claude-mem run 继续昨天的内存问题我打算改 order_processor.goclaude-mem 自动检索到昨天的卡片注入参考信息。模型立刻知道“昨天已经定位到计时器泄漏问题”并且直接建议按卡片里的方案写代码而不需要我重新解释背景。整个过程看起来就像模型“记得”这件事非常自然。4.3 跨项目记忆隔离实测我也测试过跨项目会不会串记忆。在~/workspace/order-service下开会话问“上次数据库加索引那个方案”它没有任何反应因为索引相关的记忆在另一个项目目录下。切到~/workspace/user-service后再问同样的问题立刻就召回了两天前给用户表加联合索引的决策记录。这个隔离效果非常重要。如果你的记忆库把多项目内容混在一起检索时很容易拿到无关上下文模型反而会被带偏。所以我在设计时坚持“项目目录是记忆的第一隔离维度”标签只是第二维度。5. 常见问题与排查方案5.1 高频问题速查表我把实际使用中遇到的问题整理成了一张速查表按出现频率排序现象原因解决办法没有生成记忆卡片会话内容被判定为“无价值”检查 JSONL 是否完整手动强制claude-mem compact召回的记忆不相关语义阈值设置太低调高recall_threshold比如从 0.55 调到 0.7注入后模型回答变啰嗦注入的卡片过多调低recall_count限制在 3~5 条存储目录膨胀太快日志文件从未清理设置定期清理策略保留最近 30 天原始日志多次会话后记忆重复相似任务每次都生成新卡片启用卡片合并基于标题相似度做融合模型不认记忆内容注入格式不够清晰检查注入前缀是否是英文指令中文提示词效果会弱一些中断的会话没被提炼进程异常退出手动补执行claude-mem process --pending5.2 三条最容易被忽略的经验第一记忆卡片一定要注明来源。我在字段里加了session_xxx的链接指向方便回溯原始讨论。否则三个月后你只会看到一条孤零零的结论完全不知道它怎么来的自然也不敢信它。第二原始会话日志不要随便删。提炼模型再强也可能会在摘要时丢失一些关键细节。日志是“原始证据”卡片是“加工产物”两者是配套关系。我保留近 30 天的原始日志超过的自动清理因为更早之前的内容基本都已经在卡片里了反复回溯老日志的频率很低。第三如果你的工作流里经常要涉及“决策演化史”建议在检索卡片之外再加一个“按时间线查看”的命令。claude-mem 里我额外写了一个 timeline 子命令按时间顺序打印项目内所有卡片的标题和结论摘要直接在终端里看项目决策的历史流。这个功能写起来很简单就是按文件名排序读卡片但实际用途超出预期尤其在复盘项目时非常有帮助。5.3 性能与隐私优化经验内存方面如果卡片数量上万每次做语义检索直接暴力扫描有一点压力但大多数个人项目卡在几千张以内完全无感。如果哪个项目真的一下子产生上万张卡片大概率是提炼策略太宽松生成了大量低质量记忆这时候应该收紧提炼阈值而不是升级硬件。隐私方面claude-mem 默认所有数据都存在本地不发送任何存储内容到外部服务。唯一会上传的就是你在“提炼”和“检索注入”这两个环节主动提交给模型的文本这一点和直接用在线 Claude 没有区别。如果你处理的是敏感代码库可以增加一条规则在提炼前用正则做一次脱敏比如替换内网 IP、key 或者自定义机密格式本质上就是一个过滤器的问题。6. 写在最后的个人体会claude-mem 的完整实现并不复杂核心代码量控制在 1500 行左右但它在我的日常工作中带来的体验提升是实打实的。以前我和模型协作本质上是“每次重新认识一个聪明但健忘的同事”现在更像是“组建了一个带共享笔记的两人小团队”。记忆让模型越用越懂你的项目也让重复解释背景的成本归零。我实际使用中最大的感受是不要贪多记忆不是越多越好。早期我把所有会话都强制提炼两周后记忆库变得又杂又乱检索结果反而不如不检索。后来改成“有价值才提炼 低分不注入”效果反而更好。做记忆系统和做知识管理一样过滤和沉淀比收集更重要。如果你也在做类似方向的探索我的建议是别急着上复杂的向量数据库和分布式架构先用文件加关键词检索把链路跑通再逐步加语义层和自动触发。这个项目目前的扩展方向是支持会话内的迭代式压缩也就是当对话超长时不止做一次性摘要而是每隔一段时间把已讨论内容压缩成阶段性结论让模型在长任务中始终把握全局上下文同时控制窗口占用。做记忆系统的本质是在帮模型维护一个它自己看不见的“第二大脑”这个方向值得长期投入。