Claude Code架构梳理以及重点设计思路分享
Claude Code架构梳理以及重点设计思路分享
注意!以下内容是基于2026-03-31无意间被泄漏的Claude Code的代码(v2.1.88)进行总结的,在最新版Claude Code中,该文中的某些属性或者功能并不支持。
2026年6月,Claude Code被爆出在后台根据系统时区和代理域名将中国机构和用户检测出来并打标,这一动作导致了严重的安全危机,不再推荐使用Claude Code。
2026年7月,阿里巴巴集团宣布不再使用claude code和claude系列模型。
Claude code不是什么
-
Claude code不是Anthropic推出的大语言模型,而是以大语言模型为大脑,支持多种API
-
Claude code不是单一功能的对话机器人
-
Claude code不是简单的一次性对话式代码补全助手,如Copilot(最近Copilot也有Agent模式了)
Claude code是什么
Claude code主要是一个AI编码Agent,当用户给 Claude 一个编码任务时,它会经历三个阶段:收集上下文、采取行动和验证结果,这些阶段相互融合。Claude 始终使用工具,无论是搜索文件以了解代码、编辑以进行更改,还是运行测试以检查其工作。它还可以帮助人们完成从命令行可以做的任何事情:编写文档、运行构建、搜索文件、研究主题等。Claude Code遵循ReAct Agent范式。(注意区分ReAct范式与claude code用来实现用户界面的react包)
ReAct(Reasoning + Acting)Agent范式

用户输入 → LLM推理 → 判断是否需要工具调用 → 执行工具 → 将结果反馈给LLM → 继续循环


