ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

Claude Code 源码分析(五):分层记忆体系 —— AI Agent 的知识持久化架构与 TaoToken 统一接入实践

Claude Code 源码分析(五):分层记忆体系 —— AI Agent 的知识持久化架构与 TaoToken 统一接入实践

1. 从一次上下文丢失说起:分层记忆体系到底解决什么问题

如果你用 Claude Code 写过稍大一点的项目,大概率遇到过这种场景:上午跟它聊清楚了整个模块的目录结构和命名约定,下午新开一个会话,它又像失忆一样问你「这个项目用什么框架」。这不是模型变笨了,而是单次会话的上下文窗口天然有边界——一旦触发压缩,早期对话就被摘要甚至丢弃,跨会话更是完全从零开始。

Claude Code 的分层记忆体系,本质上就是给 AI Agent 装了一套「分时间尺度的记忆系统」。我把它类比成人的记忆:会话内记忆像工作记忆,处理当前手头的事;跨会话记忆像长期记忆,沉淀项目级知识;Agent 级记忆像专业技能记忆,让不同角色的 Agent 各自积累领域经验。三者协作,才让 Agent 从「一次性问答工具」变成「越用越懂你项目的协作伙伴」。

这套架构在源码里对应几个核心目录:src/services/SessionMemory/负责会话内记忆的提取与管理,src/services/extractMemories/负责跨会话知识提取,src/tools/AgentTool/agentMemory.ts和agentMemorySnapshot.ts负责 Agent 级记忆的目录管理与快照同步。理解它们的分工,是你在自己项目里落地知识持久化架构的第一步。

本文面向正在构建 AI Agent 的 TypeScript 开发者,会从源码机制讲到可复制的配置片段,并演示如何通过 TaoToken 统一接入通道把记忆模块接进实际工作流。你不需要读完整个 Claude Code 源码,但需要有一点 Agent 循环和上下文管理的概念。

2. 三层记忆的源码拆解:SessionMemory、extractMemories 与 agentMemory

2.1 会话内记忆:基于活动量的增量提取

src/services/SessionMemory/目录下有三个文件,核心逻辑围绕「什么时候提取」和「提取到哪」展开。提取时机由shouldExtractMemory(messages)判断,它内部会调用countToolCallsSince(messages, lastMessageId)统计自上次总结以来的工具调用次数。这种基于活动量的触发机制很关键——太频繁会浪费 API 调用,太稀疏又会丢失重要信息。

并发控制是另一个容易被忽略的细节。markExtractionStarted()和markExtractionCompleted()配合一把锁,防止两次提取同时跑;waitForSessionMemoryExtraction()则让后续操作能等待提取完成,确保读到最新记忆状态。我在自己项目里复刻这套逻辑时踩过的坑是:没加等待,结果压缩线程读到了半截记忆文件,导致摘要错乱。

位置追踪靠getLastSummarizedMessageId()和setLastSummarizedMessageId(id)这对函数。它记录「上次总结到哪条消息」,让增量提取不重复处理。注意一个关键点:当对话被压缩后,这个标记会被重置为undefined,因为旧消息的 UUID 已经不存在了。这个重置逻辑在autoCompact.ts里通过setLastSummarizedMessageId(undefined)完成。

记忆大小分析由analyzeSectionSizes(content)和generateSectionReminders(sizes)负责。前者把记忆文件按 section 统计字符数,后者在某个 section 过大时生成优化建议。这防止了记忆文件无限膨胀——毕竟记忆不是越多越好,冗余信息反而会稀释有效上下文。

2.2 跨会话记忆:CLAUDE.md 的沉淀机制

src/services/extractMemories/负责把对话中可复用的知识提取出来,持久化到CLAUDE.md。你可以把CLAUDE.md理解成项目级的「AI 说明书」——类似.editorconfig或.eslintrc,但存的是 AI 助手需要了解的项目知识:编码规范、架构决策、常用命令、目录约定。

提取发生在会话结束时或用户显式触发时,提取出的知识被追加到CLAUDE.md。这里有个设计取舍值得学习:它不做全量重写,而是增量追加,避免覆盖用户手写的记忆内容。如果你自己实现,建议给每条记忆加上时间戳和来源标记,方便后续清理过期知识。

2.3 Agent 级记忆:目录隔离与快照同步

