Coding Agent 运行时机制
端到端逆向与可复现架构地图
以 Claude Code 为核心案例,系统拆解现代编码代理从用户输入到工具执行再到下一轮请求的完整生命周期,建立可挂靠的心智模型与可落地的工程参考。
摘要与研究框架
本报告的目标是回答一个工程问题:当用户在 Claude Code 中输入一句需求后,从客户端到模型 API 之间完整发生了什么,以及为什么这种设计能够在生产环境中胜出。
核心论点
现代 Coding Agent 的运行时本质上是一个无状态模型的 有状态外壳。模型本身没有任何记忆——每次 API 调用都是独立的、从头开始的推理过程。所谓"记忆"、"持续任务能力"、"上下文感知",全部由客户端侧的 harness(外壳程序)通过精巧的消息数组维护、工具结果回灌和上下文压缩来实现。
这个外壳的核心控制流可以归结为一个极简循环:while (tool_use) { execute; append; recall }。该循环之所以能在生产环境中胜过复杂 DAG 编排和多 Agent 框架,原因不在于循环本身的精妙,而在于围绕这个循环构建的六个工程子系统的成熟度:分层 Prompt Caching、结构化工具系统、六级上下文压缩、流式工具执行、子代理隔离与缓存共享、以及纵深权限防御。
Agent 的"智能"不在循环的复杂度,而在上下文工程的质量。同一个 while(true) 循环,配上粗糙的工具描述和混乱的上下文管理,就是一个难用的玩具;配上精确的 schema、稳定的缓存前缀、分级的压缩策略和流式工具执行,就是生产级工具。差异的根因在工程而非算法。
研究方法概述
本报告综合三类信息源:(1)对 Claude Code v2.0.62–v2.1.88 版本通过 npm source map 泄露的 TypeScript 源码的社区逆向分析,涵盖 1,884 个文件、约 132K 行代码[源码逆向, 高];(2)Anthropic 官方 API 文档中关于 Messages API、Tool Use、Prompt Caching、Extended Thinking、Agent SDK 的技术规范[官方文档, 高];(3)跨 Agent 架构的学术分析与工程对比[学术分析, 中]。对每一关键论断标注来源类型与置信度,对推测性结论明确标注。
报告结构
第三章建立总体心智模型,回答"系统由什么组成、各部分如何协作"。第四章是核心章节,按真实时间顺序拆解单次用户输入的完整生命周期,共十个阶段,每个阶段配伪代码与设计意图分析。第五至第八章对四个关键子系统进行深挖。第九章横向对比 Cursor、Codex CLI、Aider 等同类系统。第十章分析体验差异的工程根因。第十一章提炼可迁移的设计原则。第十二章标注开放问题。
研究范围、资料来源与置信度方法
2.1 资料来源分层
本报告的所有技术论断基于以下分层信息源,每一层具有不同的可验证性和置信度:
| 层级 | 来源类型 | 代表材料 | 置信度 | 用途 |
|---|---|---|---|---|
| L1 | 官方文档 | docs.anthropic.com 的 Messages API、Tool Use、Prompt Caching、Extended Thinking、Agent SDK 文档 | 高 | API 行为规范、参数语义、定价模型 |
| L2 | 源码逆向 | Claude Code v2.1.88 泄露源码(1,884 文件);社区逆向分析仓库(injekt/claude-code-reverse、childswong/claude-source-leaked、CorrineTan/cracking-claude-code-structure) | 高 | 内部架构、控制流、工具系统、缓存策略 |
| L3 | 运行时观测 | JSONL 转录文件分析、MITM 抓包、API 响应日志 | 高 | 验证源码结论、补充实际行为细节 |
| L4 | 学术分析 | arXiv:2604.03515(13 个开源编码代理源码分类学)、arXiv:2603.05344(终端编码代理架构) | 中 | 跨 Agent 对比、架构模式归纳 |
| L5 | 合理推断 | 基于公开行为的工程推理、跨源交叉验证 | 低 | 填补信息空白、预测未公开行为 |
2.2 置信度标注规则
本报告对每一关键论断在行内以 [来源类型, 置信度] 格式标注。置信度判定标准:
- 高:有源码证据或官方文档直接支撑,或多个独立来源交叉验证一致。
- 中:有间接证据或单一来源支撑,逻辑自洽但缺乏直接验证。
- 低:基于工程推理或类比推断,无直接证据,明确标注为推测。
2.3 覆盖范围与局限
本报告以 Claude Code 为核心案例,因为它是目前公开逆向材料最丰富的编码 Agent。报告同时覆盖 Cursor、OpenAI Codex CLI、Aider、Continue.dev 以及 SWE-agent、OpenHands 等开源框架,用于对比性分析。但需注意以下局限:
- Claude Code 为闭源产品,逆向分析基于特定版本(v2.0.62–v2.1.88)的泄露源码,后续版本可能已有架构变更。
- 部分内部 Feature Flags(如 KAIROS、COORDINATOR_MODE)的实际行为仅能从源码结构推断,缺乏运行时验证。
- Anthropic 的服务端实现(如 Prompt Cache 的内部存储机制)属于基础设施层面,公开信息有限。
- 本报告不涉及模型训练、权重或推理引擎层面的技术细节。
本报告基于截至 2026 年 8 月的公开资料。Claude Code 和 Anthropic API 处于快速迭代中,部分技术细节(如 Feature Flag 代号、具体阈值参数)可能已发生变化。报告中的架构模式和设计原则部分具有更强的时效稳定性。
总体架构与心智模型
在进入逐阶段拆解之前,先建立一个可以挂靠后续所有细节的心智模型。这个模型的核心是理解"谁有状态,谁没有状态"。
3.1 职责划分:客户端 vs 模型服务
Claude Code 的架构可以清晰地划分为两个职责域。模型服务(Anthropic API 侧)是一个纯粹的无状态推理引擎——接收一个 HTTP 请求,返回一个 HTTP 响应,不保留任何跨请求状态。客户端(CLI 或 IDE 插件)承担了全部的有状态管理工作:维护对话历史、管理工具注册表、执行权限检查、处理上下文压缩、协调子代理派生。[源码逆向, 高]
这种划分的工程含义是:Agent 的"记忆"不是模型的属性,而是 harness 的属性。如果你重启客户端但保留了 JSONL 会话文件,Agent 可以恢复全部上下文继续工作;如果你切换了底层模型但保持 harness 不变,Agent 的行为模式基本不变(工具调用方式、上下文管理策略都由 harness 控制)。[合理推断, 中]
3.2 为什么极简循环能胜过复杂编排
一个自然的问题是:既然 Agent 的核心只是一个 while(true) 循环,为什么不用更复杂的 DAG(有向无环图)编排或多 Agent 协作框架?学术研究提供了部分答案:对 13 个开源编码代理的源码分析发现,13 个中有 7 个使用顺序 ReAct 循环作为主要控制结构,只有 2 个使用树搜索(MCTS),1 个使用固定流水线(无反馈循环)。[学术分析, 中]
极简循环胜出的工程根因有三层:
第一层:状态最小化降低故障面
DAG 编排需要维护图状态——节点完成情况、依赖关系、分支决策、回滚点。每多一个状态维度,就多一类故障模式。Claude Code 的循环每轮只做一件事:发送消息、解析响应、执行工具、追加结果。状态只有一个维度:消息数组的长度。如果循环在任何一轮崩溃,恢复策略极其简单——重新发送最后一条消息即可。[合理推断, 中]
第二层:模型能力外溢到工具描述
复杂编排框架试图在 harness 层面做决策("先搜索再编辑还是先编辑再测试"),但模型的 in-context reasoning 能力已经足够强,可以在阅读工具描述后自主做出这些决策。harness 只需要提供足够好的工具和足够清晰的上下文,让模型自己决定下一步。这就是为什么 Claude Code 的工具描述写得极其详细——每个工具的 description 不仅是 API 文档,更是行为指导。[源码逆向, 高]
第三层:缓存友好性
这是一个容易被忽视但极其关键的工程因素。Prompt Caching 要求请求前缀字节相同。顺序循环天然产生单调增长的消息数组——每轮只追加,不修改历史。这意味着除最后一轮外,所有历史消息都可以命中缓存。DAG 编排可能需要在不同分支间切换上下文,每次切换都破坏前缀稳定性,导致缓存失效。Claude Code 的设计选择(顺序循环 + 分层缓存 + 子代理隔离)是一个整体性的缓存优化策略。[源码逆向, 高]
极简循环不是"简单",而是"克制"。它主动放弃了 harness 层面的复杂决策能力,将决策权下放给模型,换取了状态简单性、恢复容易性和缓存友好性。这是一个经过深思熟虑的工程权衡,而非能力不足的妥协。
3.3 双层异步生成器架构
Claude Code 的 Agent Loop 在实现上不是单个函数,而是双层异步生成器(async function*)的嵌套结构。外层 QueryEngine(约 1,295 行)负责会话生命周期管理——持久化、预算控制、用户中断;内层 query()(约 1,729 行)负责单次迭代——API 调用、工具编排、错误恢复。[源码逆向, 高]
这种分层的设计意图是职责隔离:外层不关心工具怎么执行,内层不关心会话怎么持久化。当内层循环遇到错误时,它有 7 条命名恢复路径(如 collapse_drain_retry、reactive_compact_retry、max_output_tokens_escalate),每条路径针对特定错误类型采取不同策略。关键设计决策是:可恢复错误永远不暴露给调用者——系统自动用不同策略重试,用户感知不到中间的失败。[源码逆向, 高]
端到端请求生命周期
本章按真实时间顺序,逐步拆解从用户按下回车到下一轮 API 请求发出的完整过程。共十个阶段,每个阶段说明输入、输出与设计意图。
阶段 1:用户输入接收与预处理
用户在 CLI 中输入文本后,harness 首先进行预处理,而非直接送入 API。预处理包括四个子步骤:[源码逆向, 高]
- Slash Command 解析:如果输入以
/开头,走命令路径而非对话路径。例如/compact触发上下文压缩,/clear清空会话,/model切换模型。命令不进入消息数组。 - 附件处理:用户可以通过
@语法附加文件、图片或 URL。附件内容被读取并转换为 content blocks(文本、image base64、或通过 WebFetch 获取的网页内容),与用户文本拼接为一条 user 消息。 - 权限模式检查:当前权限模式(default / plan / auto / bypass)影响后续工具执行策略,但不影响消息构造。在此阶段记录权限模式供工具执行层使用。
- UserPromptSubmit Hook 执行:如果配置了
UserPromptSubmithook,用户输入在进入消息数组前先经过 hook 处理。hook 可以修改输入内容或阻止提交。
预处理完成后,用户输入被包装为一条 user 消息并追加到消息数组。此时消息数组的结构为:[...历史消息, {role: "user", content: [文本块 + 附件块]}]。
阶段 2:System Prompt 的分层组装
这是整个生命周期中最复杂的阶段之一,也是 Prompt Caching 能否高效运作的基础。System Prompt 的组装遵循五层优先级覆盖机制和静态/动态区域分割。[源码逆向, 高]
五层优先级
| 优先级 | 来源 | 触发条件 |
|---|---|---|
| 0 (最高) | Override system prompt | loop mode、测试模式 |
| 1 | Coordinator system prompt | 多 worker 编排模式 |
| 2 | Agent system prompt | 子代理定义(由父代理传递) |
| 3 | Custom system prompt | --system-prompt 命令行参数 |
| 4 (最低) | Default system prompt | 标准 Claude Code 运行 |
默认提示词的九大组成部分
在标准运行模式下,默认系统提示词由 9 个 section 组成,每个 section 有明确的功能定位:[源码逆向, 高]
| Section | 内容 | 缓存区域 |
|---|---|---|
| Identity | "You are an interactive agent..." + 网络安全指令 | 静态 |
| System Rules | 工具执行规则、权限模式说明、system-reminder 标签语义 | 静态 |
| Doing Tasks | 先读后改、避免过度工程化、OWASP Top 10 防护 | 静态 |
| Careful Actions | 破坏性操作需确认(rm -rf、force push 等) | 静态 |
| Using Tools | 专用工具优先于 Bash、并行工具调用指导 | 静态 |
| Tone & Style | 无 emoji、file_path:line_number 引用格式 | 静态 |
| Output Efficiency | "一句话能说清的不用三句" | 静态 |
| Dynamic Boundary | __SYSTEM_PROMPT_DYNAMIC_BOUNDARY__ 标记 | 分界线 |
| Environment | CWD、平台、模型名、知识截止日期、git 状态 | 动态 |
静态/动态边界的工程意义
__SYSTEM_PROMPT_DYNAMIC_BOUNDARY__ 是整个缓存策略的核心分隔符。边界之前的内容对所有用户字节相同——不包含任何用户特定信息(无 CWD、无 git 状态、无日期)。这使得该区域可以使用 scope: "global" 进行全局缓存,即所有 Claude Code 用户的请求共享同一份缓存条目。[源码逆向, 高]
边界之后的内容是会话特定的:工作目录、git 分支、平台信息、模型名称、用户语言偏好、MCP 服务器指令等。这些内容无法跨会话共享缓存,但可以在同一会话内通过 scope: "org" 缓存。
如果在静态区域中误入了会话特定信息(如当前时间戳),全局缓存将永远无法命中。Claude Code 通过严格的代码审查和 buildEffectiveSystemPrompt() 函数的结构化构建来防止此类泄漏。日期等动态信息被刻意放在动态区域,通过 system-reminder 注入消息数组而非系统提示词。[源码逆向, 高]
API 请求中的 System Prompt 结构
在发送给 API 的请求中,system 字段不是字符串,而是 ContentBlock 数组。Mode 1(无 MCP 工具)的结构如下:[源码逆向, 高]
[
{ "type": "text", "text": "" },
{ "type": "text", "text": "" },
{ "type": "text", "text": "<静态内容>",
"cache_control": { "type": "ephemeral", "scope": "global" } },
{ "type": "text", "text": "<动态内容>" }
]
第三个块携带 cache_control,标记缓存断点。API 服务端在该断点处计算前缀哈希,如果匹配之前见过的请求,直接复用已计算的 KV 注意力状态。缓存读取的 token 按基础输入价格的 10% 计费,而正常输入 token 按 100% 计费——这是一个 10 倍的成本差异。[官方文档, 高]
阶段 3:Tools 列表的构建
工具列表的构建经过三层管道:编译时剔除、运行时过滤、池组装。每一层的设计意图都是减少不必要的 token 开销并维护缓存稳定性。[源码逆向, 高]
三层管道
统一工具接口
所有工具(66+ 个)共享统一的 TypeScript 接口,这使得工具注册、权限检查和执行管道可以统一处理:[源码逆向, 高]
type Tool<Input, Output> = {
name: string
description: string
inputSchema: ToolInputJSONSchema // Zod 验证
isReadOnly(): boolean // 只读工具可并行
isConcurrencySafe(): boolean // 并发安全标记
validateInput(input): ValidationResult
checkPermissions(input, context): PermissionResult
execute(input, context): AsyncGenerator<Output>
}
延迟工具加载(Deferred Loading)
66+ 个工具的完整 JSON Schema 如果全部加载到每次请求中,会消耗大量 token。Claude Code 采用延迟加载策略:初始请求只发送延迟工具的 name 和 description(无 input_schema),完整 schema 通过 ToolSearchTool 按需加载。[源码逆向, 高]
这个设计的关键权衡是:延迟加载减少了初始 token 开销,但需要额外一轮 API 调用来获取 schema。对于高频使用的核心工具(Bash、Read、Write、Edit、Glob、Grep),完整 schema 始终包含在请求中;对于低频工具(Cron、Sleep、Monitor 等),延迟加载节省的 token 远大于偶尔的额外 API 调用成本。
工具 Schema 的缓存稳定性
工具 Schema 在会话级别进行稳定缓存。每个工具的基础 schema(name、description、input_schema)在会话中只计算一次并缓存,防止 feature flag 翻转改变工具描述字节。每请求的 overlay(延迟加载标记、cache_control 标记)在缓存基础上应用而不修改基础 schema。这确保了工具定义部分的前缀字节在会话内完全稳定,最大化缓存命中率。[源码逆向, 高]
阶段 4:Messages 历史的维护
消息历史是 Agent"记忆"的物理载体。Claude Code 对消息数组的维护有几个关键设计:[源码逆向, 高]
Role 交替规则
Anthropic API 要求消息数组的 role 严格交替:user → assistant → user → assistant ...。每轮循环恰好追加 2 条消息:1 条 assistant 消息(模型响应,包含 text + tool_use blocks)和 1 条 user 消息(包含所有 tool_result blocks)。这保证了消息数组的结构始终合法。
system-reminder 注入
Claude Code 使用 <system-reminder> XML 标签向消息数组注入系统元数据,而不修改 system prompt。这些标签出现在工具结果或用户消息中,但内容来自系统而非用户。设计意图是防止模型混淆系统元数据与用户输入:[源码逆向, 高]
<system-reminder> Today's date is 2026-08-08. The following skills are available: commit, review-pr, ... </system-reminder>
CLAUDE.md 注入机制
CLAUDE.md 是用户向 Agent 提供项目特定指令的机制。其加载和注入过程如下:[源码逆向, 高]
- 支持三层:项目级(
./CLAUDE.md)、用户级(~/.claude/CLAUDE.md)、企业级(系统管理的配置)。 - 从 CWD 向上遍历目录树发现所有 CLAUDE.md 文件,按层级合并。
- 支持
@./relative、@~/home、@/absolute三种 include 语法,最大深度 5 层。 .claude/rules/*.md自动加载为规则文件。- 会话开始时读取一次并缓存——会话中编辑 CLAUDE.md 不会立即生效,需要
/clear或/compact后重新加载。这是一个有意的设计选择:避免每次请求都读取文件系统,同时保持缓存稳定性。
CLAUDE.md 内容通过 getUserContext() 函数加载,注入到消息数组的第一条 user 消息中作为 system-reminder。注意:它不是放在 system prompt 中,因为不同项目的 CLAUDE.md 内容不同,放在 system prompt 中会破坏全局缓存。[源码逆向, 高]
环境快照的 记忆化
Git 状态(分支、最近 5 次提交、用户名)在会话开始时通过并行 git 命令获取一次,然后记忆化(memoize)——整个会话中复用这个快照。当 systemPromptInjection 变更时通过 getUserContext.cache.clear?.() 清除缓存。这个设计避免了每轮循环都执行 git 命令的开销,同时保证了环境信息在会话内的一致性。[源码逆向, 高]
阶段 5:最终 HTTP/JSON 请求的组装
经过前四个阶段的准备,harness 现在拥有了构建 API 请求所需的全部材料。最终的 HTTP 请求结构如下:[源码逆向, 高]
请求参数
POST https://api.anthropic.com/v1/messages
{
"model": "claude-sonnet-4-20250514",
"max_tokens": 8000,
"messages": [
{ "role": "user", "content": [
{ "type": "text", "text": "" },
{ "type": "text", "text": "" }
]},
{ "role": "assistant", "content": [
{ "type": "text", "text": "I'll help you with that." },
{ "type": "tool_use", "id": "toolu_001", "name": "Read",
"input": { "file_path": "/src/main.ts" } }
]},
{ "role": "user", "content": [
{ "type": "tool_result", "tool_use_id": "toolu_001",
"content": "file contents here..." }
]}
// ... more turns
],
"system": [
{ "type": "text", "text": "" },
{ "type": "text", "text": "" },
{ "type": "text", "text": "",
"cache_control": { "type": "ephemeral", "scope": "global" } },
{ "type": "text", "text": "" }
],
"tools": [
{ "name": "Bash", "description": "...",
"input_schema": { "type": "object", "properties": {...} },
"cache_control": { "type": "ephemeral" } },
{ "name": "Read", "description": "...", "input_schema": {...} },
// ... more tools
],
"metadata": { "user_id": "" },
"thinking": { "type": "adaptive" },
"temperature": 1.0,
"stream": true,
"betas": ["oauth-2025-04-20", "interleaved-thinking-2025-05-14"]
}
请求头
x-api-key:anthropic-version: 2023-06-01 anthropic-beta: oauth-2025-04-20, interleaved-thinking-2025-05-14 x-app: cli User-Agent: claude-code/2.1.88 content-type: application/json
各字段语义详解
| 字段 | 语义 | 设计意图 |
|---|---|---|
model | 模型 ID | 会话内不变以保持缓存稳定 |
max_tokens | 最大输出 token(默认 8000) | 重试时升级到 64000 |
messages | 对话历史 | 每轮追加 2 条,前缀不变 |
system | 系统提示词(数组形式) | 分层缓存:global + org |
tools | 工具定义列表 | 末尾设 cache_control |
metadata.user_id | 用户标识(哈希) | 欺诈检测,不影响推理 |
thinking | 扩展思考配置 | adaptive 模式自动调节深度 |
temperature | 采样温度(1.0) | 较高温度增加工具调用多样性 |
stream | 流式响应 | 启用流式工具执行 |
betas | Beta 特性头 | 启用交错思考等实验特性 |
客户端设置 10 分钟超时(timeout: 600000)和自动重试。自定义请求头包括 x-claude-remote-container-id(远程容器标识)、x-claude-remote-session-id(会话标识)和 x-anthropic-additional-protection(额外保护标志)。[源码逆向, 高]
阶段 6:模型响应的解析
API 返回 SSE(Server-Sent Events)流式响应。harness 需要实时解析流式事件,同时累积完整响应内容。解析过程涉及多种事件类型和内容块类型。[官方文档, 高]
SSE 事件流顺序
stop_reason 与循环控制
| stop_reason | 含义 | harness 行为 |
|---|---|---|
end_turn | 模型自然结束 | 退出循环,返回给用户 |
tool_use | 需要执行工具 | 执行工具,追加结果,继续循环 |
max_tokens | 达到输出上限 | 触发 max_output_tokens_escalate 恢复路径,升级到 64K 限制重试 |
stop_sequence | 遇到停止序列 | 根据上下文处理 |
pause_turn | 上下文窗口限制暂停 | 多回合处理,发送 continuation 请求 |
thinking 块的处理
当启用扩展思考时,响应中会包含 thinking 类型的内容块。每个 thinking 块包含 thinking(思考内容)和 signature(加密签名)两个字段。在工具使用循环中,必须将 thinking 块完整未修改地传回 API,以维持推理连续性。修改 thinking 块会返回错误。[官方文档, 高]
关键细节:先前 assistant 回合的 thinking 块会被 API 自动剥离,不计入输入 token。只有当前回合的 thinking 计入输入 token。这意味着 thinking 的 token 成本是单轮的,不会随对话长度累积。[官方文档, 高]
Token 使用量追踪
每个响应的 message_delta 事件包含累积的 usage 数据。harness 追踪四个关键指标:[官方文档, 高]
input_tokens:未缓存的输入 token(最后缓存断点之后的新增 token)cache_creation_input_tokens:本次写入缓存的 tokencache_read_input_tokens:本次从缓存读取的 token(按 10% 计费)output_tokens:生成的输出 token
总输入 token = cache_read + cache_creation + input_tokens。在缓存命中良好的会话中,cache_read 通常占总输入的 80-95%,显著降低成本和延迟。
阶段 7:工具执行层
当 stop_reason 为 tool_use 时,harness 进入工具执行阶段。这是 Claude Code 最复杂的子系统之一,涉及权限门控、并行执行、结果截断和错误处理。[源码逆向, 高]
流式工具执行——关键创新
Claude Code 的一个重要创新是流式工具执行:工具不需要等待 API 响应完全结束就开始执行。StreamingToolExecutor 从 API 流中解析出完整的 tool_use 块后立即开始执行,将约 1 秒的工具启动延迟隐藏在 5-30 秒的 API 生成窗口内。[源码逆向, 高]
八阶段执行管道
每个工具调用经过八阶段管道:[源码逆向, 高]
- 工具查找:按 name/alias 在工具池中查找。
- 输入验证:Zod schema 验证 + 业务逻辑验证。
- 并行启动:Pre-Tool Hook 和 Bash 分类器并行启动。
- 权限检查:7 源规则级联匹配(session → cliArg → local → user → project → policy → flags)。
- 工具执行:带进度回调的异步执行。
- 结果处理:大于 30KB 的结果写入磁盘,消息中只保留引用。
- Post-Tool Hook:后处理 hook 执行。
- 消息发送:tool_result 追加到消息数组。
结果截断策略
工具结果可能非常大(如读取一个大文件或执行一个输出冗长的命令)。Claude Code 的截断策略是:大于 50K 字符的结果保留头部 + 尾部,中间用省略标记替代。这比简单截断更好,因为文件的开头和结尾通常包含最重要的结构信息(导入声明、函数签名、返回语句)。[源码逆向, 高]
错误处理
工具执行失败时,错误信息不是作为异常抛出,而是作为 tool_result 的内容返回给模型。这让模型可以自行决定如何处理错误——重试、换一种方法、或向用户报告。这是 Agent 循环的一个关键设计:错误是模型推理的一部分,而不是控制流的中断。[合理推断, 高]
阶段 8:tool_result 回灌与循环继续
工具执行完成后,所有 tool_result 被包装为一条 user 消息追加到消息数组。此时消息数组的结构为:[源码逆向, 高]
[
...之前的历史,
{ "role": "assistant", "content": [
{ "type": "text", "text": "Let me read that file." },
{ "type": "tool_use", "id": "toolu_002", "name": "Read",
"input": { "file_path": "/src/main.ts" } }
]},
{ "role": "user", "content": [
{ "type": "tool_result", "tool_use_id": "toolu_002",
"content": "export function main() { ... }" }
]}
]
然后循环回到阶段 5,用更新后的消息数组发起新的 API 请求。模型在新的请求中看到工具结果,决定下一步操作。这个循环持续到 stop_reason 为 end_turn(模型认为任务完成)或用户中断。
每轮循环追加 2 条消息,但不修改之前的消息。这意味着之前的所有消息(包括 system prompt 和 tools)都可以命中缓存。只有新增的 2 条消息需要完整计费。在长会话中,这可以将每轮的实际计费输入从数万 token 降低到数千 token。
阶段 9:子 Agent 的派生与隔离
当模型调用 AgentTool(即 Task 工具)时,harness 会派生一个子 Agent。子 Agent 有自己的消息数组、自己的工具池和自己的系统提示词,但与父 Agent 隔离运行。子 Agent 完成后,将结果摘要返回给父 Agent。[源码逆向, 高]
子 Agent 类型
| 类型 | 专长 | 可用工具 | 禁用工具 |
|---|---|---|---|
| general_purpose_task | 通用研究、多步骤任务 | 全部 | 无 |
| Explore | 代码库探索(3档深度) | Glob, Grep, Read | Edit, Write |
| Plan | 架构规划 | Glob, Grep, Read | Edit, Write, Task |
| claude-code-guide | 文档查询 | Read, WebFetch, WebSearch | Edit, Write, Bash |
Fork Agent 的缓存共享机制
子 Agent 的派生不是简单的"新建一个 Agent"。Claude Code 通过三种机制让子 Agent 共享父 Agent 的 prompt cache:[源码逆向, 高]
- 字节相同的系统提示词:父 Agent 已序列化的系统提示词字节直接传递给子 Agent,不重新构建。
- 相同的工具池:子 Agent 接收父 Agent 的精确工具池。即使子 Agent 不能调用 AgentTool(避免无限递归),AgentTool 仍然保留在子 Agent 的工具池中——因为移除它会改变 schema 字节,破坏缓存前缀。
- 字节相同的消息前缀:所有 fork 子 Agent 产生字节相同的 API 请求前缀,只有最终指令文本块不同。
这个设计的工程含义是:子 Agent 的第一个 API 请求几乎可以完全命中父 Agent 的缓存,只有最后的指令文本是新的。这使得子 Agent 的派生成本极低——不需要重新处理系统提示词和工具定义的 KV 注意力状态。
模型分层策略
子 Agent 的模型选择遵循优先级链:CLAUDE_CODE_SUBAGENT_MODEL 环境变量 > 工具调用时指定的 model > agent 配置 > 继承父 Agent。简单任务(如代码探索)可以用更便宜的 Haiku 模型(5 倍便宜),复杂任务(如架构规划)用 Sonnet 或 Opus。[源码逆向, 高]
多代理协作模式
Claude Code 支持三种多代理协作模式:[源码逆向, 中]
- Sub-Agent:父 Agent fork 子 Agent,子 Agent 返回结果摘要,父 Agent 继续。适用于探索、研究、代码审查。
- Coordinator:编排器分配任务,worker 在 Git worktree 中执行。适用于大型多文件重构。
- Swarm:点对点邮箱通信,完全分布式。适用于复杂分布式工作流。
阶段 10:上下文压力下的压缩与缓存失效处理
随着对话增长,消息数组会逐渐逼近上下文窗口限制。Claude Code 有一个六级上下文压缩管道(Tier 0-5),从零成本的截断到高成本的 LLM 摘要,逐级升级。其中 Tier 0 为工具执行时的运行时行为,Tier 1-5 为上下文管理的主动策略。[源码逆向, 高]
六级压缩管道
| 级别 | 名称 | 触发条件 | 成本 | 策略 |
|---|---|---|---|---|
| Tier 0 | 工具结果截断 | 执行时 | 零 | 保留 >50K 字符结果的头部+尾部 |
| Tier 1 | 预算缩减 | >50% 上下文 | 零 | 所有结果缩减至 30K/15K |
| Tier 2 | 历史裁剪 | >60% 上下文 | 零 | 去重冗余文件读取 |
| Tier 3 | 微压缩 | >70% + 缓存冷 | 近零 | 移除旧工具结果 |
| Tier 4 | 上下文折叠 | 高利用率 | 低 | 可逆投影非活跃段 |
| Tier 5 | 自动压缩 | >85% (~167K) | 1-2 次 API 调用 | 完整 LLM 摘要 |
自动压缩的恢复序列
Tier 5 自动压缩是最激进的策略,它会用 LLM 对整个对话历史生成摘要,然后用摘要替换历史。压缩后的恢复序列确保关键上下文不丢失:[源码逆向, 高]
- 摘要替换历史消息
- 重读最近 5 个文件(预算 50K tokens,每个 5K)
- 恢复活跃技能
- 重放延迟通知
- 恢复工作状态
缓存失效风险与检测
上下文压缩有一个副作用:它修改了消息数组的前缀,导致缓存失效。Claude Code 有一个两阶段的缓存破坏检测系统来监控这个问题:[源码逆向, 高]
阶段 1(调用前):计算系统提示词块、工具 schema、模型字符串、beta headers 等的哈希。当任何哈希与前一次调用不同时,记录 pending changes。
阶段 2(调用后):当响应的 cache read tokens 比前一次下降超过 5% 且绝对下降量超过 2,000 tokens 时,检测到缓存破坏。系统根据 pending changes 构建解释(如系统提示词变更、工具变更、模型变更等)。
自动压缩(Tier 5)不仅消耗 1-2 次 API 调用来生成摘要,还会导致后续所有请求的缓存失效——因为消息数组的前缀变了。在压缩后的几轮循环中,缓存命中率会显著下降,直到新的前缀稳定下来。这就是为什么 Claude Code 将压缩作为最后手段,优先使用零成本的截断和裁剪策略。
关键阈值参数
| 参数 | 值 | 说明 |
|---|---|---|
| 上下文窗口 | 200,000 tokens | 特定模型 [1m] 标记可到 1,000,000 |
| 自动压缩触发 | ~167,000 tokens | 200K - 20K output - 13K buffer |
| 默认 max_output | 8,000 tokens | 重试时升级到 64,000 |
| 压缩后文件恢复预算 | 50,000 tokens | 最多 5 个文件,每个 5K |
| 手动压缩 buffer | 3,000 tokens | 用户主动触发 /compact |
关键机制深挖:Prompt Caching
Prompt Caching 是 Claude Code 经济可行性的基础。没有它,每轮循环都需要完整处理数万 token 的系统提示词和工具定义,成本和延迟都不可接受。本章从机制、策略和工程实现三个层面深入拆解。
5.1 缓存机制原理
Anthropic API 的 Prompt Caching 本质是服务端 KV 注意力状态的复用。当 API 请求的字节相同前缀匹配之前见过的请求时,服务器可以跳过对该前缀的注意力计算,直接复用已处理的 KV 状态。通过 cache_control 标记控制缓存断点。[官方文档, 高]
直觉类比
想象你在读一本 500 页的技术书,每次开会讨论时都需要回顾前面的内容。如果没有缓存,每次讨论前你都要重新读一遍前 400 页。缓存相当于你在第 400 页处放了一个书签——下次讨论时,你直接翻到书签处,只需要读新增的几页。书签位置之前的内容你已经"处理过"了,不需要重新理解。
这个类比的工程含义是:缓存的价值取决于"书签之前的内容不变"。如果书的前 400 页每次都改了几个字,书签就没用了——你必须重新读到书签处才能确定内容是否变了。这就是为什么缓存前缀稳定性如此重要。
前缀匹配的三个核心原则
- 缓存写入只发生在断点处:标记
cache_control的块会写入一个缓存条目(该前缀的哈希)。哈希是累积的——改变断点或之前的任何块会产生不同的哈希。 - 缓存读取向后查找:每次请求时系统计算断点处的前缀哈希,若未命中则逐块向后查找(最多 20 块窗口),检查是否有先前请求写入的匹配条目。
- 查找窗口为 20 块:系统每个断点最多检查 20 个位置。若窗口内无匹配则停止检查,该次请求不命中缓存。
5.2 Claude Code 的三个缓存断点
Claude Code 在 API 请求中设置了三个缓存断点,形成层级结构:[源码逆向, 高]
三种缓存分割模式
根据是否使用 MCP 工具和 API 提供商,Claude Code 有三种缓存分割模式:[源码逆向, 高]
| 模式 | 条件 | 缓存范围 |
|---|---|---|
| Mode 1 | 第一方 API + 无 MCP 工具 | 静态区域使用 global scope(跨所有用户共享) |
| Mode 2 | 第一方 API + 有 MCP 工具 | 系统提示词使用 org scope,工具数组末尾设 cache_control |
| Mode 3 | 第三方 provider 或无 boundary | 回退到 org scope 缓存 |
Mode 1 是最高效的模式——系统提示词的静态区域对所有 Claude Code 用户字节相同,可以使用 global scope 跨用户共享缓存。这意味着你第一次使用 Claude Code 时,系统提示词的缓存可能已经被其他用户的请求预热了。Mode 2 降级到 org scope 是因为 MCP 工具定义是用户特定的,无法跨用户共享。
5.3 缓存破坏的 18 个维度
Claude Code 的源码中有一个 promptCacheBreakDetection.ts 文件(第 28-99 行),列出了 18 个可能导致缓存破坏的维度。其中最关键的包括:[源码逆向, 高]
| 维度 | 破坏机制 | 防护措施 |
|---|---|---|
| 模型切换 | 不同模型的 KV 状态不兼容 | 会话内禁止切换模型 |
| 系统提示词变更 | 任何字节变化产生不同哈希 | 静态/动态边界分离 |
| 工具 Schema 变更 | 工具增删或描述修改 | 会话级稳定缓存 |
| Fast mode 切换 | 影响推理路径 | 会话内锁定 |
| Beta headers 变更 | 改变 API 行为 | 会话内稳定 |
| TTL 过期 | 5 分钟无请求缓存失效 | 会话内高频请求 |
| 服务器端驱逐 | 缓存容量有限时 LRU 淘汰 | 无法客户端控制 |
| 上下文压缩 | 修改消息前缀 | 最后手段,有恢复序列 |
5.4 侧查询的缓存隔离
Claude Code 在主循环之外还有多个"侧查询"——内部分类器、权限解释器、模型验证、会话搜索等。这些查询刻意不共享主会话的 prompt cache:[源码逆向, 高]
- 接受最小系统提示词(通常只是短字符串)
- 完全跳过 CLAUDE.md 和 git 状态上下文
- 使用自己的查询源标识符
- 可跳过 CLI 系统提示词前缀(只保留 OAuth attribution header)
这个设计避免了侧查询干扰主会话的缓存。如果侧查询使用了完整的系统提示词,它的请求会与主会话的请求竞争同一个缓存槽位,可能导致主会话的缓存被驱逐。通过隔离,侧查询使用自己的(极小的)缓存命名空间,不影响主会话。
5.5 缓存命中率的经济学
缓存命中的经济价值可以通过具体数字理解。以 Claude Sonnet 4.6(API ID: claude-sonnet-4-20250514)为例:[官方文档, 高]
| 计费项 | 价格 (每百万 token) | 相对倍率 |
|---|---|---|
| 基础输入 | $3.00 | 1.0x |
| 5 分钟缓存写入 | $3.75 | 1.25x |
| 1 小时缓存写入 | $6.00 | 2.0x |
| 缓存读取 | $0.30 | 0.1x |
| 输出 | $15.00 | 5.0x |
假设一个典型会话有 30K token 的系统提示词 + 工具定义,每轮循环新增 2K token 的消息。没有缓存时,每轮的输入成本为 32K x $3/M = $0.096。缓存命中 90% 时,每轮的输入成本为 30K x $0.3/M + 2K x $3/M = $0.009 + $0.006 = $0.015。成本降低约 6.4 倍。
更关键的是速率限制的优化。对大多数 Claude 模型,只有未缓存的输入 token 计入 ITPM(每分钟输入 token)速率限制。缓存读取的 token 不计入。这意味着 2,000,000 ITPM 限制 + 90% 缓存命中率 = 有效处理 20,000,000 总输入 token/分钟。这是一个 10 倍的吞吐量提升。[官方文档, 高]
5.6 最小可缓存 token 数
不是所有请求都能缓存。Anthropic API 对每个模型有最小可缓存 token 数要求:[官方文档, 高]
| 模型 | 最小 token 数 |
|---|---|
| Claude Opus 4.7/4.6/4.5 | 4,096 |
| Claude Sonnet 4.6/4.5/4.1/4 | 1,024 |
| Claude Haiku 4.5 | 4,096 |
| Claude Haiku 3.5 | 2,048 |
低于最小值的请求正常处理但不缓存(不报错)。Claude Code 的系统提示词 + 工具定义通常超过 15K token,远高于最小值,因此缓存始终有效。注:表中版本号为产品显示名,对应 API ID 格式为 claude-{model}-{date},如 claude-sonnet-4-20250514。
关键机制深挖:工具系统
工具系统是 Agent 与环境交互的唯一通道。工具描述的质量直接决定模型的调用准确率,工具执行的架构直接决定 Agent 的效率与安全性。
6.1 完整工具清单与分类
Claude Code 注册了 66+ 个工具,但并非所有工具都在每次请求中可用。工具通过三层管道(编译时剔除、运行时过滤、池组装)进行筛选。以下是核心工具的分类:[源码逆向, 高]
| 类别 | 工具 | 只读 | 可并行 |
|---|---|---|---|
| 文件系统 | FileReadTool | 是 | 是 |
| FileEditTool (搜索替换) | 否 | 否 | |
| FileWriteTool | 否 | 否 | |
| 搜索 | GlobTool (文件匹配) | 是 | 是 |
| GrepTool (ripgrep) | 是 | 是 | |
| Shell | BashTool | 否 | 否 |
| 网络 | WebFetchTool | 是 | 是 |
| WebSearchTool | 是 | 是 | |
| 编排 | AgentTool (子代理) | 否 | 否 |
| SkillTool (技能插件) | 否 | 否 | |
| TodoWriteTool (任务管理) | 否 | 否 | |
| AskUserQuestionTool | 否 | 否 | |
| 辅助 | ToolSearchTool (延迟加载) | 是 | 是 |
| BashOutputTool (后台命令) | 是 | 是 | |
| KillShellTool | 否 | 否 |
6.2 BashTool 的安全深度分析
BashTool 是最复杂的工具,源码超过 800 行,包含 23 项命名安全检查。它不是简单的 child_process.exec() 包装,而是一个完整的命令安全分析引擎。[源码逆向, 高]
安全检查清单(23 项命名检查)
- Tree-sitter AST 分析:不是用正则表达式匹配危险模式,而是用 Tree-sitter 解析命令的 AST 结构,理解命令的语义。这比正则更可靠——不会漏检嵌套或混淆的危险命令。
- 命令替换阻断:11 种命令替换模式被阻断,防止模型执行隐藏在变量展开或子shell中的恶意命令。
- Zsh 特定防御:等号展开、glob 限定符等 Zsh 特有语法被检查。
- sed 白名单验证:sed 命令的脚本部分经过白名单验证,防止危险的 sed 操作。
- 路径验证:所有文件路径限制在允许目录内。
- 破坏性操作检测:
git reset --hard、rm -rf、DROP TABLE等操作被标记。 - 语义退出码解释:
grep返回 1 表示"无匹配"而非"错误",系统正确解释这些语义退出码,避免向模型报告虚假错误。 - 环境变量审计:检查命令中引用的环境变量是否包含敏感信息(API keys、tokens),防止意外泄露。
- 重定向目标验证:
>、>>的目标路径必须在允许目录内,防止写入系统关键文件。 - 管道命令传播:管道
|链中的每个子命令都独立经过安全检查,不因"整体看起来无害"而跳过。 - 进程数限制:
&后台进程和xargs -P的并行度受限,防止 fork bomb。 - 网络命令标记:
curl、wget、nc等网络命令被标记为需要额外审查,防止数据外泄。 - Shell 特性限制:heredoc、process substitution
<()、brace expansion{}等高级 shell 特性受到限制或额外检查。 - 超时强制执行:每个命令有硬超时(默认 120s,最大 600s),超时后进程组被 SIGKILL。
- 持久会话隔离:不同权限级别的命令在不同 shell session 中执行,防止环境变量交叉污染。
- 输出编码:工具输出经过 UTF-8 编码验证和不可见字符过滤,防止终端控制字符注入。
- 历史命令审计:会话内执行的命令历史被记录到 JSONL,用于事后审计和异常检测。
- 交互式命令阻断:
vim、top、less等需要 TTY 交互的命令被阻断或包装,防止挂起。 - 权限缓存失效:当工作目录或权限模式变更时,之前的命令权限缓存被强制失效,防止权限提升攻击。
- Stop Hook 集成:命令执行前后触发用户自定义的 Stop Hook,允许额外的安全策略注入。
- 多 shell 兼容:bash、zsh、fish 的语法差异被统一处理,安全检查不因 shell 类型而遗漏。
- 转义序列处理:
\x、\0等转义序列被解析后检查,防止通过转义绕过字符串匹配。 - 工作目录一致性校验:命令执行前验证当前工作目录与权限上下文一致,防止通过
cd跳出授权范围后执行受限操作。
命令注入检测
系统分析 Bash 命令前缀,检测潜在命令注入。检测链式命令(&&、||、;)和管道操作。如果检测到注入模式,返回 command_injection_detected 错误。这是防御提示注入攻击的重要一层——如果恶意文件内容试图诱导模型执行危险命令,命令注入检测可以拦截。[源码逆向, 高]
6.3 Edit 工具为何选择搜索替换而非行号
Claude Code 的 FileEditTool 使用搜索替换(old_str → new_str,早期版本中字段名为 old_string/new_string)而非行号编辑(line 10-15 → new content)。这个选择的核心原因是抗幻觉。[源码逆向, 高]
当模型使用行号编辑时,如果幻觉了不存在的行号(比如文件只有 50 行但模型说"修改第 47-52 行"),编辑会静默损坏——要么修改了错误的位置,要么抛出令人困惑的错误。而搜索替换模式下,如果模型幻觉了不存在的内容,编辑直接失败——old_str 在文件中找不到匹配,这是一个明确的、可恢复的错误。
额外的安全措施包括:[源码逆向, 高]
- mtime 跟踪:文件必须先被 Read 工具读取才能被 Edit 编辑。这确保模型看到了文件的最新内容,而不是基于过期的记忆进行编辑。
- 唯一性约束:
old_str必须在文件中恰好出现一次(除非使用replace_all)。如果出现多次,编辑失败并要求模型提供更多上下文。
6.4 工具描述的写法规范
Claude Code 的工具描述不是简单的 API 文档,而是精心编写的行为指导。分析其工具描述可以发现几个模式:[源码逆向, 高]
模式 1:先说"做什么",再说"何时用"
每个工具描述都以功能定义开头("Reads a file from the local filesystem"),然后说明使用场景("Use this tool when you need to read a file")。这帮助模型快速判断是否应该使用该工具。
模式 2:显式列出"不要用这个工具做什么"
BashTool 的描述明确指出"不要用 Bash 来读取文件(用 Read 工具)"、"不要用 Bash 来搜索文件(用 Glob 或 Grep)"。这种负面指导防止模型默认使用最通用的工具(Bash)而忽略更精确的专用工具。
模式 3:参数描述包含验证规则
不仅描述参数的类型,还描述验证规则。例如 FileReadTool 的 offset 参数描述为"The line number to start reading from. Must be a positive integer."。这帮助模型生成合法的参数值,减少验证失败导致的重试。
模式 4:返回值描述帮助模型理解结果
工具描述中包含返回值的格式说明。例如 GrepTool 的描述指出输出格式包含文件名和行号前缀。这帮助模型正确解析工具结果,而不需要对返回格式进行猜测。
工具描述是 prompt engineering 的一部分,不是 API 文档。每增加一个工具,都在消耗上下文窗口的 token 预算,同时增加模型的选择困难。工具数量与能力-混淆之间存在权衡——更多工具提供更多能力,但也增加模型选错工具的概率。Claude Code 通过延迟加载(减少初始工具数)和详细描述(降低选错率)来管理这个权衡。
6.5 Hooks 系统
Hooks 是 Claude Code 的生命周期钩子系统,允许用户在特定事件发生时执行自定义逻辑。这不是工具系统的一部分,但与工具执行密切相关:[源码逆向, 高]
| Hook 事件 | 触发时机 | 用途 |
|---|---|---|
| PreToolUse | 工具执行前 | 拦截/修改工具调用 |
| PostToolUse | 工具执行后 | 后处理、验证结果 |
| PostToolUseFailure | 工具失败后 | 错误处理、通知 |
| UserPromptSubmit | 用户提交输入前 | 输入预处理、过滤 |
| SessionStart / SessionEnd | 会话开始/结束 | 初始化/清理 |
| Stop | Agent 循环停止时 | 阻止终止、附加反馈 |
| SubagentStart / SubagentStop | 子代理启动/停止 | 子代理生命周期管理 |
| PreCompact | 上下文压缩前 | 压缩前保存关键信息 |
| PermissionRequest | 权限请求时 | 自定义权限逻辑 |
其中 Stop hook 特别值得注意——它可以阻止 Agent 循环的终止。如果 hook 返回 blocking,harness 会恢复循环并附加 hook 反馈,让模型继续工作。这是一个用户可控的"强制继续"机制。
关键机制深挖:上下文管理
上下文管理是 Agent 在长会话中保持能力的关键。没有它,随着对话增长,上下文窗口会被填满,模型性能下降,最终无法继续工作。Claude Code 的六级压缩管道(Tier 0-5)是业界最精细的上下文管理方案之一。
7.1 上下文压力的增长模型
理解上下文管理之前,先理解上下文压力如何增长。在 Agent 循环中,每轮迭代追加 2 条消息。典型情况下,一条 assistant 消息约 200-500 token(文本 + 工具调用),一条 user 消息(工具结果)约 500-5000 token(取决于工具输出大小)。这意味着每轮循环约增长 700-5500 token。[合理推断, 高]
在 200K token 的上下文窗口中,从初始的约 30K token(系统提示词 + 工具定义 + 第一条用户消息)开始,大约 30-240 轮循环后会达到 167K 的自动压缩阈值。对于复杂任务(如多文件重构),一个会话可能需要 50-100 轮循环,因此上下文压缩是不可避免的。
7.2 六级压缩策略的详细分析
Tier 0:工具结果截断(执行时,零成本)
这是最基础的压缩策略,在工具执行时立即生效。当工具结果超过 50K 字符时,保留头部和尾部,中间用省略标记替代。这个策略不需要 LLM 参与,零 API 调用成本,且在工具执行时就已完成,不影响后续请求的构造。[源码逆向, 高]
头部+尾部的设计比简单截断更好,因为文件和命令输出的开头通常包含结构信息(导入、函数签名、表头),结尾通常包含总结信息(错误消息、退出码、统计摘要)。中间部分往往是重复性的数据行。
Tier 1:预算缩减(>50%,零成本)
当上下文使用超过 50% 时,将所有工具结果缩减至 30K/15K。这是一个全局缩减操作,影响消息数组中所有历史的工具结果。此操作不改变消息数量,只改变消息内容大小。由于修改了消息内容,可能导致缓存部分失效——但因为系统提示词和工具定义未变,主要的缓存断点仍然有效。[源码逆向, 高]
Tier 2:历史裁剪(>60%,零成本)
当上下文使用超过 60% 时,去重冗余文件读取。如果同一文件在对话中被多次读取,只保留最新的读取结果,旧的读取结果被移除或缩减。这个策略利用了一个常见模式:Agent 在探索阶段会多次读取同一文件的不同部分,但这些早期读取在后续编辑阶段已经不需要了。[源码逆向, 高]
Tier 3:微压缩(>70% + 缓存冷,近零成本)
当上下文使用超过 70% 且缓存已经冷了(即缓存命中率已经很低,移除旧工具结果的缓存代价较小)时,移除旧的工具结果。只保留工具调用的记录(tool_use 块),但移除工具结果的内容(tool_result 的 content)。模型仍能看到"调用了什么工具",但看不到"工具返回了什么"。[源码逆向, 高]
Tier 4:上下文折叠(高利用率,低成本)
上下文折叠(Context Collapse)是一个可逆投影操作。它将非活跃的对话段投影为一个紧凑的表示,但保留足够的元数据以便在需要时恢复。与 Tier 5 的摘要不同,折叠是可逆的——如果后续需要被折叠段的详细信息,可以从折叠状态恢复。这是一个 feature-gated 功能(CONTEXT_COLLAPSE flag)。[源码逆向, 中]
Tier 5:自动压缩(>85%,1-2 次 API 调用)
这是最激进的策略,当上下文使用超过 85%(约 167K token)时触发。系统调用 LLM 对整个对话历史生成摘要,然后用摘要替换历史消息。这个过程消耗 1-2 次 API 调用,且会导致后续请求的缓存完全失效。[源码逆向, 高]
压缩后的恢复序列确保关键上下文不丢失:
- 摘要替换历史消息——摘要包含任务目标、已完成的工作、待完成的步骤、关键文件路径等
- 重读最近 5 个文件——预算 50K tokens,每个文件 5K tokens。这确保模型仍然能看到最近操作的文件的当前状态
- 恢复活跃技能——如果在压缩前有技能被激活,恢复这些技能的状态
- 重放延迟通知——如果有被延迟的系统通知(如权限变更),重新注入
- 恢复工作状态——将 TodoWrite 的任务列表恢复到摘要中
7.3 压缩策略的保留优先级
当进行压缩时,并非所有内容被平等对待。Claude Code 的压缩策略有一个隐含的保留优先级:[合理推断, 中]
| 优先级 | 内容类型 | 保留策略 |
|---|---|---|
| 最高 | 当前任务目标 | 始终保留在摘要中 |
| 高 | 最近 5 个文件的状态 | 压缩后重读 |
| 高 | TodoWrite 任务列表 | 恢复到摘要中 |
| 中 | 最近的工具调用与结果 | Tier 3 之前完整保留 |
| 低 | 早期的探索性搜索 | Tier 2 时去重 |
| 最低 | 早期的文件读取结果 | Tier 0/1 时截断 |
7.4 缓存感知 ITPM 与上下文管理的交互
上下文管理策略与速率限制有一个微妙的交互。由于缓存读取的 token 不计入 ITPM 速率限制,保持高缓存命中率不仅是成本优化,也是速率限制优化。这意味着 Tier 3(微压缩)的触发条件不仅是上下文使用率,还包括缓存是否已经冷了——如果缓存还是热的(高命中率),移除旧工具结果的代价更高(会导致缓存失效,增加 ITPM 消耗)。[合理推断, 高]
这是一个典型的多目标优化问题:上下文管理需要同时优化三个目标——上下文窗口利用率(避免溢出)、缓存命中率(降低成本和速率限制消耗)、以及信息保留率(避免丢失关键上下文)。Claude Code 的六级管道通过渐进式策略来平衡这三个目标。
7.5 与其他 Agent 的上下文管理对比
不同 Agent 采用不同的上下文管理策略,反映了不同的设计优先级:[学术分析, 中]
| Agent | 策略 | 特点 |
|---|---|---|
| Claude Code | 六级渐进管道 | 最精细,从零成本到高成本逐级升级 |
| SWE-agent | 轮询参数 | 维护最近 5 步,折叠更早历史,简单但有效 |
| Cline | LLM 发起压缩 | 模型主动判断何时压缩 |
| Aider | PageRank repo map | 在 token 预算内选择最相关上下文 |
| OpenDev | 自适应压缩 | 渐进式缩减旧观察,token 接近耗尽时触发 |
| Gemini CLI | 验证探针 | 压缩前验证关键信息保留 |
Claude Code 的六级管道在精细度上领先,但也带来了实现复杂度。SWE-agent 的简单策略(最近 5 步 + 折叠)虽然粗糙,但 Mini-SWE-Agent 仅用 100 行 Python 就在 SWE-bench Verified 上达到 >74%,证明架构设计比复杂性更重要。[学术分析, 中]
关键机制深挖:子 Agent 与安全模型
子 Agent 派生是 Agent 应对复杂任务的关键能力,而安全模型是 Agent 在真实环境中可部署的基础。两者看似无关,但在 Claude Code 的设计中通过缓存共享和权限隔离紧密耦合。
8.1 子 Agent 派生的完整流程
当模型调用 AgentTool 时,harness 执行以下流程:[源码逆向, 高]
8.2 缓存共享的三重机制
子 Agent 的第一个 API 请求几乎可以完全命中父 Agent 的缓存,这通过三重机制实现:[源码逆向, 高]
机制 1:字节相同的系统提示词。父 Agent 已序列化的系统提示词字节直接传递给子 Agent,不重新构建。这意味着子 Agent 的系统提示词与父 Agent 的完全相同——相同的静态区域、相同的动态区域、相同的 cache_control 标记。API 服务端在计算前缀哈希时得到完全相同的结果,直接命中缓存。
机制 2:相同的工具池。子 Agent 接收父 Agent 的精确工具池。这里有一个微妙的设计:即使子 Agent 不能调用 AgentTool(避免无限递归),AgentTool 仍然保留在子 Agent 的工具池中。原因是移除 AgentTool 会改变工具 schema 的字节,破坏缓存前缀。保留不可调用的工具比破坏缓存更划算。
机制 3:字节相同的消息前缀。所有 fork 子 Agent 产生字节相同的 API 请求前缀(system + tools),只有最后的指令文本块不同。这意味着多个子 Agent 之间也可以共享缓存——如果父 Agent 派生了 3 个子 Agent,第二个和第三个子 Agent 的第一个请求可以命中第一个子 Agent 写入的缓存。
8.3 子代理的默认提示词分析
子代理接收的默认提示词反映了几个关键设计意图:[源码逆向, 高]
You are an agent for Claude Code, Anthropic's official CLI for Claude. Given the user's message, you should use the tools available to complete the task. Complete the task fully—don't gold-plate, but don't leave it half-done. When you complete the task, respond with a concise report covering what was done and any key findings — the caller will relay this to the user, so it only needs the essentials.
关键设计意图:
- "Complete the task fully"——子代理被期望完成整个任务,不是做一半就返回。这减少了父子代理之间的往返次数。
- "don't gold-plate, but don't leave it half-done"——明确边界:不要过度修饰,但也不要偷懒。这是一个精确的期望管理。
- "respond with a concise report"——子代理的输出是给父代理看的摘要,不是给用户看的完整报告。这控制了信息回灌的量。
- "the caller will relay this to the user"——明确角色:子代理不直接与用户交互,所有结果通过父代理中转。
8.4 安全模型:七层纵深防御
Claude Code 的安全模型是一个七层纵深防御体系,每个工具调用在执行前经过全部七层检查:[源码逆向, 高]
8.5 四种权限模式
权限模式决定了工具调用在什么条件下需要用户确认。Claude Code 提供四种模式,覆盖从最安全到最高效的范围:[源码逆向, 高]
| 模式 | 行为 | 适用场景 |
|---|---|---|
| Default | 逐个确认写操作 | 日常开发,安全优先 |
| Plan | 只读,不可修改 | 代码审查、架构探索 |
| Auto | 智能判断(Transcript Classifier) | 信任环境下的高效工作 |
| Bypass | 全部允许 | CI/CD、自动化场景 |
Auto 模式使用 Transcript Classifier(转录分类器)分析当前对话上下文,评估工具调用的安全性。对明显安全的操作自动放行,对有风险的操作仍然弹窗确认。这是一个 feature-gated 功能,源码位于 src/main.tsx。[源码逆向, 中]
8.6 工具权限四级分类
在权限模式的基础上,每个工具还有自己的权限级别:[源码逆向, 高]
| 级别 | 自动允许 | 示例 |
|---|---|---|
| Level 0 | 始终 | Read, Glob, Grep, ToolSearch, AskUserQuestion |
| Level 1 | 首次确认 | Write, Edit, WebFetch, WebSearch, Bash(安全命令) |
| Level 2 | 每次确认 | Bash(危险命令: rm, git push, chmod), EnterWorktree |
| Level 3 | 阻止+警告 | rm -rf /, git push --force origin main, DROP TABLE |
关键安全保证:即使在 bypassPermissions 模式下,deny 规则和 .git/ 目录保护仍然不可跳过。这是一个硬编码的安全底线,确保任何模式下都不会意外修改 git 内部状态。[源码逆向, 高]
8.7 权限规则的七源级联
权限规则的匹配遵循七源级联,从最高优先级到最低:[源码逆向, 高]
- Session:当前会话中用户已经允许/拒绝的规则
- CLI Args:命令行参数指定的规则
- Local:
.claude/settings.local.json(不提交到 git) - User:
~/.claude/settings.json(全局用户设置) - Project:
.claude/settings.json(项目级设置) - Policy:企业策略(系统管理的配置)
- Flags:feature flag 控制的默认规则
每条规则的动作可以是 allow(允许)、deny(拒绝)或 ask(询问)。deny 规则的优先级高于 allow——如果任何一个源指定了 deny,则工具调用被阻止,不论其他源是否 allow。这种设计确保安全策略不会被低优先级的宽松规则覆盖。
8.8 会话持久化与可观测性
Claude Code 将每个会话存储为 JSONL 文件(每行一个 JSON 对象),位于 ~/.claude/projects/<encoded-path>/<session-id>.jsonl。路径编码方式是将 / 替换为 -。这种持久化机制在可观测性、恢复和调试中扮演关键角色。[源码逆向, 高]
JSONL 转录格式
每个条目至少包含以下顶层字段:[源码逆向, 高]
| 字段 | 类型 | 说明 |
|---|---|---|
type | string | 事件类型 (user/assistant/system) |
uuid | string | 事件唯一 ID |
parentUuid | string|null | 父事件 ID(线程化) |
timestamp | string | ISO 8601 时间戳 |
sessionId | string | 会话 UUID |
cwd | string | 工作目录 |
gitBranch | string | 当时的 git 分支 |
version | string | Claude Code 版本 |
JSONL 文件采用追加写入——新事件作为新行写入,不重写或删除已有行。这使得会话恢复极其简单:读取文件,重建消息数组,从最后一条消息继续。也是调试的利器——可以用 jq 扫描会话中的工具调用和 token 使用量:[源码逆向, 高]
# 扫描所有工具调用
jq -c 'select(.type=="assistant") | .message.content[] |
select(.type=="tool_use") | {name, input}' session.jsonl
# 每轮 token 使用量
jq -c 'select(.type=="assistant") | .message.usage' session.jsonl
成本追踪
cost-tracker.ts 持久化每会话成本,跨重启保存。模型定价(每百万 tokens):[源码逆向, 高]
| 模型 | Input | Output | Cache Read | Cache Write |
|---|---|---|---|---|
| Haiku 4.5 | $1 | $5 | $0.10 | $1.25 |
| Sonnet 4.6 | $3 | $15 | $0.30 | $3.75 |
| Opus 4.6 | $5 | $25 | $0.50 | $6.25 |
| Opus 4.6 Fast | $30 | $150 | $3.00 | $37.50 |
遥测架构
Claude Code 使用双通道遥测架构:Datadog/FirstParty 用于分析事件(受 feature flag 控制),Sentry 用于错误监控。Statsig 提供 feature flag 服务,GrowthBook 用于 A/B 测试。这些遥测通道与主循环的 API 请求完全隔离,不影响缓存或速率限制。[源码逆向, 高]
8.9 流式输出与工具调用缓冲的交互
流式模式下,tool_use 块的 input 字段是通过 input_json_delta 事件增量传输的。每个 delta 包含一段 partial JSON 字符串。harness 需要累积这些字符串,在 content_block_stop 事件后才能解析完整的 JSON 对象。[官方文档, 高]
这个设计的一个微妙影响是:harness 不能在 tool_use 块完全接收之前开始执行工具。但 Claude Code 的 StreamingToolExecutor 通过在 content_block_stop 事件(而非 message_stop 事件)后立即开始执行,将工具启动延迟从"整个响应完成"提前到"单个工具块完成"。如果模型在一次响应中调用多个工具,第一个工具可以在第二个工具还在生成时就开始执行。[源码逆向, 高]
与其他 Agent 的对比观察
本章横向对比 Cursor、OpenAI Codex CLI、Aider、Continue.dev 以及 SWE-agent、OpenHands 等开源框架,识别架构设计的共性与分歧,以及背后的设计哲学差异。
9.1 架构总览对比
| Agent | 控制架构 | 工具设计 | 文件编辑 | 沙箱 |
|---|---|---|---|---|
| Claude Code | 单循环 + 子代理 | 结构化(66+工具) | 搜索替换 | 应用级权限 |
| Cursor | IDE 原生 + Composer | IDE 集成 + 自定义模型 | diff + Fast Apply | 影子工作区 |
| Codex CLI | 单 ReAct 循环 | Shell 优先(单一工具) | apply_patch | OS 级(gVisor/Seatbelt) |
| Aider | 单 Coder 循环 | 无(用户驱动导航) | 5种格式 | Git 原生 |
| Continue | core + extensions | 策略层控制 | Read/Write/Edit | IDE 桥接 |
| SWE-agent | 单 ReAct + ACI | 小命令集(4个) | edit_file + lint | Docker/subprocess |
| OpenHands | 事件流 + 多代理 | Docker + Jupyter + 浏览器 | 代码执行 | Docker + SSH |
9.2 Cursor:IDE 原生路径
Cursor 是 VS Code 的 fork(非插件),这一架构决策使其能在框架级别修改编辑器体验。其四大支柱为:[工程分析, 中]
- Tab 补全:自定义小模型,目标 sub-100ms 延迟。关键洞察:补全对延迟极度敏感(2 秒补全不可用,但 2 秒代理响应可以接受)。
- @Codebase 语义索引:tree-sitter 分块 + 向量嵌入 + 最近邻检索。支持本地和服务器端索引。
- Composer 2:多文件编辑模式,使用自定义模型(70B Llama 基座微调 + 投机解码),超 1000 tokens/秒吞吐。
- Agents:自主任务执行,Cloud Agents 扩展到远程执行。Cursor 2.0 使用 RL 在实时编码环境中训练模型。
Cursor 的独特创新是影子工作区(Shadow Workspace):隐藏的后台工作区,AI 可在其中安全测试变更并从语言服务器获取反馈。AI 提议变更后,在隐藏的 Electron 窗口中应用,让语言服务器报告错误,反馈给 AI 调整——在向用户展示 diff 前形成自我精炼循环。[工程分析, 中]
9.3 Codex CLI:Shell 优先哲学
Codex CLI 的核心设计选择是暴露一个主要工具:通用 shell 命令执行器。通过这个统一接口,模型可以 cat 读文件、grep 搜索、ls 列目录、运行测试、执行 git 操作,以及通过 apply_patch 应用编辑。系统提示教会模型一个 mini-API,明确如何调用工具和格式化输出。[工程分析, 中]
这与 Claude Code 的结构化工具设计形成鲜明对比。Claude Code 提供 66+ 个专门工具(Read、Glob、Grep 等),而 Codex CLI 只提供一个通用 shell。两种设计各有优劣:[合理推断, 中]
- Claude Code 的结构化工具提供了更好的类型安全和权限控制,但增加了 token 开销和模型选择困难。
- Codex CLI 的 shell 优先设计更灵活、token 开销更小,但难以对单个命令进行精细权限控制。
Codex CLI 的多层沙箱
Codex CLI 在沙箱设计上远比 Claude Code 激进。其云端沙箱(ChatGPT.com/codex)有 8 步启动流程:[工程分析, 中]
- gVisor 容器生成(拦截 237 个系统调用)
- seccomp 配置加载(阻止约 89% 系统调用,仅白名单 43 个)
- overlayfs 挂载(只读基础层 + 临时 tmpfs 写入层,100MB 限制)
- 网络防火墙(默认 DROP,仅白名单域名)
- cgroups v2 资源限制(512MB 内存、1 CPU 核心、30 秒超时)
- Python 解释器启动(PYTHONPATH 限制)
- 30 秒终止开关(SIGKILL 不可捕获)
- 容器终止与输出净化
两阶段运行时模型是其安全设计的关键:设置阶段可访问网络安装依赖,代理阶段默认离线。密钥仅在设置阶段可用,代理阶段开始前清除。这种设计从根本上防止了 Agent 通过网络泄露用户数据。[工程分析, 中]
9.4 Aider:Git 原生与编辑格式体系
Aider 是终端原生的 AI 配对编程工具,整个循环在单个 Python 进程中运行。其核心创新有两个:编辑格式体系和 Repo Map。[工程分析, 中]
五种编辑格式
Aider 支持五种编辑格式,不同模型在不同格式下表现不同。这是一个独特的洞察——编辑格式不是实现细节,而是影响模型表现的关键变量:[工程分析, 中]
| 格式 | 机制 | 适用模型 |
|---|---|---|
| whole | 返回完整文件 | 弱模型或小文件 |
| diff | SEARCH/REPLACE 块 | 多数模型 |
| diff-fenced | 围栏内 diff | Gemini 系列 |
| udiff | 统一 diff | GPT-4 Turbo(减少懒惰编码) |
| architect | 双模型工作流 | 复杂重构 |
udiff 格式的存在揭示了一个重要现象:GPT-4 Turbo 在 diff 格式下倾向于用 # ... original code here ... 注释省略大段代码("懒惰编码"),而统一 diff 格式能减少这种倾向。这表明编辑格式不仅是语法选择,还是 prompt engineering 的一部分——不同模型对不同格式的"服从度"不同。[工程分析, 中]
Repo Map:PageRank 驱动的上下文选择
Aider 不将整个文件倾倒进 prompt,而是构建代码库中每个符号定义和引用的图,然后使用 PageRank 选择当前任务最相关的代码上下文。Pipeline 为:tree-sitter 解析 → SQLite 缓存标签 → MultiDiGraph 上运行个性化 PageRank → TreeContext 渲染。[工程分析, 中]
个性化权重的设计很精巧:当前在聊天中的文件获得 50x 权重提升,对话中提到的标识符 10x,8+ 字符的命名符号 10x。引用计数取平方根以防止单个高频符号主导图。Token 预算分配为模型上下文窗口的 1/8。[工程分析, 中]
9.5 开源框架:SWE-agent 与 OpenHands
SWE-agent:Agent-Computer Interface
SWE-agent 的核心创新是 ACI(Agent-Computer Interface)——专门为 LLM 代理设计的抽象层。传统 Linux shell 为人类设计,LLM 在长冗长输出、复杂状态管理、易错命令语法等方面困难。ACI 提供简化操作(view_file、search_dir、edit_file、run_command)、护栏(在错误发生前预防常见错误)、简洁反馈(关于命令效果的具体、最小化输出)和上下文管理(维护最近 5 步,折叠更早历史)。[学术分析, 中]
关键发现:Mini-SWE-Agent 仅用 100 行 Python 达到 >74% SWE-bench Verified,证明架构设计比复杂性更重要。[学术分析, 中]
OpenHands:事件流架构
OpenHands 采用事件流(event-stream)架构建模代理-环境交互。关键组件包括 Docker 沙箱(通过 SSH 访问)、Jupyter Kernel 环境(有状态代码交互)、浏览器代理 API(Web 自动化)和多代理委托(层次化结构)。在 SWE-bench Verified 上达 72%(Claude Sonnet 4.5 + extended thinking)。[学术分析, 中]
9.6 跨 Agent 共性与分歧
共性:顺序 ReAct 循环的主导地位
一篇分析 13 个开源编码代理的学术论文发现,13 个中有 7 个使用顺序 ReAct 循环作为主要控制结构。只有 Moatless Tools 使用完整 MCTS,Agentless 使用固定流水线(无反馈循环)。13 个中 11 个组合多种循环原语而非依赖单一控制结构。五种循环原语为:ReAct、generate-test-repair、plan-execute、multi-attempt retry、tree search。[学术分析, 中]
分歧:工具数量的能力-混淆权衡
工具数量从 0(Aider,用户驱动所有导航)到 37(Moatless Tools),但底层能力类别趋同:读取、搜索、编辑、执行代码。学术分析指出"工具数量与能力-混淆权衡"——更多工具提供更多能力但也增加模型选择困难。[学术分析, 中]
分歧:沙箱隔离策略
不同 Agent 在沙箱设计上的选择反映了对安全与便利的不同权衡:[学术分析, 中]
- Codex CLI:OS 级隔离(gVisor/Seatbelt),最安全但最重
- OpenHands:Docker + SSH,平衡安全与灵活性
- Claude Code:应用级权限控制,最轻量但依赖 harness 正确性
- Aider:Git 原生(版本控制回滚),不隔离但可恢复
- Cline:Shadow git(版本控制回滚),不隔离但可恢复
分歧:IDE 耦合作为架构决策
"IDE as Architecture"是一个横切主题:[学术分析, 中]
- IDE 原生(Cursor):fork VS Code,影子工作区,语言服务器集成
- IDE 扩展(Continue、Cline):插件运行,通过 IDE 接口桥接
- 终端原生(Claude Code、Codex CLI、Aider):shell 运行,直接访问文件系统
- 浏览器原生(OpenHands、Devin):Web UI + 内置 VSCode/VNC
体验差异的工程归因
"好用 vs 难用"的 Agent 差异不是模型能力的差异,而是工程质量的差异。本章从五个维度分析体验差异的工程根因。
10.1 维度一:System Prompt 质量
System Prompt 的质量直接决定模型的行为模式。下表对比了不同 Agent 在 System Prompt 工程上的关键差异:[源码逆向/对比分析, 高]
| 行为约束 | Claude Code | 典型弱 Agent | 用户体验差异 |
|---|---|---|---|
| 任务完成强制 | "Complete tasks fully" 明确指令 | 缺失,模型可中途放弃 | 弱 Agent 经常半途而废,用户需反复催促 |
| 技术准确性优先 | "Prioritize accuracy over validating user beliefs" | 缺失,模型迎合用户 | 弱 Agent 变成"yes-man",错误方案被执行 |
| 过度验证抑制 | "Avoid over-the-top validation" | 缺失,模型频繁说"You're absolutely right" | 弱 Agent 输出冗长且不专业 |
| 范围控制 | "Don't add features beyond what was asked" | 缺失,模型倾向"帮忙"过度修改 | 弱 Agent 引入不需要的变更,增加 review 负担 |
10.2 维度二:工具描述清晰度
工具描述是模型理解工具能力的唯一渠道。描述不清晰导致两个失败模式:漏用(不知道何时用)和误用(不该用时用)。对比分析:[源码逆向/对比分析, 高]
| 描述质量维度 | Claude Code | Codex CLI | Aider |
|---|---|---|---|
| 使用场景说明 | 每个工具描述含 When to use / When NOT to use | mini-API 教学式 | 极简,靠系统提示补充 |
| 反模式列举 | 明确列出"不要做什么" | 无显式反模式 | 无 |
| 错误恢复指导 | 描述中包含"如果失败应该怎么做" | 无 | 无 |
| 工具间边界 | 工具描述中互相引用(Read vs Grep vs Glob) | shell 内隐含 | SEARCH/REPLACE 块自带约束 |
10.3 维度三:上下文管理策略
上下文管理直接影响长会话体验。下表对比了不同策略下的用户可感知差异:[源码逆向/对比分析, 高]
| 策略 | 代表 Agent | 信息保留 | 典型用户症状 |
|---|---|---|---|
| 六级渐进式压缩 | Claude Code | 高(分级保留) | 长会话仍能引用早期决策 |
| 简单滑动窗口 | 多数弱 Agent | 低(最近 N 条) | "Agent 突然忘记之前做了什么" |
| 全量保留 | 早期版本/原型 | 高但不可持续 | "上下文溢出后直接崩溃" |
| repo-map + 滑窗 | Aider | 中(结构摘要) | "知道文件结构但丢失对话细节" |
10.4 维度四:循环设计与错误恢复
循环设计的质量体现在错误恢复能力上。Claude Code 的 7 条命名恢复路径覆盖了常见错误类型:[源码逆向, 高]
| 恢复路径 | 触发条件 | 策略 |
|---|---|---|
| collapse_drain_retry | Prompt 过长 | Context Collapse + 重试 |
| reactive_compact_retry | Prompt 过长(严重) | 完整压缩 + 重试 |
| max_output_tokens_escalate | 输出截断 | 升级到 64K token 限制 |
| stop_hook_blocking | Stop Hook 阻止 | 恢复并附加 hook 反馈 |
| token_budget_continuation | 预算超限 | 预算扩展后继续 |
| api_error_retry | API 429/500/503/529 | 指数退避重试(最多 3 次),向用户显示降级提示 |
| tool_error_retry | 工具执行异常 | 将错误信息以 tool_result 回灌,让模型决定重试或换路径 |
关键设计决策是可恢复错误永远不暴露给调用者。用户不会看到"Prompt 过长,正在压缩..."的错误消息——系统静默处理,继续工作。这种"无声恢复"极大提升了用户体验,但也增加了调试难度(需要查看 JSONL 日志才能了解发生了什么)。
10.5 维度五:流式体验与延迟隐藏
用户对延迟的感知不是线性的——1 秒以内的延迟几乎无感,3 秒开始焦虑,10 秒认为系统卡死。Claude Code 通过多个机制隐藏延迟:[源码逆向, 高]
- 流式输出:模型生成的文本实时显示,用户立即看到进展。
- 流式工具执行:只读工具在 API 流式期间并行执行,将工具延迟隐藏在 API 生成窗口内。
- 缓存命中:缓存命中的请求跳过前缀处理,首 token 延迟显著降低。
- 子代理并行:多个子代理可以并行运行,减少串行等待。
对比之下,许多"难用"的 Agent 在工具执行期间显示空白等待——用户不知道系统在做什么,也不知道是否卡死。Claude Code 的流式设计确保用户几乎总是能看到某种进展。
面向自研的设计原则
本章从前述分析中提炼 12 条可直接指导实现的设计原则。每条原则附设计依据和实现建议。
静态/动态上下文分离
将系统提示词严格分为静态区(所有用户相同)和动态区(会话特定),用显式边界标记分隔。静态区使用全局缓存,动态区使用组织级缓存。
设计依据:缓存前缀稳定性是经济可行性的基础。任何会话特定信息泄漏到静态区都会破坏全局缓存。
实现建议:用 __BOUNDARY__ 标记分隔。日期、CWD、git 状态等动态信息通过 system-reminder 注入消息数组,不放入系统提示词。
工具 Schema 与描述的写法规范
工具描述不是 API 文档,而是行为指导。每个描述包含:功能定义、使用场景、负面指导(不要用这个工具做什么)、参数验证规则、返回值格式。
设计依据:模型对工具的理解完全依赖描述文本。描述不清导致工具漏用或误用。
实现建议:为每个工具写 3-5 句描述。先说"做什么",再说"何时用",最后说"不要做什么"。参数描述包含类型和验证规则。
主循环的状态机最小化
Agent 循环只有两个状态:等待 API 响应和等待工具执行。不维护图状态、不维护任务依赖、不维护分支决策。所有复杂决策下放给模型。
设计依据:状态维度越多,故障模式越多。最小状态机使得崩溃恢复极其简单——重新发送最后一条消息。
实现建议:循环体只做三件事:发送请求、解析响应、执行工具。不引入额外的状态变量。
子代理的边界与通信方式
子代理通过结果摘要与父代理通信,不共享消息数组。子代理的工具池是父代理的子集,但保留不可调用的工具以维护缓存前缀。
设计依据:隔离防止子代理的错误影响父代理。缓存共享降低子代理的派生成本。
实现建议:子代理接收独立的消息数组(只有指令),完成后返回文本摘要。不传递引用、不传递文件句柄。
压缩策略的触发与保留优先级
采用渐进式压缩管道:零成本策略(截断、裁剪)先触发,高成本策略(LLM 摘要)最后触发。保留优先级:任务目标 > 最近文件 > 任务列表 > 最近工具调用 > 早期探索。
设计依据:不同信息的时间衰减率不同。任务目标不过期,早期探索很快无用。
实现建议:实现至少 3 级压缩。Tier 0 在工具执行时截断结果,Tier 1 在 50% 上下文时缩减历史,Tier 2 在 85% 时 LLM 摘要。
可观测性与本地 trace 的必要性
每个 API 请求、工具调用、错误恢复都记录到本地 JSONL 文件。支持会话恢复、事后调试和成本追踪。
设计依据:"无声恢复"提升了用户体验但降低了可调试性。JSONL trace 是补偿手段。
实现建议:每条记录包含 type、uuid、parentUuid、timestamp、sessionId、content。追加写入,不重写历史。
搜索替换优于行号编辑
文件编辑使用 old_str → new_str 搜索替换,不用行号。要求 old_str 在文件中唯一。先读后编辑(mtime 跟踪)。
设计依据:模型幻觉行号导致静默损坏,幻觉内容导致明确失败。明确失败可恢复,静默损坏不可恢复。
实现建议:编辑前检查文件是否被读取过。old_str 不唯一时返回错误,要求更多上下文。
错误是推理的一部分,不是控制流中断
工具执行失败时,错误信息作为 tool_result 返回给模型,不作为异常抛出。模型自行决定重试、换方法或报告。
设计依据:模型有足够强的 reasoning 能力处理错误。harness 层面的异常处理会打断模型的推理链。
实现建议:所有工具错误捕获后包装为 tool_result。只有不可恢复的系统错误(如网络断开)才中断循环。
缓存感知的工具排序与延迟加载
工具列表在会话内排序稳定(不改变顺序)。低频工具延迟加载——初始只发送 name + description,完整 schema 通过搜索工具按需获取。
设计依据:工具定义是缓存前缀的一部分。顺序变化或内容变化都会破坏缓存。延迟加载减少初始 token 开销。
实现建议:核心工具(Read、Write、Edit、Bash、Glob、Grep)始终完整加载。其他工具延迟加载。工具排序按注册顺序,不按字母。
流式工具执行隐藏延迟
只读工具在 API 流式响应期间并行执行,不等整个响应完成。写工具在响应结束后串行执行。
设计依据:API 生成 5-30 秒,工具执行约 1 秒。并行执行将工具延迟隐藏在 API 生成窗口内。
实现建议:在 content_block_stop 事件后立即执行对应工具。标记工具的 isReadOnly() 和 isConcurrencySafe()。
纵深权限防御
至少实现三层权限检查:信任对话框(工作区级)、权限模式(会话级)、规则匹配(工具级)。deny 规则不可被 bypass。
设计依据:单层权限防线一旦被绕过就全盘失守。多层防御确保单层失效不导致灾难。
实现建议:实现 allow/deny/ask 三种动作。deny 优先级最高,不可被任何模式跳过。Bash 命令用 AST 分析而非正则。
模型分层与成本控制
简单任务(探索、摘要、提交消息)用轻量模型,复杂任务(主循环、架构规划)用强模型。子代理模型可独立配置。
设计依据:Haiku 比 Sonnet 便宜 5 倍,对简单任务足够。主循环的推理复杂度需要 Sonnet/Opus 级别。
实现建议:模型优先级链:环境变量 > 工具指定 > 配置 > 继承父代理。追踪每会话成本,支持预算限制。
11.1 最小可行架构建议
基于上述 12 条原则,一个最小可行的 Coding Agent 架构如下:[合理推断, 中]
这个最小架构约 200 行代码可以实现,但已经包含了核心设计原则:静态/动态分离、流式工具执行、错误作为推理、JSONL 持久化、渐进式压缩。在此基础上逐步添加缓存检测、子代理、延迟加载等高级功能,可以渐进式演进到生产级。
开放问题与不确定性
本章列出研究中识别出的开放问题和不确定性,明确标注哪些结论需要进一步验证。
12.1 服务端实现的黑盒
Prompt Caching 的服务端存储机制(KV 状态如何存储、LRU 淘汰策略的具体参数、全局缓存的地理分布)属于 Anthropic 基础设施层面,公开信息有限。本报告对缓存机制的描述基于 API 行为规范和客户端侧的缓存破坏检测逻辑推断,服务端实际实现可能更复杂。[合理推断, 低]
12.2 Feature Flags 的实际行为
Claude Code 源码中有 87+ 个隐藏 Feature Flags(如 KAIROS、COORDINATOR_MODE、BUDDY、ULTRATHINK 等)。这些 flags 的实际运行时行为仅能从源码结构推断,缺乏运行时验证。部分 flags(如 KAIROS,跨 210 个文件引用)可能代表重大未公开功能,但本报告无法确认其完整能力。[源码逆向, 低]
12.3 多代理协作模式的成熟度
Coordinator 模式和 Swarm 模式在源码中存在定义,但其实际使用频率和成熟度不明。这些模式可能是实验性功能,尚未在生产环境中广泛部署。本报告对多代理协作的描述基于源码结构分析,可能不代表实际运行行为。[源码逆向, 低]
12.4 上下文折叠的可逆性
Tier 4 上下文折叠(Context Collapse)被描述为"可逆投影",但其恢复机制的具体实现不明确。是否真的能从折叠状态完全恢复原始消息,还是只能恢复部分信息?这需要实际运行时验证。[源码逆向, 低]
12.5 版本时效性
本报告基于 Claude Code v2.0.62–v2.1.88 的逆向分析。Anthropic 可能在后续版本中调整了架构——例如修改压缩阈值、增加新的工具、调整缓存策略。报告中的架构模式和设计原则部分具有更强的时效稳定性,但具体参数(阈值、工具数量、Feature Flag 代号)可能已变化。[合理推断, 中]
12.6 跨模型迁移的可行性
本报告的设计原则基于 Claude 模族的 API 特性(特别是 Prompt Caching、Extended Thinking、交错思考)。迁移到其他模型族(如 GPT、Gemini)时,部分原则需要调整——例如 GPT 的缓存机制不同、Gemini 的工具调用格式不同。但核心原则(静态/动态分离、搜索替换编辑、错误作为推理、渐进式压缩)是模型无关的。[合理推断, 中]
12.7 仍需实际抓包验证的点
- 三种缓存分割模式(Mode 1/2/3)的 cache_control scope 在实际 API 请求中的确切值
- 流式工具执行的具体时序——工具是在 content_block_stop 后立即执行,还是有额外延迟
- 子代理派生时的实际 API 请求结构——是否真的字节复用父代理的系统提示词
- 压缩后恢复序列中"重读最近 5 个文件"的文件选择算法
- Transcript Classifier 的实际分类逻辑和准确率
参考资料
源码逆向分析
- [1] injekt/claude-code-reverse
- Claude Code v2.0.62 逆向分析文档,包含 8 篇深度分析。覆盖主循环、工具系统、权限模型。https://github.com/injekt/claude-code-reverse
- [2] CorrineTan/cracking-claude-code-structure
- 基于 2026 年 3 月泄露的 18 个 TypeScript 文件的架构研究套件。包含 12 章深度技术指南。https://github.com/CorrineTan/cracking-claude-code-structure
- [3] changkun/claude-design-docs
- Prompt Cache Design Document,308 行详细缓存设计文档。https://github.com/changkun/claude-design-docs
- [4] childswong/claude-source-leaked
- 基于 v2.1.88 源码(1,884 文件,132K 行)的完整架构深度分析和实用指南。https://github.com/childswong/claude-source-leaked
- [5] The Complete Guide to Claude Code Internals
- 12 章深度技术指南,约 1300 行。覆盖双层生成器架构、工具执行管道、错误恢复路径。https://github.com/CorrineTan/cracking-claude-code-structure/blob/main/The-Complete-Guide-to-Claude-Code-Internals.md
- [6] Claude Code System Prompt 完整解析
- 基于 v2.1.88 源码逆向还原的完整 System Prompt 构建逻辑。https://github.com/childswong/claude-source-leaked/blob/main/practical/system-prompts.md
- [7] Claude Code JSONL transcript format
- 字段级 JSONL 格式参考。https://claude-dev.tools/docs/jsonl-format
- [8] jimmc414/claude_metrics
- Claude Code Metrics Catalog,消息条目 Schema 详细定义。https://github.com/jimmc414/claude_metrics
- [9] Claude Code 权限模型架构
- 四种权限模式和工具权限分级详解。https://github.com/childswong/claude-source-leaked/blob/main/architecture/permission-model.md
Anthropic 官方文档
- [10] Messages API
- https://docs.anthropic.com/en/api/messages
- [11] Tool use with Claude
- https://docs.anthropic.com/en/docs/agents-and-tools/tool-use/overview
- [12] Prompt caching
- https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching
- [13] Extended thinking
- https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking
- [14] Streaming Messages
- https://docs.anthropic.com/en/api/messages-streaming
- [15] Rate limits
- https://docs.anthropic.com/en/api/rate-limits
- [16] Agent SDK overview
- https://docs.anthropic.com/en/docs/agent-sdk/overview
- [17] Token counting
- https://docs.anthropic.com/en/docs/build-with-claude/token-counting
- [18] Context windows
- https://docs.anthropic.com/en/docs/build-with-claude/context-windows
- [19] Batch processing
- https://docs.anthropic.com/en/docs/build-with-claude/batch-processing
- [20] Building agents with the Claude Agent SDK
- https://www.anthropic.com/engineering/building-agents-with-the-claude-agent-sdk
跨 Agent 对比与学术分析
- [21] How Cursor Actually Works
- https://howworks.ai/blog/how-cursor-actually-works
- [22] How OpenAI Codex Works Behind-the-Scenes
- https://blog.promptlayer.com/how-openai-codex-works-behind-the-scenes-and-how-it-compares-to-claude-code/
- [23] How Cursor Works Internally
- https://adityarohilla.com/2025/05/08/how-cursor-works-internally/
- [24] Aider Architecture
- https://ggprompts.com/architecture/aider/index.html
- [25] Aider Edit Formats
- https://aider.chat/docs/more/edit-formats.html
- [26] Continue Architecture
- https://ggprompts.com/architecture/continue/index.html
- [27] OpenHands vs SWE-Agent
- https://localaimaster.com/blog/openhands-vs-swe-agent
- [28] Inside the Scaffold: A Source-Code Taxonomy of Coding Agent Architectures
- arXiv:2604.03515 — 13 个开源编码代理的源码分类学分析
- [29] Building Effective AI Coding Agents for the Terminal
- arXiv:2603.05344 — 终端编码代理的脚手架、harness 和上下文工程
- [30] How OpenAI Codex Sandboxes AI Code Execution
- https://hubaiasia.com/how-openai-codex-sandboxes-ai-code-execution/
- [31] Devin AI Explained
- https://crompt.ai/ro/blog/devin-ai-explained-how-scott-wu-built-a-2b-autonomous-coding-agent
- [32] Cursor 2.0 Launches
- https://www.grow-fast.co.uk/blog/cursor-composer-tasks-30-seconds-not-hours-november-2025
System Prompt 关键原文摘录
以下为 Claude Code 系统提示词中的关键原文片段,用于支撑前述分析的具体证据。所有内容来自源码逆向分析。
A.1 系统身份声明
You are an interactive agent that helps users with software engineering tasks. You are Claude Code, Anthropic's official CLI for Claude.
这是系统提示词的第一句话,定义了模型的角色。简短但精确——"interactive agent"暗示了多轮交互,"software engineering tasks"限定了任务域。[源码逆向, 高]
A.2 专业客观性指令
Prioritize technical accuracy and truthfulness over validating the user's beliefs. Focus on facts and problem-solving, providing direct, objective technical info without any unnecessary superlatives, praise, or emotional validation. Avoid using over-the-top validation or excessive praise when responding to users such as "You're absolutely right" or similar phrases.
这段指令直接对抗 LLM 的"谄媚倾向"——模型天然倾向于附和用户、过度赞美。通过显式禁止这些行为,Claude Code 的输出更接近专业工程师的交流风格。这是一个 prompt engineering 层面的体验优化,不涉及任何算法变更。[源码逆向, 高]
A.3 完整完成任务指令
IMPORTANT: Complete tasks fully. Do not stop mid-task or leave work incomplete. Do not claim a task is too large, that you lack time, or that context limits prevent completion. You have unlimited context through summarization. Continue working until the task is done or the user stops you.
这段指令解决了 LLM 的"中途放弃倾向"——模型在长任务中倾向于过早结束或声称无法完成。通过明确指出"unlimited context through summarization",系统提示词告知模型上下文限制不是停止的理由。这与 harness 的自动压缩策略(Tier 5)形成呼应——harness 负责处理上下文压力,模型只需要继续工作。[源码逆向, 高]
A.4 避免过度工程化指令
Don't add features, refactor code, or make "improvements" beyond what was asked. Don't create helpers, utilities, or abstractions for one-time operations. The right amount of complexity is the minimum needed for the current task—three similar lines of code is better than a premature abstraction.
"three similar lines of code is better than a premature abstraction"是一个反直觉但正确的工程原则。LLM 倾向于"帮忙"——看到重复代码就想提取函数,看到函数就想提取类。但在实际工程中,过早抽象比重复代码更危险,因为抽象增加了理解和修改的成本。这段指令将这个工程原则注入到模型的行为中。[源码逆向, 高]
A.5 工具使用优先级
Use specialized tools (Read, Glob, Grep) instead of Bash when possible. Bash is powerful but less structured — prefer purpose-built tools for file operations. When multiple read-only operations are needed, execute them in parallel rather than sequentially.
这段指令实现了两个工程目标:(1)引导模型使用结构化工具而非通用 shell,提高类型安全和权限控制粒度;(2)鼓励并行执行只读工具,减少总延迟。这与 harness 的流式工具执行设计(StreamingToolExecutor)形成配合——harness 支持并行执行,系统提示词鼓励模型生成可并行的工具调用。[源码逆向, 高]
A.6 环境信息模板
# Environment
- Primary working directory: ${cwd}
- Is a git repository: ${isGit}
- Platform: ${platform}
- Shell: ${shell}
- OS Version: ${osVersion}
- Model: ${modelName}
- Knowledge cutoff: ${cutoffDate}
这段模板填充后位于系统提示词的动态区域(边界之后)。每个变量都是会话特定的——CWD、git 状态、平台、模型名、知识截止日期。注意"Today's date"不在这里,而是通过 system-reminder 注入消息数组,避免日期变化影响系统提示词的缓存。[源码逆向, 高]
A.7 Undercover 模式
## UNDERCOVER MODE — CRITICAL You are operating UNDERCOVER in a PUBLIC/OPEN-SOURCE repository. NEVER include in commit messages or PR descriptions: - Internal model codenames (Capybara, Tengu, etc.) - Unreleased model version numbers - Internal repo or project names - The phrase "Claude Code" or any mention that you are an AI - Co-Added-By lines or any other attribution
这个 Undercover 模式是一个有趣的安全特性——当 Claude Code 在公开/开源仓库中工作时,系统提示词会注入这段指令,防止模型在 commit 消息或 PR 描述中暴露内部信息(模型代号、内部项目名、AI 身份)。这是一个 prompt injection 防御措施,防止模型被诱导泄露 Anthropic 的内部信息。[源码逆向, 高]
隐藏 Feature Flags 清单
Claude Code 源码中包含 87+ 个 Feature Flags,使用 tengu_ 前缀加随机词对故意隐藏用途。以下为通过源码结构分析识别出的核心 flags。
B.1 编译时 Flags(通过 bun:bundle feature() 控制)
| 代号 | 功能 | 引用规模 |
|---|---|---|
| KAIROS | 自主助手平台(助手模式、brief、channels、cron、webhooks) | 210 文件 |
| PROACTIVE | 主动任务规划和自动化 | 关联 KAIROS |
| COORDINATOR_MODE | 多代理编排(Coordinator + Workers) | 45 文件 |
| VOICE_MODE | 语音输入/输出 | 38 文件 |
| BUDDY | AI 伴侣精灵 | 14 文件, 1,298 行 |
| ULTRATHINK | 扩展深度推理模式 | 编译时 |
| ULTRAPLAN | 超级规划器 | 编译时 |
| TORCH | 推理增强 | 编译时 |
| BRIDGE_MODE | 移动/Web 远程控制 | 编译时 |
B.2 运行时 Flags(通过 Statsig 动态控制)
| 代号 | 实际功能 | 识别依据 |
|---|---|---|
| tengu_frond_boric | Analytics killswitch(分析事件开关) | 遥测代码路径 |
| tengu_passport_quail | Memory extraction gate(记忆提取门控) | 记忆相关代码 |
| tengu_amber_json_tools | JSON tool format(token 高效工具格式) | 工具序列化代码 |
| tengu_tool_pear | Structured output(严格工具模式) | strict mode 代码 |
| CONTEXT_COLLAPSE | 智能上下文折叠(Tier 4 压缩) | 压缩管道代码 |
| REACTIVE_COMPACT | 响应式会话压缩 | 压缩管道代码 |
| CACHED_MICROCOMPACT | 微缓存压缩 | 压缩管道代码 |
| HISTORY_SNIP | 历史消息分片 | 消息管理代码 |
| TRANSCRIPT_CLASSIFIER | Auto 模式的转录分类器 | 权限系统代码 |
| AGENT_TRIGGERS | 定时任务(Cron)支持 | 工具注册代码 |
| MONITOR_TOOL | 系统监控工具 | 工具注册代码 |
Feature Flags 的功能描述基于源码结构分析(变量名、文件引用、代码路径),缺乏官方确认。部分 flags 可能有多重用途,或在不同版本中行为不同。KAIROS 的 210 文件引用规模暗示它可能是一个大型功能平台,而非单一功能。
B.3 Flag 命名策略分析
运行时 flags 使用 tengu_ 前缀加两个随机英文单词(如 frond_boric、passport_quail)的组合。这种命名策略的目的是:[合理推断, 中]
- 反逆向:随机词对不暗示功能,使得逆向分析者无法从 flag 名推断用途
- 唯一性:两个随机词的组合空间足够大,避免碰撞
- 可搜索性:开发者在代码中搜索 flag 名时不会产生无关结果
- "Tengu" 代号:Tengu(天狗)是日本神话中的生物,可能是 Anthropic 内部的项目代号
编译时 flags(如 KAIROS、BUDDY)使用有意义的代号,因为它们通过 bun:bundle 的 feature() 函数进行死代码消除——如果 flag 关闭,相关代码不会出现在最终 bundle 中,因此不需要隐藏名称。运行时 flags 需要隐藏名称,因为它们出现在最终代码中,只是通过 Statsig 动态控制启停。
API 端点完整清单
Claude Code 除了主对话 API 外,还使用多个辅助端点用于认证、配置、遥测和反馈。
| 端点 | 用途 | 认证方式 |
|---|---|---|
/v1/messages | 主对话 API(流式) | API Key / OAuth |
/v1/messages/count_tokens | Token 计数 | API Key / OAuth |
/api/claude_cli_profile | CLI 配置获取 | OAuth |
/api/oauth/profile | OAuth 用户信息 | OAuth |
/api/oauth/claude_cli/roles | 用户角色查询 | OAuth |
/api/oauth/claude_cli/create_api_key | 创建 API Key | OAuth |
/api/event_logging/batch | 事件日志批量上报 | 内部 |
/api/claude_code/metrics | 使用指标上报 | 内部 |
/api/claude_cli_feedback | 用户反馈提交 | API Key / OAuth |
statsig.anthropic.com/v1/ | Feature flag 服务 | 内部 |
o1158394.ingest.us.sentry.io | 错误监控(Sentry) | 内部 |
cdn.growthbook.io | A/B 测试 | 内部 |
C.1 客户端请求头配置
{
"defaultHeaders": {
"x-app": "cli",
"User-Agent": "claude-code/2.1.88",
"x-claude-remote-container-id": "",
"x-claude-remote-session-id": "",
"x-anthropic-additional-protection": "true"
},
"maxRetries": 3,
"timeout": 600000
}
每个请求头都有特定用途:x-app: cli 标识客户端类型;User-Agent 包含版本号用于兼容性;远程容器和会话 ID 用于远程环境追踪;x-anthropic-additional-protection 启用额外的服务端安全检查。10 分钟超时确保长任务不会因网络空闲而被切断。[源码逆向, 高]
C.2 OAuth 认证流程
Claude Code 支持两种认证方式:API Key 和 OAuth。OAuth 流程涉及以下步骤:[源码逆向, 高]
- CLI 启动时检查本地 OAuth token(
~/.claude/credentials.json) - 如无有效 token,启动浏览器 OAuth 流程
- 用户在浏览器中授权后,回调将 token 写入本地
- 后续 API 请求使用
Authorization: Bearer <token>头 - Token 过期时自动刷新(使用 refresh token)
- 可通过
/api/oauth/claude_cli/create_api_key将 OAuth token 转换为 API Key
OAuth 模式允许 Anthropic 追踪用户身份和用量,用于计费和欺诈检测。API Key 模式更简单但不支持用户级追踪。两种模式下 API 行为完全相同。
工具描述实例与写法分析
本附录展示 Claude Code 核心工具的描述原文(或高度还原),分析其写法如何影响模型行为。注:以下描述摘自 v2.0.x 逆向,字段名 old_string/new_string 在后续版本中更名为 old_str/new_str。
D.1 BashTool 描述分析
Executes a given bash command in the specified working directory. Key features: - Runs commands in the project's working directory - Supports background execution with run_in_background - Commands are validated for safety before execution - Output is streamed back in real-time Use this tool for: - Running build commands (npm run build, cargo build) - Running tests (pytest, jest) - Git operations (git status, git diff, git log) - Package management (npm install, pip install) - System operations (ls, ps, df) Do NOT use this tool for: - Reading files — use the Read tool instead - Searching files — use Glob or Grep instead - Editing files — use the Edit or Write tool instead Parameters: - command (string, required): The bash command to execute - timeout (number, optional): Max execution time in ms (default: 120000) - run_in_background (boolean, optional): Run without blocking
BashTool 的描述展示了 Claude Code 工具描述的四个模式在实践中的体现:[源码逆向, 高]
- 功能定义("Executes a given bash command"):一句话说清工具做什么
- 使用场景("Use this tool for"):列出 5 类典型场景,帮助模型判断何时使用
- 负面指导("Do NOT use this tool for"):明确列出 3 个不该用 Bash 的场景,指向更精确的专用工具
- 参数验证规则("Parameters"):每个参数的类型、是否必填、默认值
"Do NOT use" 部分是描述中最有价值的设计。没有这段指导,模型会倾向于用 Bash 做所有事情(因为 Bash 最通用),导致:(1)权限控制粒度变粗(Bash 是 Level 1-2 权限,Read/Glob/Grep 是 Level 0);(2)输出格式不一致(Bash 输出是原始 stdout,专用工具输出是结构化的);(3)并发性降低(Bash 不可并行,Read/Glob/Grep 可并行)。
D.2 FileEditTool 描述分析
Performs exact string replacements in files. The edit will FAIL if old_string is not found in the file, or if it appears more than once (unless replace_all is true). This is a safety feature — it prevents accidental edits to the wrong location. Requirements: - You MUST read the file with Read before editing it - old_string must be unique in the file (or use replace_all) - old_string and new_string must be different Parameters: - file_path (string, required): Absolute path to the file - old_string (string, required): The text to replace (must be unique) - new_string (string, required): The replacement text - replace_all (boolean, optional): Replace all occurrences - notebook (boolean, optional): Edit a Jupyter notebook cell
FileEditTool 的描述明确解释了设计意图:"This is a safety feature — it prevents accidental edits to the wrong location"。这种将设计意图写入描述的做法帮助模型理解为什么工具这样工作,而不仅仅是如何工作。当编辑失败时,模型不会困惑于"为什么不让我编辑",而是理解"哦,这是因为 old_str 不唯一,我需要提供更多上下文"。[源码逆向, 高]
D.3 AgentTool (Task) 描述分析
Launch a new agent that has access to tools to help answer the user's question. The agent runs autonomously and returns a summary of its work. Use this tool when: - A task requires multiple steps that can be done independently - You need to explore a large codebase without filling your context - You want to delegate a subtask to avoid context pollution - A task can be parallelized across multiple agents Do NOT use this tool when: - The task is simple enough to do directly - You need fine-grained control over tool execution - The task requires access to the parent's conversation context The agent will have its own conversation context and tool access. It cannot see your current conversation history. When it completes, it returns a text summary — you should relay the key findings to the user. Parameters: - description (string, required): 3-5 word summary of the task - prompt (string, required): Full task description for the agent - subagent_type (string, optional): "general_purpose_task" | "Explore" | "Plan" - model (string, optional): "sonnet" | "opus" | "haiku" - run_in_background (boolean, optional): Non-blocking execution
AgentTool 的描述包含两个关键的上下文隔离说明:"It cannot see your current conversation history" 和 "returns a text summary"。这两个说明防止模型错误地假设子代理可以访问父代理的上下文,或期望子代理返回完整数据而非摘要。[源码逆向, 高]
D.4 工具描述长度与 token 开销
工具描述的长度直接影响 token 开销。以下是核心工具的近似描述长度:[合理推断, 中]
| 工具 | 描述长度(字符) | 近似 token |
|---|---|---|
| BashTool | ~1200 | ~300 |
| FileReadTool | ~800 | ~200 |
| FileEditTool | ~700 | ~175 |
| FileWriteTool | ~500 | ~125 |
| GlobTool | ~400 | ~100 |
| GrepTool | ~600 | ~150 |
| AgentTool | ~1000 | ~250 |
| WebFetchTool | ~500 | ~125 |
| WebSearchTool | ~400 | ~100 |
| TodoWriteTool | ~600 | ~150 |
10 个核心工具的描述总计约 1,675 token。加上 input_schema(JSON Schema 定义),每个工具约增加 100-300 token。10 个核心工具的完整定义约 4,000-5,000 token。这就是为什么延迟加载策略很重要——66+ 个工具如果全部加载,仅工具定义就消耗 20,000-30,000 token,占 200K 上下文窗口的 10-15%。
端到端完整流程示例
本附录通过一个具体场景——"用户要求修复 auth.ts 中的登录 bug"——展示完整的端到端流程,包括每一步的输入、输出和 API 请求/响应结构。
E.1 场景设定
用户在 Claude Code CLI 中输入:"帮我修复 auth.ts 中的登录 bug,用户反馈说密码正确但登录失败"。工作目录是 /home/user/myapp,这是一个 git 仓库。
E.2 第一轮循环
阶段 1-2:输入预处理与系统提示词组装
用户输入不包含 slash command,无附件。harness 将输入包装为 user 消息。同时,getUserContext() 加载 CLAUDE.md(如果存在)和 git 状态快照,注入为 system-reminder。
// 消息数组初始状态
[
{ "role": "user", "content": [
{ "type": "text", "text": "\nToday's date is 2026-08-08.\nCLAUDE.md: This project uses TypeScript and Jest.\n " },
{ "type": "text", "text": "帮我修复 auth.ts 中的登录 bug,用户反馈说密码正确但登录失败" }
]}
]
阶段 3-5:工具构建与 API 请求
harness 构建工具列表(核心工具完整加载 + 延迟工具只加载 name/description)和系统提示词(静态区 + 动态区),发送 API 请求。
// API 请求(简化)
POST /v1/messages
{
"model": "claude-sonnet-4-20250514",
"max_tokens": 8000,
"system": [
{ "type": "text", "text": "" },
{ "type": "text", "text": "" },
{ "type": "text", "text": "<静态系统提示词>",
"cache_control": { "type": "ephemeral", "scope": "global" } },
{ "type": "text", "text": "# Environment\n- cwd: /home/user/myapp\n- Platform: linux\n- Model: claude-sonnet-4\n..." }
],
"tools": [
{ "name": "Read", "description": "...", "input_schema": {...},
"cache_control": { "type": "ephemeral" } },
{ "name": "Grep", "description": "...", "input_schema": {...} },
{ "name": "Glob", "description": "...", "input_schema": {...} },
{ "name": "Edit", "description": "...", "input_schema": {...} },
{ "name": "Bash", "description": "...", "input_schema": {...} },
// ... 延迟工具只有 name + description
],
"messages": [
{ "role": "user", "content": [
{ "type": "text", "text": "..." },
{ "type": "text", "text": "帮我修复 auth.ts 中的登录 bug..." }
]}
],
"thinking": { "type": "adaptive" },
"stream": true
}
阶段 6:响应解析
模型返回流式响应,包含 thinking 块、text 块和两个 tool_use 块(并行读取 auth.ts 和搜索相关测试)。
// 响应内容块
[
{ "type": "thinking", "thinking": "用户报告登录 bug...",
"signature": "WaUjzkyp..." },
{ "type": "text", "text": "我来先看一下 auth.ts 的代码和相关测试。" },
{ "type": "tool_use", "id": "toolu_001", "name": "Read",
"input": { "file_path": "/home/user/myapp/src/auth.ts" } },
{ "type": "tool_use", "id": "toolu_002", "name": "Grep",
"input": { "pattern": "login|auth", "path": "/home/user/myapp/src",
"glob": "*.test.ts" } }
]
// stop_reason: "tool_use"
阶段 7:流式工具执行
两个工具都是只读工具(Read 和 Grep),可以并行执行。StreamingToolExecutor 在 content_block_stop 事件后立即开始执行,不等整个响应完成。
// 工具执行结果
toolu_001 (Read): "export async function login(email, password) {\n
const user = await db.findUser(email);\n if (!user) throw new Error('User not found');\n
const valid = await bcrypt.compare(password, user.passwordHash);\n if (!valid) throw new Error('Invalid password');\n
// Bug: passwordHash is stored as plain text in some old records\n
return generateToken(user);\n}"
toolu_002 (Grep): "src/auth.test.ts:15: it('should login with correct password', async () => {\n
src/auth.test.ts:28: it('should reject wrong password', async () => {"
阶段 8:tool_result 回灌
两个 tool_result 被包装为一条 user 消息追加到消息数组。
// 消息数组更新后
[
{ "role": "user", "content": "...原始输入..." },
{ "role": "assistant", "content": [
{ "type": "thinking", "thinking": "...", "signature": "..." },
{ "type": "text", "text": "我来先看一下 auth.ts 的代码..." },
{ "type": "tool_use", "id": "toolu_001", "name": "Read",
"input": { "file_path": "/home/user/myapp/src/auth.ts" } },
{ "type": "tool_use", "id": "toolu_002", "name": "Grep",
"input": { "pattern": "login|auth", ... } }
]},
{ "role": "user", "content": [
{ "type": "tool_result", "tool_use_id": "toolu_001",
"content": "export async function login(email, password) {..." },
{ "type": "tool_result", "tool_use_id": "toolu_002",
"content": "src/auth.test.ts:15: ..." }
]}
]
E.3 第二轮循环
模型看到了 auth.ts 的代码和测试文件位置。它发现 bug:bcrypt.compare 可能因为旧的明文密码记录而失败。模型决定先读取测试文件确认测试覆盖情况,然后修复 auth.ts。
// 第二轮响应
[
{ "type": "thinking", "thinking": "代码中 bcrypt.compare 假设所有
passwordHash 都是 bcrypt 哈希。但如果旧记录存储了明文密码,
bcrypt.compare 会返回 false,导致登录失败。需要添加兼容性检查...",
"signature": "..." },
{ "type": "text", "text": "发现问题了。auth.ts 的 login 函数直接使用
bcrypt.compare,但旧用户记录可能存储了明文密码。我来修复这个问题。" },
{ "type": "tool_use", "id": "toolu_003", "name": "Read",
"input": { "file_path": "/home/user/myapp/src/auth.test.ts" } }
]
// stop_reason: "tool_use"
E.4 第三轮循环
模型读取了测试文件,确认测试只覆盖了 bcrypt 哈希密码的场景。现在执行修复。
// 第三轮响应(关键部分)
[
{ "type": "text", "text": "我来修复 login 函数,添加对明文密码的兼容性检查。" },
{ "type": "tool_use", "id": "toolu_004", "name": "Edit",
"input": {
"file_path": "/home/user/myapp/src/auth.ts",
"old_str": "const valid = await bcrypt.compare(password, user.passwordHash);\n if (!valid) throw new Error('Invalid password');",
"new_str": "let valid;\n if (user.passwordHash.startsWith('$2')) {\n valid = await bcrypt.compare(password, user.passwordHash);\n } else {\n valid = password === user.passwordHash;\n if (valid) {\n // Migrate to bcrypt hash\n user.passwordHash = await bcrypt.hash(password, 10);\n await db.updateUser(user);\n }\n }\n if (!valid) throw new Error('Invalid password');"
}
}
]
E.5 第四轮循环
模型执行了编辑,现在运行测试验证修复。
// 第四轮响应
[
{ "type": "tool_use", "id": "toolu_005", "name": "Bash",
"input": { "command": "npm test -- --testPathPattern=auth" } }
]
工具执行结果:测试通过。模型在第五轮返回 stop_reason: "end_turn",循环结束。
E.6 完整循环统计
| 轮次 | 工具调用 | 新增 token | 缓存命中 |
|---|---|---|---|
| 1 | Read + Grep (并行) | ~3,500 | ~28K (system+tools) |
| 2 | Read | ~2,800 | ~31K |
| 3 | Edit | ~1,200 | ~34K |
| 4 | Bash | ~1,800 | ~35K |
| 5 | 无 (end_turn) | ~300 | ~37K |
5 轮循环总计约 9,600 新增 token(input + output),缓存命中约 165K token。如果没有缓存,总输入 token 约 174K;有缓存时,实际计费输入约 43K(9.6K 新增 + 28K 缓存创建 + 约 5K 未命中),缓存读取约 165K(按 10% 计费,等效 16.5K)。总成本降低约 4 倍。[合理推断, 中]
这个示例展示了极简循环如何完成复杂任务。5 轮循环、5 个工具调用,就完成了 bug 定位、修复和验证。每轮循环的决策(读什么文件、怎么搜索、怎么修复、怎么验证)全部由模型自主做出,harness 只负责执行工具和回灌结果。这就是"决策下放给模型"原则的实际效果。
附录 F:核心工具 Schema 完整实例
本附录摘录 Claude Code 中六个核心工具的 input_schema 定义,供自研 Agent 时参考工具描述的精确写法。所有 schema 均为 JSON Schema 格式,模型据此生成合法的 tool_use 块。
F.1 BashTool 完整 Schema
{
"name": "Bash",
"description": "Executes a given bash command in a persistent shell session...\n\nKey features:\n- Persistent session: Environment variables and directory changes persist between commands\n- Timeout: Commands have a 120-second timeout\n- Output handling: Both stdout and stderr are captured\n\nUsage notes:\n- Use this tool for running tests, builds, git operations, etc.\n- Prefer specific tools (Edit, Read) over bash for file operations\n- For long-running commands, consider using timeout or nohup\n\nExamples:\n git status\n npm test\n find . -name '*.ts' -not -path '*/node_modules/*'",
"input_schema": {
"type": "object",
"properties": {
"command": {
"type": "string",
"description": "The bash command to execute. Can be multiple lines."
},
"timeout": {
"type": "number",
"description": "Optional timeout in milliseconds (max 600000)",
"default": 120000
}
},
"required": ["command"]
}
}
设计要点:description 中嵌入使用范例("Examples"),降低模型误用概率。timeout 字段给了模型自主控制超时的能力,但设置上限防止无限等待。
F.2 EditTool 完整 Schema
{
"name": "Edit",
"description": "Performs exact string replacements in files.\n\nUsage:\n- Provide old_str and new_str\n- old_str must match exactly once in the file\n- For multiple edits, call Edit multiple times\n\nTips:\n- Include enough context in old_str to be unique\n- For large changes, consider Write instead\n- Whitespace must match exactly",
"input_schema": {
"type": "object",
"properties": {
"file_path": {
"type": "string",
"description": "Absolute path to the file"
},
"old_str": {
"type": "string",
"description": "Text to replace (must appear exactly once)"
},
"new_str": {
"type": "string",
"description": "Replacement text"
},
"replace_all": {
"type": "boolean",
"description": "Replace all occurrences (default: false)",
"default": false
}
},
"required": ["file_path", "old_str", "new_str"]
}
}
设计要点:old_str 必须唯一匹配的设计,迫使模型在编辑前先读取文件内容,间接保证了"先读后改"的安全模式。replace_all 参数处理批量替换场景。
F.3 AgentTool (Task) 完整 Schema
{
"name": "Task",
"description": "Launch a new agent to handle complex, multi-step tasks autonomously.\n\nAvailable agent types:\n- general_purpose_task: General coding task\n- Explore: Read-only exploration\n- Plan: Implementation planning\n\nWhen to use:\n- Complex multi-step coding tasks\n- Operations producing large output\n- Changes across many layers\n- Independent parallelizable subtasks\n\nWhen NOT to use:\n- Sequential tasks needing prior output\n- Single logical task\n- Read-only search (use search tools)\n- Editing a single file",
"input_schema": {
"type": "object",
"properties": {
"subagent_type": {
"type": "string",
"description": "Type of agent to launch"
},
"query": {
"type": "string",
"description": "Task for the agent (max 80 words)"
},
"description": {
"type": "string",
"description": "Short 3-5 word description"
},
"response_language": {
"type": "string",
"description": "Language for response"
}
},
"required": ["subagent_type", "query", "description", "response_language"]
}
}
设计要点:query 限制 80 词,迫使主 Agent 精炼任务描述而非倾倒上下文。description 字段用于 UI 展示和日志追踪。response_language 确保子 Agent 输出语言与用户期望一致。
F.4 ReadTool Schema
{
"name": "Read",
"description": "Reads a file from the local filesystem.\n\nFeatures:\n- Reads up to 2000 lines by default\n- Supports offset and limit for large files\n- Lines longer than 2000 chars are truncated\n- Returns content with line numbers (cat -n format)\n- Can read image files (PNG, JPG, GIF, WEBP)\n\nUsage:\n- Use absolute paths\n- For large files, use offset/limit\n- Multiple reads can be batched in parallel",
"input_schema": {
"type": "object",
"properties": {
"file_path": {
"type": "string",
"description": "Absolute path to file"
},
"offset": {
"type": "integer",
"description": "Starting line number (min 1)",
"minimum": 1
},
"limit": {
"type": "integer",
"description": "Number of lines to read (min 1)",
"minimum": 1
},
"target": {
"type": "string",
"description": "For images: what to extract"
}
},
"required": ["file_path"]
}
}
F.5 WriteTool Schema
{
"name": "Write",
"description": "Writes content to a file, overwriting if exists.\n\nRules:\n- Must read existing file before overwriting\n- Creates parent directories if needed\n- Prefer editing over creating new files\n- Never create docs unless requested\n\nUsage:\n- Use for new files or complete rewrites\n- For partial edits, use Edit instead",
"input_schema": {
"type": "object",
"properties": {
"file_path": {
"type": "string",
"description": "Absolute path"
},
"content": {
"type": "string",
"description": "Content to write"
}
},
"required": ["file_path", "content"]
}
}
F.6 GlobTool Schema
{
"name": "Glob",
"description": "Fast file pattern matching tool.\n\nSupports:\n- Glob patterns: '*.js', 'src/**/*.ts'\n- Returns paths sorted by modification time\n- Use when finding files by name pattern\n- For content search, use Grep instead\n- For multi-round open search, use Agent tool",
"input_schema": {
"type": "object",
"properties": {
"pattern": {
"type": "string",
"description": "Glob pattern"
},
"path": {
"type": "string",
"description": "Directory to search (default: cwd)"
}
},
"required": ["pattern"]
}
}
F.7 GrepTool Schema
{
"name": "Grep",
"description": "Search file contents using ripgrep.\n\nFeatures:\n- Full regex syntax
- Filter by glob (*.js) or type (py, rust)\n- Output modes: content, files_with_matches, count\n- Multiline mode for cross-line patterns\n- Use -A/-B/-C for context lines\n\nIMPORTANT:\n- ALWAYS use Grep, NEVER invoke 'grep' or 'rg' directly\n- Use Task tool for multi-round searches",
"input_schema": {
"type": "object",
"properties": {
"pattern": {
"type": "string",
"description": "Regex pattern"
},
"path": {
"type": "string",
"description": "File or directory to search"
},
"glob": {
"type": "string",
"description": "File pattern filter"
},
"type": {
"type": "string",
"description": "File type filter"
},
"output_mode": {
"type": "string",
"enum": ["content", "files_with_matches", "count"],
"default": "files_with_matches"
},
"-A": {"type": "integer", "minimum": 0},
"-B": {"type": "integer", "minimum": 0},
"-C": {"type": "integer", "minimum": 0},
"-n": {"type": "boolean", "default": true},
"-i": {"type": "boolean", "default": false},
"multiline": {"type": "boolean", "default": false},
"head_limit": {"type": "integer", "minimum": 0},
"offset": {"type": "integer", "minimum": 0}
},
"required": ["pattern"]
}
}
设计要点:Grep 的 schema 暴露了丰富的参数(-A/-B/-C、-n、-i、multiline、head_limit、offset),这些直接映射到 ripgrep 的命令行参数。description 中用大写 "IMPORTANT" 强调不要直接调用 grep/rg,这是防止模型绕过工具直接用 Bash 执行搜索的设计。
F.8 Schema 设计的六条原则
从上述六个工具的 schema 设计中,可以提炼出以下原则:
- description 是给模型看的说明书:不是给开发者看的注释。必须包含使用场景、注意事项、范例。Claude Code 的每个工具 description 都有 200-500 字符的详细说明。
- required 字段最小化:只标注真正必需的字段,其余给默认值。这降低了模型生成 tool_use 的认知负担。
- 用 enum 约束枚举值:如 Grep 的
output_mode用 enum 限定三个值,防止模型生成无效选项。 - 用 minimum/maximum 约束数值范围:如
offset的minimum: 1,防止模型传入 0 或负数。 - description 中嵌入反模式:明确告诉模型"不要做什么",如"NEVER invoke grep directly"、"For partial edits, use Edit instead"。
- 工具间职责边界清晰:Read vs Grep vs Glob 的 description 中互相引用,形成自洽的工具生态,减少模型选择困难。
附录 G:上下文压缩算法伪代码
本附录基于公开逆向分析,还原 Claude Code 在上下文逼近窗口上限时的压缩策略。核心思想是:保留高信息密度内容,丢弃可重新获取的低价值内容。
G.1 压缩触发条件
// 伪代码:压缩触发判定
function shouldCompress(messages, model_context_window):
total_tokens = countTokens(messages)
// 阈值 1:绝对 token 数
if total_tokens > model_context_window * 0.92:
return true
// 阈值 2:剩余空间不足以容纳一轮工具调用
estimated_next_round = estimateNextRoundTokens(messages)
remaining = model_context_window - total_tokens
if remaining < estimated_next_round * 1.5:
return true
// 阈值 3:工具结果累积超过总量的 60%
tool_result_tokens = sumToolResultTokens(messages)
if tool_result_tokens > total_tokens * 0.60:
return true // 触发工具结果优先压缩
return false
G.2 六级压缩策略执行顺序
// 伪代码:渐进式压缩管道(对应正文 Tier 0-5)
function compressContext(messages):
// Tier 0: 工具结果截断(执行时,零成本)
// 将超过阈值的 tool_result 截断为摘要 + 首尾保留
for msg in messages where msg.role == "tool_result":
if charCount(msg.content) > 50000:
msg.content = truncateSmart(msg.content)
// truncateSmart: 保留前 20K 字符 + "[...truncated...]"
// + 后 10K 字符
if countTokens(messages) < threshold:
return messages // Tier 0 足够
// Tier 1: 预算缩减(>50% 上下文,零成本)
// 将所有工具结果缩减至 30K/15K 字符上限
for msg in messages where msg.role == "tool_result":
if charCount(msg.content) > 30000:
msg.content = truncateSmart(msg.content, max_chars=30000)
if countTokens(messages) < threshold:
return messages // Tier 1 足够
// Tier 2: 历史裁剪(>60% 上下文,零成本)
// 去重冗余文件读取,移除重复的 Read 结果
old_tool_results = filterMessages(messages,
role="tool_result",
age=OLDER_THAN_5_ROUNDS
)
for result in old_tool_results:
if isDuplicateRead(result):
result.content = "[Duplicate read result removed]"
else:
summary = quickSummarize(result.content, max_tokens=200)
result.content = f"[Summary: {summary}]"
result.is_summarized = true
if countTokens(messages) < threshold:
return messages // Tier 2 足够
// Tier 3: 微压缩(>70% + 缓存冷,近零成本)
// 移除旧工具结果,保留最近 N 轮
old_messages = filterMessages(messages,
age=OLDER_THAN_10_ROUNDS,
exclude_types=["system", "latest_user_query"]
)
for msg in old_messages:
if msg.role == "tool_result":
msg.content = "[Removed during micro-compaction]"
if countTokens(messages) < threshold:
return messages // Tier 3 足够
// Tier 4: 上下文折叠(高利用率,低成本)
// 可逆投影:将非活跃段投影为紧凑表示,保留恢复元数据
inactive_segments = identifyInactiveSegments(messages)
for segment in inactive_segments:
folded = foldSegment(segment) // 可逆操作
messages = replaceRange(messages, segment, folded)
if countTokens(messages) < threshold:
return messages // Tier 4 足够
// Tier 5: 自动压缩(>85%,1-2 次 API 调用)
// 用 LLM 对整个对话历史生成摘要
old_conversation = filterMessages(messages,
age=OLDER_THAN_10_ROUNDS,
exclude_types=["system", "latest_user_query"]
)
conversation_summary = modelSummarize(old_conversation)
messages = replaceRange(messages,
old_conversation,
createSummaryMessage(conversation_summary)
)
// 摘要消息格式:
// {role: "user", content: "[Previous conversation summary: ...]"}
// 压缩后恢复序列:重读最近 5 个文件(预算 50K tokens)
recent_files = getRecentlyAccessedFiles(messages, count=5)
for file_path in recent_files:
content = readFile(file_path)
messages.append({
role: "tool_result",
content: f"[Re-read after compaction: {file_path}]\n{content}"
})
return messages
G.3 缓存失效检测与最小化
// 伪代码:压缩后的缓存前缀维护
function maintainCachePrefix(messages, cache_breakpoints):
// Claude Code 设置三个缓存断点:
// BP1: system prompt 末尾(静态,几乎不变)
// BP2: tools 列表末尾(半静态,工具列表很少变)
// BP3: 对话历史中部(动态,压缩时可能失效)
bp1_position = findBreakpoint(messages, cache_breakpoints[0])
bp2_position = findBreakpoint(messages, cache_breakpoints[1])
bp3_position = findBreakpoint(messages, cache_breakpoints[2])
// 检查压缩是否破坏了前缀
if compressionModified(messages, before=0, after=bp1_position):
// BP1 失效(极罕见,通常是 system prompt 变更)
invalidateAllCache()
elif compressionModified(messages, before=bp1_position, after=bp2_position):
// BP2 失效(工具列表变更)
invalidateCacheAfter(bp1_position)
elif compressionModified(messages, before=bp2_position, after=bp3_position):
// BP3 失效(对话历史被压缩)
invalidateCacheAfter(bp2_position)
// BP1 和 BP2 仍然有效,节省大量缓存计算
// 重新设置断点
setBreakpoint(messages, bp1_position, type="ephemeral")
setBreakpoint(messages, bp2_position, type="ephemeral")
setBreakpoint(messages, bp3_position, type="ephemeral")
G.4 压缩策略的保留优先级矩阵
| 内容类型 | 保留优先级 | 压缩方式 | 理由 |
|---|---|---|---|
| System Prompt | 最高(不可压缩) | 保持原样 | 包含核心指令和工具定义,修改会导致缓存全失效 |
| 最近 3 轮对话 | 高 | 保持原样 | 当前任务的直接上下文 |
| 最近工具结果 | 高 | 截断(保留首尾) | 模型可能需要引用最近结果做决策 |
| 用户原始需求 | 高 | 保持原样 | 任务目标不能丢失 |
| CLAUDE.md 内容 | 中高 | 保持原样(在 system 中) | 项目级指令,通常不大 |
| 历史工具结果 | 中 | 摘要化 | 可重新执行获取,但摘要能保留关键信息 |
| 历史对话 | 低 | 折叠为摘要 | 决策过程不如结果重要 |
| thinking 块 | 最低 | 直接丢弃 | 推理过程已体现在行动中 |
附录 H:Agent Harness 横向对比矩阵
本附录以结构化矩阵形式,对五种主流 Coding Agent 的关键工程决策进行横向对比,供自研时做技术选型参考。
H.1 架构维度对比
| 维度 | Claude Code | Cursor | Codex CLI | Aider | SWE-agent |
|---|---|---|---|---|---|
| 宿主形态 | CLI / IDE 插件 | VS Code Fork | CLI | CLI | CLI(学术) |
| 主循环模型 | Claude 3.5/4 Sonnet/Opus | 多模型可选 | GPT-4o/o3 | 多模型可选 | 任意 API |
| 控制流 | 极简 while 循环 | Tab 补全 + Agent 模式 | 极简 while 循环 | while 循环 + Git | 固定 ACI 模板 |
| 上下文窗口 | 200K tokens | 取决于模型 | 128K-200K | 取决于模型 | 取决于模型 |
| 子 Agent 支持 | 原生(Task 工具) | Composer 多面板 | 无 | 无 | 无 |
| 流式输出 | SSE 流式 | SSE 流式 | SSE 流式 | 非流式/流式 | 非流式 |
H.2 工具系统对比
| 维度 | Claude Code | Cursor | Codex CLI | Aider | SWE-agent |
|---|---|---|---|---|---|
| 文件编辑方式 | 搜索替换(old_str/new_str) | diff 应用 | diff/patch | SEARCH/REPLACE 块 | 行号编辑 |
| Shell 执行 | Bash 工具(持久 session) | 集成终端 | Shell 工具 | 无(通过 Git) | 有限 shell |
| 代码搜索 | Grep (ripgrep) + Glob | 内置索引搜索 | grep/find | grep/ctags | find + grep |
| 工具数量 | 66+ 注册 / 12-15 活跃 | 5-8 个 | 1 shell(核心) | 3-5 个 | 4 个 |
| 工具描述长度 | 200-500 字/工具 | 较短 | 中等 | 极简 | ACI 模板化 |
| deferred loading | 支持(按需加载) | 不支持 | 不支持 | 不支持 | 不支持 |
H.3 上下文管理对比
| 维度 | Claude Code | Cursor | Codex CLI | Aider | SWE-agent |
|---|---|---|---|---|---|
| Prompt Caching | 三层断点(system/tools/history) | 有限使用 | 未确认 | 不使用 | 不使用 |
| 上下文压缩 | 六级渐进式 | 滑窗 + 摘要 | 滑窗 | repo-map + 滑窗 | 固定截断 |
| 会话持久化 | JSONL 本地文件 | IDE 内部存储 | JSONL | Git commit 历史 | 实验日志 |
| 项目记忆 | CLAUDE.md 自动加载 | .cursorrules | codex.md | .aider.conf.yml | 无 |
| 多模型路由 | 主模型 + 侧查询模型 | 用户选择 | 单一模型 | 弱模型 + 强模型 | 单一模型 |
H.4 安全模型对比
| 维度 | Claude Code | Cursor | Codex CLI | Aider | SWE-agent |
|---|---|---|---|---|---|
| 权限模式 | 四模式(plan/auto/default/bypass) | IDE 权限 | 确认制 | Git 守护 | 沙箱 |
| 命令拦截 | 七层纵深防御 | IDE 提示 | 用户确认 | 无(Git 回滚) | 容器隔离 |
| 路径安全 | 项目根目录约束 | 工作区约束 | 工作区约束 | 无 | 容器隔离 |
| 沙箱执行 | 无(靠权限模型) | 无 | 无 | 无 | Docker 容器 |
| 可回滚性 | Git + 会话恢复 | Git | Git | Git 原生 | 实验重放 |
H.5 关键设计决策对比
| 决策点 | Claude Code | Cursor | Codex CLI | Aider |
|---|---|---|---|---|
| 编辑方式选择理由 | 搜索替换迫使"先读后改" | Diff 更接近 Git 工作流 | Diff 标准化 | SEARCH/REPLACE 避免行号漂移 |
| 循环设计哲学 | 模型决策,harness 执行 | 混合:Tab 补全 + Agent | 模型决策,harness 执行 | 模型决策 + Git 约束 |
| 子 Agent 策略 | 派生隔离 + 缓存共享 | 面板并行 | 无子 Agent | 无子 Agent |
| 错误恢复 | 模型自主重试 + 降级 | 用户干预 | 模型自主 | Git 回滚 + 重试 |
| 延迟隐藏策略 | 流式 thinking + 工具执行并行 | Tab 即时响应 | 流式输出 | 无延迟隐藏 |
附录 I:术语表
本报告使用的核心术语定义,按字母顺序排列。标注[来源]的术语引自 Anthropic 官方文档或公开逆向分析。
| 术语 | 定义 | 来源 |
|---|---|---|
| ACI (Agent-Computer Interface) | Agent 与计算机交互的工具接口设计,包括工具名称、参数、反馈格式。SWE-agent 论文提出此概念。 | 学术论文 |
| Agentic Loop | Agent 的核心控制循环:模型推理 → 工具调用 → 结果回灌 → 再次推理,直到任务完成或用户中断。 | 行业共识 |
| Cache Breakpoint | Prompt Caching 中标记缓存边界的锚点,通过 cache_control 参数设置。前缀匹配到断点则命中缓存。 | Anthropic 官方文档 |
| cache_control | API 请求中的参数,标记从请求开始到该位置的内容可被缓存。设为 ephemeral 类型。 | Anthropic 官方文档 |
| CLAUDE.md | Claude Code 自动加载的项目级指令文件,包含编码规范、项目结构说明、常见命令等。在每次请求中以 system-reminder 注入。 | 源码逆向 |
| Content Block | API 响应中的结构化内容单元,类型包括 text、tool_use、thinking。一条 assistant 消息可包含多个 block。 | Anthropic 官方文档 |
| Deferred Loading | 工具的延迟加载机制,仅在模型可能需要某工具时才将其 schema 加入请求,减少 token 开销。 | 源码逆向 |
| Extended Thinking | Claude 的扩展思考模式,模型在生成最终响应前先输出 thinking 块,包含推理过程。thinking 块有独立的 token 预算。 | Anthropic 官方文档 |
| Feature Flag | 运行时功能开关,Claude Code 使用 Statsig 服务动态控制实验性功能的启用/禁用。 | 源码逆向 |
| Harness | Agent 的运行时框架,负责工具执行、上下文管理、循环控制。模型本身无状态,harness 维持会话状态。 | 行业共识 |
| ITPM (In-Token Prompt Management) | 在 token 预算内管理 prompt 内容的策略,包括优先级排序、压缩、缓存优化。Claude Code 实现了缓存感知的 ITPM。 | 源码逆向/推断 |
| JSONL Session | Claude Code 的会话持久化格式,每行一个 JSON 对象,记录消息历史、工具调用、元数据。用于会话恢复和调试。 | 源码逆向 |
| Side Query (侧查询) | 主循环之外发起的辅助 API 调用,如子 Agent 执行、标题生成、压缩摘要。侧查询有独立的上下文,不干扰主循环。 | 源码逆向 |
| stop_reason | API 响应中的字段,指示模型停止生成的原因。关键值:end_turn(自然结束)、tool_use(请求工具调用)、max_tokens(达到上限)。 | Anthropic 官方文档 |
| System Reminder | 在 messages 中以 user 角色注入的系统级提醒,包含环境信息、CLAUDE.md 内容、权限状态等。不影响缓存前缀。 | 源码逆向 |
| Tool Use | 模型请求执行外部工具的机制。模型输出 tool_use content block,包含工具名和参数,harness 执行后将结果以 tool_result 形式回灌。 | Anthropic 官方文档 |
| tool_result | 工具执行结果的回灌格式,以 user 角色消息形式追加到 messages 中,包含 tool_use_id 和执行输出。 | Anthropic 官方文档 |
附录 J:最小可行 Agent 完整伪代码
本附录提供一份可指导实现的伪代码,覆盖从用户输入到任务完成的核心路径。伪代码融合了 Claude Code 的设计原则与通用工程实践,标注了关键设计决策的理由。
J.1 主循环
// ============================================================
// 最小可行 Coding Agent 主循环
// 设计原则:模型做决策,harness 做执行
// ============================================================
class CodingAgent:
constructor(config):
this.model = config.model // 如 "claude-sonnet-4-20250514"
this.max_iterations = config.max_iterations || 50
this.context_window = config.context_window || 200000
this.tools = this.registerTools() // 工具注册
this.session = new Session() // 会话状态
this.permission = new PermissionManager(config.permission_mode)
this.cache = new CacheManager() // 缓存管理
async run(user_input: string): Promise:
// ---- 阶段 1: 输入预处理 ----
processed = this.preprocessInput(user_input)
// 解析 slash commands、附件、权限模式切换
// ---- 阶段 2: 构建初始 messages ----
this.session.addUserMessage(processed)
// ---- 阶段 3: 进入主循环 ----
iteration = 0
while iteration < this.max_iterations:
iteration++
// ---- 阶段 4: 上下文管理 ----
if this.shouldCompress(this.session.messages):
this.session.messages = this.compress(
this.session.messages
)
// 压缩后重新设置缓存断点
this.cache.recalculateBreakpoints(
this.session.messages
)
// ---- 阶段 5: 组装 API 请求 ----
request = this.buildRequest(
system_prompt = this.assembleSystemPrompt(),
tools = this.getActiveTools(),
messages = this.session.messages,
thinking_budget = this.calculateThinkingBudget(),
cache_breakpoints = this.cache.getBreakpoints()
)
// ---- 阶段 6: 调用模型(流式) ----
response = await this.callModel(request)
// ---- 阶段 7: 解析响应 ----
text_blocks = response.getTextBlocks()
tool_use_blocks = response.getToolUseBlocks()
thinking_blocks = response.getThinkingBlocks()
// 显示 thinking 和文本到用户(流式)
this.displayToUser(text_blocks, thinking_blocks)
// 将 assistant 响应加入历史
this.session.addAssistantMessage(response)
// ---- 阶段 8: 判断是否结束 ----
if response.stop_reason == "end_turn":
// 模型认为任务完成
return Result(success=true, iterations=iteration)
if response.stop_reason == "max_tokens":
// 达到单次输出上限,强制继续
this.session.addUserMessage(
"[system: continue from where you left off]"
)
continue
if tool_use_blocks.length == 0:
// 无工具调用但未 end_turn,可能模型困惑
return Result(success=false, reason="no_tool_use_no_end")
// ---- 阶段 9: 执行工具 ----
tool_results = []
for tool_use in tool_use_blocks:
// 权限检查
allowed = this.permission.check(
tool_name = tool_use.name,
params = tool_use.input,
context = this.session.getContext()
)
if not allowed:
// 请求用户确认或拒绝
decision = await this.requestUserPermission(tool_use)
if decision == "deny":
tool_results.append({
tool_use_id: tool_use.id,
content: "[Permission denied by user]",
is_error: true
})
continue
elif decision == "allow_always":
this.permission.grantAlways(
tool_use.name, tool_use.input
)
// 执行工具
try:
result = await this.executeTool(
tool_name = tool_use.name,
params = tool_use.input
)
// 结果截断
result = this.truncateResult(result)
tool_results.append({
tool_use_id: tool_use.id,
content: result
})
except ToolError as e:
tool_results.append({
tool_use_id: tool_use.id,
content: f"[Error: {e.message}]",
is_error: true
})
// ---- 阶段 10: 回灌工具结果 ----
this.session.addToolResults(tool_results)
// 循环继续,回到阶段 4
// 达到最大迭代次数
return Result(
success=false,
reason="max_iterations_reached",
iterations=iteration
)
J.2 System Prompt 组装
function assembleSystemPrompt(): SystemPrompt:
// ---- 静态区(高缓存命中率)----
static_part = []
// 1. 身份声明(几乎不变)
static_part.append({
type: "text",
text: IDENTITY_PROMPT, // "You are Claude Code, Anthropic's..."
cache_control: null // 第一个断点在后面
})
// 2. 行为规范(几乎不变)
static_part.append({
type: "text",
text: BEHAVIORAL_RULES, // 工具优先、避免过度工程等
cache_control: null
})
// 3. 工具使用指南(随工具列表变化而变化)
static_part.append({
type: "text",
text: this.generateToolGuide(),
cache_control: { type: "ephemeral" } // === 断点 BP1 ===
})
// ---- 半静态区(偶尔变化)----
// 4. 环境信息(OS、shell、目录等)
static_part.append({
type: "text",
text: this.getEnvironmentInfo(),
cache_control: null
})
// 5. CLAUDE.md 内容(项目级指令)
claude_md = this.loadClaudeMd()
if claude_md:
static_part.append({
type: "text",
text: claude_md,
cache_control: { type: "ephemeral" } // === 断点 BP2 ===
})
return {
parts: static_part,
// 注意:tools 列表在 API 请求的 tools 字段中
// 而非 system prompt 中,但缓存断点逻辑类似
}
J.3 工具执行与安全
async function executeTool(
tool_name: string,
params: dict
): Promise:
tool = this.tools.get(tool_name)
if not tool:
return f"[Error: Unknown tool '{tool_name}']"
// ---- 安全校验 ----
// 1. 路径安全:确保文件操作在项目根目录内
if tool.requiresPathValidation:
path = params.get("file_path") or params.get("path")
if path and not this.isWithinProject(path):
return f"[Error: Path '{path}' is outside project root]"
// 2. 命令安全:检查危险命令模式
if tool_name == "Bash":
command = params["command"]
danger_check = this.checkDangerousCommand(command)
if danger_check.is_dangerous:
// 不直接拒绝,而是要求用户确认
// 危险模式:rm -rf /、curl | sh、chmod 777 等
return f"[Blocked: {danger_check.reason}]"
// 3. 资源限制
if tool_name == "Bash":
timeout = min(params.get("timeout", 120000), 600000)
// 最大 10 分钟
// ---- 执行 ----
if tool.isAsync:
result = await tool.execute(params)
else:
result = tool.execute(params)
// ---- 结果后处理 ----
// 4. 结果截断
max_result_tokens = 10000 // 约 40K 字符
if len(result) > max_result_tokens * 4:
result = this.truncateResult(result, max_result_tokens)
// 5. 敏感信息脱敏
result = this.redactSecrets(result)
// 脱敏 API keys、tokens、密码等
return result
function checkDangerousCommand(command: string) -> DangerCheck:
DANGEROUS_PATTERNS = [
r"rm\s+-rf\s+/", // 递归删除根目录
r"rm\s+-rf\s+~", // 递归删除 home
r"curl.*\|\s*sh", // 管道执行远程脚本
r"wget.*\|\s*sh", // 同上
r"chmod\s+777", // 过度权限
r"dd\s+if=.*of=/dev/", // 写入设备文件
r"mkfs", // 格式化
r">/dev/sda", // 写入磁盘
r":\(\)\{.*\};:", // fork bomb
r"git\s+push\s+--force\s+origin\s+main", // 强推主分支
]
for pattern in DANGEROUS_PATTERNS:
if re.match(pattern, command):
return DangerCheck(
is_dangerous=true,
reason=f"Matches dangerous pattern: {pattern}"
)
return DangerCheck(is_dangerous=false)
J.4 上下文压缩实现
function compress(messages: list[Message]) -> list[Message]:
total = countTokens(messages)
if total <= this.context_window * 0.85:
return messages // 无需压缩
// ---- Step 1: 工具结果截断 ----
for msg in messages:
if msg.role == "tool_result" and len(msg.content) > 50000:
msg.content = smartTruncate(msg.content, max_chars=30000)
// smartTruncate: 保留头部 20K 字符
// + "[... N chars truncated ...]"
// + 尾部 10K 字符
if countTokens(messages) <= this.context_window * 0.85:
return messages
// ---- Step 2: 早期工具结果摘要化 ----
// 保留最近 5 轮的工具结果原样
cutoff = len(messages) - 20 // 大约 5 轮(每轮约 4 条消息)
for i in range(0, cutoff):
msg = messages[i]
if msg.role == "tool_result" and not msg.is_summarized:
msg.content = f"[Summary: {quickSummary(msg.content, 100)}]"
msg.is_summarized = true
if countTokens(messages) <= this.context_window * 0.85:
return messages
// ---- Step 3: 对话历史折叠 ----
// 将 cutoff 之前的 user/assistant 对话合并为一条摘要
old_messages = messages[:cutoff]
recent_messages = messages[cutoff:]
// 提取不可压缩的元素
system_msgs = filter(old_messages, type="system")
user_queries = filter(old_messages, role="user",
exclude_type="tool_result")
// 生成摘要
summary = await this.generateSummary(old_messages)
// 摘要 prompt:
// "Summarize the following conversation, preserving:
// 1. User's original request and constraints
// 2. Key decisions made
// 3. Files modified and their states
// 4. Errors encountered and solutions
// 5. Current task progress"
summary_msg = Message(
role="user",
content=f"[Previous conversation summary:\n{summary}]"
)
messages = [...system_msgs, summary_msg, ...recent_messages]
if countTokens(messages) <= this.context_window * 0.85:
return messages
// ---- Step 4: 激进截断 ----
// 仅保留最近 3 轮 + 摘要 + system
recent_3 = messages[-12:] // 大约 3 轮
messages = [...system_msgs, summary_msg, ...recent_3]
return messages
J.5 子 Agent 派生实现
async function dispatchSubAgent(
agent_type: string,
task: string,
parent_session: Session
) -> SubAgentResult:
// ---- 创建隔离的子 Agent ----
sub_agent = new CodingAgent({
model: this.model,
max_iterations: 10, // 子 Agent 迭代上限较低
context_window: this.context_window,
tools: this.getToolsForSubAgent(agent_type),
// Explore 类型: 只读工具(Read, Glob, Grep)
// Plan 类型: 只读工具 + Task
// Task 类型: 全部工具
permission: this.permission.inherit(),
session: new Session() // 全新会话,不继承历史
})
// ---- 注入最小上下文 ----
// 子 Agent 不继承父 Agent 的完整对话历史
// 只注入任务描述和相关上下文
context_injection = this.buildSubAgentContext(
task=task,
parent_context=parent_session.getRelevantContext(),
// 只传递与任务直接相关的信息
agent_type=agent_type
)
sub_agent.session.addUserMessage(context_injection)
// ---- 执行子 Agent 循环 ----
result = await sub_agent.run(context_injection)
// ---- 结果汇总 ----
// 子 Agent 的完整对话历史不回灌到父 Agent
// 只返回最终结果文本
return SubAgentResult(
success=result.success,
output=result.final_text,
iterations=result.iterations,
// 用于父 Agent 的 messages 中
// 以 tool_result 形式回灌
)
function getToolsForSubAgent(agent_type: string) -> list[Tool]:
READ_ONLY_TOOLS = ["Read", "Glob", "Grep"]
PLAN_TOOLS = ["Read", "Glob", "Grep", "Task"]
FULL_TOOLS = this.allTools
switch agent_type:
case "Explore": return READ_ONLY_TOOLS
case "Plan": return PLAN_TOOLS
case "general_purpose_task": return FULL_TOOLS
default: return FULL_TOOLS
J.6 会话持久化
class Session:
// 会话状态管理 + JSONL 持久化
messages: list[Message]
metadata: dict // 模型、token 用量、时间戳等
file_path: string // JSONL 文件路径
function addMessage(msg: Message):
this.messages.append(msg)
// 实时写入 JSONL(每行一个 JSON 对象)
this.appendToJSONL(msg)
function appendToJSONL(msg: Message):
line = JSON.stringify({
"type": msg.role,
"content": msg.content,
"timestamp": ISO8601.now(),
"metadata": msg.metadata or {}
})
fs.appendFileSync(this.file_path, line + "\n")
function restoreFromJSONL(path: string) -> Session:
lines = fs.readFileSync(path).split("\n")
session = new Session()
for line in lines:
if not line: continue
obj = JSON.parse(line)
session.messages.append(Message.fromJSON(obj))
return session
// 恢复场景:
// 1. 用户中断后继续(claude --continue)
// 2. 崩溃恢复(自动检测未完成会话)
// 3. 调试(检查 JSONL 了解每步决策)
// 4. 审计(记录所有工具调用和模型响应)