Claude Code部件
-
Bun:选 Bun 而非 Node.js,追求启动速度和构建时 DCE
-
React + Ink:用 React 的声明式模型写终端 UI
-
Zod v4:运行时类型校验,工具输入/输出的安全边界
-
ripgrep:高性能代码搜索
-
Commander.js:轻量 CLI 解析
Claude Code 源码理解锚点
-
依赖模型,模型充当:
-
观察者:阅读文件、分析代码、理解项目结构
-
决策者:选择合适的工具、决定下一步操作
-
通过准确强大(但是很长)的提示词来实现不同功能:每个工具的 prompt() 方法生成详细的工具使用说明;getUsingYourToolsSection 等函数组合成完整 system prompt
-
-
最为稀缺的资源是上下文窗口(Context)
-
Prompt 的分层策略:把 System Prompt 切为"可全局缓存"(SYSTEM_PROMPT_DYNAMIC_BOUNDARY 之前)和"会话特异"(边界之后)两部分,最大化 Anthropic API 的 prompt cache 命中率。Blake2b 对静态前缀做哈希,同一个静态前缀跨用户共享 scope: 'global' 缓存
-
三级压缩体系:
-
Snip compact(微压缩):每轮 API 请求前自动执行,清除较为久远的工具执行结果和旧的 Read/Bash/Grep/Glob 等工具的 tool_result 内容,替换为' [Old tool result content cleared]'。
-
Microcompact(自动压缩):当 estimateMessageTokens(messages) 超过阈值时触发,用 LLM 将早期对话总结为一条 SystemMessage,保留最近 N 轮完整对话
-
Reactive Compact(手动/自动):/compact 命令或自动触发,完整重写对话历史
-
-
Token 预算与精确计量:estimateMessageTokens() 遍历 messages 中每个 block(text/tool_result/tool_use/thinking/image),估算 token 数并乘以 4/3 作为保守 padding;getMaxOutputTokensForModel() 限制输出长度
-
精细的消息管理:normalizeMessagesForAPI() 在每次请求前清洗消息——剥离 tool_search 字段、修复 tool_use/tool_result 配对、清除超过限制的 media 块、剥离 advisor 块;addCacheBreakpoints() 在消息序列中插入 cache_control 断点
-
通过 schema 控制输出格式:SyntheticOutputTool 将用户指定的 JSON Schema 作为 tool 的 inputJSONSchema,模型调用该工具时必须输出符合 schema 的参数,结合 Ajv 运行时校验 + PostToolUse hook 强制执行
-
-
对速度有要求
-
Prompt 的分层策略:同上,静态前缀命中 Global Cache → 免传输 ~50K+ tokens,首包时延大幅降低
-
并行工具执行:runTools() 将 tool_use block 按 isConcurrencySafe() 分区,读操作(Read/Glob/Grep)并发执行(默认最多 10 路并发),写操作串行执行。all() 生成器同时驱动多个 runToolUse 协程
-
Fine-Grained Tool Streaming(FGTS):通过 eager_input_streaming: true 启用,模型无需等 tool 参数完整生成就开始流式发送,避免大参数输入时的分钟级阻塞
-
投机性安全检查:BashTool 在权限检查阶段提前启动 startSpeculativeClassifierCheck(),让安全分类器与权限决策并行运行
-
Fast Mode:通过 speed: 'fast' + beta header 启用,使用相同模型但更快的输出速度
-
Cached Microcompact:通过 cache_edits API 直接在服务端缓存中删除旧 tool result,无需重传整个消息前缀
-
Non-streaming Fallback:当流式请求失败时,自动降级到非流式请求重试,超时独立配置
-
-
不同层级的记忆处理
-
System Prompt 层面的 # Memory 章节(loadMemoryPrompt()):每次对话加载 MEMORY.md 索引文件作为 system prompt 的动态部分,列出所有记忆文件的标题和一行摘要。同时从 memdir.ts 注入完整的记忆使用指南(何时保存、文件格式、类型体系等)
-
类型化记忆文件(~/.claude/projects/<slug>/memory/):四种类型分文件存储:
-
user:用户角色、偏好、知识水平 → 定制回复风格
-
feedback:用户对 Claude 行为方式的纠正/认可 → 避免重复犯错
-
project:项目背景、截止日期、决策理由 → 理解工作上下文
-
reference:外部资源位置(Linear 项目、Grafana 面板、Slack 频道)
-
-
Session 内存(/memory 命令):当前会话内通过对话自然积累的记忆,CLAUDE.md 替代方案
-
Team Memory:共享记忆,通过 teamMemPaths.ts 管理,跨用户协作共享
-
Nested Memory:子目录中的 CLAUDE.md 文件,通过 nestedMemoryAttachmentTriggers 追踪已注入的路径,避免重复注入
-
记忆的生命周期:memoryAge.ts 追踪记忆的新鲜度;memoryScan.ts 扫描记忆目录;findRelevantMemories.ts 按相关性检索记忆;MEMORY.md 作为索引文件,单行不超过 150 字符,超过 200 行会被截断
-
上下文管理
Prerequisites
Claude API的function calling调用方式
src/services/api/claude.ts:1699
return {
model: normalizeModelStringForAPI(options.model), # 模型选择
messages: addCacheBreakpoints( # 带有历史回复的message list
messagesForAPI,
enablePromptCaching,
options.querySource,
useCachedMC,
consumedCacheEdits,
consumedPinnedEdits,
options.skipCacheWrite,
),
system, # system prompt
tools: allTools, # 所有可用的tools
tool_choice: options.toolChoice, # tool选择选项,自动判断是否需要调用工具、至少调用一个工具等
...(useBetas && { betas: betasParams }),
metadata: getAPIMetadata(),
max_tokens: maxOutputTokens,
thinking, # 思考深度
...(temperature !== undefined && { temperature }),
...(contextManagement &&
useBetas &&
betasParams.includes(CONTEXT_MANAGEMENT_BETA_HEADER) && {
context_management: contextManagement,
}),
...extraBodyParams,
...(Object.keys(outputConfig).length > 0 && {
output_config: outputConfig,
}),
...(speed !== undefined && { speed }),
}
System prompt分割
把 System Prompt 切为"可全局缓存"和"会话特异"两部分,最大化 LLM API的 prompt cache 命中率:
┌─────────────────┐
│ 静态前缀 (cacheable, scope='global') │
│ · 身份定义 · 系统规则 · 工具使用说明 │
│ · 代码风格 · 安全约束 · 沟通风格 │
├─────────────────┤
│ SYSTEM_PROMPT_DYNAMIC_BOUNDARY ← 标记 │
├─────────────────┤
│ 动态后缀 (per-session, 不缓存) │
│ · 会话指导 · 记忆 · MCP 指令 │
│ · 环境信息 · 语言偏好 · 输出风格 │
└────────────────┘
关键细节:
-
SYSTEM_PROMPT_DYNAMIC_BOUNDARY (src/constants/prompts.ts:114) 是一个显式分隔标记,splitSysPromptPrefix() 以此切分 prefix,分别打上 cache_control: { scope: 'global' } 和不缓存的标记
-
systemPromptSection() vs DANGEROUS_uncachedSystemPromptSection() (src/constants/systemPromptSections.ts) —— 前者计算结果被 memoize 并在 /clear 和 /compact 时统一失效;后者每个 turn 都重新计算,显式声明会破坏缓存,要求调用者写 _reason 说明必要性
对tools、skill和MCP等工具的渐进式披露
Tools
分层载入
通过白名单方式载入非延迟工具的完整schema(~20个)
通过硬编码,所有内置工具都是显式硬编码在对应tool定义中,如果在使用buildTool工厂函数创建工具时,shouldDefer参数值为true,则为延迟载入工具,如WebSearchTool,反之则为非延迟载入工具,例如ToolSearchTool
注意一个特殊工具:ToolSearchTool
ToolSearchTool
ToolSearchTool用来找延迟载入的工具(Tool)、MCP
判断逻辑在 src/tools/ToolSearchTool/prompt.ts:62-108 的 isDeferredTool() 函数中,采用白名单排除法——一个工具只要不满足任何"应该延迟"的条件,就是非延迟工具:
isDeferredTool(tool):
├── alwaysLoad === true → return false(非延迟)—— 显式豁免
├── isMcp === true → return true(延迟) —— 所有 MCP 工具一律延迟
├── name === 'ToolSearch' → return false(非延迟)—— 它自己不能被延迟
├── AgentTool(fork 模式) → return false(非延迟)—— 必须首轮可用
├── BriefTool(KAIROS) → return false(非延迟)—— 主要通信通道
├── SendUserFileTool(KAIROS) → return false(非延迟)—— 文件投递通道
└── shouldDefer === true → return true(延迟)
shouldDefer !== true → return false(非延迟)← 大多数基础工具走这里
结论:非延迟工具 = 没有显式标记 shouldDefer: true 且没有被特殊规则命中的内置工具。比如 BashTool、FileReadTool、FileEditTool、GlobTool、GrepTool 等约 20 个基础工具,它们的 shouldDefer 字段根本不存在(undefined),因此 isDeferredTool() 返回 false,每轮都发送完整 schema。
延迟工具载入逻辑
通过ToolSearchTool找到延迟载入的工具,然后拼装延迟工具名字列表给到用户消息,用tag<system-reminder>或者<available-deferred-tools>包裹。例如:
<system-reminder>
The following deferred tools are now available via ToolSearch:
TaskCreate
TaskGet
TaskUpdate
...
</system-reminder>
同时提醒模型这些工具只有名字没有完整schema:src/tools/ToolSearchTool/prompt.ts:44
模型发现需要某个延迟载入工具,调用ToolSearch("select:TOOLNAME"),src/tools/ToolSearchTool/ToolSearchTool.ts:328
通过正则表达式提取具体工具名,src/tools/ToolSearchTool/ToolSearchTool.ts:363
提取工具名成功之后发送给客户端,由客户端获取完整schema,src/services/api/claude.ts:1235
将已发现的工具纳入常规tools中:src/utils/toolSearch.ts:545-592
MCP(参考MCP 协议解析 & MasterGo 设计稿转代码实践)
所有MCP都是延迟载入
其实整体延迟载入逻辑与延迟载入的tools是一样的,只不过需要对应一下:
| MCP | Tools |
|---|---|
| tools/list请求返回的name字段 | Tool name |
| ools/list请求返回的inputSchema字段 | Tool shema |
下面是一个MCP的tool/list request结果简单示例
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [
{
"name": "add_numbers",
"description": "计算两个数字的和",
"inputSchema": {
"type": "object",
"properties": {
"a": {
"type": "number",
"description": "第一个加数"
},
"b": {
"type": "number",
"description": "第二个加数"
}
},
"required": ["a", "b"]
}
}
]
}
}
Skills(https://github.com/jarmuine/claude-code.git中缺少相关文件)
首先载入多个来源的skills,src/commands.ts:428
getCommands()
└─ loadAllCommands() ← memoize by cwd
├─ getSkills() ← 并行加载四个来源
├─ getPluginCommands() ← 插件的普通命令
├─ getWorkflowCommands() ← workflow 命令
└─ COMMANDS() ← 内置命令列表(skills 放在最前面)
-
从 .claude/skills/ 目录加载(异步,需遍历文件系统)
-
启动时同步注册的捆绑 skills
-
启用的内置插件的 skills
示例skill
---
name: terminal-title
description: Automatically updates terminal window title to reflect the current high-level task. Use at the start of every Claude Code session when the user provides their first prompt, and whenever the user switches to a distinctly new high-level task. Helps developers manage multiple Claude Code terminals by providing clear, at-a-glance identification of what each terminal is working on.
min_claude_code_version: "1.0.0"
version: "1.1.0"
---
# Terminal Title
## Overview
Automatically sets descriptive terminal window titles based on the task Claude is working on. Essential for developers running multiple Claude Code instances who need to quickly identify which terminal is handling which task.
## When to Use
**Always trigger this skill:**
- At the start of every new Claude Code session (after receiving the first user prompt)
- When switching to a substantially different task (e.g., from "API Integration" to "Database Migration")
**Trigger on task switches like these:**
- Switching from frontend work to backend work
- Moving from debugging to new feature development
- Changing from one module/component to a completely different one
- Starting work on a different part of the system (e.g., from auth to payments)
**Do NOT trigger for:**
- Follow-up questions about the same task ("Can you add a comment to that function?")
- Small refinements to current work ("Make it blue instead of red")
- Debugging the same feature you just built
- Clarifications ("What did you mean by X?")
- Iterating on the same component or module
- Mid-task status updates or progress checks
## How It Works
1. **Extract Task Summary**: Analyze the user's prompt to identify the high-level task
2. **Generate Title**: Create a concise, descriptive title (max 40 characters)
3. **Set Title**: Execute the `scripts/set_title.sh` script with the generated title
4. **No Confirmation Needed**: This happens automatically in the background
## Title Format Guidelines
**Good titles:**
- "API Integration: Auth Flow"
- "Fix: Login Bug"
- "DB Migration: Users Table"
- "Build: Dashboard UI"
- "Refactor: Payment Module"
**Displayed as (with automatic folder prefix):**
- `my-project | API Integration: Auth Flow`
- `my-project | Fix: Login Bug`
**Bad titles:**
- Too long: "Implementing the new authentication system with OAuth2.0 support" (exceeds 40 chars)
- Too generic: "Working" or "Coding"
- Too verbose: "The user wants me to help them with..."
**Format pattern:**
[Action/Category]: [Specific Focus]
The script automatically prefixes titles with the current directory name (usually the repo name) for easy identification across multiple terminals.
Keep titles concise, actionable, and immediately recognizable.
## Common Mistakes to Avoid
**❌ Too Verbose:**
- Bad: "Working on implementing the user authentication system with JWT tokens"
- Good: "Build: JWT Auth"
**❌ Too Vague:**
- Bad: "Code stuff"
- Bad: "Working"
- Good: "Refactor: API Layer"
**❌ Including System Information:**
- Bad: "john-macbook-pro: Debug app"
- Bad: "/Users/john/project: Build feature"
- Good: "Debug: App Issues"
**❌ Using Complete Sentences:**
- Bad: "I am working on the dashboard component"
- Good: "Build: Dashboard UI"
## Implementation
**Execute the title script:**
```bash
bash scripts/set_title.sh "Your Title Here"
Example workflow:
# User asks: "Help me debug the authentication flow in the API"
bash scripts/set_title.sh "Debug: Auth API Flow"
# User asks: "Create a React component for the user profile page"
bash scripts/set_title.sh "Build: User Profile UI"
# User asks: "Write tests for the payment processing module"
bash scripts/set_title.sh "Test: Payment Module"
Script Details
The scripts/set_title.sh script uses ANSI escape sequences to set the terminal title. It's compatible with:
- macOS Terminal
- iTerm2
- Alacritty
- Most modern terminal emulators (xterm, rxvt, screen, tmux)
The script accepts a single argument (the title string) and exits silently if no title is provided (fail-safe behavior).
Automatic Directory Prefix
The script automatically prefixes all titles with the current directory name (usually the repo/project name). This makes it easy to identify which project each terminal is working on:
my-project | Build: Dashboard UI
another-repo | Debug: Auth API
Optional Custom Prefix
Users can optionally add an additional custom prefix by setting the CLAUDE_TITLE_PREFIX environment variable:
export CLAUDE_TITLE_PREFIX="🤖"
This produces titles like: 🤖 my-project | Build: Dashboard UI
Note: You don't need to check for these variables or modify your behavior. The script handles this automatically.
通过skilltools构建理解和处理skill的prompt
然后将skill相关信息作为skill原数据清单skill\_listing编排进attachment的,实现在src/utils/attachments\.ts:743,超过250个字符硬截断
```TypeScript
[system-reminder]
The following skills are available for use with the Skill tool:
- skill-name: description - when to use
- ...
Slash命令调用skill功能或者模型自主决策需要调用skill
然后再读取 SKILL.md → 替换skill正文loadSkillsDir.ts:344-376
将替换后的内容包装为对话消息,并标记为isMeta:true,不在客户端渲染
将对话消息追加到全局messages
上下文压缩
Claude Code设计了一套四层递进压缩体系,像齿轮组一样层层递进:
第一层:Snip(轻量裁剪) ——对过期的工具调用结果做最小化修剪,几乎零成本。
第二层:MicroCompact(微压缩) ——在缓存内容的基础上做局部编辑,零API调用。比如把冗长的grep结果截断,把大文件内容替换为摘要指纹。对用户完全透明。
第三层:AutoCompact(自动全量压缩) ——当Token用量接近上下文窗口上限时触发(阈值 = 窗口大小 - 2万Token的缓冲区)。系统调用LLM生成一份最多2万Token的结构化摘要,包含9个段落:核心请求、关键概念、文件/代码、错误/修复、解决过程、用户消息、任务列表、当前工作、下一步行动。src/services/compact/prompt.ts:293
第四层:Reactive Compact(紧急压缩) ——当API直接返回413(请求体过大)错误时的"熔断"机制,强制压缩。
一个关键设计原则:用户的原始表述永远不被修改——"AI可以遗忘,但绝不能扭曲用户的意图。"

Claude Code任务执行全流程
总览
Claude Code 接收到用户任务后,经过 10个阶段 完成从输入到输出的完整流程。核心架构是一个基于 AsyncGenerator 的 微回合循环(micro-turn loop),每次 API 调用 + 工具执行 = 一个微回合,模型可以在多轮中逐步完成任务。
阶段一:入口启动
文件:src/entrypoints/cli.tsx
函数:****main() — 第 33 行
这是 CLI 的引导入口,采用快速路径优先策略,最小化模块加载:
如果以上都不匹配(第 288-297 行):
-
调用
startCapturingEarlyInput()捕获早期输入 -
动态导入
../main.js的main函数(重命名为cliMain) -
调用
cliMain()进入完整 CLI
*// 第 293-297 行*const { main: cliMain } = await import('../main.js');
profileCheckpoint('cli_after_main_import');
await cliMain();
阶段二:CLI 设置与命令解析
文件:src/main.tsx
函数:****main() — 第 585 行(约 5000 行)
这是 CLI 的主入口函数,负责初始化、命令解析和会话启动:
-
全局错误/警告处理(第 593-606 行)
-
deep-link URI 处理(第 612-677 行)
-
**调用 **
init()— 初始化配置、认证、遥测 -
**调用 **
setup()— 工作树创建、会话记忆、插件加载 -
Commander.js 命令注册(第 888 行起)
-
主
.action()处理器(第 1006 行)— 根据模式分支:
文件:src/setup.ts
函数:****setup() — 第 56 行
初始化运行时环境:
文件:src/replLauncher.tsx
函数:****launchRepl() — 第 12 行
用 Ink 渲染 <App><REPL /></App>:
<App getFpsMetrics={...} stats={...} initialState={...}>
<REPL {...replProps} />
</App>
阶段三:REPL 主循环组件
文件:src/screens/REPL.tsx
函数:****REPL() — 第 572 行(约 5000 行)
这是最核心的交互组件,管理整个会话生命周期:
关键状态:
QueryGuard 状态机:
idle → dispatching(预留) → running → idle
-
reserve(): idle → dispatching(预留执行槽位) -
tryStart(): dispatching → running(原子性开始执行) -
end(): running → idle(执行完成) -
cancelReservation(): dispatching → idle(取消预留)
阶段四:用户输入提交
文件:src/utils/handlePromptSubmit.ts
函数:****handlePromptSubmit() — 第 120 行
这是用户按下 Enter 后的处理入口:
流程分支:
-
队列处理器路径(第 150-172 行):如果
queuedCommands存在,跳过输入验证,直接调用executeUserInput() -
空输入检查(第 188-190 行):空输入直接返回
-
退出命令检查(第 194-211 行):
exit,quit,:q,:q!,:wq,:wq!触发/exit命令 -
粘贴引用展开(第 216 行):
expandPastedTextRefs()替换[Pasted text #N]等占位符 -
即时斜杠命令(第 229-310 行):
-
匹配
immediate+local-jsx类型命令(如/config,/doctor) -
直接执行,不调用模型
-
通过
onDone回调处理结果
-
-
并发保护/排队(第 313-351 行):
-
如果
queryGuard.isActive或isExternalLoading,将输入加入队列 -
如果有可中断工具在执行,发送 abort 信号
-
通过
enqueue()将命令加入消息队列
-
-
正常执行(第 359-386 行):
-
构造
QueuedCommand -
调用
executeUserInput()
-
函数:executeUserInput() — 第 396 行
核心执行逻辑:
1. 创建新的 AbortController(第 419 行)
2. queryGuard.reserve() 预留槽位(第 437 行)
3. 遍历 queuedCommands,对每个命令调用 processUserInput()(第 473-496 行)
- 第一条命令:完整处理(附件、IDE选择、粘贴内容)
- 后续命令:跳过附件(避免重复上下文)
4. 文件历史快照(第 525-538 行)
5. 调用 onQuery() 发送给模型(第 560 行)
6. 处理 nextInput 链式输入(第 589-595 行)
7. finally: cancelReservation() + clearUserInputOnProcessing()(第 597-609 行)
阶段五:输入分类处理
文件:src/utils/processUserInput/processUserInput.ts
函数:****processUserInput() — 第 85 行
-
显示输入提示(第 145-146 行):
setUserInputOnProcessing(prompt)在 UI 中立即显示用户输入 -
调用 ****
processUserInputBase()(第 281 行),根据输入类型分派: -
执行 UserPromptSubmit hooks(第 182-190 行)
文件:src/utils/processUserInput/processTextPrompt.ts
函数:****processTextPrompt() — 第 19 行
创建 UserMessage 对象,包含文本内容和图片(如果有粘贴):
return { messages: [userMessage], shouldQuery: true };
阶段六:发送给模型 — onQuery
文件:src/screens/REPL.tsx
函数:****onQuery() — 第 2855 行
-
队友活跃标记(第 2857-2864 行):如果启用了 Agent Swarms,标记当前 agent 为活跃
-
并发守卫(第 2869 行):
queryGuard.tryStart()原子性检查并转换状态- 如果返回
null(已有查询在运行),将消息加入队列并返回
- 如果返回
-
更新 messages 状态(第 2891 行)
-
token 预算初始化(第 2893-2896 行)
-
调用 ****
onQueryImpl()(第 2918 行)
函数:onQueryImpl() — 第 2661 行
-
IDE 集成(第 2667-2671 行):关闭打开的 diff
-
会话标题生成(第 2684-2698 行):从第一条用户消息生成标题(Haiku 模型)
-
技能工具权限(第 2711-2726 行):更新
alwaysAllowRules -
获取工具使用上下文(第 2746 行)
-
并行加载系统提示词和上下文(第 2768-2772 行):
getSystemPrompt() + getUserContext() + getSystemContext()
-
构建有效系统提示词(第 2781 行):
buildEffectiveSystemPrompt() -
进入核心循环(第 2793-2803 行):
for await (const event of query({
messages, systemPrompt, userContext, systemContext,
canUseTool, toolUseContext, querySource
})) {
onQueryEvent(event);
}
阶段七:Agent 主循环(核心)
文件:src/query.ts
函数:****query() — 第 219 行,****queryLoop() — 第 241 行
这是整个系统的核心——一个 while(true) 无限循环(第 307-1728 行),每次迭代是一个"微回合"(micro-turn)。
循环体详解
步骤 1:准备消息(压缩、裁剪、折叠)
位置:src/query.ts 第 365-447 行
*// query.ts:365-366*
let messagesForQuery = [...getMessagesAfterCompactBoundary(messages)]
1a. Tool Result 预算裁剪
位置:src/query.ts 第 369-394 行
-
调用
applyToolResultBudget()对工具结果总大小进行预算限制 -
在 microcompact 之前运行,两者可组合使用
-
persist 逻辑:只有
agent:和repl_main_thread查询源持久化替换记录
1b. Snip 裁剪
位置:src/query.ts 第 400-409 行
-
调用
snipModule.snipCompactIfNeeded(messagesForQuery)(src/services/compact/snipCompact.ts) -
裁剪过长的工具输出,由
feature('HISTORY_SNIP')控制 -
返回
{ messages, tokensFreed, boundaryMessage } -
snipTokensFreed会传递给后续 autocompact 阈值检查,确保阈值判断不基于过时的 token 计数
1c. Microcompact
位置:src/query.ts 第 413-426 行
-
调用
deps.microcompact()→src/services/compact/microCompact.ts的microcompactMessages() -
针对可缓存工具(Read、Grep、Glob、WebFetch、WebSearch、Bash、FileEdit、FileWrite 等,定义在
COMPACTABLE_TOOLS集合中,见microCompact.ts第 41-50 行)清理旧结果 -
由缓存微压缩模块
cachedMicrocompact.ts处理,支持基于时间戳的缓存编辑 -
对
CACHED_MICROCOMPACT特性,延迟生成 boundary message,使用 API 实际报告的cache_deleted_input_tokens替代客户端估算值
1d. Context Collapse(上下文折叠)
位置:src/query.ts 第 440-447 行
-
调用
contextCollapse.applyCollapsesIfNeeded()(src/services/contextCollapse/index.js) -
在 autocompact 之前运行——如果 collapse 已经将 token 数降到阈值以下,autocompact 就是空操作
-
由
feature('CONTEXT_COLLAPSE')控制,是一种读时投影机制,summary 消息存在 collapse store 中而不是 REPL 数组里,所以能跨 turn 持久化 -
不 yield 任何东西——折叠视图是对 REPL 完整历史的读时投影
步骤 2:组装 System Prompt
位置:src/query.ts 第 449-451 行
const fullSystemPrompt = asSystemPrompt(
appendSystemContext(systemPrompt, systemContext),
)
核心逻辑
-
appendSystemContext()(src/utils/api.ts第 437-447 行)-
将
systemContext(key-value 对)追加到 system prompt 数组末尾,格式为key: value -
SystemContext 通常包含
currentDate、CLAUDE.md 内容等
-
-
prependUserContext()(src/utils/api.ts第 449-474 行)-
在 API 调用时,在消息数组头部插入一个
<system-reminder>块 -
包含 CLAUDE.md 内容、memory、currentDate 等
-
带有
IMPORTANT: this context may or may not be relevant...提示
-
-
API 调用中的 System Prompt 组装(
src/services/api/claude.ts第 1358-1369 行)-
最终拼接顺序:
getAttributionHeader()+getCLISyspromptPrefix()+ 原始 systemPrompt + advisor 指令 + chrome 工具搜索指令 -
通过
filter(Boolean)过滤空字符串
-
-
buildSystemPromptBlocks()(claude.ts第 1376 行 →src/utils/api.ts第 321+ 行)-
将 system prompt 拆分为带缓存控制标记(cache_control)的块
-
策略取决于:是否有 MCP 工具(skipGlobalCacheForSystemPrompt)、是否 1P 提供商、是否有动态边界标记
SYSTEM_PROMPT_DYNAMIC_BOUNDARY -
结果:attribution header(无缓存)+ system prompt prefix(org 级缓存)+ 其余内容(org 或 global 级缓存)
-
api.ts:437 appendSystemContext() ← 追加 key: value 到 prompt
api.ts:449 prependUserContext() ← 消息头部插入 <system-reminder>
claude.ts:1358-1369 systemPrompt 拼接 ← 完整 system prompt 组装
claude.ts:1376 buildSystemPromptBlocks() ← 拆分缓存块
步骤 3:检查是否需要 Auto-Compact
位置:src/query.ts 第 454-543 行
const { compactionResult, consecutiveFailures } = await deps.autocompact(
messagesForQuery, toolUseContext, cacheSafeParams, querySource,
tracking, snipTokensFreed,
)
核心逻辑(src/services/compact/autoCompact.ts)
阈值计算(第 72-91 行 getAutoCompactThreshold())
*// autoCompact.ts:72-91*export function getAutoCompactThreshold(model: string): number {
const effectiveContextWindow = getEffectiveContextWindowSize(model)
const autocompactThreshold = effectiveContextWindow - AUTOCOMPACT_BUFFER_TOKENS *// 13000// 可通过 CLAUDE_AUTOCOMPACT_PCT_OVERRIDE 环境变量覆盖*
}
-
有效上下文窗口(第 33-49 行):
contextWindow - MAX_OUTPUT_TOKENS_FOR_SUMMARY(20000) -
可通过
CLAUDE_CODE_AUTO_COMPACT_WINDOW限制窗口上限
判断是否触发(第 160-239 行 shouldAutoCompact())
守卫条件(按检查顺序):
-
session_memory或compact查询源 → 跳过(防死锁) -
marble_origami查询源(Context Collapse 开启时) → 跳过 -
isAutoCompactEnabled() === false→ 跳过-
DISABLE_COMPACT或DISABLE_AUTO_COMPACT环境变量 -
用户设置
autoCompactEnabled: false
-
-
feature('REACTIVE_COMPACT')激活 + GrowthBook flag → 跳过 -
feature('CONTEXT_COLLAPSE')激活 → 跳过(collapse 自己管理上下文) -
Token 计数检查:
tokenCountWithEstimation(messages) - snipTokensFreed >= threshold
执行压缩(第 241-350 行 autoCompactIfNeeded())
-
熔断检查(第 260-265 行):
consecutiveFailures >= MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES(3)→ 跳过 -
Session Memory 压缩(第 288-310 行):先尝试
trySessionMemoryCompaction()(sessionMemoryCompact.ts)- 成功后重置
lastSummarizedMessageId,调用runPostCompactCleanup()和notifyCompaction()
- 成功后重置
-
Legacy 压缩(第 312-333 行):回退到
compactConversation()(compact.ts)- 成功后同样重置状态
-
失败处理(第 334-350 行):递增
consecutiveFailures,达到上限后 trip 熔断器
压缩后处理(query.ts 第 470-543 行)
-
记录
tengu_auto_compact_succeeded事件(含 token 计数细节) -
处理
taskBudget的 remaining 跟踪(第 508-515 行) -
重置
tracking状态(compacted: true,turnCounter: 0,consecutiveFailures: 0) -
调用
buildPostCompactMessages(compactionResult)生成摘要消息,yield 出去给 UI 展示 -
用压缩后的消息替换
messagesForQuery(第 535 行)
步骤 4:调用 Claude API(流式)
4a. 入口
位置:src/query.ts 第 652-708 行
for await (const message of deps.callModel({
messages: prependUserContext(messagesForQuery, userContext),
systemPrompt: fullSystemPrompt,
thinkingConfig: toolUseContext.options.thinkingConfig,
tools: toolUseContext.options.tools,
signal: toolUseContext.abortController.signal,
options: {
model: currentModel,
getToolPermissionContext,
querySource,
agents,
*// ... 等*
},
})) {
*// 处理流式消息...*
}
deps.callModel 即 queryModelWithStreaming(),定义在 src/query/deps.ts 第 33-38 行。
4b. 请求构建(claude.ts queryModel() 函数,第 1017+ 行)
预处理阶段
-
Off-switch 检查(第 1031-1049 行):非订阅用户 + 非自定义 Opus 模型 + GrowthBook off-switch 激活 → 直接返回错误
-
模型解析(第 1057-1062 行):Bedrock inference profile 解析回底层模型
-
Beta 头构建(第 1071-1078 行):根据是否是 agentic 查询(
repl_main_thread/agent:/sdk/hook_agent/verification_agent)决定包含哪些 beta -
Advisor 配置(第 1080-1116 行):检查是否支持 advisor server-tool
-
工具搜索配置(第 1118-1148 行):决定是否使用
ToolSearchTool(条件:模型支持 + 有 pending MCP 服务器或有 deferred 工具) -
工具过滤(第 1152-1172 行):
-
启用工具搜索时:只包含已通过
tool_reference块发现的 deferred 工具 -
未启用时:排除
ToolSearchTool
-
-
工具 Schema 构建(第 1235-1246 行):并行调用
toolToAPISchema()生成每个工具的 API schema -
消息规范化(第 1266 行):调用
normalizeMessagesForAPI(messages, filteredTools) -
后处理(第 1283-1315 行):剥离不支持的字段(tool_reference 块、caller 字段、advisor 块、超量媒体)
System Prompt 最终组装(第 1358-1369 行)
systemPrompt = asSystemPrompt([
getAttributionHeader(fingerprint),
getCLISyspromptPrefix({ isNonInteractive, hasAppendSystemPrompt }),
...systemPrompt, *// ← 步骤 2 中已组装好的 fullSystemPrompt*
...(advisorModel ? [ADVISOR_TOOL_INSTRUCTIONS] : []),
...(injectChromeHere ? [CHROME_TOOL_SEARCH_INSTRUCTIONS] : []),
].filter(Boolean))
请求参数构建(paramsFromContext(),第 1538-1729 行)
组装 BetaMessageStreamParams,包含:
-
model、max_tokens、messages -
system(frombuildSystemPromptBlocks()) -
tools(含defer_loading标记和advisorserver tool) -
thinking(budget + type: enabled/disabled) -
betas(含 sticky-on latches:afk header、fast-mode header、cache-editing header、thinking-clear header) -
output_config(effort value) -
context_management、speed(latched) -
extra_body(Bedrock 特定参数)
4c. 发起流式请求(第 1776-1857 行)
*// claude.ts:1822-1831*const result = await anthropic.beta.messages
.create(
{ ...params, stream: true },
{ signal, headers: { ... } },
)
.withResponse()
-
通过
withRetry()包装(src/services/api/withRetry.js),支持自动重试和模型回退 -
使用原始流而非
BetaMessageStream以避免 O(n²) 的部分 JSON 解析 -
withResponse()获取request_id和 HTTPResponse对象
4d. 流式事件处理(第 1940-2130+ 行)
for await (const part of stream) {
switch (part.type) {
case 'message_start': *// 初始化 partialMessage, usage, ttftMs*case 'content_block_start': *// text / tool_use / thinking / server_tool_use / advisor_tool_result*case 'content_block_delta': *// text_delta / input_json_delta / signature_delta*case 'message_delta': *// 更新 stop_reason, usage*case 'message_stop': *// 流结束*
}
*// yield 中间助理消息给 query.ts 外循环*
}
-
流式空闲看门狗(第 1874-1919 行):90 秒无数据则 abort,45 秒先 warning
-
流式停顿检测(第 1936-1966 行):30 秒无事件则记录
tengu_streaming_stall事件 -
累积
contentBlocks数组,最终组装为AssistantMessage -
tool_use块的input以字符串形式累积partial_json,最后通过resolveToolUseInput()解析为 JSON
4e. 模型回退(query.ts 第 650-954 行)
let attemptWithFallback = truewhile (attemptWithFallback) {
attemptWithFallback = falsetry {
*// 正常 streaming...*
} catch (innerError) {
if (innerError instanceof FallbackTriggeredError && fallbackModel) {
currentModel = fallbackModel
attemptWithFallback = true *// ← 用 fallback model 重试// 清理 assistantMessages、toolResults、toolUseBlocks// 对 ANT 用户:剥离 thinking signature blocks*continue
}
throw innerError
}
}
步骤 5:收集 Assistant 消息和工具调用
位置:src/query.ts 第 826-862 行
if (message.type === 'assistant') {
assistantMessages.push(message) *// ← 收集完整 assistant 消息*const msgToolUseBlocks = message.message.content.filter(
content => content.type === 'tool_use',
) as ToolUseBlock[]
if (msgToolUseBlocks.length > 0) {
toolUseBlocks.push(...msgToolUseBlocks) *// ← 收集工具调用块*
needsFollowUp = true *// ← 需要继续循环*
}
*// 如果有 streaming tool executor,尽早提交工具*if (streamingToolExecutor && !aborted) {
for (const toolBlock of msgToolUseBlocks) {
streamingToolExecutor.addTool(toolBlock, message)
}
}
}
*// 收集 streaming tool executor 的已完成结果*if (streamingToolExecutor && !aborted) {
for (const result of streamingToolExecutor.getCompletedResults()) {
if (result.message) {
yield result.message
toolResults.push(...normalizeMessagesForAPI([result.message], tools)
.filter(_ => _.type === 'user'))
}
}
}
消息回填(第 747-787 行)
在 yield 之前,对 tool_use 块调用 tool.backfillObservableInput() 补全 SDK 展示字段(如文件工具的绝对路径)。只有当 backfill 新增了字段时才 clone 消息。
错误暂扣(第 792-822 行)
以下可恢复错误在流中被暂扣(不 yield),等后续恢复路径处理:
-
prompt_too_long(Context Collapse 或 Reactive Compact 的isWithheldPromptTooLong()) -
max_output_tokens(query.ts的isWithheldMaxOutputTokens()) -
media_size_error(Reactive Compact 的isWithheldMediaSizeError())
这些消息仍然被 push 到 assistantMessages 数组,以便恢复检测。
步骤 6:执行工具(可并发)
位置:src/query.ts 第 1363-1408 行
const toolUpdates = streamingToolExecutor
? streamingToolExecutor.getRemainingResults()
: runTools(toolUseBlocks, assistantMessages, canUseTool, toolUseContext)
两种执行路径
路径 A:StreamingToolExecutor(src/services/tools/StreamingToolExecutor.ts)
-
流式模式下使用:模型还在输出时,已完成输入解析的工具(
input_json_delta流结束)就提前开始执行 -
addTool()在步骤 5 中调用,提交待执行工具 -
getRemainingResults()获取还在队列中或执行中的工具结果 -
支持 abort 时生成合成
tool_result以确保 tool_use/tool_result 配对
路径 B:runTools()(src/services/tools/toolOrchestration.ts 第 19-82 行)
非流式回退路径。通过 partitionToolCalls() 分批:
*// toolOrchestration.ts:91-116*function partitionToolCalls(
toolUseMessages: ToolUseBlock[],
toolUseContext: ToolUseContext,
): Batch[] {
*// 通过 tool.isConcurrencySafe(parsedInput) 判断每个工具// 并发安全工具分组到一起,非并发安全的单独成批*
}
-
并发安全(只读工具如 Read、Grep、Glob、WebSearch、WebFetch 等):
-
通过
runToolsConcurrently()并行执行 -
最大并发数由
CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY环境变量(默认 10)控制 -
使用
src/utils/generators.ts的all()函数实现并发生成器 -
context modifier 排队,批次完成后按顺序应用
-
-
非并发安全(写入工具如 Write、Edit、Bash、Agent 等):
-
通过
runToolsSerially()逐个串行执行 -
每个工具执行后立即更新
currentContext
-
runToolUse()(src/services/tools/toolExecution.ts)
每个工具实际执行的核心函数:
-
权限检查(
canUseTool) -
输入验证(Zod schema)
-
工具 Hook 执行(
toolHooks.ts) -
实际工具调用
-
生成
MessageUpdate包含{ message, contextModifier }
步骤 7:收集工具结果
位置:src/query.ts 第 1384-1408 行
for await (const update of toolUpdates) {
if (update.message) {
yield update.message *// ← yield 工具结果给 UI// 检查是否有 hook 阻止继续*if (update.message.type === 'attachment' &&
update.message.attachment.type === 'hook_stopped_continuation') {
shouldPreventContinuation = true
}
toolResults.push( *// ← 归一化后收集到 toolResults 数组*
...normalizeMessagesForAPI(
[update.message],
toolUseContext.options.tools,
).filter(_ => _.type === 'user'),
)
}
if (update.newContext) {
updatedToolUseContext = { *// ← 更新上下文(供后续迭代使用)*
...update.newContext,
queryTracking,
}
}
}
normalizeMessagesForAPI()(src/utils/messages.ts:1989)将工具结果消息转换为 API 兼容格式(如将 UI 消息拆分为 tool_result 块)。
步骤 8:检查是否需要继续(有工具调用?)
位置:src/query.ts 第 1062 行
if (!needsFollowUp) {
*// → 进入步骤 9(Stop Hooks),然后退出循环*
}
*// 否则 → 继续循环*
needsFollowUp 在步骤 5(第 834 行)被设为 true,当 assistant 消息包含 tool_use 块时。
继续循环时的处理(第 1535-1727 行)
命令队列消费(第 1547-1643 行)
const queuedCommandsSnapshot = getCommandsByMaxPriority(
sleepRan ? 'later' : 'next',
).filter(cmd => {
if (isSlashCommand(cmd)) return false *// slash 命令走单独路径*if (isMainThread) return cmd.agentId === undefinedreturn cmd.mode === 'task-notification' && cmd.agentId === currentAgentId
})
-
如果
Sleep工具刚执行过,使用'later'优先级(等 task notification) -
Agent 作用域过滤:主线程只取无 agentId 的命令,子代理只取自己的 task-notification
-
消费后的命令从队列移除,通知
commandLifecycle('started')
Memory Prefetch 消费(第 1599-1614 行)
if (pendingMemoryPrefetch?.settledAt !== null &&
pendingMemoryPrefetch.consumedOnIteration === -1) {
const memoryAttachments = filterDuplicateMemoryAttachments(
await pendingMemoryPrefetch.promise,
toolUseContext.readFileState, *// ← 过滤掉模型已读/写/编辑的 memory*
)
for (const memAttachment of memoryAttachments) {
const msg = createAttachmentMessage(memAttachment)
yield msg
toolResults.push(msg)
}
}
-
Memory prefetch 在
query()入口处(第 301 行)通过startRelevantMemoryPrefetch()启动 -
每轮检查是否已完成(
settledAt !== null),若未完成则跳过等下一轮 -
使用
readFileState累加过滤,去重
Skill Prefetch 注入(第 1620-1628 行)
if (skillPrefetch && pendingSkillPrefetch) {
const skillAttachments =
await skillPrefetch.collectSkillDiscoveryPrefetch(pendingSkillPrefetch)
for (const att of skillAttachments) {
yield createAttachmentMessage(att)
toolResults.push(msg)
}
}
-
Skill discovery prefetch 在每轮迭代开始时(第 331 行)启动
-
收集已发现的 skill,注入为 attachment
Tool Use Summary(第 1411-1482 行)
*// 异步生成工具使用摘要(Haiku 模型,~1s),不阻塞主流程*
nextPendingToolUseSummary = generateToolUseSummary({
tools: toolInfoForSummary,
signal,
isNonInteractiveSession,
lastAssistantText,
}).then(summary => ...).catch(() => null)
-
只在
emitToolUseSummariesgate 开启、有工具调用、未 abort、非子代理时生成 -
异步生成,下一轮才 yield(
pendingToolUseSummary传入下轮 state)
MCP 工具刷新(第 1659-1671 行)
if (updatedToolUseContext.options.refreshTools) {
const refreshedTools = updatedToolUseContext.options.refreshTools()
if (refreshedTools !== updatedToolUseContext.options.tools) {
updatedToolUseContext = {
...updatedToolUseContext,
options: { ...updatedToolUseContext.options, tools: refreshedTools },
}
}
}
Task Summary(第 1685-1702 行)
if (feature('BG_SESSIONS') && !toolUseContext.agentId &&
taskSummaryModule!.shouldGenerateTaskSummary()) {
taskSummaryModule!.maybeGenerateTaskSummary({ ... })
}
- 供
claude ps展示当前任务进度的摘要
Max Turns 检查(第 1704-1712 行)
if (maxTurns && nextTurnCount > maxTurns) {
yield createAttachmentMessage({ type: 'max_turns_reached', maxTurns, turnCount: nextTurnCount })
return { reason: 'max_turns', turnCount: nextTurnCount }
}
更新状态继续循环(第 1714-1727 行)
const next: State = {
messages: [...messagesForQuery, ...assistantMessages, ...toolResults],
toolUseContext: toolUseContextWithQueryTracking,
autoCompactTracking: tracking,
turnCount: nextTurnCount,
maxOutputTokensRecoveryCount: 0, *// ← 重置恢复计数器*hasAttemptedReactiveCompact: false, *// ← 重置*pendingToolUseSummary: nextPendingToolUseSummary, *// ← 下轮展示*maxOutputTokensOverride: undefined,
stopHookActive,
transition: { reason: 'next_turn' },
}
state = next *// ← while(true) 下一次迭代*
步骤 9:执行 Stop Hooks(含记忆提取)
位置:src/query.ts 第 1267-1306 行 → src/query/stopHooks.ts 第 65-473 行
const stopHookResult = yield* handleStopHooks(
messagesForQuery, assistantMessages,
systemPrompt, userContext, systemContext,
toolUseContext, querySource, stopHookActive,
)
handleStopHooks() 详细流程(stopHooks.ts)
构建 Hook 上下文(第 84-98 行)
const stopHookContext: REPLHookContext = {
messages: [...messagesForQuery, ...assistantMessages],
systemPrompt, userContext, systemContext,
toolUseContext, querySource,
}
- 只有
repl_main_thread或sdk查询源才保存CacheSafeParams(供/btw和 SDKside_question使用)
模板任务分类(第 108-131 行)
-
如果运行在 job 模式(
CLAUDE_JOB_DIR环境变量)下,异步调用classifyAndWriteState()更新state.json -
60 秒超时
后台任务启动(非 bare 模式,第 136-157 行)
if (!isBareMode()) {
*// a. Prompt Suggestion*void executePromptSuggestion(stopHookContext)
*// b. 记忆提取*if (feature('EXTRACT_MEMORIES') && !toolUseContext.agentId && isExtractModeActive()) {
void extractMemoriesModule!.executeExtractMemories(
stopHookContext,
toolUseContext.appendSystemMessage,
)
}
*// c. Auto Dream*if (!toolUseContext.agentId) {
void executeAutoDream(stopHookContext, toolUseContext.appendSystemMessage)
}
}
记忆提取(src/services/extractMemories/extractMemories.ts):
-
使用
runForkedAgent()模式——fork 主对话上下文 -
调用 Haiku 模型分析对话并提取持久记忆
-
写入
~/.claude/projects/<path>/memory/目录 -
非交互模式(
-p/SDK)下由print.tsdrain 该 promise
Chicago MCP 清理(第 164-173 行)
- 自动 un-hide + lock 释放(Computer Use 功能)
Stop Hook 执行(第 180-295 行)
const generator = executeStopHooks(
permissionMode, signal, undefined, stopHookActive ?? false,
toolUseContext.agentId, toolUseContext,
[...messagesForQuery, ...assistantMessages],
toolUseContext.agentType,
)
for await (const result of generator) {
if (result.message) { yield result.message; */* 收集 hook 信息 */* }
if (result.blockingError) { */* 注入恢复消息 */* }
if (result.preventContinuation) { */* 停止 */* }
}
-
executeStopHooks()位于src/utils/hooks.js,触发用户配置的 Stop hook 脚本 -
收集 hook 进度消息(command、durationMs、promptText)
-
生成 stop hook 摘要消息(错误列表、hook 计数等)
-
通过
addNotification发送通知(快捷键提示查看详情)
Teammate Hooks(第 334-453 行)
如果是 teammate 子代理:
-
TaskCompleted hooks:对所有
status === 'in_progress'且owner === teammateName的任务执行 -
TeammateIdle hooks:队友空闲时触发
阻塞错误处理(回到 query.ts 第 1282-1305 行)
if (stopHookResult.preventContinuation) {
return { reason: 'stop_hook_prevented' }
}
if (stopHookResult.blockingErrors.length > 0) {
const next: State = {
messages: [...messagesForQuery, ...assistantMessages, ...stopHookResult.blockingErrors],
*// ...*stopHookActive: true,
}
state = next
continue *// ← 重试,让模型处理 hook 错误*
}
步骤 10:若无工具调用 → 退出循环返回用户
位置:src/query.ts 第 1357 行
return { reason: 'completed' }
在此之前有多层检查:
Token Budget 检查(第 1308-1355 行)
if (feature('TOKEN_BUDGET')) {
const decision = checkTokenBudget(budgetTracker!, agentId, budget, turnTokens)
if (decision.action === 'continue') {
*// 注入 nudge 消息,continue 循环*
state = { ...state, messages: [...messages, nudgeMessage], transition: { reason: 'token_budget_continuation' } }
continue
}
*// 否则记录完成事件,正常退出*
}
-
checkTokenBudget()在src/query/tokenBudget.ts -
支持
+500k指令的 token 预算自动继续 -
若已 diminishing returns → 提前退出
Max Output Tokens 恢复(第 1188-1255 行)
if (isWithheldMaxOutputTokens(lastMessage)) {
*// 1. 先尝试提升到 64k(一次,通过 ESCALATED_MAX_TOKENS)*if (capEnabled && maxOutputTokensOverride === undefined) {
continue *// 用 64k 重试*
}
*// 2. 注入恢复消息,最多 3 次*if (maxOutputTokensRecoveryCount < MAX_OUTPUT_TOKENS_RECOVERY_LIMIT) {
*// 注入: "Output token limit hit. Resume directly..."*continue *// 让模型从中断处继续*
}
*// 3. 恢复耗尽 → surface 错误并退出*yield lastMessage
}
MAX_OUTPUT_TOKENS_RECOVERY_LIMIT = 3(第 164 行)
Prompt-too-long / Media Size 恢复(第 1065-1183 行)
if (isWithheld413) {
*// 1. Collapse drain(如果 Context Collapse 启用且上次 transition 不是 drain_retry)*const drained = contextCollapse.recoverFromOverflow(messagesForQuery, querySource)
if (drained.committed > 0) {
continue *// 用 drained 后的消息重试*
}
}
if ((isWithheld413 || isWithheldMedia) && reactiveCompact) {
*// 2. Reactive compact*const compacted = await reactiveCompact.tryReactiveCompact({ ... })
if (compacted) {
*// 用 compacted 消息重试*continue
}
*// 3. 无恢复 → surface 错误,执行 stop failure hooks,退出*yield lastMessage
void executeStopFailureHooks(lastMessage, toolUseContext)
return { reason: isWithheldMedia ? 'image_error' : 'prompt_too_long' }
}
API 错误退出(第 1262-1265 行)
if (lastMessage?.isApiErrorMessage) {
void executeStopFailureHooks(lastMessage, toolUseContext)
return { reason: 'completed' } *// ← 不重试,静默退出*
}
额外退出路径汇总
内部 continue(不退出)
完整流程图
query() [query.ts:219]
│
└─ queryLoop() [query.ts:241]
│
├─ 初始化 State (messages, toolUseContext, turnCount=1, ...)
├─ buildQueryConfig() [query.ts:295]
├─ startRelevantMemoryPrefetch() [query.ts:301] ← 异步预热
│
└─ while(true) [query.ts:307]
│
├─ [1] 准备消息 ─────────────────────────────────────────────────────┐
│ ├─ getMessagesAfterCompactBoundary() [query.ts:365] │
│ ├─ applyToolResultBudget() [query.ts:379] ← 工具结果预算 │
│ ├─ snipCompactIfNeeded() [query.ts:404] ← snip 裁剪 │
│ ├─ deps.microcompact() [query.ts:414] ← 微压缩 │
│ └─ contextCollapse.applyCollapsesIfNeeded() [query.ts:441] ← 折叠 │
│ │
├─ [2] 组装 System Prompt ─────────────────────────────────────────── │
│ └─ appendSystemContext(systemPrompt, systemContext) [query.ts:449] │
│ │
├─ [3] 检查 Auto-Compact ──────────────────────────────────────────── │
│ ├─ deps.autocompact() [query.ts:454] │
│ │ ├─ shouldAutoCompact() [autoCompact.ts:160] ← 阈值判断 │
│ │ ├─ trySessionMemoryCompaction() [autoCompact.ts:288] │
│ │ └─ compactConversation() [autoCompact.ts:313] ← 回退压缩 │
│ └─ 若已压缩: yield 摘要消息, 替换 messagesForQuery │
│ │
├─ Blocking limit 检查 ────────────────────────────────────────────── │
│ └─ calculateTokenWarningState() [query.ts:637] │
│ │
├─ [4] 调用 Claude API (流式) ─────────────────────────────────────── │
│ ├─ deps.callModel() [query.ts:659] │
│ │ └─ queryModelWithStreaming() [claude.ts:752] │
│ │ └─ queryModel() [claude.ts:1017] │
│ │ ├─ 构建 tool schemas [claude.ts:1235] │
│ │ ├─ normalizeMessagesForAPI [claude.ts:1266] │
│ │ ├─ 组装 systemPrompt [claude.ts:1358] │
│ │ ├─ anthropic.beta.messages.create() [claude.ts:1822] │
│ │ └─ for await (part of stream) [claude.ts:1940] │
│ │ ├─ content_block_start/delta → 累积内容块 │
│ │ ├─ yield 中间 assistant 消息 │
│ │ └─ 空闲看门狗 + 停顿检测 │
│ ├─ Model fallback 重试 [query.ts:893-952] │
│ └─ 收集 assistant 消息到数组 [query.ts:826-845] │
│ │
├─ Post-sampling hooks [query.ts:999-1009] │
│ │
├─ [5] 收集 Assistant 消息和工具调用 ───────────────────────────────── │
│ ├─ assistantMessages.push() [query.ts:827] │
│ ├─ toolUseBlocks.push() [query.ts:833] │
│ └─ needsFollowUp = true [query.ts:834] │
│ │
├─ Abort 检查 [query.ts:1015-1052] │
│ └─ yieldMissingToolResultBlocks() 确保 tool_use/tool_result 配对 │
│ │
├─ Yield 上轮 Tool Use Summary [query.ts:1054-1060] │
│ │
├─ 若无工具调用 (needsFollowUp === false) ──────────────────────────── │
│ ├─ Prompt-too-long/Media 恢复 [query.ts:1065-1183] │
│ │ ├─ Collapse drain retry → continue │
│ │ ├─ Reactive compact retry → continue │
│ │ └─ 失败 → surface error, exit │
│ ├─ Max output tokens 恢复 [query.ts:1188-1255] │
│ │ ├─ 64k escalate → continue │
│ │ ├─ Recovery message → continue (最多 3 次) │
│ │ └─ 耗尽 → surface error │
│ ├─ [9] Stop Hooks [query.ts:1267-1306] │
│ │ └─ handleStopHooks() [stopHooks.ts:65] │
│ │ ├─ 保存 CacheSafeParams │
│ │ ├─ Template 任务分类 │
│ │ ├─ 记忆提取 (fire-and-forget) │
│ │ ├─ Auto Dream (fire-and-forget) │
│ │ ├─ executeStopHooks() → stop hook 脚本 │
│ │ ├─ TeammateIdle / TaskCompleted hooks │
│ │ └─ blocking errors → continue │
│ ├─ Token Budget [query.ts:1308-1355] │
│ │ ├─ continue → nudge message, continue │
│ │ └─ done → exit │
│ └─ [10] return { reason: 'completed' } [query.ts:1357] │
│ │
├─ [6] [7] 执行工具 + 收集结果 ─────────────────────────────────────── │
│ ├─ StreamingToolExecutor.getRemainingResults() │
│ │ 或 runTools() [toolOrchestration.ts:19] │
│ │ ├─ partitionToolCalls() → 并发/串行分批 │
│ │ ├─ runToolsConcurrently() (并行, max 10) │
│ │ └─ runToolsSerially() (串行) │
│ ├─ for await (update of toolUpdates) [query.ts:1384] │
│ │ ├─ yield update.message │
│ │ └─ toolResults.push(normalizeMessagesForAPI(...)) │
│ └─ 检查 shouldPreventContinuation [query.ts:1519] │
│ │
├─ Abort during tools 检查 [query.ts:1485-1516] │
│ │
├─ Generate Tool Use Summary [query.ts:1412-1482] │
│ │
├─ 附加处理 ────────────────────────────────────────────────────────── │
│ ├─ 命令队列消费 [query.ts:1579] │
│ ├─ getAttachmentMessages() [query.ts:1580] │
│ ├─ Memory prefetch 消费 [query.ts:1599] │
│ ├─ Skill prefetch 注入 [query.ts:1620] │
│ ├─ MCP 工具刷新 [query.ts:1659] │
│ └─ Task Summary [query.ts:1685] │
│ │
├─ Max turns 检查 [query.ts:1705] │
│ │
└─ [8] state = { ...next, messages: [...msgs, ...assistant, ...tools] }
continue → while(true) 下一轮迭代 │
关键文件索引
阶段八:API 调用详情
文件:src/services/api/claude.ts
函数:****queryModelWithStreaming() — 第 752 行
封装 VCR 录制和模型调用:
export async function* queryModelWithStreaming({...}) {
return yield* withStreamingVCR(messages, async function* () {
yield* queryModel(messages, systemPrompt, thinkingConfig, tools, signal, options);
});
}
函数:****queryModel() — 第 1017 行
API 调用的实际实现:
关闭开关检查(第 1031-1049 行)
if (!isClaudeAISubscriber() && isNonCustomOpusModel(options.model) && ...) {
*// GrowthBook 控制的关闭开关*yield getAssistantMessageFromError(new Error(CUSTOM_OFF_SWITCH_MESSAGE), ...);
return;
}
模型名称解析(第 1057-1062 行)
const modelName = normalizeModelStringForAPI(options.model);
配置 betas、advisor、tool search(第 1064-1248 行)
-
组装 beta headers(Fast Mode, Afk, Effort, Task Budgets 等)
-
配置 advisor 模型(实验性功能)
-
配置工具搜索(延迟加载工具 schema)
构建系统提示词(第 1374-1379 行)
const systemPromptBlocks = buildSystemPromptBlocks(...);
流式 API 调用(第 1822-1832 行)
const result = await anthropic.beta.messages.create(
{ ...params, stream: true },
{ signal, ...headers }
).withResponse();
SSE 事件解析(第 1940-2310 行)
性能监控(第 1868-1929 行)
-
TTFT(Time to First Token)追踪
-
流式空闲超时看门狗(stall detection)
-
流式传输空闲超时(streaming idle timeout)
阶段九:工具执行
文件:src/services/tools/StreamingToolExecutor.ts
类:****StreamingToolExecutor — 第 40 行
流式工具执行器,支持模型还在生成时就并行执行已返回的工具调用。
addTool(block, assistantMessage) — 第 76 行:
-
查找工具定义
-
Zod schema 安全解析输入
-
通过
toolDefinition.isConcurrencySafe()判断并发安全性 -
推入工具队列,状态为
queued -
调用
processQueue()
canExecuteTool(isConcurrencySafe) — 第 129 行:
private canExecuteTool(isConcurrencySafe: boolean): boolean {
const executingTools = this.tools.filter(t => t.status === 'executing');
return (
executingTools.length === 0 ||
(isConcurrencySafe && executingTools.every(t => t.isConcurrencySafe))
);
}
-
没有正在执行的工具:可以执行
-
工具安全且所有正在执行的工具也安全:可以并行
-
否则:必须等待
processQueue() — 第 140 行:
-
按顺序遍历队列
-
并发安全的工具并行执行
-
非并发安全的工具阻塞后续工具
getCompletedResults() — 第 412 行:
-
非阻塞获取已完成工具的结果
-
在流式循环中每收到一个助理消息就调用一次
getRemainingResults() — 第 453 行:
-
异步等待所有工具完成
-
在流式结束后调用
文件:src/services/tools/toolOrchestration.ts
函数:****runTools() — 第 19 行
传统工具执行(非流式路径),按并发安全性分区执行:
export async function* runTools(...) {
for (const { isConcurrencySafe, blocks } of partitionToolCalls(...)) {
if (isConcurrencySafe) {
*// 只读批次:并发执行*for await (const update of runToolsConcurrently(blocks, ...)) {
yield update;
}
} else {
*// 非只读批次:串行执行*for await (const update of runToolsSerially(blocks, ...)) {
yield update;
}
}
}
}
partitionToolCalls() — 第 91 行:
-
调用
tool.inputSchema.safeParse()验证输入 -
调用
tool.isConcurrencySafe()判断并发安全性 -
将工具调用分为并发安全批次和非安全批次
文件:src/services/tools/toolExecution.ts
函数:****runToolUse() — 第 337 行
单个工具的调度器:
-
查找工具(第 345-356 行):
-
先在可用工具中查找
-
回退检查是否为别名调用(如旧名称
KillShell→TaskStop)
-
-
工具不存在(第 369-410 行):返回错误
-
中止检查(第 415-452 行):如果 abort 信号已触发,返回取消消息
-
调用权限检查管道(第 455-466 行):
for await (const update of streamedCheckPermissionsAndCallTool(
tool, toolUse.id, toolInput, toolUseContext, canUseTool, ...
)) {
yield update;
}
函数:****checkPermissionsAndCallTool() — 第 599 行
完整的权限检查与工具执行管道:
-
Zod schema 验证(第 615-680 行):
-
使用
tool.inputSchema.safeParse(input)验证输入类型 -
失败时检查是否需要提示模型加载延迟工具
-
-
输入值验证(第 683-733 行):
-
调用
tool.validateInput(parsedInput.data, toolUseContext) -
每个工具有自己的验证逻辑
-
-
Bash 分类器推测启动(第 740-752 行):
-
Bash 工具在权限检查前推测性启动安全分类器
-
并行运行以提前获得结果
-
-
输入处理(第 761-793 行):
-
防御性剥离
_simulatedSedEdit字段 -
回填遗留/派生字段(
backfillObservableInput)
-
-
Pre-tool hooks(第 799+ 行):
-
运行
runPreToolUseHooks() -
hooks 可以修改输入、阻止执行
-
-
权限检查(第 ~910-980 行):
-
调用
canUseTool(tool, input, ...) -
三种模式:
-
allow:直接允许 -
deny:拒绝 -
ask:需要用户交互确认
-
-
-
权限 hooks(第 ~990-1060 行):
- 运行 hooks 处理权限决策
-
调用 ****
tool.call()(第 1207 行):
const toolResult = await tool.call(parsedInput, toolUseContext);
-
Post-tool hooks(第 ~1320-1380 行):
- 运行
runPostToolUseHooks()
- 运行
-
返回工具结果消息
阶段十:流式响应渲染到 UI
文件:src/screens/REPL.tsx
第 2793-2803 行:
for await (const event of query({
messages, systemPrompt, userContext, systemContext,
canUseTool, toolUseContext, querySource
})) {
onQueryEvent(event);
}
onQueryEvent — 第 2584 行:
通过 handleMessageFromStream 更新 React state 中的 messages 数组。Ink 渲染引擎将增量更新渲染到终端。
事件类型包括:
-
stream_request_start:新 API 请求开始 -
assistant:助理消息(文本、thinking、tool_use) -
user:用户消息(工具结果) -
attachment:附件消息(文件变更、队列命令、内存) -
system:系统消息(错误、警告) -
progress:进度更新(工具执行进度) -
tombstone:墓碑消息(清理 UI) -
tool_use_summary:工具使用摘要
完整调用链路图
cli.tsx:297 cliMain()
└─ main.tsx:3760 launchRepl()
└─ replLauncher.tsx:12 <App><REPL /></App>
└─ REPL.tsx:572 REPL() 组件挂载
│
│ 用户按下 Enter
▼
handlePromptSubmit.ts:120 handlePromptSubmit()
│
├─ 即时命令? → 直接执行 (不调模型)
├─ 已有查询在运行? → enqueue()
└─ 正常执行:
└─ handlePromptSubmit.ts:396 executeUserInput()
│
├─ processUserInput.ts:85 processUserInput()
│ ├─ $ 开头 → processBashCommand()
│ ├─ / 开头 → processSlashCommand()
│ └─ 其他 → processTextPrompt()
│
└─ REPL.tsx:2855 onQuery()
└─ REPL.tsx:2661 onQueryImpl()
│
├─ 并行加载系统提示词 + 上下文
│
└─ query.ts:219 query() / query.ts:241 queryLoop()
│
┌─ while(true) ─────────────────────┐
│ │
├─ 内存/技能预取 │
├─ 多级压缩 (snip/micro/collapse/auto)│
├─ Token预算检查 │
│ │
├─ API调用 ─────────────────────┐ │
│ claude.ts:752 queryModel() │ │
│ ├─ betas/advisors/tools配置 │ │
│ ├─ anthropic.messages.create │ │
│ └─ SSE事件流解析 │ │
│ ├─ content_block_start │ │
│ ├─ content_block_delta │ │
│ └─ content_block_stop │ │
└───────────────────────────────┘ │
│
├─ 流式响应处理 │
│ ├─ tool_use 块 → addTool() │
│ └─ getCompletedResults() │
│ │
├─ 错误恢复 (PTL/max_tokens/fallback) │
│ │
├─ 工具执行完成 │
│ └─ getRemainingResults() │
│ └─ runToolUse() │
│ └─ checkPermissions() │
│ └─ tool.call() │
│ │
├─ 附件注入 (队列/内存/技能) │
│ │
├─ needsFollowUp? ──── 是 ──→ continue│
│ │
└─ 否 → 返回 { reason: 'completed' } │
└────────────────────────────────────┘
关键设计特点
-
AsyncGenerator 模式:整个循环使用
async function*实现,通过yield将中间结果流式传给 UI,无需等待整个回合完成 -
流式工具执行:
StreamingToolExecutor在模型还在生成剩余响应时,就并行执行已返回的工具调用 -
多级上下文管理:五种压缩机制(工具结果预算、Snip、微压缩、上下文折叠、自动压缩)协同工作,确保不超出上下文窗口
-
权限门控:每个工具调用经过 Zod 验证 → validateInput → preToolHooks → canUseTool → tool.call() → postToolHooks 的完整管道
-
错误恢复:prompt_too_long、max_output_tokens、media_size_error 等可恢复错误有多级恢复策略
-
模型回退:当主模型高负载时,自动切换到备选模型继续服务
-
内存与技能系统:后台预取相关记忆和技能发现,在当前循环末尾注入为附件消息
关键文件索引
claude code为什么不使用语义embedding方式来查找信息?
Claude Code 之所以不使用语义 Embedding(向量化)的方式来查找代码或工具信息,主要是基于工程实践中的性能、安全性、维护成本以及实际效果的综合考量。
简单来说,Anthropic 团队发现,在代码检索这个特定场景下,“足够聪明的大脑(LLM)+ 最朴素的搜索工具(grep)”远比复杂的向量数据库更好用6。具体原因可以归纳为以下几点:
性能与准确率实测更优
在 Claude Code 的早期版本中,团队确实尝试过使用 Voyage AI 等现成的向量数据库方案对代码库进行索引。但在内部基准测试(Benchmark)中发现,这种语义搜索的性能表现甚至不如直接使用 ripgrep(一个超快的文本搜索工具)。因此,他们果断转向了“Search, Don't Index”(去搜索,而不是去建索引)的哲学,让 AI 直接调用 grep、glob 等文件系统原语进行实时搜索。
规避“索引滞后”与维护噩梦
传统的 RAG(检索增强生成)系统需要预先将文档切片并转化为向量存入数据库。这带来了巨大的维护成本:
实时更新难: 代码库是高度动态的。一旦源文件发生修改,整个 Embedding 管道就需要重新运行,否则向量数据库里的内容就会过时。
幻觉风险: 如果使用过期的向量索引,AI 可能会一本正经地引用早已重构或删除的代码行号,导致严重的误导。
相比之下,直接读取文件系统的 grep 搜索永远是实时的,AI 看到的永远是“热腾腾”的最新代码状态。
安全性与数据隐私
Embedding 方案通常需要将代码数据托管在向量数据库中(无论是第三方服务还是内部系统),这增加了数据泄露或被攻击的风险。而 Claude Code 采用本地运行的 Agentic Search(智能体搜索)模式,直接在开发者的本地机器上执行搜索命令,代码无需离开本地环境,天然具备了极高的安全性和隐私保护。
可审计性与透明性
向量检索本质上是一个“黑箱”,很难解释为什么某个相似度分数的文档会被选中。而在代码开发和合规要求高的领域,工程师需要完全可控、可审计的工具。grep 的搜索结果基于精确的文本匹配,过程完全透明、可重现,更符合开发者的思维习惯和信任链条。
“Agentic Search”的新范式
Claude Code 并不是简单地跑一条死板的 grep 命令,而是采用了Agentic Search(智能体搜索)。它会像一名经验丰富的开发者一样,主动发起多轮查询:先宽泛后精确,顺着代码的引用关系自然跳转。现代大模型的能力已经足够强大,能够很好地理解 grep 搜出来的精确结果之间的语义关系,因此不再需要一个同样复杂且昂贵的向量搜索引擎来辅助。
MCP vs Skills
设计初衷
MCP —— 做 AI 的“通用连接器”
它的初衷是消灭 LLM 与外部世界之间“点对点”的碎片化集成。
在 MCP 出现前,每让模型接入一个数据库、一个 API、一个文件系统,就要写一套定制代码。MCP 像一个 AI 版的 USB-C 协议:提供统一的、开放的标准,让模型能安全、双向地访问各种数据源和工具,不管后端是什么。
Skills —— 做 AI 的“专业应用程序”
它的初衷是把领域知识、工作流程和最佳实践打包成可复用的能力单元。
过去要让模型做好一项专业任务(比如按法规审核合同),需要在提示词里写一整套冗长的流程说明。Skills 把这个过程封装成一个“技能包”:里面包含了执行步骤、判断标准、约束条件、以及需要调用哪些工具。激活它,模型就能像受过培训的员工一样,按标准流程做事。
各自擅长领域
MCP 擅长
-
连接任何外部系统:数据库、SaaS 软件、本地文件、第三方 API……装上对应的 MCP 服务器即可。
-
实时、双向的数据交互:查询最新库存、写入工单、发送邮件。
-
降低集成成本:开发者写一次 MCP 服务端,不同 AI 应用都能复用。
-
安全与权限控制:在协议层做好认证、授权,精细控制模型能看什么、能做什么。
Skills 擅长
-
结构化专业任务:例如生成合规报告、执行退款 SOP、按规范审查代码。
-
保证行为一致性与质量:不管谁来用、用多少次,只要激活对应技能,流程和标准都一样。
-
降低提示工程的门槛:用户只需说“用合同分析技能查看这份文件”,不用每次重复长达几页的指令。
-
组合与复用:一个“数据分析技能”可以在金融、运营、科研等不同 MCP 数据源上通用。
各自短板领域
MCP 不擅长
-
定义“怎么做一件事”的领域逻辑:它把锤子、扳手递给你,但不教你怎么修车。
-
提供任务层面的智能与决策:连接了 CRM 和邮件系统后,模型还需要知道“什么情况下给客户发哪封跟进邮件”。
-
封装可复用的专业知识:同一个业务流程,换一个 AI 应用就得重新写提示、重新组合工具。
Skills 擅长
-
结构化专业任务:例如生成合规报告、执行退款 SOP、按规范审查代码。
-
保证行为一致性与质量:不管谁来用、用多少次,只要激活“退款技能”,流程和标准都一样。
-
降低提示工程的门槛:用户只需说“用合同分析技能查看这份文件”,不用每次重复长达几页的指令。
-
组合与复用:一个“数据分析技能”可以在金融、运营、科研等不同 MCP 数据源上通用。
适用场景
MCP 的典型场景
-
企业想让自己内部 AI 助手能查 Salesforce、ERP、数据库,而不为每个系统开发单独插件。
-
个人助手需要读写 日历、邮件、云盘,且能跨服务协同工作。
Skills 的典型场景
-
客服部门:把“退货流程”“投诉升级规则”做成技能,所有 AI 客服一致执行。
-
法律 / 合规:将某类合同审查的检查清单、风险标引规则封装为技能。
-
代码规范审核:按照团队内部编码规范、安全红线进行系统性检查。
-
医疗 / 教育:把诊断辅助路径、个性化教学策略打包成可调用的技能。
总结它们在架构中是两个不同的层级,是互补关系:
-
MCP 是基础设施层(连接与工具)
-
Skills 是应用能力层(知识与流程)
记忆系统初探
Claude Code 的Memory 系统有一个核心理念:用 Markdown 文件作为「外部大脑」,用 LLM 做检索引擎。Claude Code 的记忆系统是一个持久化、基于文件的记忆系统,它的核心目标是让跨会话的对话能够"记住"关于用户、项目、反馈等重要信息。
在claude code命令行中输入:/memory,会出现以下选项:

两套记忆系统
Claude code有两套记忆系统,所有的记忆都会被当作上下文,在每次谈话轮次中都会被载入。如果不特别指定,记忆文件都是以英文书写和保存。
-
CLAUDE.md(CLAUDE.local.md):用户自己写的指令,用于给claude code加上永久性上下文。
-
Auto memory:claude code自动生成的内容,基于用户的反馈修复和偏好,放在用户对应的~/.claude/projects/<hash>/memory/ 路径下,其中:
-
MEMORY.md:索引文件,每行一条记忆指针(最多 200 行 or 25K,超出截断)
-
各记忆文件(.md):具体记忆内容,带 frontmatter 元数据
-
-
两套记忆系统路径不同、管理者不同、互不写入、互不影响。

CLAUDE.md
CLAUDE.md文件有以下存放方式:
上述文件(不包含最后一行)在claude code启动时全量载入,载入CLAUDE.md 文件的方式是从当前工作目录向上遍历目录树,并检查沿途每个目录中是否存在 CLAUDE.md 和 CLAUDE.local.md 文件。这意味着,如果当前在 foo/bar/ 目录下运行 Claude Code,将加载 foo/bar/CLAUDE.md、foo/CLAUDE.md 以及它们路径下的任何 CLAUDE.local.md 文件。所有符合格式的文件都会被拼接起来,拼接顺序是:路径从上级往下级,先找到的文件先拼接,同路径下的CLAUDE.md和CLAUDE.local.md就按照这样的顺序拼接。优先级越高,载入越靠后,利用LLM的近因效应:赋予靠后的内容更高的注意力权重,这不是靠prompt规定或者程序实现,单纯靠LLM的特性。
这其中:
拼接结果会用<system-reminder>tag进行包裹,放到messages中所有其他消息之前,role为user。
另外还可以通过.claude/rules/下的md文件路径保存具体的规则,该路径下的每一个md文件的文件名称是一个描述性的表述,也是可以递归遍历的,所以可以有更下级的子文件夹,文件夹名称也要有描述性,比如frontend/ or backend/,它们都会随着程序启动而一次性载入。
claude.md示例(https://raw.githubusercontent.com/multica-ai/andrej-karpathy-skills/refs/heads/main/CLAUDE.md)
# CLAUDE.md
Behavioral guidelines to reduce common LLM coding mistakes. Merge with project-specific instructions as needed.
**Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment.
## 1. Think Before Coding
**Don't assume. Don't hide confusion. Surface tradeoffs.**
Before implementing:
- State your assumptions explicitly. If uncertain, ask.
- If multiple interpretations exist, present them - don't pick silently.
- If a simpler approach exists, say so. Push back when warranted.
- If something is unclear, stop. Name what's confusing. Ask.
## 2. Simplicity First
**Minimum code that solves the problem. Nothing speculative.**
- No features beyond what was asked.
- No abstractions for single-use code.
- No "flexibility" or "configurability" that wasn't requested.
- No error handling for impossible scenarios.
- If you write 200 lines and it could be 50, rewrite it.
Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.
## 3. Surgical Changes
**Touch only what you must. Clean up only your own mess.**
When editing existing code:
- Don't "improve" adjacent code, comments, or formatting.
- Don't refactor things that aren't broken.
- Match existing style, even if you'd do it differently.
- If you notice unrelated dead code, mention it - don't delete it.
When your changes create orphans:
- Remove imports/variables/functions that YOUR changes made unused.
- Don't remove pre-existing dead code unless asked.
The test: Every changed line should trace directly to the user's request.
## 4. Goal-Driven Execution
**Define success criteria. Loop until verified.**
Transform tasks into verifiable goals:
- "Add validation" → "Write tests for invalid inputs, then make them pass"
- "Fix the bug" → "Write a test that reproduces it, then make it pass"
- "Refactor X" → "Ensure tests pass before and after"
For multi-step tasks, state a brief plan:
- [Step] → verify: [check]
- [Step] → verify: [check]
- [Step] → verify: [check]
Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.
---
**These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes.
Auto memory
md文件生成路径
路径A:主Agent自主决策
规定何时保存记忆和何时使用该prompt的prompt:
'## Types of memory',
'',
'There are several discrete types of memory that you can store in your memory system. Each type below declares a <scope> of `private`, `team`, or guidance for choosing between the two.',
'',
'<types>',
'<type>',
' <name>user</name>',
' <scope>always private</scope>',
" <description>Contain information about the user's role, goals, responsibilities, and knowledge. Great user memories help you tailor your future behavior to the user's preferences and perspective. Your goal in reading and writing these memories is to build up an understanding of who the user is and how you can be most helpful to them specifically. For example, you should collaborate with a senior software engineer differently than a student who is coding for the very first time. Keep in mind, that the aim here is to be helpful to the user. Avoid writing memories about the user that could be viewed as a negative judgement or that are not relevant to the work you're trying to accomplish together.</description>",
" <when_to_save>When you learn any details about the user's role, preferences, responsibilities, or knowledge</when_to_save>",
" <how_to_use>When your work should be informed by the user's profile or perspective. For example, if the user is asking you to explain a part of the code, you should answer that question in a way that is tailored to the specific details that they will find most valuable or that helps them build their mental model in relation to domain knowledge they already have.</how_to_use>",
' <examples>',
" user: I'm a data scientist investigating what logging we have in place",
' assistant: [saves private user memory: user is a data scientist, currently focused on observability/logging]',
'',
" user: I've been writing Go for ten years but this is my first time touching the React side of this repo",
" assistant: [saves private user memory: deep Go expertise, new to React and this project's frontend — frame frontend explanations in terms of backend analogues]",
' </examples>',
'</type>',
'<type>',
' <name>feedback</name>',
' <scope>default to private. Save as team only when the guidance is clearly a project-wide convention that every contributor should follow (e.g., a testing policy, a build invariant), not a personal style preference.</scope>',
" <description>Guidance the user has given you about how to approach work — both what to avoid and what to keep doing. These are a very important type of memory to read and write as they allow you to remain coherent and responsive to the way you should approach work in the project. Record from failure AND success: if you only save corrections, you will avoid past mistakes but drift away from approaches the user has already validated, and may grow overly cautious. Before saving a private feedback memory, check that it doesn't contradict a team feedback memory — if it does, either don't save it or note the override explicitly.</description>",
' <when_to_save>Any time the user corrects your approach ("no not that", "don\'t", "stop doing X") OR confirms a non-obvious approach worked ("yes exactly", "perfect, keep doing that", accepting an unusual choice without pushback). Corrections are easy to notice; confirmations are quieter — watch for them. In both cases, save what is applicable to future conversations, especially if surprising or not obvious from the code. Include *why* so you can judge edge cases later.</when_to_save>',
' <how_to_use>Let these memories guide your behavior so that the user and other users in the project do not need to offer the same guidance twice.</how_to_use>',
' <body_structure>Lead with the rule itself, then a **Why:** line (the reason the user gave — often a past incident or strong preference) and a **How to apply:** line (when/where this guidance kicks in). Knowing *why* lets you judge edge cases instead of blindly following the rule.</body_structure>',
' <examples>',
" user: don't mock the database in these tests — we got burned last quarter when mocked tests passed but the prod migration failed",
' assistant: [saves team feedback memory: integration tests must hit a real database, not mocks. Reason: prior incident where mock/prod divergence masked a broken migration. Team scope: this is a project testing policy, not a personal preference]',
'',
' user: stop summarizing what you just did at the end of every response, I can read the diff',
" assistant: [saves private feedback memory: this user wants terse responses with no trailing summaries. Private because it's a communication preference, not a project convention]",
'',
" user: yeah the single bundled PR was the right call here, splitting this one would've just been churn",
' assistant: [saves private feedback memory: for refactors in this area, user prefers one bundled PR over many small ones. Confirmed after I chose this approach — a validated judgment call, not a correction]',
' </examples>',
'</type>',
'<type>',
' <name>project</name>',
' <scope>private or team, but strongly bias toward team</scope>',
' <description>Information that you learn about ongoing work, goals, initiatives, bugs, or incidents within the project that is not otherwise derivable from the code or git history. Project memories help you understand the broader context and motivation behind the work users are working on within this working directory.</description>',
' <when_to_save>When you learn who is doing what, why, or by when. These states change relatively quickly so try to keep your understanding of this up to date. Always convert relative dates in user messages to absolute dates when saving (e.g., "Thursday" → "2026-03-05"), so the memory remains interpretable after time passes.</when_to_save>',
" <how_to_use>Use these memories to more fully understand the details and nuance behind the user's request, anticipate coordination issues across users, make better informed suggestions.</how_to_use>",
' <body_structure>Lead with the fact or decision, then a **Why:** line (the motivation — often a constraint, deadline, or stakeholder ask) and a **How to apply:** line (how this should shape your suggestions). Project memories decay fast, so the why helps future-you judge whether the memory is still load-bearing.</body_structure>',
' <examples>',
" user: we're freezing all non-critical merges after Thursday — mobile team is cutting a release branch",
' assistant: [saves team project memory: merge freeze begins 2026-03-05 for mobile release cut. Flag any non-critical PR work scheduled after that date]',
'',
" user: the reason we're ripping out the old auth middleware is that legal flagged it for storing session tokens in a way that doesn't meet the new compliance requirements",
' assistant: [saves team project memory: auth middleware rewrite is driven by legal/compliance requirements around session token storage, not tech-debt cleanup — scope decisions should favor compliance over ergonomics]',
' </examples>',
'</type>',
'<type>',
' <name>reference</name>',
' <scope>usually team</scope>',
' <description>Stores pointers to where information can be found in external systems. These memories allow you to remember where to look to find up-to-date information outside of the project directory.</description>',
' <when_to_save>When you learn about resources in external systems and their purpose. For example, that bugs are tracked in a specific project in Linear or that feedback can be found in a specific Slack channel.</when_to_save>',
' <how_to_use>When the user references an external system or information that may be in an external system.</how_to_use>',
' <examples>',
' user: check the Linear project "INGEST" if you want context on these tickets, that\'s where we track all pipeline bugs',
' assistant: [saves team reference memory: pipeline bugs are tracked in Linear project "INGEST"]',
'',
" user: the Grafana board at grafana.internal/d/api-latency is what oncall watches — if you're touching request handling, that's the thing that'll page someone",
' assistant: [saves team reference memory: grafana.internal/d/api-latency is the oncall latency dashboard — check it when editing request-path code]',
' </examples>',
'</type>',
'</types>',
'',
该prompt构建system prompt,然后根据prompt规定,模型自身决定调用Write/Edit工具,在代码层面没有任何判断条件。
而auto memory存放在项目对应的 .claude/projects/<hash>/memory/ (例如:/Users/yangfucai/.claude/projects/-Users-yangfucai-Downloads-code-claude-code-src/memory)目录下,还有一个索引md文件:MEMORY.md。
路径B:Hook机制
每一轮对话结束之后,发动hook机制,通过fork主Agent创建独立的subagent,并在后台运行,该subagent能看到所有的主agent对话历史和system prompt等(也就是说能够看到上面的auto memory prompt),注意有一条新加的关键指令:
`You are now acting as the memory extraction subagent. Analyze the most recent ~${newMessageCount} messages above and use them to update your persistent memory systems.`,
'',
`Available tools: ${FILE_READ_TOOL_NAME}, ${GREP_TOOL_NAME}, ${GLOB_TOOL_NAME}, read-only ${BASH_TOOL_NAME} (ls/find/cat/stat/wc/head/tail and similar), and ${FILE_EDIT_TOOL_NAME}/${FILE_WRITE_TOOL_NAME} for paths inside the memory directory only. ${BASH_TOOL_NAME} rm is not permitted. All other tools — MCP, Agent, write-capable ${BASH_TOOL_NAME}, etc — will be denied.`,
'',
`You have a limited turn budget. ${FILE_EDIT_TOOL_NAME} requires a prior ${FILE_READ_TOOL_NAME} of the same file, so the efficient strategy is: turn 1 — issue all ${FILE_READ_TOOL_NAME} calls in parallel for every file you might update; turn 2 — issue all ${FILE_WRITE_TOOL_NAME}/${FILE_EDIT_TOOL_NAME} calls in parallel. Do not interleave reads and writes across multiple turns.`,
'',
`You MUST only use content from the last ~${newMessageCount} messages to update your persistent memories. Do not waste any turns attempting to investigate or verify that content further — no grepping source files, no reading code to confirm a pattern exists, no git commands.` +
manifest,
收到N条最新消息,根据when_to_save规则由模型判断哪些信息值得存,并行读取需要更新的已有记忆文件,并行写入新记忆并更新索引文件MEMORY.md,该subagent权限被规定为读+写memory目录的md文件。
Auto memory落盘记忆文件时,需要明确记忆的类型:
export const MEMORY_TYPES = [
'user', // 用户画像:角色、偏好、知识水平
'feedback', // 行为反馈:该做什么、不该做什么
'project', // 项目动态:在做什么、截止日期、协作信息
'reference', // 外部指针:哪里能找到什么信息
] as const
记忆类型说明
- user — 用户记忆
目的:记录用户的角色、目标、职责、知识背景和偏好,以便后续对话中能够针对性地调整协作方式。
存储内容:
-
用户的职业角色(如"数据科学家"、"后端工程师")
-
技术栈熟悉度(如"Go 十年经验,但 React 新手")
-
当前关注领域(如"正在做可观测性/日志系统")
使用方式:当你要回答用户问题时,根据用户记忆中的背景信息调整解释的深度和角度。例如对一个资深后端工程师解释前端代码时,用后端概念做类比。
示例:
用户是数据科学家,当前关注可观测性/日志系统
→ 回答问题时侧重数据分析视角
- feedback — 反馈记忆
目的:记录用户对你工作方式的纠正和确认,让你避免重复犯错、持续采用被验证有效的做法。这是最重要的一类记忆。
存储内容:
-
纠正:"不要 mock 数据库"、"不要每次回复末尾总结"
-
确认:"对,单个 PR 合并是正确的做法"
存储结构(最重要):
规则本身
Why: 原因(历史事故 / 强偏好)
How to apply: 何时/何地适用此规则
Why 的重要性:知道原因才能判断边界情况,而不是盲目遵守规则。
使用方式:让这些记忆指导你的行为,使用户无需重复相同的指导。遇到相似场景时主动回顾相关反馈。
示例:
"集成测试必须用真实数据库,不能 mock"
Why: 之前 mock/prod 差异导致迁移在生产环境失败
How to apply: 所有集成测试都要验证
触发保存时机:
-
用户纠正你的做法("不要这样"、"停止做 X")
-
用户确认了非常规做法("对,就这样")—— 这种确认比较安静,需要主动捕捉
- project — 项目记忆
目的:记录项目中的临时性事实——谁在做什么、为什么做、什么时候截止。这些信息无法从代码或 git 历史中推导。
存储内容:
-
合并冻结时间(如"3 月 5 日后冻结非关键合并")
-
决策动机(如"重写认证中间件是由于合规要求,非技术债清理")
-
利益相关者的约束
存储结构:
事实/决策
Why: 动机(约束、截止日、利益相关者需求)
How to apply: 如何影响建议和决策
特点:这类记忆衰减很快——截止日过了、决策改了,记忆就过时了。所以 Why 特别重要,帮助判断记忆是否仍然有效。
示例:
"3月5日起合并冻结,移动端发版"
Why: 移动团队要切 release 分支
How to apply: 标记该日期后的非关键 PR 工作
- reference — 引用记忆
目的:存储外部系统中信息的位置指针,而不是信息本身。
存储内容:
-
Bug 追踪系统(如 "pipeline bugs 在 Linear 的 INGEST 项目中")
-
监控面板(如 "API 延迟看板在 grafana.internal/d/api-latency")
-
文档/设计稿位置
使用方式:当用户提到外部系统或可能需要查找外部信息时,通过这些指针定位。
示例:
"grafana.internal/d/api-latency 是 oncall 延迟看板"
→ 编辑请求路径代码时,提醒关注这个看板
例如:
---
name: feedback-terse-answering
description: "User wants direct, scoped answers without tangential explanation of related systems"
metadata:
node_type: memory
type: feedback
originSessionId: 4db367ce-68ea-4f29-a736-6cd97ccac909
---
Only answer exactly what the user asked — don't expand into related systems or other file types unless asked.
**Why:** User explicitly corrected me for explaining other memory file types when they only asked about `.claude/rules/*.md`.
**How to apply:** When asked about a specific mechanism/file type, limit the answer to that scope. Don't preemptively explain related systems.
在载入auto memory记忆md文件时,根据文件所在路径,分配具体的记忆类型:

记住哪些
不记住哪些
记忆what/why
| 类型 | 记住什么 | 原因 | 稳定性 | 示例 |
|---|---|---|---|---|
| user |
用户角色、知识背景、偏好、目标 | 让后续协作能"看人下菜碟",调整回答深度和视角 | 很高,基本不变 | 用户是后端转前端的工程师,解释 UI 概念时用后端类比帮助理解 |
| feedback | 行为纠正和认可,含 Why 和适用场景 | 避免重复犯错,也避免过度修正导致越来越保守 | 高,除非用户重新纠正 | 用户说"不要帮我自动提交 git",因为他在审查前需要自己过一遍 diff |
| project | 非代码上下文:谁在做什么、为什么、截止日期 | 帮助判断当前建议是否合理,知道什么该优先 | 低,状态变化快 | 本周五起冻结合入,移动端要切 release 分支,所以这两天优先修阻塞性 bug 而非做重构 |
| reference | 外部系统的位置指针(URL、项目名、频道名) | 外部信息动态变化,记住"在哪找"比记住具体内容更可靠 | 中,外部系统地址相对稳定 | 管道相关的 bug 都在 Linear 项目 "INGEST" 里追踪,下次排查管线问题先去那搜 |
不记忆what/why
prompt:
'## What NOT to save in memory',
'',
'- Code patterns, conventions, architecture, file paths, or project structure — these can be derived by reading the current project state.',
'- Git history, recent changes, or who-changed-what — `git log` / `git blame` are authoritative.',
'- Debugging solutions or fix recipes — the fix is in the code; the commit message has the context.',
'- Anything already documented in CLAUDE.md files.',
'- Ephemeral task details: in-progress work, temporary state, current conversation context.',
'',
// H2: explicit-save gate. Eval-validated (memory-prompt-iteration case 3,
// 0/2 → 3/3): prevents "save this week's PR list" → activity-log noise.
'These exclusions apply even when the user explicitly asks you to save. If they ask you to save a PR list or activity summary, ask what was *surprising* or *non-obvious* about it — that is the part worth keeping.',

载入记忆文件
启动时一次性加载
CLAUDE.md文件和 MEMORY.md都是一次性加载的。Claude code按照记忆的来源和作用域对这些记忆进行分类——标记每个记忆文件是从哪里加载的,控制其加载条件、处理方式和呈现形式。
src/utils/memory/types.ts:3-10:
export const MEMORY_TYPE_VALUES = [
'User', // ~/.claude/CLAUDE.md
'Project', // <项目目录>/CLAUDE.md
'Local', // <项目目录>/CLAUDE.local.md
'Managed', // /etc/claude-code/CLAUDE.md
'AutoMem', // ~/.claude/projects/<slug>/memory/MEMORY.md
'TeamMem', // ~/.claude/projects/<slug>/memory/team/MEMORY.md
] as const
渐进式载入
auto memory是渐进载入的,走相关性召回的路径。
首先搜索路径 ~/.claude/projects/<slug>/memory/,查找所有md文件(当然索引文件MEMORY.md除外),读取md内容,将以下字段type,filename,timestamp,description进行组合,每个文件占据一行,这些数据作为messages一部分,然后调用Sonnet模型,最多输出5个有用的记忆文件。
- [type] filename (timestamp): description
System prompt:
SELECT_MEMORIES_SYSTEM_PROMPT
`You are selecting memories that will be useful to Claude Code as it processes a user's query. You will be given the user's query and a list of available memory files with their filenames and descriptions.
Return a list of filenames for the memories that will clearly be useful to Claude Code as it processes the user's query (up to 5). Only include memories that you are certain will be helpful based on their name and description.
- If you are unsure if a memory will be useful in processing the user's query, then do not include it in your list. Be selective and discerning.
- If there are no memories in the list that would clearly be useful, feel free to return an empty list.
- If a list of recently-used tools is provided, do not select memories that are usage reference or API documentation for those tools (Claude Code is already exercising them). DO still select memories containing warnings, gotchas, or known issues about those tools — active use is exactly when those matter.
`
自动保存记忆md文件示例
---
name: feedback-terse-answering
description: "User wants direct, scoped answers without tangential explanation of related systems"
metadata:
node_type: memory
type: feedback
originSessionId: 4db367ce-68ea-4f29-a736-6cd97ccac909
---
Only answer exactly what the user asked — don't expand into related systems or other file types unless asked.
**Why:** User explicitly corrected me for explaining other memory file types when they only asked about `.claude/rules/*.md`.
**How to apply:** When asked about a specific mechanism/file type, limit the answer to that scope. Don't preemptively explain related systems.
选定最多5个记忆文件之后,读取文件内容,有双重上限限制:200行或4096字节,超限则截断(不是丢弃)。这部分注入到attachment,然后成为messages的一部分,如
<system-reminder>
Memory (saved 3 days ago): /Users/.../memory/user_preferences.md:
Open source + Python are the top two filters when searching for repos...
> This memory file was truncated (4096 byte limit). Use the FileReadTool
> tool to view the complete file at: /Users/.../memory/user_preferences.md
</system-reminder>
Tips:可以通过/context命令来查看当前会话中各个部分的具体比例:

Auto Dream(不完善)
2.1.169好像没有该功能,或者该功能默认关闭
在claude code启动情况下,异步执行。每24小时且新对话大于等于5轮才执行,不限最高token消耗上限。
`# Dream: Memory Consolidation
You are performing a dream — a reflective pass over your memory files. Synthesize what you've learned recently into durable, well-organized memories so that future sessions can orient quickly.
Memory directory: \`${memoryRoot}\`
${DIR_EXISTS_GUIDANCE}
Session transcripts: \`${transcriptDir}\` (large JSONL files — grep narrowly, don't read whole files)
---
## Phase 1 — Orient
- \`ls\` the memory directory to see what already exists
- Read \`${ENTRYPOINT_NAME}\` to understand the current index
- Skim existing topic files so you improve them rather than creating duplicates
- If \`logs/\` or \`sessions/\` subdirectories exist (assistant-mode layout), review recent entries there
## Phase 2 — Gather recent signal
Look for new information worth persisting. Sources in rough priority order:
1. **Daily logs** (\`logs/YYYY/MM/YYYY-MM-DD.md\`) if present — these are the append-only stream
2. **Existing memories that drifted** — facts that contradict something you see in the codebase now
3. **Transcript search** — if you need specific context (e.g., "what was the error message from yesterday's build failure?"), grep the JSONL transcripts for narrow terms:
\`grep -rn "<narrow term>" ${transcriptDir}/ --include="*.jsonl" | tail -50\`
Don't exhaustively read transcripts. Look only for things you already suspect matter.
## Phase 3 — Consolidate
For each thing worth remembering, write or update a memory file at the top level of the memory directory. Use the memory file format and type conventions from your system prompt's auto-memory section — it's the source of truth for what to save, how to structure it, and what NOT to save.
Focus on:
- Merging new signal into existing topic files rather than creating near-duplicates
- Converting relative dates ("yesterday", "last week") to absolute dates so they remain interpretable after time passes
- Deleting contradicted facts — if today's investigation disproves an old memory, fix it at the source
## Phase 4 — Prune and index
Update \`${ENTRYPOINT_NAME}\` so it stays under ${MAX_ENTRYPOINT_LINES} lines AND under ~25KB. It's an **index**, not a dump — each entry should be one line under ~150 characters: \`- [Title](file.md) — one-line hook\`. Never write memory content directly into it.
- Remove pointers to memories that are now stale, wrong, or superseded
- Demote verbose entries: if an index line is over ~200 chars, it's carrying content that belongs in the topic file — shorten the line, move the detail
- Add pointers to newly important memories
- Resolve contradictions — if two files disagree, fix the wrong one
---
Return a brief summary of what you consolidated, updated, or pruned. If nothing changed (memories are already tight), say so.${extra ? `\n\n## Additional context\n\n${extra}` : ''}
Phase 1 — Orient
目标:建立上下文,不遗漏也不重复。
设计手法:
-
用具体命令引导(ls、read _index.md、skim),避免 agent 空转
-
先看骨架(索引)再看血肉(topic 文件),自顶向下
-
特别的 fallback 路径:如果存在 logs/ 或 sessions/ 子目录(assistant-mode 布局),也纳入视野——兼容不同记忆布局
Phase 2 — Gather
目标:高效收集待整合的信号。
设计手法:
-
给出优先级排序的源列表(daily logs > drifted memories > transcript search,读取路径:~/.claude/projects/<file>/*.jsonl,比如/Users/yangfucai/.claude/projects/-Users-yangfucai-Downloads-code-claude-code-src/69897bb3-b866-4782-9507-df50f361417c.jsonl),引导 agent 先省力后费劲
-
对最昂贵的操作(transcript grep)给出具体命令模板 grep -rn "<narrow term>" ${transcriptDir}/ --include="*.jsonl" | tail -50,同时给出行为约束 "Don't exhaustively read transcripts. Look only for things you already suspect matter."
-
用一个具体例子说明何时才值得搜 transcript:"what was the error message from yesterday's build failure?"——引导 agent 只在有明确线索时才触发昂贵操作
Phase 3 — Consolidate
目标:写入高质量、不冗余的记忆文件。
设计手法:
-
不重复文件格式规范,而是引用上游来源:"Use the memory file format and type conventions from your system prompt's auto-memory section"——避免两份文档不同步
-
三个具体的"反模式"指导:merge 而非 duplicate、相对日期转绝对日期、矛盾直接修复——这些都是记忆系统容易腐化的常见问题
-
隐含的质量标准:写出来的记忆要经得起"时间流逝后依然可解读"
Phase 4 — Prune & Index
目标:索引保持小而精。
设计手法:
-
硬性指标:≤25KB 且 ≤200 行——可度量、可验证
-
格式约束:- [Title](file.md) — one-line hook(~150 字符)——明确模板
-
反面清单:移除 stale/wrong/superseded 的指针、缩短过长条目、解决矛盾——每一步都是减熵操作
-
界限声明:"an index, not a dump" "Never write memory content directly into it"——防止索引膨胀为核心记忆
多Agent编排
Claude code提供了三种多智能体编排的方案
-
Coordinator Mode
-
Agent Teams
-
Subagents
| 机制 | 一句话 |
|---|---|
| SubAgent | 主 agent 通过 Agent 工具 spawn 子 agent,子 agent 完成任务后返回结果 |
| Team | 多个 agent 组成团队,通过 mailbox 互相通信、协同工作,有 leader/worker 角色 |
| Coordinator Mode | 主 agent 退化成一个"指挥者",工具集只剩下 Agent/SendMessage/TaskStop,所有实际工作都委托给 worker |
三者的联系(共同基础)
三者共享的底层基础设施如下:
-
都最终进入 runAgent() 与 query() 微回合循环:常规 Subagent 的 runAgent(runAgent.ts:248-860)内部调 query();in-process teammate 复用常规 Subagent 的核心 API 基础设施(inProcessRunner.ts:1175 for await (const message of runAgent({...})) 共享 query() 内部的流式 API 调用、tool execution、permission check 逻辑);Coordinator worker spawn 最终调用 runAgent(AgentTool.tsx:736-738),复用与主线程相同的 query 引擎。
-
worker 类 subagent 都通过 LocalAgentTask 进入统一任务状态机:常规 async agent 和 coordinator worker 都注册为 LocalAgentTaskState(LocalAgentTask.tsx:466-515),复用 Task 接口(kill/registerTask);notifyOnCompletion 构造的 <task-notification> XML 机制,与常规 background agent 一致。
-
都支持 worktree/cwd/remote 隔离:常规 Subagent 支持 worktree(createAgentWorktree)、cwd 覆盖(runWithCwdOverride)、remote 隔离(teleportToRemote);Coordinator worker 同样支持 worktree/cwd/remote(AgentTool.tsx:590-593,640-641);in-process teammate 共享 leader 文件系统,不自动创建 worktree/cwd 隔离。
-
清理逻辑都覆盖 MCP、hooks、prompt cache 跟踪、文件状态缓存:registerCleanup 在会话退出时 kill agent(LocalAgentTask.tsx:466-515);cloneFileStateCache/createFileStateCacheWithSizeLimit 文件状态缓存克隆(fileStateCache.ts);cleanupWorktreeIfNeeded 按变更检测决定保留或 removeAgentWorktree(AgentTool.tsx:644-685)。
-
都通过 task-notification 通知父级(除 teammate 用 mailbox 双向):enqueueAgentNotification(LocalAgentTask.tsx:197-262)构造 <task-notification> XML 并入队;字段含 task-id/tool-use-id/output-file/status/summary/result/usage/worktree(constants/xml.ts:28-38);notified 标志原子检查防止重复(LocalAgentTask.tsx:228-237);abortSpeculation 丢弃推测结果避免引用 stale task output(:245)。
-
都复用 createSubagentContext() 创建 subagent 的 ToolUseContext:createSubagentContext(src/utils/forkedAgent.ts:345)创建 agentToolUseContext;所有路径共用此 helper;fork path(useExactTools=true)让 child 继承 parent 的 system prompt、exact tool array、thinkingConfig、isNonInteractiveSession,产生字节一致的 API request prefix 以命中 prompt cache。
-
都复用 resolveAgentTools 工具集解析:resolveAgentTools(agentToolUtils.ts:122-225)负责实际工具池解析,过滤 disallowedTools;filterToolsForAgent(agentToolUtils.ts:70-116)过滤掉 ALL_AGENT_DISALLOWED_TOOLS/CUSTOM_AGENT_DISALLOWED_TOOLS/非 ASYNC_AGENT_ALLOWED_TOOLS 的工具;MCP 工具始终通过;ExitPlanMode 在 plan 模式下放行。
-
都复用 Task 框架:registerTask/evictTerminalTask/updateTaskState(task/framework.ts)+ Task 接口(Task.js)+ TaskStateBase;emitTaskTerminatedSdk/evictTaskOutput SDK 事件与任务输出清理。
-
都复用 AbortController 生命周期管理:createAbortController(abortController.ts);registerCleanup 优雅 shutdown。
-
都复用 compactConversation/autoCompactThreshold 长对话压缩:in-process teammate 的 allMessages 超过 getAutoCompactThreshold 时触发 compactConversation(inProcessRunner.ts:1072-1126);常规 Subagent 和 coordinator worker 也复用同一压缩机制。
-
都复用 CacheSafeParams + onCacheSafeParams:CacheSafeParams + onCacheSafeParams(src/utils/forkedAgent.ts)暴露 cache-safe 参数供 background summarization fork agent 对话;fork path 和 workflow 都用。
-
都复用 getTaskOutputPath/initTaskOutputAsSymlink:getTaskOutputPath/initTaskOutputAsSymlink(src/utils/task/diskOutput.ts)任务输出文件路径计算与 symlink 初始化。
-
都复用 emitTaskProgress:emitTaskProgress(src/utils/task/sdkProgress.js)向 SDK consumer 发送 task_progress 事件。
-
都复用 Perfetto tracing:registerPerfettoAgent/unregisterPerfettoAgent 层级可视化。
三者的核心区别
三种 subagent 编排方式概览
常规 Subagent:由 Agent 工具触发的最基础子 Agent 委托机制。主 Agent 通过 AgentTool.tsx:call() 调用 spawn 一个子 Agent,子 Agent 拥有独立的 system prompt、工具集、权限上下文和 cwd,同步模式下共享 parent 的 abortController/setAppState。异步路径通过 LocalAgentTask 注册后台任务,完成后通过 enqueueAgentNotification 构造 task-notification XML 消息入队,以 user-role 消息注入父 Agent 的后续 turn。支持 fork(继承父对话全量上下文)、worktree/cwd/remote 三种隔离,前台/后台无缝切换。
Teammate/Swarm:多 Agent 长期协作机制。与常规 Subagent(一次性、fork 父对话、同步等待)不同,in-process teammate 在同一 Node.js 进程内通过 AsyncLocalStorage 隔离上下文(runWithTeammateContext + runWithAgentContext 双层包裹),长期存活,通过文件式 mailbox(~/.claude/teams/{team}/inboxes/{agent}.json)双向通信,通过共享 task list 协调工作。核心入口是 AgentTool.call 中 team_name + name 参数触发 spawnTeammate → handleSpawnInProcess → spawnInProcessTeammate + startInProcessTeammate。实际 agent 执行复用常规 Subagent 的 runAgent + query 核心 API。
Coordinator 模式:一条将会话级主线程降级为"纯编排器"的会话级模式。当 CLAUDE_CODE_COORDINATOR_MODE 环境变量为真且 feature('COORDINATOR_MODE') 编译门控开启时,主线程的系统提示词被 getCoordinatorSystemPrompt() 替换为编排范式,工具集被 applyCoordinatorToolFilter 硬编码过滤为仅 AGENT_TOOL/SEND_MESSAGE/TASK_STOP/SYNTHETIC_OUTPUT(外加 PR activity 后缀工具)。主线程不再直接执行 Bash/Edit/Read,而是通过 AgentTool spawn 异步 worker subagent,worker 完成后以 <task-notification> user-role 消息回注 coordinator,coordinator 可继续 worker 或 spawn 新 worker。
触发方式对比
生命周期对比
隔离模式对比
通信机制对比
权限处理对比
上下文继承对比
工具集解析对比
适用场景对比
Loop Engineering

成本。loop 一旦跑起来,就不是问一次答一次的计费方式了。它会反复读上下文、反复试错、反复验证,有时还拉好几个 Agent 一起干,token 消耗可能非常大。如果任务不值得反复跑、没有稳定的反馈,或者只是一次性的小事,loop 很可能还没帮你省时间,先把成本烧光了。
边界。loop 能替你推进流程,但不能替你担责任。AI 说“完成了”,不等于真没问题;说“测试通过了”,也不等于业务逻辑对。一个没人盯着的 loop,也会没人盯着地犯错。还有一个更隐蔽的代价:AI 干得越多,人越容易不再去看过程,时间一长,代码越堆越多,自己真正理解的却越来越少。
现阶段看没有什么好的场景来使用loop engineering,往后放。
TODO
-
权限管理
-
slash命令运行规则
-
流式响应和流式处理
-
通信细节
-
运行模式
-
钩子(Hook)系统
-
plan模式
-
feature属性控制功能
参考资料
上下文窗口探索:https://code.claude.com/docs/zh-CN/context-window
https://github.com/jarmuine/claude-code.git
https://github.com/claude-code-best/claude-code.git
https://github.com/chengjl19/awesome-agent-harness-notes/tree/main
https://code.claude.com/docs/zh-CN/quickstart
https://openedclaude.github.io/claude-reviews-claude/zh-CN/overview
https://openedclaude.github.io/claude-reviews-claude/zh-CN/overview
https://xavierzhang2002.github.io/agent-state-management/claude-code/context/
https://github.com/Piebald-AI/claude-code-system-prompts.git
https://github.com/anomalyco/opencode.git
https://xavierzhang2002.github.io/agent-state-management/claude-code/memory/
https://github.com/VILA-Lab/Dive-into-Claude-Code
https://openedclaude.github.io/claude-reviews-claude/zh-CN/chapters/09-session-persistence
https://ccb.agent-aura.top/docs/context/project-memory#记忆注入-system-prompt-的链路

浙公网安备 33010602011771号