agentMemory.ts为每个 Agent 类型维护独立记忆目录。sanitizeAgentTypeForPath(agentType)把 Agent 类型名转成安全的文件系统路径(比如把code-reviewer里的特殊字符处理掉),getAgentMemoryDir(agentType, scope)返回具体目录,isAgentMemoryPath(absolutePath)则用于权限检查——Agent 对自己的记忆目录有特殊读写权限,这个判断在权限系统里很关键。

agentMemorySnapshot.ts实现了快照与同步。getSnapshotDirForAgent、getSnapshotJsonPath、getSyncedJsonPath分别定位快照目录、快照 JSON 和同步后的 JSON;copySnapshotToLocal(agentType)把共享快照复制到本地,让 Agent 在离线环境也能访问记忆。这套机制支持记忆的版本管理和跨作用域同步,适合多 Agent 协作场景。

2.4 记忆与压缩的协同

src/services/compact/sessionMemoryCompact.ts实现了基于会话记忆的压缩策略。当上下文需要压缩时,系统优先尝试「记忆压缩」——把对话关键信息提取为记忆,再用记忆替代原始对话。这比简单截断保留了更多有价值信息。配置通过setSessionMemoryCompactConfig、getSessionMemoryCompactConfig、initSessionMemoryCompactConfig三个函数管理。

2.5 记忆在 Agent 派生中的传递

当 Agent 加载记忆时,会记录遥测事件tengu_agent_memory_loaded,带上agent_type、scope、source字段。不同类型的 Agent 对CLAUDE.md需求不同,比如EXPLORE_AGENT设置了omitClaudeMd: true——它是快速只读搜索代理,不需要了解编码规范,主 Agent 会基于它的结果做判断。

嵌套记忆去重靠ToolUseContext里的loadedNestedMemoryPaths: Set<string>。源码注释提到,readFileState是 LRU 缓存,繁忙会话中会驱逐条目,导致readFileState.has()检查失效,同一个CLAUDE.md可能被注入数十次。独立去重集合解决了这个问题——这个坑我在自研 Agent 时也遇到过,症状是 token 消耗莫名飙升。

3. 用 TaoToken 统一接入:可复制的分层记忆配置片段

理解了机制,接下来落地。Claude Code 本身通过 Anthropic 官方通道调用模型,但在自研 Agent 或需要统一管理多个模型通道时,用 TaoToken 做统一接入会更省心——一个 Key 走通对话、编码、Agent 工作流,Base URL 和 Model ID 集中配置。

先拿 Key:访问https://taotoken.net/api-keys(带 UTM:?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite)创建 API Key。然后按你的工具形态选配置方式。

方式一:Claude Code 的 settings.json

如果你在 Claude Code 里接入,编辑~/.claude/settings.json:

