April 2026
Claude Code 为啥好用,他做了什么别人没做的
记忆/上下文/检索/工程 代码深入解析,对比其他 Agent
Last Updated: 2026-04-01
我扒了 Claude Code v2.1.88 泄漏的完整源码(57MB TypeScript),发现了几个亮点:
- Markdown + Frontmatter 统一模式——记忆、技能、Agent、命令用同一套"摘要选择 + 全文注入"范式,简单好用的 AI Native 数据管理
- 四层渐进压缩——从每轮静默截断到 9 段结构化摘要,按需逐层升级
- 三层检索——索引注入、Sonnet 异步选文件、模型 Grep 精确查找,层层递进互不阻塞
- Prompt Cache 作为一等公民——10x 缓存命中成本差,每个架构决策都优先保护缓存前缀不被破坏
上一篇以 OpenClaw 为主线,这篇深入 Claude Code,对比 Codex 和 Gemini CLI。
一、记忆——MD + Frontmatter:一种 AI Native 的存储模式
主流编程 Agent 都有项目指令文件——Claude Code 的 CLAUDE.md、Codex 的 AGENTS.md、Gemini CLI 的 GEMINI.md——启动时加载,提供项目级上下文。这一层大家做法类似。
Claude Code 在此基础上额外构建了项目维度的长期记忆系统。
存储格式:MD + Frontmatter
每个记忆文件长这样:
---
name: user prefers vim keybindings
description: User always requests vim mode in editors, has 10+ years vim experience
type: feedback
---
User explicitly asked to enable vim mode. They mentioned using vim since college.
**Why:** Deep muscle memory, can't work efficiently without vim bindings.
**How to apply:** Always suggest vim-compatible options when configuring editors.
YAML frontmatter 是结构化摘要(name、description、type),Markdown body 是完整内容。检索时只读 frontmatter 做决策,按需加载全文。
这个模式的核心思路是:先让 AI 看目录和摘要,再决定要不要读全文。和人类翻书一样——先看目录,再翻到具体章节。简单但有效。
不只是记忆——整个扩展体系的统一模式
这个 "frontmatter 选择 + body 注入" 模式贯穿整个 Claude Code 扩展体系:
| 内容类型 | Frontmatter 字段 | 用途 |
|---|---|---|
| 记忆文件 | name, description, type | LLM 选择相关记忆 |
| Skills | name, description, whenToUse | LLM 决定是否触发技能 |
| 自定义 Agent | name, description, tools, model | LLM 决定是否派遣 Agent |
| Commands | name, description | Slash 命令注册 |
| Rules | globs | 按文件路径匹配条件注入 |
记忆、技能、Agent、命令——对 Claude Code 来说,它们本质上是同一种东西:带结构化元数据的 Markdown 文件。同一套范式,复用在整个系统中。
记忆类型与边界
源码中硬编码了四种记忆类型(memdir/memoryTypes.ts):
export const MEMORY_TYPES = [
'user', // 用户角色、偏好、知识
'feedback', // 做事方式的指导(纠正和确认都记)
'project', // 项目进展、截止日期、决策背景
'reference', // 外部系统指针(Linear、Grafana 等)
] as const
Frontmatter 的 type 字段通过 parseMemoryType() 校验,不在这四种里的会 graceful degradation。
同样重要的是源码中明确定义了不存什么(WHAT_NOT_TO_SAVE_SECTION):不存代码模式和架构、不存 git 历史、不存调试方案、不存 CLAUDE.md 已有的内容。定位很清楚——记忆只存代码和工具推导不出来的信息。
实验特性:AutoDream 与 Team Memory Sync
源码中还有两个 feature gate 后的实验特性:
- AutoDream:后台 agent 定期(≥24h 且 ≥5 次会话)对记忆文件做合并、去重、裁剪。Prompt 原话:"You are performing a dream — a reflective pass over your memory files."
- Team Memory Sync:Client-Server 记忆同步,delta 上传 + secret 扫描,面向团队共享记忆
对比:Codex(
AGENTS.md)和 Gemini CLI(GEMINI.md)目前在指令文件阶段,类似 Claude Code 的CLAUDE.md。Claude Code 在此之上多走了一步做长期记忆。
二、上下文——四层压缩
Claude Code 的上下文管理有四层压缩系统,从轻到重按需逐层升级。
第一层:Time-Based Microcompaction
最轻量的一层。纯代码逻辑,不调用 LLM,开销几乎为零。
核心思路:如果距离上一条 assistant 消息已经过了较长时间(说明 prompt cache 已过期),就把旧的工具输出内容替换为 [Old tool result content cleared],只保留最近几条。
// microCompact.ts:438-441
const gapMinutes =
(Date.now() - new Date(lastAssistant.timestamp).getTime()) / 60_000
if (!Number.isFinite(gapMinutes) || gapMinutes < config.gapThresholdMinutes) {
return null // 间隔不够长,不触发
}
只对特定工具的输出动手——COMPACTABLE_TOOLS 包含 File Read/Write/Edit、Bash、Grep、Glob、Web 工具。Agent 子代理的返回保留全文,因为那些是经过处理的高质量结果。
第二层:Cached Microcompaction
在 prompt cache 仍然有效时(cache 没过期),用 Anthropic 的 cache editing API 删除旧工具结果,不破坏缓存前缀。
和第一层的区别:第一层直接改本地消息内容(cache 已经冷了,无所谓);第二层通过 cache_edits 指令让 API 端删除,本地消息不动,保证 cache key 不变。
// microCompact.ts:296-301
// Key differences from regular microcompact:
// - Does NOT modify local message content (cache_edits are added at API layer)
// - Uses count-based trigger/keep thresholds
// - Tracks tool results and queues cache edits for the API layer
第三层:Full Compaction
上下文接近窗口上限时触发。这一层调用 LLM 生成 9 段结构化摘要,是四层中最昂贵的。
关键设计:压缩通过一次独立的 side call 完成,压缩过程本身不污染主对话历史。
最直觉的做法是在主对话里直接插入压缩 prompt,让模型返回摘要:
主对话: [msg1, msg2, ..., msgN, 压缩prompt]
→ API 返回摘要(assistant 消息)
→ 继续: [msg1, ..., msgN, 压缩prompt, 摘要, 新用户消息, ...]
问题:压缩 prompt 和摘要都留在对话历史里,占上下文空间
Claude Code 的做法是发一次独立的 API 调用(源码中叫 runForkedAgent),压缩过程不进入主对话:
主对话: [msg1, msg2, ..., msgN] ← 上下文快满了
独立 API 调用(不在主对话里):
发送: [同样的 system prompt + tools + msg1...msgN + 9段结构化压缩prompt]
返回: 摘要文本
调用结束,不影响主对话历史
回到主对话:
[msg1, ..., msgN] 全部替换为 [摘要(user message)]
继续: [摘要, 新用户消息, ...]
压缩 prompt 本身是客户端构造的详细模板(BASE_COMPACT_PROMPT),要求模型生成 9 段结构化摘要(请求意图、技术概念、文件和代码、错误与修复、用户消息、待办任务、当前工作等),先在 <analysis> 标签里做分析草稿,再输出 <summary>。分析草稿在格式化后会被剥离,只有 summary 进入后续上下文。
为什么不直接发一个全新的 API 调用? 为了 cache hit。这次独立调用通过 CacheSafeParams 传入和主对话完全相同的 system prompt、tools、model、messages、thinking config,前缀一致就能命中主对话已缓存的内容,只为新增的压缩请求付费。
// compact.ts:431-433 (代码注释原文)
// forked-agent path reuses main conversation's prompt cache.
// Experiment (Jan 2026) confirmed: false path is 98% cache miss
摘要 prompt 要求模型先在 <analysis> 标签里做详细分析(文件名、代码片段、函数签名、用户反馈),然后生成结构化 <summary>。<analysis> 在格式化后被剥离,只有 summary 进入后续上下文。
第四层:Snip Compaction
Feature gate 后的老上下文直接移除(HISTORY_SNIP),通过 lazy-loaded 模块实现。这是最激进的策略——不做摘要,直接丢弃。
模型自知预算
通过 <budget:token_budget> 标签和 <system_warning> 注入,Claude 4.5+ 模型实时感知上下文使用情况,可以自我调节——比如主动精简回复、或者决定是否启动一个可能输出很长的工具调用。
另外,扩展思维(thinking block)的 token 在下一轮自动清除,防止推理痕迹污染后续 turn。
对比:Codex 有写入时截断和中途压缩;Gemini CLI 有二次探针验证压缩质量。各有特色。
三、检索——三层机制,从被动加载到精确定位
存储只是基础,检索决定了记忆是否有用。Claude Code 的检索分三层,从零成本到高成本逐层递进。
Layer 1:MEMORY.md 索引注入(启动时,零额外成本)
会话启动时,getMemoryFiles()(utils/claudemd.ts:979)把 MEMORY.md 和 CLAUDE.md 一起加载进上下文:
// claudemd.ts:979-992
const { info: memdirEntry } = await safelyReadMemoryFileAsync(
getAutoMemEntrypoint(), // → MEMORY.md 路径
'AutoMem',
)
限制硬编码在 memdir/memdir.ts:
export const MAX_ENTRYPOINT_LINES = 200 // 最多 200 行
export const MAX_ENTRYPOINT_BYTES = 25_000 // 最多 25KB
MEMORY.md 只是索引(标题 + 单行描述),不是全文。它的价值在于让模型"知道"记忆系统里有什么,为 Layer 3 的主动搜索提供线索。
有意思的发现:源码中有一个 feature flag 开启后会通过 filterInjectedMemoryFiles()(claudemd.ts:1142)把 MEMORY.md 从系统提示中过滤掉,完全靠 Layer 2 推送。说明 Anthropic 在 A/B 测试 Layer 1 是否可以被 Layer 2 替代。
Layer 2:Sonnet sideQuery(每个用户 turn 一次,有门控)
这是最精妙的一层。入口在 query.ts:297,while(true) 工具循环之前:
// query.ts:297-304
// Fired once per user turn — the prompt is invariant across loop iterations,
// so per-iteration firing would ask sideQuery the same question N times.
using pendingMemoryPrefetch = startRelevantMemoryPrefetch(
state.messages,
state.toolUseContext,
)
同一 turn 内多轮工具调用只触发一次。startRelevantMemoryPrefetch()(utils/attachments.ts:2361)有 5 个门控条件:auto-memory 开启、feature flag 为 true、有真实用户消息、用户输入包含空格(单词查询跳过)、会话累计推送字节数低于上限。
通过门控后,findRelevantMemories()(memdir/findRelevantMemories.ts:98)执行核心流程:
// findRelevantMemories.ts:98-108
const result = await sideQuery({
model: getDefaultSonnetModel(),
system: SELECT_MEMORIES_SYSTEM_PROMPT,
messages: [{ role: 'user', content: `Query: ${query}\n\nAvailable memories:\n${manifest}` }],
max_tokens: 256,
output_format: { type: 'json_schema', schema: { ... selected_memories: string[] ... } },
})
流程:scanMemoryFiles() 读所有记忆文件 frontmatter(前 30 行,最多 200 个文件)→ formatMemoryManifest() 格式化为清单(每行一个:- [type] filename (timestamp): description)→ Sonnet 返回最多 5 个文件名 → 读取选中文件完整内容 → 注入为 <system-reminder> 附件。
不阻塞。 sideQuery 是 prefetch 模式——启动后在后台运行。消费端(query.ts:1599)只在已 settle 时消费(settledAt !== null),没 settle 就跳过,下次迭代再检查。用户按 Escape 自动 abort。
去重:alreadySurfaced set 过滤已推送的文件;readFileState 过滤模型已 Read/Write/Edit 的文件。
为什么不用向量搜索? 规模有界——单用户、单项目、最多 200 个文件 × ~150 字符描述 ≈ 几千 token。Sonnet 一次调用就能处理。刻意简单,因为场景不需要复杂。
Layer 3:自主搜索(模型决定,Grep)
当 Layer 1/2 不够时,模型自己决定搜索。buildSearchingPastContextSection()(memdir/memdir.ts:375)在系统提示词中注入两种 Grep 搜索方式:
// memdir.ts:393-403 (注入到系统提示词的内容)
'1. Search topic files in your memory directory:',
memSearch, // → Grep pattern="<search term>" path="<memoryDir>" glob="*.md"
'2. Session transcript logs (last resort — large files, slow):',
transcriptSearch, // → Grep pattern="<search term>" path="<projectDir>/" glob="*.jsonl"
'Use narrow search terms (error messages, file paths, function names) rather than broad keywords.',
Layer 3 的关键:不是盲搜。Layer 1 的索引让模型知道记忆里大概有哪些主题;Layer 2 推送的文件让模型知道某些领域有更多细节。当模型感知到"应该有相关信息但需要更多细节"时,它知道自己要搜什么——被前两层引导的精确检索。
背景提取:写入端
检索好不好,取决于有没有东西可检索。Claude Code 有持续运行的背景记忆提取 agent:
- 触发:主 agent 产出最终响应时 fire-and-forget
- 机制:forked agent,共享父进程 prompt cache,最多 5 轮
- 互斥:主 agent 当轮已写记忆则提取 agent 跳过
- 职责:Turn 1 并行读取,Turn 2 并行写入记忆目录
用户不需要手动维护,系统在每次对话后自动提取值得记住的信息。
三层关系
Layer 1(索引)→ 让模型知道记忆系统的存在和大致内容
↓
Layer 2(推送)→ 异步补充最相关的具体文件
↓
Layer 3(搜索)→ 兜底,模型自己精确查找
↑
背景提取 agent → 持续写入,保证有东西可选
对比:Codex 和 Gemini CLI 的指令文件在启动时加载,没有运行时的按需检索层。
四、65+ Prompt 模块 + 子代理体系
模块化系统提示词
Claude Code 的系统提示词不是一个大文件,而是 65+ 个模块化文件按需组装,覆盖安全检查、文件修改规范、git 操作流程、输出效率要求、代码风格约束等方方面面。
更关键的是 20+ 个 <system-reminder> 模板在运行时动态注入——文件被外部修改时提醒过期、Skill 激活时注入指令、token 预算变化时实时告知、记忆文件推送时附带时效性警告等。
<system-reminder> 本身是一个值得说的设计。API 消息流里只有三个角色:system(系统提示词)、user(用户输入)、assistant(模型返回)。其中 assistant 的内容绝对不能修改(那是模型的输出),system 是缓存前缀不应该碰(改了就破缓存)。那运行时动态产生的系统信息放哪里?
Claude Code 的做法是在 user 消息中嵌入 <system-reminder> 标签,创建了一个第四通道——既不是用户输入,也不是模型返回,而是系统注入。系统提示词告诉模型:"Tags contain information from the system. They bear no direct relation to the specific tool results or user messages in which they appear." 模型知道这是系统信息,给予相应权重。
实现上极其简单——就是一个 XML 标签包装:
export function wrapInSystemReminder(content: string): string {
return `<system-reminder>\n${content}\n</system-reminder>`
}
但这个简单的设计解决了三个问题:保护 prompt cache(不碰 system 前缀)、保持角色语义(不篡改 assistant 输出)、代码可识别(用 startsWith('<system-reminder>') 做过滤和合并)。
模型始终在接收最相关的上下文指令——这是编码体感强的直接原因。
子代理体系
6+ 专业化子代理,每种有针对性的系统提示词和工具集:
| 子代理 | 用途 | 工具限制 |
|---|---|---|
| Explore | 只读代码搜索 | 只读工具,快速搜索 |
| Plan | 架构规划 | 只读工具,聚焦设计 |
| Code Reviewer | 代码审查 | 审查标准和输出格式 |
| Code Explorer | 深度分析 | 追踪执行路径 |
| Code Architect | 架构设计 | 蓝图输出格式 |
Coordinator Mode:编排多个 Worker,通过 <task-notification> XML 块通信。
Agent Swarm(详见 claude-code-swarm.research.md):
- 文件信箱(JSON + file lock),1 秒轮询
- 8+ 结构化消息类型(权限请求、关机生命周期、模式变更等完整协议)
- 集中权限模型:所有 Worker 的权限请求流向 Leader 终端
- 系统级空闲通知保证:query loop 退出时系统自动发送空闲通知,不依赖模型记住上报
Tool 并发分类:每个工具声明 isConcurrencySafe()(默认 false),读操作并行、写操作串行。
对比:Codex 采用单循环 + plan 模式;Gemini CLI 有进程内子代理。思路不同,Claude Code 在专业化分工上投入更多。
五、工程纪律
Claude Code 好用不只因为某个技术创新,而是系统级的工程纪律。几个代表性例子:
Prompt Cache 作为一等公民
缓存命中 vs 未命中有 10x 成本差,每会话几十到几百轮 tool use,累计差异巨大。三个保护机制:
DANGEROUS_uncachedSystemPromptSection()—— 函数名本身是防护栏,强迫开发者意识到自己在破坏缓存- 动态内容走 user message —— 所有
<system-reminder>注入用户消息,不碰系统提示词前缀 - Beta header 单向锁 —— 开了就不关,语义不精确但 cache 友好
Message Normalization Pipeline
5500+ 行代码,每次 API 调用前处理消息流:重排附件 → 过滤虚拟消息 → 确保 tool-use/tool-result 配对 → 合并连续 thinking/user block。为中断的工具执行创建合成错误块,防止 API 拒绝。
Fail-Closed 默认
isConcurrencySafe()默认 false —— 没声明安全就不并行isReadOnly()默认 false —— 没声明只读就当写操作- 权限系统默认 ask —— 不确定就问用户
Forked Agent 共享 Cache
记忆提取、full compaction、AutoDream 都用 forked agent,共享父进程的 prompt cache,避免重复支付系统提示词的 API 成本。这个模式被复用了至少 5 次。
System-Level Guarantees
Swarm 空闲通知:query loop 退出时系统级发送 sendIdleNotification()。"模型忘了汇报"在系统层面解决,不靠 prompt。
结语
Claude Code 为啥好用?
不是某个单点技术碾压,而是系统完整性:
- 记忆有存储(MD + Frontmatter)、有检索(三层递进)、有维护(AutoDream)、有提取(后台 agent)
- 上下文有四层渐进压缩,按需逐层升级
- 65+ prompt 模块让模型始终得到最相关的指令
- 6+ 种子代理各司其职
- 工程纪律贯穿每个设计决策
每个维度都做到了足够好,而且在同一套工程纪律下协同运作。
预告
下一篇:从 Claude Code Buddy/Companion 看 AI 陪伴类产品。
一个编程工具为什么要做"虚拟宠物"?gacha 无氪设计、sideQuery 模式复用、非侵入原则——以及这对 AI companion 产品设计的启示。
参考资料
基于源码分析和逆向工程,完整研究材料:
Claude Code 研究:
对比 Agent 研究:
全部研究:lin-guanguo.github.io/llm-memory-research
基于 Claude Code v2.1.88 源码(source map 泄漏)和系统提示词逆向工程。鸣谢:Claude / Codex / Gemini,进行了大部分源码分析工作。