
1. 记忆没被召回先别急着怀疑模型Claude Code 的记忆系统是一套完全落在本地目录的文件机制不依赖云端数据库支持个人、团队、项目三层隔离。它把用户偏好、项目规则、工作习惯、外部资源入口这些信息写成 Markdown 片段存在~/.claude/projects/sanitized-project-root/memory/下面再用一个MEMORY.md当索引在每次对话时按相关性挑出最多 5 条注入 system prompt。问题就出在这个“挑”字上。很多人跑 Claude Code 时会遇到两种很像、但根因完全不同的现象一种是旧记忆明明写在文件里模型却像没看见另一种是记忆被选中了但内容没进上下文或者进了上下文却没被用上。前者可能是模型通道没通后者才是记忆召回链路本身的问题。这篇按排障视角来拆。我会先把getRelevantMemoryAttachments→findRelevantMemories→selectRelevantMemories这条链路讲清楚再给你一套可复制的配置把模型通道切到 TaoToken最后用selectRelevantMemories的后校验逻辑只返回原始列表里的文件名来区分到底是通道没通还是记忆没被选中。适合谁看已经在用 Claude Code、配过MEMORY.md、但发现“相关记忆没生效”的同学以及想先把模型通道跑通、再回头调记忆召回的开发者。2. 把模型通道落到 TaoToken 上Claude Code 的记忆召回里有一个关键动作selectRelevantMemories会调用 Sonnet 做一次侧边查询让模型根据MEMORY.md索引里的文件名和描述挑出最多 5 条相关记忆。这一步是要走模型 API 的。如果通道没通这个侧边查询直接失败表现就是“记忆一条都没被召回”而不是“召回错了”。所以排障的第一步是先把模型通道确认下来。TaoToken 在这里的角色就是模型通道 Key 的落脚点你到官网创建 Key把 Base URL 填成https://taotoken.net/apiClaude Code 的请求就会走这条通道。注意 Base URL 不要带/v1。这一点很容易踩坑因为不少工具的默认习惯是带/v1但这里填https://taotoken.net/api就行多一段路径反而会让请求打到不存在的端点上。创建 Key 的入口在控制台配好之后建议先单独验证一次模型对话确认通道是活的再回去看记忆召回。顺序反了的话你会把“通道 401”误判成“记忆没被选中”白白折腾半天。3. 可复制的配置与目录结构3.1 环境变量与 Base URLClaude Code 读取模型通道一般走环境变量。你可以这样设置export ANTHROPIC_BASE_URLhttps://taotoken.net/api export ANTHROPIC_API_KEYsk-你的TaoTokenKey如果你用的是配置文件方式把对应的base_url和api_key字段填成上面的值即可。填完先别急着跑记忆场景用一句最简单的对话验证通道# 验证通道是否可用先跑一次普通对话 claude -p 用一句话说明你现在能正常响应能正常返回说明通道通了。返回 401 或连接错误先解决 Key 和 Base URL 的问题别往下走。3.2 记忆目录长什么样记忆系统的目录结构是这样的先确认你的文件真的写对了位置~/.claude/ └── projects/ └── sanitized-project-root/ └── memory/ ├── MEMORY.md # 记忆索引入口 ├── user_expertise_profile.md ├── testing_preferences.md ├── mobile_release_merge_freeze.md └── team/ # 团队记忆子目录可选 ├── MEMORY.md └── coding_standards.md每个记忆片段是一个带 frontmatter 的 Markdown 文件结构和你熟悉的SKILL.md很接近--- name: user_expertise_profile description: 用户的技术背景和专业知识画像 type: user --- 用户是数据科学家专注于可观察性和日志分析领域。 **技术栈**: - 深度 Go 语言专家10 年经验 - React 和前端开发新手本项目首次接触MEMORY.md是索引不是记忆本身。它每一行是一条指针格式是- [Title](file.md) — one-line hook没有 frontmatter行数上限 200大小上限 25KB每行描述控制在 150 字符以内。超过 200 行的部分会被截断这也是“旧记忆没被召回”的一个高频原因——索引太长后面的条目根本没进 prompt。3.3 召回链路的关键参数把召回链路拆开看几个参数决定了“能不能被选中”环节关键约束排障含义scanMemoryFiles每个路径最多扫 200 个记忆文件文件超过 200 个后面的扫不到MEMORY.md索引最多 200 行25KB索引超限条目被截断selectRelevantMemoriesSonnet 侧边查询最多选 5 条通道不通则一条都选不出后校验只返回原始列表里的文件名过滤幻觉选不中就是真没选注入最多 5 条带 freshness 标记超过 5 条不会全进这张表建议存下来。你后面排查“为什么这条没被召回”基本就是在这几行里找答案。4. 验证请求与成功结果4.1 先确认侧边查询有没有发生记忆召回的核心是findRelevantMemories里的selectRelevantMemories。它会把MEMORY.md格式化成一份清单形如- [type] filename (timestamp): description然后带着用户 query 去问 SonnetQuery: 帮我修复数据库连接超时的问题 Available memories: - [user] user_expertise_profile.md (2026-04-01T10:30:00.000Z): User is a senior backend engineer with deep PostgreSQL expertise - [feedback] testing_preferences.md (2026-03-28T15:45:00.000Z): Integration tests must hit real database, not mocks. Recently used tools: FileReadTool, GrepTool预期返回是一段 JSON{ selected_memories: [ connection_pool_config.md, db_monitoring_grafana_dashboard.md, testing_preferences.md ] }如果你在日志里看到这个请求发出去了、但返回空数组说明通道是通的只是 Sonnet 判断“没有明显有用的记忆”。这时候问题在索引描述写得太模糊不在通道。4.2 用后校验区分两类问题selectRelevantMemories里有一段后校验非常关键const parsed: { selected_memories: string[] } jsonParse(textBlock.text) return parsed.selected_memories.filter(f validFilenames.has(f)) // 只返回存在于原始列表中的文件名防止幻觉这段逻辑的意思是模型返回的文件名必须能在原始清单里找到否则被过滤掉。它本来是防幻觉的但拿来排障特别好用如果模型返回了文件名但过滤后为空 → 模型“幻觉”了一个不存在的文件说明索引里的文件名和实际文件对不上去核对MEMORY.md里的链接。如果模型直接返回空数组 → 通道通了但 Sonnet 认为没有相关记忆去优化description字段。如果请求根本没发出去 → 通道没通回到第 2 节检查 Base URL 和 Key。我试过把description从“用户偏好”改成“集成测试必须打真实数据库不用 mock原因上季度 mock 与生产分叉掩盖了迁移问题”同一句 query 下命中率明显不一样。描述越具体Sonnet 越容易判断相关性。4.3 成功注入后的样子记忆被选中后会作为 attachment 注入主模型看到的是带新鲜度标记的内容## Relevant Memories [user] user_expertise_profile.md (2 days ago) _Some content here..._ [feedback] testing_preferences.md (5 days ago) _Some content here..._ This memory is 5 days ago. Memories are point-in-time observations, not live state — claims about code behavior or file:line citations may be outdated. Verify against current code before asserting as fact.看到这段说明整条链路是通的通道通、侧边查询成功、后校验通过、注入完成。如果模型还是“没按记忆来”那问题就不在召回而在记忆内容本身写得太泛或者和当前代码状态冲突了。5. 本篇常见错排查5.1 报错401 / 连接失败先看 Base URL。填成https://taotoken.net/api/v1是高频错误这里不要带/v1。再确认 Key 没有多余空格环境变量有没有被其他配置覆盖。用claude -p跑一句普通对话能返回就说明通道没问题。5.2 记忆文件在但一条都没召回按顺序查三件事MEMORY.md是否存在且路径正确索引行数是否超过 200scanMemoryFiles是否因为文件数超过 200 而漏扫。这三个都是“文件在但扫不到”的典型原因。确认无误后再看侧边查询有没有发出。5.3 侧边查询返回空数组通道是通的问题在索引描述。description写得太抽象Sonnet 判断不出相关性。把描述改成包含具体场景、技术栈、触发条件的句子。另外注意 prompt 里有一条约束如果提供了近期使用过的工具列表不要选这些工具的用法参考类记忆但仍要选包含这些工具警告或已知问题的记忆。所以工具类记忆的描述要突出“注意事项”而不是“用法”。5.4 召回了但内容不对检查记忆的type和description是否匹配。user类型记用户画像feedback记工作指导project记项目背景reference记外部资源指针。类型错了Sonnet 在筛选时的判断也会偏。另外超过 1 天的记忆会带新鲜度警告如果记忆内容和当前代码冲突模型会倾向相信代码这时候要更新记忆而不是怪召回。5.5 索引更新了但没生效MEMORY.md在启动时会预加载到readFileState缓存每次查询前会重新读取。如果你在会话中途手动改了索引理论上下次查询会重新读。但如果改的是记忆文件内容而不是索引且文件已经被读过可能命中缓存。最稳的做法是重启会话或者用/memory save触发一次显式保存流程。6. 通道通了再谈记忆调优把模型通道切到 TaoToken 之后记忆召回这条链路才算有了可观测的基础。你可以先到 API Keys 页面确认 Key 状态再对照接入文档核对 Base URL 和参数如果只是想验证模型本身能不能正常选记忆用模型对话跑几次侧边查询的等价 prompt 最直接长期在 Claude Code 里做编码和 Agent 任务的话Coding Plan 更适合把通道和额度固定下来。回到排障本身selectRelevantMemories的后校验逻辑是最好用的分诊工具。它只返回原始列表里的文件名所以“返回空”和“请求没发出”是两件完全不同的事。前者去改description后者去查通道。把这两类问题分开你就不用再对着“记忆没被召回”这五个字瞎猜了。