{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_AUTH_TOKEN": "sk-你的TaoToken密钥", "ANTHROPIC_MODEL": "claude-sonnet-4-20250514" } }

三件套齐全:Base URL 指向https://taotoken.net/api,Key 用刚创建的,Model ID 按需替换。

方式二:Codex 的 auth.json

如果你用 Codex 形态,编辑~/.codex/auth.json:

{ "OPENAI_BASE_URL": "https://taotoken.net/api", "OPENAI_API_KEY": "sk-你的TaoToken密钥", "model": "gpt-4o" }

方式三:Cline MCP 配置

在 Cline 的 MCP 设置里填:

{ "mcpServers": { "taotoken": { "command": "npx", "args": ["-y", "@taotoken/mcp-server"], "env": { "TAOTOKEN_BASE_URL": "https://taotoken.net/api", "TAOTOKEN_API_KEY": "sk-你的TaoToken密钥", "TAOTOKEN_MODEL": "claude-sonnet-4-20250514" } } } }

记忆模块的接入点

配置好通道后,把记忆模块挂到 Agent 循环里。核心是在每轮对话结束后调用提取逻辑,伪代码:

import { shouldExtractMemory, waitForSessionMemoryExtraction } from './services/SessionMemory'; async function onTurnEnd(messages: Message[]) { if (shouldExtractMemory(messages)) { await waitForSessionMemoryExtraction(); await extractToClaudeMd(messages); } }

extractToClaudeMd内部走 TaoToken 通道调用模型做知识提取,把结果追加到项目根目录的CLAUDE.md。这样下次会话启动时,Agent 读取CLAUDE.md就恢复了项目级记忆。

4. 验证请求:确认记忆模块真的在工作

配置完别急着跑业务,先做三步验证。

第一步:验证通道连通

用 curl 打一次对话接口:

curl https://taotoken.net/api/v1/messages \ -H "x-api-key: sk-你的TaoToken密钥" \ -H "anthropic-version: 2023-06-01" \ -H "content-type: application/json" \ -d '{ "model": "claude-sonnet-4-20250514", "max_tokens": 64, "messages": [{"role": "user", "content": "回复 OK"}] }'

返回里能看到content数组和usage字段,说明通道正常。如果返回 401,检查 Key 是否复制完整。

第二步:验证记忆提取触发

在 Agent 里连续调用几次工具(比如读文件、搜索),观察是否触发shouldExtractMemory。你可以在提取函数里加日志:

console.log('[memory] extract triggered, toolCalls=', countToolCallsSince(messages, lastId));

看到日志输出且CLAUDE.md有新内容追加,说明会话内记忆在工作。

第三步:验证跨会话恢复

关掉会话,重新启动,让 Agent 读取CLAUDE.md。问它一个只有记忆里才有的项目细节,比如「这个项目的测试命令是什么」。如果它能答出来,跨会话记忆就通了。

成功结果长这样:CLAUDE.md里出现结构化的 section,比如## 编码规范、## 常用命令;新会话启动时日志显示tengu_agent_memory_loaded;token 消耗没有因为重复注入CLAUDE.md而异常飙升。

5. 常见报错排查:401、local proxy failed 与 reading choices

401 Unauthorized

最常见。原因通常是 Key 没带对前缀,或者ANTHROPIC_AUTH_TOKEN和ANTHROPIC_API_KEY混用。Claude Code 读的是ANTHROPIC_AUTH_TOKEN,Codex 读的是OPENAI_API_KEY,别填错字段。另外确认 Base URL 是https://taotoken.net/api,不要多加/v1后缀导致路径重复。

local proxy failed

这个报错通常出现在本地代理层。检查你的settings.json里 Base URL 是否被其他工具覆盖,或者环境变量里有残留的旧地址。清理掉ANTHROPIC_BASE_URL的环境变量,只保留配置文件里的设置。

reading choices 报错

如果你用的是 OpenAI 兼容格式,返回体里应该有choices数组。报reading choices说明返回结构不对,多半是 Model ID 填错了——比如把 Anthropic 的模型名填到了 OpenAI 格式的请求里。对照三件套检查:Base URL、Key、Model ID 是否匹配你选的接口形态。

OAuth 相关报错

Claude Code 某些版本会走 OAuth 流程,如果你用 API Key 接入,需要在配置里显式关闭 OAuth。检查settings.json里是否有forceLoginMethod之类的字段,设成apiKey模式。

记忆重复注入导致 token 飙升

症状是每轮对话 token 消耗异常高。检查loadedNestedMemoryPaths去重集合是否生效。如果你自己实现,确保在ToolUseContext里维护这个 Set,并且在读取CLAUDE.md前先has()判断。

记忆文件膨胀

如果CLAUDE.md越来越大,检查analyzeSectionSizes和generateSectionReminders是否在跑。可以设一个阈值,比如单个 section 超过 2000 字符就触发清理建议。

6. 把记忆接进你的 Agent 工作流

分层记忆体系的价值不在于单点技术,而在于三个时间尺度的协同:会话内记忆保证压缩不丢关键信息,跨会话记忆让项目知识持续积累,Agent 级记忆让专业角色各自成长。源码里的并发锁、位置追踪、去重集合这些细节,都是踩过坑之后沉淀下来的工程经验。

如果你要长期跑编码 Agent 或构建多 Agent 协作系统,建议用 TaoToken 的 Coding Plan 统一管理通道,把 Base URL、Key、Model ID 集中配置,记忆模块只管调用,不用关心底层走哪个模型。接入文档在https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite,里面有各形态的完整配置示例。

最后给一个实用技巧:记忆提取的 prompt 模板别写太泛,明确告诉模型「只提取可跨会话复用的项目知识,忽略一次性调试细节」。我试过把模板收紧之后,CLAUDE.md的信噪比明显提升,Agent 恢复上下文时也更快进入状态。

RELATED READING

延伸阅读

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