通过源码学习思路:CLaude Code 如何实现Agent Memory (下)
1. 核心认知:不是一个内存系统,而是六个
Claude Code 代码库里 memory 这个词被用于多个相互独立的子系统。最容易混淆的就是这一点——很多人以为有一个统一的"记忆模块",实际上它由六个职责不同、目录不同、生命周期不同的子系统组成。
先把全貌理清:
| 子系统 | 目录 | 生命周期 | 写入者 | 召回方式 |
|---|---|---|---|---|
| Auto memory (memdir) | ~/.claude/projects/<slug>/memory/ |
跨会话 | 主 agent + 后台 extractMemories fork | MEMORY.md 始终在系统提示里;findRelevantMemories 选 ≤5 条注入 |
| Agent memory | ~/.claude/agent-memory/<agentType>/ |
跨会话,按 agent 类型隔离 | 被派生的 subagent 自己 | loadAgentMemoryPrompt 注入到该 agent 的系统提示 |
| Team memory | <autoMemPath>/team/ |
跨会话,团队共享 | 同 auto memory + 服务器同步 | 同 auto memory,用 buildCombinedMemoryPrompt |
| Session memory | ~/.claude/session-memory/ |
单会话,为压缩续命 | extractSessionMemory post-sampling hook | 压缩时加载,不跨会话召回 |
| CLAUDE.md 指令文件 | User/Project/Local/Managed 路径 | 跨会话 | 用户手动编辑 | getMemoryFiles 加载进系统提示 |
| AgentSummary | 内存里的 AgentProgress | 短暂 UI 状态 | 30s 定时器 fork | 仅 UI,不进任何内存库 |
⚠️ 最常见的混淆
/memory命令编辑的是旧的 CLAUDE.md 指令文件(User/Project/Local/Managed),跟 auto-memory memdir 是两套东西。前者是用户手写的全局指令,后者是 agent 自动提炼的跨会话记忆。
2. 数据模型:四类封闭分类法与索引机制
2.1 四种内存类型
src/memdir/memoryTypes.ts 定义了一个封闭枚举,只允许四种类型,刻意排除可从代码或 git 推导的内容:
// memoryTypes.ts:14-19
export const MEMORY_TYPES = ['user', 'feedback', 'project', 'reference'] as const
- user:用户的角色、目标、偏好
- feedback:用户对你工作方式的纠正或确认(“record from failure AND success”)
- project:进行中的工作/目标/事故,不可从代码或 git 推导;相对日期必须转绝对日期
- reference:指向外部系统(Linear、Grafana、Slack)的指针
WHAT_NOT_TO_SAVE_SECTION 明确排除:代码模式、架构、git 历史、debug 方案、CLAUDE.md 已有内容、临时任务状态——即使用户明确要求保存也适用。这是一个硬规则,不是建议。
2.2 单条内存的格式
每条内存是一个带 YAML frontmatter 的 .md 文件:
---
name: <memory name>
description: <一句话描述 - 用于在未来对话中判断相关性,要具体>
type: <user | feedback | project | reference>
---
<memory 内容;feedback/project 类型需写成:规则/事实 + **Why:** + **How to apply:**>
description 字段是召回时的相关性信号(详见第 5 节),所以提示词反复强调 “be specific”。一个模糊的 description 意味着这条记忆永远不会被召回选中。
2.3 MEMORY.md 索引文件
// memdir.ts:34-38
export const ENTRYPOINT_NAME = 'MEMORY.md'
export const MAX_ENTRYPOINT_LINES = 200
export const MAX_ENTRYPOINT_BYTES = 25_000
MEMORY.md 是索引,不是内存本身。每条一行、<150 字符:- [Title](file.md) - one-line hook。无 frontmatter。提示词明确:“Never write memory content directly into MEMORY.md”。
保存是两步流程(buildMemoryLines):先写主题文件,再在 MEMORY.md 加一行指针。这两步合在一起才构成一次完整的"记忆写入"。
3. 存储与路径解析
3.1 是否启用
isAutoMemoryEnabled()(paths.ts:30-55)按优先级链判断(先定义者胜):
CLAUDE_CODE_DISABLE_AUTO_MEMORY环境变量CLAUDE_CODE_SIMPLE/--bare→ 关- 远程模式无
CLAUDE_CODE_REMOTE_MEMORY_DIR→ 关 - settings.json 的
autoMemoryEnabled(支持项目级 opt-out) - 默认:开
3.2 主目录解析
getAutoMemPath()(paths.ts:223-235,memoized)解析逻辑:
CLAUDE_COWORK_MEMORY_PATH_OVERRIDE环境变量- settings.json 的
autoMemoryDirectory(只信 trusted 源:policy/local/user,projectSettings 被排除——防恶意 repo 把内存目录指向~/.ssh) <memoryBase>/projects/<sanitized-git-root>/memory/,其中 memoryBase =~/.claude或CLAUDE_CODE_REMOTE_MEMORY_DIR
getAutoMemBase() 用 findCanonicalGitRoot,让同一仓库的所有 worktree 共享一个内存目录。
3.3 路径安全校验
validateMemoryPath(paths.ts:109-150)拒绝:相对路径、根/近根路径、Windows 盘根(C:)、UNC 路径、null 字节。isAutoMemPath 用 normalize 防遍历。这是防止恶意项目配置把记忆目录指向敏感路径的安全防线。
4. 三大核心内存:写入与召回
在六个子系统中,Auto memory、Agent memory 和 Session memory 是三个最核心的、与日常对话直接相关的内存。下面按"何时记 / 怎么记 / 何时召回 / 怎么召回"四个维度逐一拆解。
- 🟦 Auto Memory:跨会话的"长期记忆"。主会话自动提炼,每轮积极召回,下次会话直接生效。
- 🟪 Agent Memory:子 agent 专属的长期记忆。只在子 agent 派生时加载,按 agent 类型隔离。
- 🟩 Session Memory:本会话的草稿本。为 autocompact 续命,对话结束就作废,永不跨会话。
4.1 Auto Memory — 主会话长期记忆
目录: ~/.claude/projects/<项目>/memory/
何时记
两个写手,互斥:
- 写手 A — 主 agent 自己。 系统提示里始终带着保存指令(
howToSave),任何时候它判断某事值得记,就直接写。用户明确说"记住 X"时,指令要求立即存。 - 写手 B — 后台抽取(extractMemories)。 在每轮查询循环结束(模型给出最终回复、不再调工具)时,由
handleStopHooks触发。有门控:功能开关打开、auto memory 开着、非远程模式、只主线程(子 agent 跳过)、节流。
互斥机制: hasMemoryWritesSince 检查主 agent 这轮是否已往记忆目录写过文件;写过就跳过抽取、只推进游标。两者每轮二选一,防重复。
// extractMemories.ts:348-360 — 互斥检查
if (hasMemoryWritesSince(lastExtractCursor)) {
// 主 agent 这轮已经写过内存了,fork 跳过,只推进游标
return
}
怎么记
- 都用 Write/Edit 工具写目录下的
.md文件,一文件一记忆 - 两步流程:写
.md+ 在 MEMORY.md 加一行指针 - 后台抽取的具体方式:分叉(fork)主对话(共享提示词缓存),先把现有记忆扫成清单预塞进去(省一轮查目录),5 轮预算
- 权限沙箱
createAutoMemCanUseTool:只读工具放开,Edit/Write 只能写在记忆目录内
何时召回
两条路:
- 路径 A — 常驻。 会话启动构建系统提示时,
loadMemoryPrompt把 MEMORY.md 拼进系统提示,全程在场。 - 路径 B — 按请求。 每次用户发消息(且超过一个词),触发
findRelevantMemories做动态召回。
怎么召回
- 路径 A:同步读 MEMORY.md,截断到 200 行/25KB,拼进系统提示
- 路径 B:扫最新 200 个文件头 → 拼成清单 → 调一次小模型(Sonnet)按文件名+描述选 ≤5 条 → 读全文注入
4.2 Agent Memory — 子 agent 专属长期记忆
目录: 按 scope 分三种
| Scope | 路径 | VCS | 提示词引导 |
|---|---|---|---|
user |
~/.claude/agent-memory/<type>/ |
不进 VCS | “keep learnings general, apply across all projects” |
project |
<cwd>/.claude/agent-memory/<type>/ |
建议进 VCS,团队共享 | “tailor to this project” |
local |
<cwd>/.claude/agent-memory-local/<type>/ |
gitignore | “tailor to this project and machine” |
何时记
- 只有子 agent 自己跑时写,它判断值得记就写
- 没有后台抽取:
extractMemories通过agentId检查跳过子 agent - 快照机制:加载 agent 定义时,若有快照且本地记忆为空,
initializeFromSnapshot把快照文件拷进本地;若快照比本地新,标记pendingSnapshotUpdate,主线程 agent 启动时弹窗让用户选是否合并
怎么记
- 子 agent 用 Write/Edit 写自己类型的目录
- 同样的前置元数据格式、一文件一记忆,复用
buildMemoryPrompt - Write/Edit/Read 被强制注入子 agent 的工具表;写权限通过
isAgentMemoryPath开窗放行
// loadAgentsDir.ts:726-730 — 注入到子 agent 系统提示
getSystemPrompt: () => {
if (isAutoMemoryEnabled() && memory) {
const memoryPrompt = loadAgentMemoryPrompt(agentType, memory)
return systemPrompt + '\n\n' + memoryPrompt
}
return systemPrompt
}
何时召回 & 怎么召回
- 只在子 agent 被派生(或用 SendMessage 恢复)时,
loadAgentMemoryPrompt构建子 agent 的系统提示 - 每次派生/恢复都从磁盘重读,没有跨调用的缓存
- 和主会话的 auto memory 完全隔离,不同 agent 类型互不共享
4.3 Session Memory — 本会话压缩续命的草稿本
目录: ~/.claude/session-memory/
何时记
- 每次模型输出后触发
extractSessionMemory(post-sampling hook),但门槛控制: - 初始化:对话累计 ≥ 10k token(一次性)
- 更新:自上次更新增长 ≥ 5k token(每次都要)
- 且 ≥ 3 次工具调用,或上一轮 assistant 没工具调用(自然断点)
- 只主线程(
querySource === 'repl_main_thread'),跳过子 agent
关键区别
Session memory 的 hook 每轮模型输出都触发一次(门槛控制),auto memory 的 extractMemories 只在整轮 query loop 结束时触发。两者频率不同、目的不同。
怎么记
- 分叉主对话,但沙箱极严:
createMemoryFileCanUseTool只允许 Edit 工具编辑那一个会话笔记文件,其他全拒 - 更新单文件,分节:标题 / 当前状态 / 文件和函数 / 工作日志 / 错误与纠正
- 上限:每节 2000 token、总 12000 token
何时召回 & 怎么召回
- 不跨会话召回
- 只在自动压缩时:压缩前
waitForSessionMemoryExtraction等 ≤15 秒让在途抽取完成,然后会话笔记作为上下文喂给压缩 - 不像 auto 那样每轮当附件注入,它只在压缩这个节点出场
5. 召回机制的设计哲学:没有向量检索
这是整个 Agent Memory 设计中最反直觉、也最有意思的决策:没有向量数据库、没有 embedding、没有 token 重叠排序。
动态召回管线(findRelevantMemories)的工作方式是:
- 扫描:
scanMemoryFiles递归 readdir,过滤.md(排除 MEMORY.md),对每个文件只读前 30 行(frontmatter),按 mtime 降序,上限 200 个文件 - 拼清单:
- [type] filename (ISO时间): description - LLM 选择:调
sideQuery,用getDefaultSonnetModel()(比主循环更小更快的模型),系统提示要求 “up to 5”、“不确定就不选”,输出 JSON schema - 读 + 注入:选中后读全文(带截断),作为
{ type: 'relevant_memories' }附件,渲染成<system-reminder>
// findRelevantMemories.ts:77-141 — LLM 选择器
// 系统提示要求 "up to 5", "不确定就不选"
// 输出 JSON schema: { selected_memories: string[] }
// max_tokens: 256
// recentTools 一并传给选择器,避免给正在用的工具再推参考文档
// 结果过滤到 validFilenames(防幻觉文件名)
设计意图
排序完全委托给 Sonnet 的 sideQuery,唯一信号是 filename + description + type + 时间戳。这正是
description字段为何被反复强调要具体——模糊的 description = 永远不会被召回的记忆。
新鲜度信号
memoryAge.ts 提供 memoryAge()(如 “47 days ago”)。注释说得很直白:“模型不擅长日期算术,原始 ISO 时间戳触发不了陈旧推理”。memoryFreshnessNote() 包成 <system-reminder>,且 header 在附件创建时预算好,避免渲染时调 Date.now() 导致 prompt cache 失效。
七层防爆
| 层级 | 机制 | 限制 |
|---|---|---|
| 1 | 索引截断 | MEMORY.md ≤ 200 行 / 25KB |
| 2 | 扫描上限 | 最多扫 200 个文件 |
| 3 | 选择上限 | Sonnet 最多选 5 条 |
| 4 | 单条截断 | 每条 ≤ 200 行 / 4KB |
| 5 | 单轮上限 | 每轮注入 ≤ 20KB |
| 6 | 会话上限 | 累计 ≤ 60KB |
| 7 | 去重 | 已注入过的、模型已读过的不再注入;压缩重置计数 |
6. Query Loop 中的出场时机
src/query.ts:241 的 queryLoop 是一个异步生成器(async function*),核心是个 while (true) 循环。一次迭代 = 一次模型调用 + 这次调用的工具执行。一个用户请求 = 一个 query loop,可能跑很多轮迭代。
下面把一轮迭代从头到尾走一遍,标出三个 memory 各在哪一步出场:
进入循环前(每个用户请求只做一次)
// query.ts:296-304
using pendingMemoryPrefetch = startRelevantMemoryPrefetch(state.messages, state.toolUseContext)
Auto 召回 在 while 循环之前发起一次,后台跑 findRelevantMemories。注释说得很清楚:“Fired once per user turn - the prompt is invariant across loop iterations, so per-iteration firing would ask sideQuery the same question N times.”(提示词在各轮迭代间不变,每轮都问就是重复问同一个问题)。它不阻塞模型流式输出,结果在流式过程中被消费。
while 每轮迭代(按代码顺序)
| 步骤 | 代码位置 | 动作 | Memory 出场 |
|---|---|---|---|
| ① | 365 | 准备消息(取压缩边界之后) | — |
| ② | 379 | 控制工具结果体积 | — |
| ③ | 400 | 裁剪旧历史 | — |
| ④ | 413 | 微压缩(折叠旧工具结果) | — |
| ⑤ | 440 | 上下文折叠 | — |
| ⑥ | 453 | 自动压缩 | Session 读 |
| ⑦ | 628 | 阻塞上限检查 | — |
| ⑧ | 654-863 | 调模型(流式)+ 流式工具执行 | — |
| ⑨ | 999-1009 | post-sampling hooks | Session 写 |
| ⑩ | 1015 | 中断处理 | — |
| ⑪ | 1062+ | 判断是否继续(needsFollowUp) | — |
| ⑫ | 1267 | handleStopHooks | Auto 抽取 |
| ⑬ | 1357 | 返回 completed | — |
关键代码段
步骤 ⑨ — Session memory 写入(每轮模型输出后触发):
// query.ts:999-1009
if (assistantMessages.length > 0) {
void executePostSamplingHooks([...messagesForQuery, ...assistantMessages], ...)
}
// extractSessionMemory 注册在这里面。注意是 void(发了就不管,异步不等)。
// 但 session memory 内部有门槛:初始化要 10k token、增长要 5k token——不达标就什么都不做。
步骤 ⑫ — Auto memory 抽取(整轮结束时触发):
// stopHooks.ts:141-153
if (!isBareMode()) {
void executePromptSuggestion(stopHookContext)
if (feature('EXTRACT_MEMORIES') && !toolUseContext.agentId && isExtractModeActive()) {
void extractMemoriesModule!.executeExtractMemories(stopHookContext, ...)
}
void executeAutoDream(stopHookContext, ...)
}
// 注意条件:!toolUseContext.agentId(只主线程,子 agent 跳过)
Agent memory 怎么和 query loop 挂上
Agent memory 不在主 query loop 里直接出场。它的接入是间接的:
主 query loop 某轮迭代里,模型调了 Agent 工具(步骤 ⑧)→ 这个工具作为一次工具调用执行 → 工具内部启动一个子 agent → 子 agent 跑它自己的 query loop(递归)→ 在子 query loop 开始构建系统提示时,loadAgentMemoryPrompt 把该类型的 MEMORY.md 注入子 agent 的系统提示。
嵌套关系
主循环某轮迭代里发起子 agent,子 agent 跑完自己的整个 query loop 返回结果,主循环拿着结果继续下一轮。子 agent 的 query loop 也会走 ⑨ 和 ⑫,但
agentId检查会让 session memory 写入和 auto memory 抽取都跳过——子 agent 的 query loop 不写主会话的 auto/session 记忆,只写自己的 agent memory。
7. 场景实战:多轮对话完整时间线
用一个具体场景把三个内存在多轮对话里的行为讲清楚。
场景设定
用户在新会话里说:“帮我看看 login.tsx 里登录按钮点了没反应。另外按我们之前定的——测试别 mock 数据库。”
这个场景能同时触发三条线:召回旧 feedback(测试别 mock)、记录新项目上下文、派生子 agent 查 auth 子系统。
会话启动:构建系统提示(用户还没发消息)
- Auto memory:
loadMemoryPrompt()跑一次:读 MEMORY.md,截断到 200 行/25KB,拼进系统提示。同时ensureMemoryDirExists建好目录。"测试别 mock 数据库"那条 feedback 已经在系统提示里了(假设之前存过)。 - Agent memory:什么都不做。子 agent 还没派生,它的内存目录不会被读。
- Session memory:什么都不做。新会话刚开始没有笔记文件;上一次会话的 session-memory 不会被加载。
第 1 轮:用户发消息 → 模型回复
- Auto 召回(模型调用前):
findRelevantMemories扫所有 .md 的 frontmatter,拼成 manifest,让 Sonnet sideQuery 挑 ≤5 条相关的,注入成<system-reminder>附件。"测试别 mock"很可能被选中注入。去重:已注入过的、模型已读过的不再注入。 - Session 写(模型回复后):
extractSessionMemoryhook 被触发,但shouldExtractMemory检查阈值:初始化要 ≥10k tokens。这轮还太早,跳过。
第 2 轮:模型边查边修,中途派生子 agent
- Agent memory(派生子 agent 瞬间):
loadAgentMemoryPrompt执行:读该 agent 类型自己的 MEMORY.md,拼到子 agent 的系统提示尾部。强制注入 Write/Edit/Read 工具,写权限通过isAgentMemoryPath开窗。它读的是 agent 类型专属目录,和主会话的 auto memory 完全隔离。 - Session 写(子 agent 跑完后):阈值可能达标了(token 增长 ≥5k 且 ≥3 次工具调用):fork 出
runForkedAgent,只允许 FileEditTool 编辑那一个 session-memory 文件,更新各节。子 agent 期间可能往自己的内存目录写了新 .md(比如发现 auth 中间件的某个坑),这些写不会进主会话的 auto memory。
第 N 轮:query loop 结束(模型给出最终回复)
- Auto 抽取(handleStopHooks 触发):
extractMemories启动:runForkedAgentfork 主对话(共享 prompt cache),先scanMemoryFiles预注入现有内存 manifest,让 fork agent 从这轮对话里提炼持久、跨会话的事实。互斥检查hasMemoryWritesSince:如果主 agent 这轮已自己写过,fork 就跳过。写完新 .md + 更新 MEMORY.md 索引。
第 N+1 轮:自动压缩(autocompact)
- Session memory 发挥作用:压缩前,
waitForSessionMemoryExtraction先等 ≤15s 让在途抽取跑完。然后 session-memory 文件被作为上下文喂给压缩:压缩后的对话保留了"当前状态/涉及的文件函数/工作日志",不会因为删掉旧消息而失忆。 - Auto memory 与压缩的关系:MEMORY.md 在系统提示里,不在消息历史里,压缩碰不到它,照常在。但之前动态注入的那些
<system-reminder>相关内存附件在消息历史里——会被压缩删掉。这反而是设计意图:压缩删掉旧附件后去重集自然重置,下一轮可以重新按需注入。
会话结束 → 下次新会话
- 会话结束(Auto):
drainPendingExtraction等 ≤60s 让在途的 extractMemories 跑完再关进程,避免半截抽取丢失。若开了 team memory,stopTeamMemoryWatcher把 debounce 里的待推送 flush 上服务器。 - 下次新会话(Auto):
loadMemoryPrompt把上一会话 extractMemories 写进 MEMORY.md 的新条目全部加载进来。用户这次说"再帮我测下登录",模型一开始就知道"测试别 mock 数据库"、"login.tsx 之前改过哪里"等。这就是 auto memory 的核心价值。 - 下次新会话(Agent):下次派生同类型子 agent 时,从磁盘读到上次该 agent 写的内存。不同 agent 类型互不共享。
- 下次新会话(Session):上次的文件不加载。新会话从空白开始,等阈值达标后重新建笔记。
8. Team Memory:跨成员同步
Team memory 是 auto memory 的子目录 <autoMemPath>/team/,按 git repo scope,跨 org 成员共享。
同步语义
- Pull 覆盖本地(server wins per-key)
- Push 只传 delta:只上传本地 hash 与 serverChecksums 不同的 key
- 删除不传播:删本地文件不会删服务器,下次 pull 会恢复
乐观锁与冲突解决
PUT 带 If-Match: "<checksum>",服务器返 412 表示冲突。冲突时 fetchTeamMemoryHashes 探测 per-key checksum(不下载正文,省带宽),重算 delta,重试(最多 MAX_CONFLICT_RETRIES = 2)。本地编辑不被静默丢弃:本地 wins on conflict。
批处理与上限
batchDeltaByBytes:按 key 排序后贪心装箱到MAX_PUT_BODY_BYTES = 200_000,避开网关 413MAX_FILE_SIZE_BYTES = 250_000单文件上限- entry 数上限从服务器结构化 413 的
extra_details.max_entries学到,缓存后下次按它截断
Watcher 与密钥扫描
- 启动时先 pull,再
fs.watch({recursive:true})监听目录,DEBOUNCE_MS = 2000写后 2s 静默再 push isPermanentFailure:no_oauth/no_repo/4xx → 设pushSuppressedReason抑制重试,防无限循环(注释提到一个 no_oauth 设备 2.5 天发 167K push 事件)- 密钥扫描:每个文件在上传前
scanForSecrets(gitleaks 规则)。命中则整个文件跳过不上传,只记 ruleId,不记路径、不记值
9. 关键设计决策与反直觉点
① 没有 schema 版本/迁移系统
系统靠优雅降级:parseMemoryType 对未知 type 返 undefined,parseFrontmatter 吞 YAML 错误并重试自动加引号,scanMemoryFiles 用 Promise.allSettled 容错单文件。没有迁移脚本,没有版本号。
② 没有压缩/compaction
唯一的容量控制是读取时截断 MEMORY.md(200 行/25KB)。主题文件本身从不截断。靠行为提示词(“按主题组织,不要按时间”、“不要写重复内存”)维持整洁。这是一种"用模型理解力替代工程机制"的设计选择。
③ 没有向量检索
召回完全靠 Sonnet sideQuery 读 filename + description。这意味着 description 的质量直接决定记忆的可用性。
④ AgentSummary 不是内存
src/services/AgentSummary/agentSummary.ts 只是每 30s fork 一次生成 3-5 词的 UI 进度字符串(“Reading runAgent.ts”),skipTranscript: true,存 AgentProgress,不进任何内存库。
⑤ Prompt cache 友好
freshness header 在附件创建时预算好,避免渲染时 Date.now() 把 “3 days ago” 变 “4 days ago” 而打碎 prompt cache。这类细节贯穿整个设计——一切可能让 prompt 变化的操作都被提前固化。
⑥ extractMemories 的 prompt-cache 共享
runForkedAgent 是主对话的完美分叉,共享父对话的 prompt cache。这意味着后台抽取几乎不花额外的 prompt 编码成本——它复用了主对话已经编码好的上下文。
设计哲学总结
整个系统的设计可以概括为一句话:用模型的理解力替代工程机制。没有向量检索(用 LLM 选),没有 schema 迁移(用优雅降级),没有 compaction(用行为提示词)。唯一投入大量工程的是安全防线(路径校验、权限沙箱、密钥扫描)和性能优化(prompt cache 共享、预取、节流)。
三者分工一句话
- Auto Memory —— 为未来的我记笔记。 每轮都积极召回(MEMORY.md 常驻 + Sonnet 选 ≤5 注入),整轮结束时抽取。压缩不丢,下次会话直接生效。
- Agent Memory —— 某个专家 agent 自己的小本本。 只在它上场时翻开,按 agent 类型隔离,和主会话井水不犯河水。没有后台抽取,靠子 agent 自己写。
- Session Memory —— 这场对话的草稿纸。 只为撑过压缩,每轮按门槛尝试更新,压缩前必读,但对话结束就作废,永不跨会话。
三者都用 runForkedAgent 这个原语来写,但写的目录、写的时机、召回的方式各管一段,互不越界。这正是"六个独立子系统、不是一个大内存系统"的具体体现——每个子系统都有自己的生命周期、自己的安全边界、自己的召回路径,通过 query loop 的不同节点接入,共同构成了 Claude Code 的完整记忆能力。
更多推荐




所有评论(0)