Claude Code 为什么放弃 RAG:用 grep 干代码搜索的真实架构
Claude Code 早期版本是用 RAG 的。Anthropic 团队后来在内部 benchmark 里发现:换成 agentic search——也就是让模型自己去
grep、glob、read——在代码场景下"性能大幅超越其他方案"。这句话来自 Boris Cherny 的播客原话。我读到时还挺意外,毕竟这两年大家默认"做代码 agent = 上 RAG"。后来翻完它的实现细节,才明白这不是简单的"grep 比向量搜索好",是一次更深的设计判断。
一、它到底解决什么问题
先把问题说清楚:让一个 agent 改一个 bug,它得先找到相关代码。
具体场景大概是这样的:用户说"修一下 payment API 的 auth bug"。agent 要先回答几个问题——
- 这个项目长什么样(目录结构 / 模块划分)
- payment API 在哪个文件(精确定位)
- auth 相关的代码散在哪些地方(跨文件依赖)
- 跑测试要用什么命令(项目惯例)
这些问题的答案不在模型的训练集里——它们在用户当前的代码库里。所以这是一个"动态上下文获取"问题。
行业里主流有三条路:
| 方案 | 代表 | 思路 |
|---|---|---|
| 向量 RAG | Cursor | 把整个代码库 embedding 化,存向量库(Cursor 用 Merkle 树 + Turbopuffer),查询时召回 top-K chunk 拼进 context |
| 传统索引引擎 | JetBrains | PSI 树 + stub 索引(20 年打磨),强符号导航,但索引构建慢 |
| Agentic search | Claude Code | 不建索引,给 agent 三个工具(grep / glob / read),让它自己多轮探索 |
Anthropic 的早期版本走的是第一条。Boris Cherny 在 Claude Code: Anthropic's Agent in Your Terminal 那篇 2025 年 5 月的博客访谈里说,他们试过 RAG + 本地向量库,但 benchmark 跑下来发现"agentic search 在性能上大幅超越所有其他方案"。后来他给 Claude Code 重新定位:
Claude Code 不是一个产品,是一个 Unix 工具。
行业里常见的解释是"grep 比向量搜索更准"。这话不算错,但它把所有难点都装进了"准"这个字里——读完源码会发现,真正的判断比这复杂。
二、核心架构是怎么分层的
Claude Code 的代码探索能力不是"grep 工具"这一件事,是一套四层架构。这个架构本身就回答了"agent 怎么获取上下文"这个问题:
Claude Code 代码探索:四层检索架构
┌────────────────────────────────────────────────┐
│ │
│ Layer 3 子 Agent 委托检索 │
│ ───────────────────────────── │
│ Explore Agent(只读 / Haiku 模型 / │
│ 独立上下文 / 防火墙作用:原始结果留在 │
│ 子上下文,只回精炼摘要给主 Agent) │
│ ▲ │
│ │ 委托 │
│ Layer 2 模型驱动检索 │ │
│ ───────────────────────────── │
│ Glob ──▶ Grep ──▶ Read(多轮循环) │
│ 由主 Agent 在 while(true) 里决定 │
│ 下一步搜什么、读什么 │
│ ▲ │
│ │ 调用 │
│ Layer 1 智能预注入 │
│ ───────────────────────────── │
│ Memory Prefetch(用 Sonnet 跑 sideQuery │
│ 挑 ≤5 条相关记忆)+ @file 扫描 + │
│ Skill 自动发现 │
│ ▲ │
│ │ 启动时 │
│ Layer 0 静态上下文 │
│ ───────────────────────────── │
│ System Prompt + CLAUDE.md + Git Status │
│ + MEMORY.md 索引 │
│ │
└────────────────────────────────────────────────┘
▲
用户输入 ──────┘
"修一下 payment API 的 auth bug"
四层都有自己的角色:
- Layer 0 是会话开始就喂进去的——
CLAUDE.md(项目惯例)、git status(当前修改)、MEMORY.md的索引。这些是确定要的信息,不进 agent 决策。 - Layer 1 是会话启动时并行预取——比如用一个轻量 Sonnet 调用做"side query",从历史记忆里挑出 ≤5 条可能相关的;扫一下用户消息里
@file引用;把可能用到的 skill 描述加载进来。这是有预测的预注入,但不是 RAG(不查向量)。 - Layer 2 才是真正的 agentic search——
Glob/Grep/Read三件套,在 while(true) 循环里被主 Agent 反复调用。搜什么、搜几次、什么时候停,全是 LLM 自己决定的。 - Layer 3 是子 Agent 委托——遇到大量探索(比如"梳理整个项目的 auth 流程"),主 Agent 可以 spawn 一个 Explore Agent,这个子 agent 用更便宜的 Haiku 跑、独立上下文、只读权限,最后只把摘要回传给主 Agent。这一层我后面会单独讲——它是 Claude Code 一个特别巧的设计。
这个分层里藏着一个关键的设计判断:把"决策权"放在 LLM 那一层,而不是产品那一层。 Cursor 的 RAG 在产品层就决定了"召回 top 30 chunk"——LLM 拿到的是已经被裁剪过的上下文。Claude Code 不裁剪,把工具给 LLM,让它自己决定怎么用。这两种哲学都对,但产生的产品形态完全不同。
三、一次代码搜索是怎么走通的
抽象讲容易飘。让我用"修 payment API auth bug"这个具体例子,把 Layer 2 的多轮调用拆开看。下面是一次真实的调用序列(基于 Claude Code 行为逆向 + 官方 docs 描述):
一次"修 auth bug"任务的检索流程(简化版)
T0 会话启动
│ 加载 Layer 0:System Prompt + CLAUDE.md + git status
│ 并行 Layer 1:sideQuery 拉 3 条相关 memory + 扫 skill
▼
T1 Glob "**/payment*" ← 找入口文件
└─ 返回 8 个候选文件路径(按 mtime 排序)
│
T2 Read src/api/payment.ts ← 读最可能的入口
└─ 返回 全文(< 25K tokens)
│
T3 Grep "import.*auth" ← 找 auth 相关引用
in src/api/
└─ 返回 5 个文件 + 上下文行
│
T4 Read src/auth/middleware.ts ← 读关键依赖
└─ 已读过?检查 mtime
→ 未变化 → 返回 "File unchanged"(省 ~25K tokens)
→ 变化 → 重新读
│
T5 ─── LLM 判断:上下文够了吗 ───
│ 够 → 进入修复阶段(Edit + Bash test)
│ 不够 → 继续 Grep / Read
▼
T6 Edit src/api/payment.ts ← 修复
│
T7 Bash "npm test -- payment" ← 验证
└─ 失败 → Read 错误日志 → 回到 T6
└─ 成功 → 完成
几个工程细节值得展开。
漏斗式调用。 顺序不是随机的——Glob → Grep → Read 是一个收敛漏斗。Glob 像目录浏览(按文件名找候选),Grep 像内容过滤(在候选里搜模式),Read 是最贵的(读全文)。每一跳都把范围收窄。LLM 内化了这个 pattern,几乎不会一上来就 Read 100 个文件。
Token 预算硬限制。
| 工具 | 限制 |
|---|---|
Grep | 默认 head_limit=250 行,结果 ≤ 20KB |
Glob | 最多返回 100 个文件,按 mtime 倒序(最近改的优先) |
Read | 单次最多 25,000 tokens / 256KB / 2000 行 |
这些数字不是拍脑袋的——它们让单轮调用的 token 消耗封顶可预测,模型不会被一次 Read 整个 50000 行的文件直接撑爆。
Read 去重。 这是我看到的时候笑出声的优化——Read 工具在调用前检查目标文件的 mtime,如果跟上次读时一样,直接返回字符串 "File unchanged",省掉整个文件内容的传输。命中率官方测算是 18%,每次省约 25K tokens。
这个优化能成立有一个前提:mtime 是文件系统层面真理,不是 agent 维护的状态。Claude Code 不需要"记得读过哪些文件"——文件系统已经记了。
Prompt Cache 分区。 System Prompt 用 __SYSTEM_PROMPT_DYNAMIC_BOUNDARY__ 分隔静态和动态部分,静态区用 scope: 'global',所有用户共享同一份缓存——这意味着 Anthropic 服务器端只为 system prompt 算一次 KV cache,所有 Claude Code 用户的请求都命中这个缓存。这是 Anthropic 自家产品才能做的优化。
把这些细节加起来,你会发现一件事:Claude Code 的"agentic search"不是简单的"模型自己 grep"——它是一整套配合 LLM 工作模式的工程系统。 grep 是看得见的部分,水面下还有 token 预算、去重、缓存、子 agent 隔离一系列东西。
四、为什么 grep 打败了向量索引
讲完 how,回头讲 why。这是这篇文章我最想说清楚的部分。
行业默认 RAG 是好东西。它确实是好东西——对很多场景。但 Anthropic 的判断是:代码这个特定场景,RAG 的优势失效了。
我把他们的理由整理成四条。
第一,代码场景下"精确匹配"比"语义相似"重要。
向量搜索的强项是语义召回——你搜"用户认证",能找到 login / authenticate / verifyUser 这种语义相近但用词不同的代码。这在产品文档、客服问答里非常有价值。
但代码不是这样工作的。代码的核心特征是符号即真理。当你说"修 processPayment 函数的 bug"时,你要的就是那个名字精确叫 processPayment 的东西。不是"语义相似的",是"那个"。grep "processPayment" 一次拿到所有引用——精确、完整、零幻觉。embedding.similarity("processPayment") 给你一堆"看起来相关"的——其中一些可能是 processOrder、handlePayment 这种结构相近但实际无关的函数。
代码搜索失败时,向量搜索的失败是模糊的:是嵌入模型不够好?是 chunk 切错了?还是 embedding 已经过期?而 grep 的失败是清晰的:关键词没拼对。可调试性差出一个数量级。
第二,状态是 bug 的温床。
向量索引必须维护。代码每改一行,理论上对应的 chunk 都要重新 embedding。Cursor 用 Merkle 树做增量更新,已经是工程上很优雅的方案——但即使如此,"索引滞后于代码"的问题还是会反复出现。
grep 没这个问题。它每次搜的都是当前文件系统真实状态。你 git checkout 一个分支,下一次 grep 立刻反映新分支的代码——不需要"重建索引"。
这背后是一个更深的判断:有状态系统的复杂度是叠加的。 索引启动 / 更新 / 失效 / 崩溃恢复 / 缓存一致性,每一项都是 bug 来源。grep 是 stateless 的——崩了就重跑,没人会觉得"丢了什么"。
我读到这里想起了 Roy Fielding 那篇 REST 论文里的那句话:服务端的 statelessness 不是性能优化,是错误模式的简化。 同一个判断在 2025 年的 AI agent 上又一次成立。
第三,隐私是架构问题,不是政策问题。
Cursor 用 RAG 必须把代码 embedding 后传到向量库(即使是托管的也要传到他们的 Turbopuffer)。学术研究已经证明 embedding 可以反推回原始内容(不是 100% 还原,但足以泄露语义)——这对企业用户是个真问题。
Claude Code 用 grep 是完全本地执行的(agent 给出 grep 命令,本地工具运行),原文上下文只在每次回传给 Anthropic 时进上下文,不存在"持久化的代码副本在他们手里"。这对合规敏感的客户是个根本性差异——不是"我们不上传"的承诺,是"架构上没有上传这一步"的事实。
第四,agent 时代的稀缺资源不是召回,是可预测性。
这一条是我自己的判断,但读完源码后觉得它呼之欲出。RAG 在 2020-2023 年大流行,是因为那时候模型上下文小、推理慢、调用贵——一次 LLM call 是稀缺资源,必须前置把最相关的内容塞进去。
到了 2025 年,Claude Sonnet 4 的上下文 200K-1M、Haiku 便宜到可以并行十几个、tool use 已经是模型一等公民。稀缺的东西换了——不再是"一次 LLM call",而是"输出的一致性和可调试性"。在这个新约束下,让 LLM 自己多调几次 grep(每次便宜可预测),比让 RAG 一次性塞 30 个 chunk 进上下文(高方差,调试困难)更划算。
实测数据印证了这个判断。第三方在 2026 年初做的 5 个重构任务对比:
| 工具 | 平均 Token | 平均时长 | 一次成功率 |
|---|---|---|---|
| Cursor (RAG) | 11,000 | 90 秒 | 60% |
| Claude Code (agentic) | 2,000 | 180 秒 | 75% |
| Cline (agentic + auto sub-task) | 8,500 | 240 秒 | 80% |
Claude Code 比 Cursor 少用 5.5 倍 token,但慢一倍,成功率高 15 个百分点。这个组合在生产环境的 agent 工作流(人挂着等结果)里是赚的——慢 90 秒不痛,省 80% token 和提升成功率却是直接赚钱。
五、设计判断里的取舍
讲到这里给人感觉好像 agentic search 完胜。它没有。它有非常明确的代价。
速度慢。 多轮调用本质上是串行的——Glob 完才能 Grep,Grep 完才能 Read。即使工具内部并发,整体时长还是被"轮次"拉长。Cursor 一次 RAG 召回完事,时长更短。在"实时交互"场景(边写代码边问 Claude),这个延迟是劣势。这也是为什么 Cursor 在 IDE 内嵌的体验里更顺手——它的 90 秒级响应配合人坐在屏幕前等,不让人难受;而 Claude Code 的 180 秒级响应更适合"挂在 terminal 后台跑"。
大型 monorepo 第一次冷启动慢。 假如你让 Claude Code 第一次进入一个 50000 文件的 monorepo 做事,它的多轮 Glob/Grep 探索会花相当多时间在"搞清楚这是什么项目"上。Cursor 因为有预先索引,进来直接能干活。这个差距在小项目里看不到,大项目里很明显。
对模型能力的强依赖。 agentic search 的有效性建立在"LLM 知道什么时候搜什么"这个假设上。Sonnet / Opus 级别的模型这件事做得很好;换到弱模型,多轮调用很容易陷入"反复搜同一个东西""搜了不该搜的"。这意味着 agentic search 不是普世解——给你一个 7B 开源模型让你做 coding agent,老老实实建索引可能反而靠谱。
不是非此即彼。 我必须强调这点:放弃 RAG 不等于"以后所有 agent 都不该用 RAG"。Claude Code 自己的 Layer 1 里就有一个"用 Sonnet 做 sideQuery 拉历史记忆"的步骤——这本质就是一个轻量 RAG。它的判断是:主代码搜索用 agentic search,但跨会话记忆这种"内容稳定、量大、需要语义召回"的场景,仍然用 RAG 风格的预取。
把这几条 trade-off 合起来看,agentic search 的适用场景大致是:
是否选 agentic search 而非 RAG?
┌───────────────────────────────────┐
│ │
│ ① 检索对象的"真理来源"在文件 │
│ 系统/数据库里吗? │
│ ├─ 是(代码、文件、数据)→ → │
│ └─ 否(文档、知识、对话历史) │
│ → 优先 RAG │
│ │
│ ② 模型 ≥ Sonnet 4 / GPT-4o 级别?│
│ ├─ 是 → → │
│ └─ 否 → 优先 RAG │
│ │
│ ③ 需要的是精确匹配,还是语义召回?│
│ ├─ 精确(符号、文件、API)→ → │
│ └─ 语义(概念、意图) │
│ → 优先 RAG │
│ │
│ ④ 用户能接受 2-3x 延迟换 token │
│ 省、可调试性、隐私保护吗? │
│ ├─ 能 → 选 agentic search │
│ └─ 不能 → 选 RAG │
│ │
└───────────────────────────────────┘
四个全是"是/能" → agentic search
有一个"否" → 考虑 RAG / 混合方案
六、自己做 agent 工具该带走什么
如果你在做一个 agent 类工具,对照 Claude Code 的判断,有几条具体的可借鉴:
1. 检索对象的"真理来源"在哪里,决定要不要建索引。 文件系统是真理来源 → 不要建索引(grep 就行)。一份每月更新一次的产品手册是真理来源 → 建索引很合理。混合场景(代码 + 文档)→ 分别处理,不要一刀切。
2. 工具集要让 LLM 形成"漏斗"。 Claude Code 的 Glob/Grep/Read 不是随便选的——它们是天然的收敛序列(粗 → 细)。你给 agent 工具时,要想清楚它们之间是不是这种关系。给十个并列的搜索工具,agent 会迷茫。
3. Token 预算要硬封顶。 每个工具的单次返回必须有上限(行数 / 字节 / token),否则一次 grep 返回 100MB 直接把上下文炸了。Claude Code 的 250 行 / 20KB / 25K tokens 是磨出来的数字,借鉴这个思路。
4. 重视"无操作"的优化。 Read 的 mtime 去重看起来不起眼,18% 命中率却很可观。任何"可以不做"的工作都值得一个 fast path。这种优化只能在 stateless 系统里做——一旦你维护了"我读过什么"的状态,反而做不了。
5. 给探索任务专门的子 Agent。 主 Agent 的上下文是宝贵的(要保留判断逻辑、用户对话历史)。把"读 100 个文件然后总结"这种任务委托给一个独立上下文的子 Agent,结果回来时只是一段摘要——这个隔离层很有用。Claude Code 的 Explore Agent 用 Haiku 跑(便宜)+ 只读权限(安全)+ 独立上下文(不污染主 agent),这三件配合起来值得抄。
6. 别把 RAG 当默认选项。 这条不是"别用 RAG"——是别在没想清楚之前默认上 RAG。问自己:我真的需要语义召回吗?我的"知识"会变化吗?变化频率?如果答案是"代码每天改几十次",agentic search 大概率比 RAG 香。
七
读完 Claude Code 这套设计,我想起 Boris Cherny 那句"它不是产品,是 Unix 工具"。这句话在 2025 年的 AI 时代听起来有点违和——大家都在做"产品"。但他们的判断是:在 agent 这个层级,最值钱的不是"自动决策""智能召回"这些黑盒能力,而是 可预测、可调试、可组合 这些 Unix 哲学早在 1973 年就讲清楚的东西。
模型在变强,工具会变多,但"知道何时遗忘"这件事——把状态扔回文件系统、把决策权交还给 LLM、把架构剥到只剩三个 Unix 工具——这套判断会比任何具体的模型 / 框架活得更久。
参考资料:
- Anthropic 博客《Claude Code: Anthropic's Agent in Your Terminal》;
- Boris Cherny 公开访谈与播客原话;
- Finisky Garden《拆解 Claude Code 的 RAG 机制》逆向分析;
- TIMEWELL Inc. 2026-01 三工具 benchmark;
- Claude Code 工具系统源码(Tool.ts / GrepTool.ts / GlobTool.ts / FileReadTool.ts)。*
关注公众号「coft」,获取更多 AI 时代的深度思考。