全景导图

%%{init: {'theme':'base', 'themeVariables': {'primaryColor':'#e3f2fd','primaryTextColor':'#1565c0','primaryBorderColor':'#1976d2','lineColor':'#42a5f5','secondaryColor':'#fff3e0','tertiaryColor':'#f3e5f5','fontSize':'14px'}}}%%
flowchart TD
    A[Entrypoints 入口层] --> B[Runtime 运行时层]
    B --> C[Engine 推理引擎层]
    C --> D[Tools & Caps 能力层]
    D --> E[Infrastructure 基础设施层]

    C --> C1[QueryEngine 单例]
    C --> C2[AsyncGenerator 循环]
    C --> C3[静态/动态提示词分界]
    C --> C4[五级上下文压缩]

    D --> D1[40+ 内置工具]
    D --> D2[多智能体编排]
    D --> D3[MCP 集成]
    D --> D4[Skills 三层加载]
    D --> D5[Hooks 9 类事件]

    E --> E1[Auth / Storage]
    E --> E2[Cache / Telemetry]
    E --> E3[Bridge 传输抽象]
    E --> E4[apiPreconnect 启动优化]

五层架构:同一个引擎驱动所有前端

模式总览

本文覆盖的可迁移架构模式:

# 模式名称 一句话口诀 覆盖场景
1 Harness 护城河 模型提供智力,Harness 提供能力边界 AI 编程工具架构设计
2 生成器驱动 AsyncGenerator 替代状态机 Agent 控制流设计
3 上下文预算 静态/动态分界 + 四个 breakpoint + 五级压缩 LLM 上下文窗口管理
4 多智能体编排 文件系统 Team + 异步 Mailbox + worktree 隔离 多 Agent 协调通信
5 工具即能力 注册-校验-执行-聚合流水线 Agentic AI 安全模型
6 缓存继承 model: inherit 共享提示词缓存 子智能体性能优化
7 切面式 Hook harness 强制执行 vs 模型选择 LLM 应用确定性切面
8 权限路径锚定 deny > ask > allow,每段独立判规则 Agentic AI 命令执行安全

源码概览:约 1,900 个文件的技术版图

2026 年 3 月 31 日,Claude Code 的完整 TypeScript 源码通过 npm source map 意外暴露——cli.js.map 文件约 59.8 MB,社区随后发现 source map 内嵌的 Cloudflare R2 bucket URL 可直接拉取 bundled 源码(CSDN 程序员鱼皮分析)。共计约 1,900 个文件、512,000 行 TypeScript。社区随即产生了一批分析文章,redreamality 通读了全部目录,ThreeFish-AI 完成了系统性逆向工程,Pragmatic Engineer 与 Sebastian Raschka 在采访 Anthropic 工程师后给出了官方背书的解读。2026 年 5 月 MBZUAI 发表的学术论文则从工程系统视角形式化了 Claude Code 的 harness 抽象,把它拆成了七个可复用组件——四支独立竞争的 AI 编程工具团队最终都收敛到了几乎相同的架构骨架。

源码揭示的核心事实:Claude Code 不是带工具访问的聊天机器人,而是一个完整的 AI 辅助软件开发操作系统——包含多智能体运行时、插件市场、跨会话记忆系统和多层安全模型。源码中能找到具体的真实生产数据:源码注释提到曾出现 1,279 个会话连续 50+ 次自动压缩失败的事故,每天浪费约 25 万 API 调用,最终通过 MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3 熔断常量解决(CSDN 程序员鱼皮)。

Harness Engineering:2026 年 AI 工程的第三阶段

Faros AI 在 2026 年 5 月的一篇行业综述中提出了一个有用的分期框架:

阶段 焦点 代表工具/方法
Phase 1 · Prompt Engineering 调教单次 LLM 调用 few-shot, CoT, system prompt 模板
Phase 2 · Context Engineering 把对的信息塞进上下文窗口 RAG, embedding 检索, 长上下文模型
Phase 3 · Harness Engineering 构建模型周围的执行环境 Claude Code, Codex CLI, Cursor Agent, Devin

Phase 3 是 2026 年 AI 工程投资的主轴:模型本身正在快速商品化,但模型外层的 harness——permission gating、tool dispatch、context compaction、subagent isolation、hook lifecycle——才是真正决定 Agent 在生产环境能否长时运行而不漂移的关键。按 TechTimes 对 MBZUAI 论文的工业转述,结论可以浓缩成一句:绝大部分可靠性差异来自 harness,而不是模型权重(TechTimes 标题里的"98%"是工业稿的概数提法,论文原文是对细分维度做的多组对比,具体百分比以论文为准,URL 见文末参考资料)。论文的事实基础是:四支独立团队(Anthropic Claude Code、OpenAI Codex CLI、Cursor、Devin)在没有相互参考的前提下,各自独立设计的 agent harness 在功能边界上几乎完全重合。这一现象指向一个结论:这些组件不是 Anthropic 的特定选择,而是 agentic 系统在生产化过程中被迫收敛到的自然形状

[PATTERN] Harness 护城河模式:AI 编程工具的真正壁垒不在模型能力,而在 Harness(智能体编排层)。模型提供智力,Harness 提供能力边界、上下文管理、确定性切面与执行环境。换句话说:“The model reasons; the harness mediates every action.”(mer.vin)

三种主流架构观的对照

社区与学术界对 Claude Code 的架构切分有三种经典视角,本文统一在五层视图下展开,但读者可以按下表互查:

视角 切分方式 来源 适用场景
5 层架构 Entrypoints / Runtime / Engine / Tools&Caps / Infrastructure 本文(基于 ThreeFish-AI 与 redreamality 源码逆向) 自顶向下理解工程实现
6 层 Harness Input / Knowledge / Execution / Integration / Multi-agent / Observability mer.vin(基于官方文档) 围绕模型的能力边界视角
7 组件抽象 UI / Agent Loop / Permissions / Tools / State&Persistence / Execution Env / Telemetry MBZUAI 论文 学术形式化与跨工具对比

三个视角讲的是同一件事,只是切分粒度不同。本文为了照顾"看一篇文就懂"的目标,会在每一层的开头点出它在另外两种视角下的对应位置。

关于本文出处:Claude Code 源码本身闭源,本文涉及内部命名(常量名、函数名、模块代号)的断言全部来自 2026 年 3-4 月 source map 泄露后的社区逆向工程报告,以及 2026 年 5 月之后社区与学术界发表的二次分析(mer.vin、jidonglab、MBZUAI 论文等)。多源交叉验证的断言会标注「多源」,仅一处来源的会显式标注「据某某分析」,请读者结合证据强度阅读。

五层架构:从入口点到基础设施

源码显示 Claude Code 采用清晰的五层分层设计:

层级 职责 关键组件
Layer 1: Entrypoints 多端入口 CLI / Desktop / Web / SDK / IDE Extensions / Headless
Layer 2: Runtime 运行时核心 REPL loop / Hook system / Permission engine / State manager
Layer 3: Engine 推理引擎 QueryEngine / Context coordinator / Compact pipeline / Streaming demux
Layer 4: Tools & Caps 能力层 40+ tools / Plugin / MCP / Skill / Sub Agent / Hooks
Layer 5: Infrastructure 基础设施 Auth / Storage / Cache / Bridge transport / Telemetry / Sandbox

这种架构的哲学是:Claude Code 是一个平台运行时,恰好附带了一个终端界面。同一个核心引擎驱动桌面应用、Web 客户端、IDE 扩展和程序化 SDK。Bridge 层抽象了传输协议,引擎永远不需要知道是哪个前端在驱动它——这是为什么 Anthropic 能在不重写核心的前提下,三个月内把一个 CLI 扩张成跨四个端的工具家族。

架构上更接近 VS Code 的扩展宿主或 Emacs 的 Lisp 核心,而非典型的 AI 包装器。Layer 3 是模型可见的边界——之上对模型透明,之下模型完全感知不到;Layer 2 是 harness 真正的控制权所在,Hook 与 permission 在这里强制执行。

核心引擎:QueryEngine 与 AsyncGenerator 循环

在 6 层 harness 视图下,本节对应"Agent Loop"中心;在 MBZUAI 7 组件视图下,对应"Agent Loop"组件。这是模型可见的边界——之上对模型透明,之下模型完全感知不到。

QueryEngine 单例

整个对话由一个 QueryEngine 单例驱动,它维护一个对话状态数组——这是所有对话状态的唯一事实来源(据 redreamality 分析,源码中具体命名为 mutableMessages,单源信息)。所有工具结果、压缩决策、子智能体协同都围绕这个数组的演进发生。

为什么用单例而非每次新建?关键考量是 prompt cache 命中:cache 是按字节级前缀匹配的,每次都从同一个 mutable array 末尾追加可以保证前缀稳定,不会因为对象重建造成对象哈希变化击穿缓存。这是个不起眼但关键的工程决策——把"对话历史"做成 mutable state 而非每轮 immutable diff。

AsyncGenerator 核心循环

Anthropic 工程团队对这个循环的官方描述是出奇地简单:“gather context → take action → verify results, repeat until done”。同一个三段式被显式刻在了源码里、Agent SDK 的文档里、以及 Anthropic Engineering 博客的 effective agents 文章里——这种跨载体的重复不是巧合,而是 Anthropic 内部对"什么是一个 agent"的共识。

核心循环是一个异步生成器(async generator):

1
2
3
4
5
6
7
8
9
10
用户消息
→ 构建系统提示词(分层上下文注入)
→ API 请求(流式)
→ yield tokens 到 UI
→ 如果收到 tool_use 块:
→ 检查权限(hook → policy → 用户审批)
→ 执行工具
→ 追加 tool_result
→ 继续循环(自然尾递归)
→ 如果 end_turn:退出
sequenceDiagram
    participant U as User
    participant Q as QueryEngine
    participant A as Anthropic API
    participant T as Tool Registry
    participant H as Hook System
    U->>Q: prompt
    Q->>Q: 拼接 system + dynamic + history
    loop until end_turn / maxTurns / maxBudget
        Q->>A: stream messages.create
        A-->>Q: content_block_delta (token)
        Q-->>U: yield token
        A-->>Q: tool_use(name, input)
        Q->>H: PreToolUse(name, input)
        alt deny
            H-->>Q: tool_result(error)
        else allow
            Q->>T: execute(name, input)
            T-->>Q: tool_result
            Q->>H: PostToolUse(name, input, result)
        end
        Q->>Q: append tool_result to mutableMessages
    end

生成器模式带来的关键优势:

  • 原生流式:token 通过 yield 流动,而非回调
  • 工具调用递归tool_use → tool_result → continue 只是另一次迭代
  • 干净的中断AbortController 取消生成器,无需复杂的清理逻辑
  • 预算控制简单:在每次迭代边界检查 maxTurnsmaxBudget

大多数 AI 工具框架使用状态机或事件循环。Claude Code 的生成器方法更简单、更可组合,这一选择也直接体现在了 claude-agent-sdk-pythonclaude-agent-sdk-typescript 中——SDK 的 query() 函数就是一个 AsyncGenerator/AsyncIterator,使用者用 async for message in query(...) 接收每一条事件。

Agent SDK 的两种入口:query() vs ClaudeSDKClient

Anthropic 后来把"Claude Code SDK"重命名为"Claude Agent SDK"——这不是文字游戏,而是承认这套 harness 的能力远超编码场景:邮件助手、调研 Agent、客服机器人、金融分析都用同一套循环。SDK 暴露了两个入口,分别对应不同的使用模式(jidonglab 整理):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 简单路径:query() 一次性任务
from claude_agent_sdk import query, ClaudeAgentOptions

async def fix_bug():
async for message in query(
prompt="Find and fix the bug in auth.py",
options=ClaudeAgentOptions(
allowed_tools=["Read", "Edit", "Bash", "Grep", "Glob"],
setting_sources=["project"], # 加载项目 CLAUDE.md / skills / hooks
max_turns=30, # 防止无限循环
max_budget_usd=2.0, # 预算闸
),
):
print(message) # AssistantMessage / ToolUseMessage / ResultMessage
1
2
3
4
5
6
7
8
9
# 生产路径:ClaudeSDKClient 双向对话 + Hook
async def multi_turn():
async with ClaudeSDKClient(options=ClaudeAgentOptions()) as client:
await client.query("第一轮")
async for msg in client.receive_response():
...
await client.query("第二轮") # 复用 session,命中 cache
async for msg in client.receive_response():
...

ResultMessagesubtype 字段区分四种结束态:success / error_max_turns / error_max_budget_usd / error_*——这让"预算感知"成为工程任务而非赌博。

与原始 Anthropic Client SDK 的关键差异:用 Client SDK 你必须自己实现 tool loop(检查 stop_reason、执行工具、追加结果、再次发请求),用 Agent SDK 这一切由 Claude Code 的 harness 自动完成。两者对外的 API 形态相同(都是 streaming + tool_use),但生命周期管理由谁负责决定了你写多少代码。

流式事件的官方时序

Anthropic Messages API 的 SSE 流式响应有严格的事件时序:

1
2
3
4
5
6
7
message_start

[content_block_start → content_block_delta* → content_block_stop]+

message_delta+

message_stop

工具调用的 tool_use 块通过 input_json_delta 流式传递——partial_json 是字符串片段,需要客户端累积后整体解析。这就是为什么生成器模式天然契合:每个 content_block_delta 是一个 yield 节点,工具参数累积完整后再触发权限检查、执行工具、把 tool_result 作为下一轮 user message 追加,重新进入生成器循环。

工程上更隐蔽的细节是只读工具的并行执行。Tool annotation 里的 readOnlyHint: True 让 harness 可以把 Read / Glob / Grep / WebFetch 一批工具调用并行起来;mutating 工具(Edit / Write / Bash)则严格串行,避免文件竞态。这是为什么 Claude Code 在"先大量探索再做修改"的任务上看起来很快——前半段被 harness 自动并行了,模型完全不感知。

[PATTERN] 生成器驱动模式:用 async generator 替代状态机/事件循环,天然支持流式、中断、递归和预算控制。这是 Agent 框架的最优控制流设计。SDK 把这个模式直接暴露给开发者,意味着 Anthropic 把这视为对外的稳定 API 抽象。

提示词工程架构:静态/动态分界与缓存优化

Claude Code 最具工程价值的设计之一,是将系统提示词拆分为静态部分动态部分,并围绕 Anthropic API 的 prompt caching 机制构建了完整的缓存优化策略。

静态提示词:可缓存的跨用户共享内容

源码中的系统提示词不是单一字符串,而是由多个 section 函数返回的字符串数组拼装而成(CSDN 程序员鱼皮的源码片段显示文件路径为 constants/prompts.ts,内含 sections = [...] 数组)。其中静态部分大致包括:

Section 类别 内容
身份定义 Claude Code 作为交互式代理的基本身份与角色
系统规则 输出格式、工具调用被拒绝后的处理、prompt injection 防护
任务执行规范 不要过度设计、读文件后再改代码、避免安全漏洞
工具使用规范 并行调用、权限检查、错误重试
语气与风格 终端友好的简洁输出、避免 emoji、不主动总结
输出效率控制 模型可见的 token 预算与简洁度规则

这些 section 在缓存策略中按"全局可缓存"标记,跨所有用户共享。

动态提示词:会话特定的实时生成内容

在静态部分之后,源码通过一个关键标记 __SYSTEM_PROMPT_DYNAMIC_BOUNDARY__ 明确划定静态/动态分界线(多源确认:CSDN、Dev.to ishaaan)。边界之后的内容在每次查询时实时生成:

1
2
3
4
5
6
7
8
9
const dynamicSections = [
systemPromptSection('session_guidance', ...),
systemPromptSection('memory', ...), // CLAUDE.md / @import
systemPromptSection('env_info_simple', ...), // 系统/cwd/git 状态
systemPromptSection('language', ...),
systemPromptSection('output_style', ...),
systemPromptSection('mcp_instructions', ...), // 不可缓存
systemPromptSection('scratchpad', ...),
]

mcp_instructions 因为每个用户配置不同,无法被缓存,会在源码中被显式标记为不可缓存(社区文章常以"DANGEROUS"前缀的辅助函数描述这一约定,但具体函数名在多源交叉中未被确认,本文不引用具体标识符)。命名约定本身是值得借鉴的设计——用名字而非注释来传达架构约束

五级覆盖优先级

构建有效系统提示词的过程支持五级覆盖机制,优先级从高到低:

优先级 来源 场景
0 Override system prompt 调试/测试时的完全覆盖
1 Coordinator system prompt 多智能体协调器的专用提示词
2 Agent system prompt 特定智能体(如 explore、plan)的提示词
3 Custom system prompt 用户自定义提示词
4 Default system prompt 默认系统提示词

此外,appendSystemPrompt 始终追加在末尾,不受优先级影响。

CLAUDE.md 的加载时机

CLAUDE.md 文件作为 userContext 注入——注意:是 user message 而非 system prompt。这是一个常被忽视的细节,意味着 Claude 对 CLAUDE.md 的"遵守"是模型行为而非 harness 强制——官方文档原文是 there's no guarantee of strict complianceCLAUDE.md 不是无条件每次重读:主 Agent 的项目级 CLAUDE.md 默认每次查询时重新读取,但 sub agent 由 omitClaudeMd 标志位控制(GitHub issue #59309 显示 v2.1.84+ 的子智能体不继承 CLAUDE.md,可大幅节省 token)。

加载顺序覆盖整个文件树:managed policy → user 级 ~/.claude/CLAUDE.md → 项目级(沿目录树自下而上)→ 同目录 CLAUDE.local.md@path import 支持递归,最大 5 层。子目录的 CLAUDE.md 不在启动时加载,是 Claude 读那个子目录文件时按需加载——这就是 Claude Code 感觉"项目感知"的机制。

围绕 cache_control 构建的两层缓存

Anthropic API 的 prompt caching 通过 cache_control 字段实现前缀缓存:

1
2
3
4
5
{ "type": "text", "text": "...long system prompt...",
"cache_control": { "type": "ephemeral" } }

// 长 TTL 直接用 ttl 字段,不再需要独立 beta header
{ "cache_control": { "type": "ephemeral", "ttl": "1h" } }

每个请求最多 4 个 cache_control 断点,每个断点回看 20 个 block。低于模型阈值(Sonnet 4.x 1024 tokens、Opus 4.5+ 与 Haiku 4.5 4096 tokens)的请求会被静默地不缓存。响应的 usage 字段返回三个关键值:cache_read_input_tokens(命中缓存)、cache_creation_input_tokens(写入新缓存)、input_tokens(最后一个 breakpoint 之后的增量——不是总输入)。

价格结构是这套设计能跑通的根因:cache read = 0.1×, cache write 5min = 1.25×, cache write 1h = 2×, base input = 1×。写一次省一万次。Claude Code 围绕这个比例构建了两层缓存:

缓存层级 范围 内容
Global Cache 跨用户共享 DYNAMIC_BOUNDARY 之前的所有静态 section
Session Cache 会话级 动态 section + 对话历史

工程上的关键约束:提示词的排列顺序至关重要。前缀匹配是字节级的——任何在前缀中发生的细微改变,都会击穿该位置之后的所有缓存。tool 定义改了,整条都失效;system 改了,messages 部分失效;改 tool_choice 或加图片,则 system 之后的部分全部失效。

Dev.to ishaaan 估算,Claude Code 这套两层缓存策略在长会话中能让 token 成本下降约 50–70%。

提示词缓存:四个 breakpoint 守住前缀

子智能体的缓存继承

model: 'inherit' 的核心价值在于:Fork 出的子智能体继承父智能体的完整对话上下文,通过 byte-identical copies 实现提示词缓存共享。子任务必须使用和父对话相同的 prompt prefix,才能复用父对话的缓存。这使得生成 5 个并发子智能体的成本仅略高于 1 个。inherit 的解析有四级优先级:环境变量 CLAUDE_CODE_SUBAGENT_MODEL → 调用时显式 model 参数 → frontmatter 的 model → 主对话 model。

[PATTERN] 缓存继承模式:子智能体不要"另起炉灶"——通过 model: 'inherit' 字节级对齐父对话 prompt prefix,复用父对话已经付费写入的缓存。把"派生子智能体"做成"复用上文",5 个并发的成本接近 1 个。

[PATTERN] 上下文预算模式:LLM 应用的上下文窗口是稀缺资源,必须像管理内存一样管理它。静态/动态分界 + 四个 breakpoint + 五级压缩是 Claude Code 的核心工程策略。SYSTEM_PROMPT_DYNAMIC_BOUNDARY 这个标记本身就是一个值得借鉴的设计模式——用命名约定而非文档来传达架构约束。

上下文压缩:受控降级流水线

在 6 层 harness 视图下,本节对应"Knowledge"层中"context survival"的部分;它是 LLM 应用最难做对的工程之一。

上下文窗口是有限的。Claude Code 通过分层压缩系统处理。社区分析对压缩级数说法不一:redreamality 描述为四级(autoCompact / apiMicrocompact / reactiveCompact / snip),CSDN 与 Dev.to 描述为五级(含 Tool Result Budget 与 Context Collapse),claudefa.st 描述为五种 distinct approaches。源码中确实存在的标识符包括 AutocompactMicrocompactSniphasAttemptedReactiveCompactContext Collapse 等:

层级 命名(社区) 触发时机 影响
1 Tool Result Budget 单次 tool_result 超预算时裁剪 仅截断当前工具输出,不动历史
2 Microcompact (apiMicrocompact) API 原生 context_management 触发 裁掉最旧的若干 tool_result
3 Snip 紧急丢弃图片、调试 trace 等非关键内容 优先剥离体积大、信息密度低的 block
4 Autocompact 上下文接近软限制时主动调用 LLM 摘要 把历史对话凝练成摘要,保留关键引用
5 Reactive Compact API 返回 context-too-large 错误后兜底 最激进的丢弃,会丢早期对话语义

Autocompact 流程的三段式

Autocompact 流程不是"丢一半旧消息"那么粗暴,而是一个三段式:

flowchart LR
    A[触发条件:<br/>上下文 > 软限制] --> B[剥离非文本:<br/>图片 / 大型 trace]
    B --> C[调用 LLM 摘要:<br/>对话历史 → 概要]
    C --> D[恢复文件引用 +<br/>skill 状态]
    D --> E[保留 preservedSegment:<br/>近 N 轮原文]
    E --> F[继续主循环]

preservedSegment 是关键设计——压缩后,最近 N 轮(社区估计 5-10 轮)保留原文,更早的内容被 LLM 摘要替换。这避免了"刚说过的事下一句就忘了"的体验。同时关键文件引用(“我们正在改 auth.py”)和 skill 状态(“已加载 pdf-processing skill”)被白名单恢复——压缩不能让 agent 失忆到不知道自己在干什么。

PreCompact / PostCompact Hook 的工程意义

Agent SDK 暴露的 PreCompactPostCompact hook 让用户可以介入压缩边界——这是 OMC、Superpowers、各类自定义 framework 都重度依赖的扩展点:

1
2
3
4
5
6
7
async def pre_compact_archive(input_data, context):
# 压缩前把完整 transcript 归档到外部存储
transcript = input_data["transcript"]
await save_to_s3(f"sessions/{input_data['session_id']}.jsonl", transcript)
# 注入一段 systemMessage 让模型知道"完整历史在 S3 上"
return {"systemMessage": "Full transcript archived to S3, "
"can be referenced by session_id."}

这个模式很像数据库的 WAL(write-ahead log)——压缩是"checkpoint",PreCompact hook 是"在 checkpoint 之前先把 redo log 落盘"。少了它,长会话的 transcript 永久丢失,事后审计就没了。OMC 的 notepad 系统就是基于这个 hook 实现"压缩前自动保存关键笔记"。

熔断与失败重试

源码中还有熔断保护:MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3——这是在踩过"1,279 sessions 连续 50+ 次失败、每天浪费约 25 万 API 调用"的真实事故后加上的(CSDN 引用源码注释)。

事故的根因是:autocompact 调用本身也是一次 LLM 调用,如果上下文已经爆到压缩 API 都拒绝,重试无穷次只会无限计费。MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3 之后会强制走 Reactive Compact 的激进丢弃路径——优雅降级 > 死循环重试,这是对 LLM 应用工程的关键启示。

compaction 与 mutableMessages 的字节级影响

一个常被忽略的细节:压缩会重写 mutableMessages 数组——这意味着压缩之后的所有 prompt cache 都会失效,下一次请求要重新支付 cache write 成本。这就是为什么 Autocompact 不能太激进:每次压缩都是一次"全量 cache 重建",频繁压缩反而比放任上下文增长更贵。源码中 autocompact 的触发阈值据 ThreeFish-AI 分析约在 75-80% 上下文容量,留出余量避免过早压缩。

这比"截断旧消息"复杂得多——这是一个受控降级流水线

Hook 系统:harness 的确定性切面

在 6 层 harness 视图下,本节对应"Observability"层;在 MBZUAI 7 组件视图下,对应"Telemetry"组件。

Hook 是 Claude Code agent loop 中由代码强制执行而非"模型可能选择做"的事——它把不应该交给 LLM 决定的逻辑(autoformat、permission 拦截、telemetry)从 prompt 里搬到 shell 命令。Hook 在固定生命周期事件触发,由 harness(不是模型)同步执行。

mer.vin 总结的口诀很到位:“Hooks fire at fixed lifecycle points—unlike skills, they are deterministic, not model-chosen.” Skill 是模型可以选择不调用的工具入口,Hook 是模型选择不到的强制切面——一个治"愿不愿意",一个治"能不能"。

9 类核心事件 + Agent SDK 的十余个细粒度事件

官方文档列出的核心 hook 事件包括:SessionStart / UserPromptSubmit / PreToolUse / PostToolUse / Notification / Stop / SubagentStop / PreCompact / PermissionRequest(社区交叉确认)。Agent SDK 把这一组进一步细化到十余个生命周期回调(下表列出 16 个有公开文档支持的事件;jidonglab 的整理给出的总数偏高,差异可能落在实验阶段的若干文件 hook 上),覆盖几乎每一个可介入的边界点:

节奏 事件 用途
会话级 SessionStart / SessionEnd 启动/结束钩子,注入欢迎语、收尾归档
用户输入 UserPromptSubmit / UserPromptExpansion 提交前重写、@import 展开后注入
工具调用 PreToolUse / PostToolUse / PostToolBatch 拦截/审计/格式化;批量工具结束后再走一次
权限 PermissionRequest 用户被弹权限请求时触发
子智能体 SubagentStart / SubagentStop 子任务边界,常用于结果聚合
压缩 PreCompact / PostCompact 压缩前归档、压缩后回灌摘要
通知 Notification 工具调用阻塞时 ding 一下用户
停止 Stop 模型 end_turn 时的最后一钩
文件 FileEdit / FileWrite 后置 autoformatter / linter 整合

事件按节奏分三档:

flowchart LR
    subgraph A[每会话一次]
        SS[SessionStart] --> SE[SessionEnd]
    end
    subgraph B[每轮一次]
        UPS[UserPromptSubmit] --> ST[Stop]
        UPS --> SAS[SubagentStop]
    end
    subgraph C[每次工具调用]
        PRE[PreToolUse] --> POST[PostToolUse]
        PRE --> PR[PermissionRequest]
    end
    subgraph D[压缩边界]
        PC[PreCompact] --> POSTC[PostCompact]
    end
    A -.触发.-> B
    B -.触发.-> C

Hook Handler 的五种类型

Hook 不止能挂 shell 命令——官方文档列出的 handler types 共有 5 类(mer.vin 整理):

Handler 类型 说明 典型用途
Shell command stdin 喂 JSON,stdout/exit code 决定行为 autoformat、git pre-commit 风格的拦截
HTTP webhook POST 到指定 URL,body 是同样的 JSON 把事件推到 Slack / 内部审计系统
MCP tool 复用已注册的 MCP 工具 用一个 LLM 写的 MCP 工具做语义判断
Prompt judge 用一段 prompt 让 LLM 当裁判 “这个 commit message 够清晰吗?”
Agent verifier(实验) 用一个独立 sub-agent 跑深度验证 安全审计、TDD 验证

后三种是 2026 年 Q2 上线的新形态——把 hook 从"shell 脚本切面"扩展到"用 LLM 当切面",意味着 hook 不再只能做规则判断,也可以做语义判断。代价是延迟:prompt judge 通常 1-3 秒,async hook(async_: True)让它不阻塞主循环。

Hook 执行模型

每个 hook 通过 stdin 接收 JSON 输入(session_id / transcript_path / cwd / permission_mode / hook_event_name / tool_name / tool_input 等),通过 exit code 与 stdout JSON 决定后续行为。一个常见误区:

For most hook events, only exit code 2 blocks the action. Claude Code treats exit code 1 as a non-blocking error and proceeds with the action, even though 1 is the conventional Unix failure code.

也就是说,写"非 0 即失败"的 hook 是错的——只有 exit 2 真正阻塞。exit 2 时 Claude Code 忽略 stdout 和任何 JSON,把 stderr 当错误消息回喂给模型。

JSON 输出与 exit code 是互斥的——选择 JSON 输出意味着精细化控制:

1
2
3
4
{ "hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Database writes are not allowed" } }

permissionDecisionallow / deny / ask / deferadditionalContext 字段会被 Claude Code 包成 system reminder 注入到对话——所有 hook 输出(含 additionalContext)截断在 10,000 字符。

双层输出设计:hookSpecificOutput vs systemMessage

Agent SDK 的 hook 协议有一个常被忽略的精巧设计——两层输出分别面向 harness 与模型(jidonglab):

1
2
3
4
5
6
7
8
9
10
11
12
async def protect_env_files(input_data, tool_use_id, context):
file_path = input_data["tool_input"].get("file_path", "")
if file_path.endswith(".env"):
return {
"hookSpecificOutput": {
"permissionDecision": "deny",
"permissionDecisionReason": "Cannot modify .env files",
},
"systemMessage": "Note: .env files are protected. "
"Use config/env.example for templates instead."
}
return {}
  • hookSpecificOutput 控制当前操作:allow / deny / 修改 input
  • systemMessage 注入对话上下文:以 system reminder 形式告诉模型"为什么"被阻塞,让它换条路而不是死循环重试

没有第二层,模型可能 5 次都试着写同一个 .env;有了第二层,模型读到"用 env.example 代替"的提示后会主动改方向。这是个小细节但效果很大——区分了"控制流决策"和"模型上下文注入"两个责任。

Matcher 与 deny-wins 链式语义

多个 hook 注册到同一个事件时,按注册顺序串行执行,任何一个返回 deny 就立刻停止后续 hook 并阻塞操作——这就是 deny-wins 语义。Matcher 用正则筛选要不要触发:

1
2
3
4
5
6
7
8
9
10
hooks:
PreToolUse:
- matcher: "Write|Edit" # 只对写操作生效
handler: protect_env_files
- matcher: "^mcp__" # 拦截所有 MCP 工具
handler: audit_mcp_calls
- matcher: "Bash"
handler:
type: command
command: "deny-rm-rf.sh"

这种"matcher + 链 + deny-wins"的设计直接对应 Linux iptables 的 chain 模型——hook 不只是 LLM 应用的工具,本质上是经典系统工程的"过滤器链"模式被搬到 agent loop 里。

从 1,279 sessions 事故看 Hook 治理价值

回到本文开头那个事故:1,279 个会话连续 50+ 次自动压缩失败、每天浪费约 25 万 API 调用。这个事故能被发现是因为 telemetry hook 把 PreCompact / PostCompact 失败计数推到了内部 dashboard——没有 hook,这种慢性出血是看不见的。最终的修复 MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3 也是 hook 风格的:在 harness 里加一个熔断常量,比让模型"自己注意不要无限重试压缩"靠谱一万倍。

[PATTERN] 切面式 Hook 模式:把"模型可能做"变成"代码强制做"——autoformat、permission、policy、telemetry 都不该让模型选择。Hook 与 Skill 互补:Skill 是模型可选择的工具入口,Hook 是模型选择不到的强制切面。把 hook 的输出拆成 hookSpecificOutput(控制流)+ systemMessage(上下文)两层,避免模型在被拦截后死循环重试。

多智能体架构:三种隔离级别与七种任务类型

在 6 层 harness 视图下,本节对应"Multi-agent"层。这一层的核心问题是:如何让一个 agent 派生子任务而不污染自己的上下文

为什么需要 Sub Agent:上下文污染问题

主 agent 的 mutableMessages 数组是稀缺资源——每多塞 1 万 token,下一次模型推理就贵 1 万 token,且击穿 cache 的概率指数级上升。"Read 整个目录树"或"grep 全仓库"这类探索性操作动辄消耗 5-10 万 token,但绝大多数内容主 agent 用完就不再需要。

Sub Agent 的核心价值是让子任务在独立上下文窗口里运行,只把摘要带回来。mer.vin 的描述很到位:“Subagents run in isolated context windows and return summaries—so a research pass does not dump thousands of tokens into the parent thread.” 主 agent 拿到 200 字符的摘要,子 agent 内部消耗的 50,000 token 上下文就被丢弃了。

这是 Claude Code 在大型仓库里依然保持低成本的关键工程手段。Anthropic 工程师在采访中提到:“主 agent 的账单一大半其实花在『找路』上”——加 sub agent 之后,"找路"开销转移到不计费(因为子任务结束就被丢弃)的临时上下文里。

智能体分类

Claude Code 有完整的智能体类型分类:

类型 来源 示例
BuiltInAgent 硬编码 Explore(Haiku,只读)/ Plan(继承,只读,给 plan mode)/ general-purpose(继承,全工具)
CustomAgent 设置文件 用户或项目级 .claude/agents/*.md
PluginAgent 插件包 市场分发的智能体

内置的 Explore/Plan 默认是只读的——这是为什么 sub agent 上的 omitClaudeMd 默认开启能在整个集群每周节省 5–15 GTok(据 redreamality 估算):只读 agent 不需要 CLAUDE.md 里那些"修改文件应该如何"的规范。

Sub Agent 定义格式

.claude/agents/code-reviewer.md 的 frontmatter 字段决定了它的能力边界:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
---
name: code-reviewer
description: Reviews code for quality and best practices
tools: Read, Glob, Grep
disallowedTools: Write, Edit
model: sonnet # sonnet | opus | haiku | inherit
permissionMode: plan # default | acceptEdits | bypassPermissions | plan
maxTurns: 20
skills: [...] # 预加载到 context
hooks: {...} # scoped 到这个 sub agent
isolation: worktree # 给 sub agent 一个临时 git worktree
memory: project # 跨会话记忆
background: true
---

工具子集生效顺序:disallowedTools 先减、tools 后限定(“If both are set, disallowedTools is applied first, then tools is resolved against the remaining pool”)。isolation: worktree 让 sub agent 跑在独立 git worktree(无修改自动清理)——这是 OMC 风格 worktree 工作流的官方原生对应物。

七种任务类型

下表来自 redreamality 分析(单源信息,其它逆向工程文章未提及完整 7 种命名):

任务类型 隔离级别 用途
InProcessTeammate AsyncLocalStorage 同进程,共享终端
LocalAgentTask 异步后台 非阻塞子智能体
RemoteAgentTask 远程 CCR 云端执行
LocalShellTask 子进程 Shell 命令
DreamTask 后台 记忆整合
LocalWorkflowTask 后台 工作流脚本
MonitorMcpTask 后台 MCP 服务器监控

Team 协调模型

多个智能体通过基于文件的 Team 系统协调:

1
2
3
~/.claude/teams/{team-name}/config.json
├── members: [{ agentId: "researcher@my-team", status: "idle" }]
└── task list: ~/.claude/tasks/{team-name}/

通信通过 Mailboxes 异步进行——每个队友有独立的消息队列。协议支持结构化消息:shutdown_requestplan_approval_response、权限冒泡。

关键设计决策:

  • model: 'inherit' 确保子智能体共享父智能体的提示词缓存——字节级对齐缓存命中
  • 据 redreamality 分析,源码中的 TEAMMATE_MESSAGES_UI_CAP = 50 防止内存泄漏(同分析提到此限制前曾在 292 个智能体下达到 36.8GB 内存——单源信息,未在其它社区分析中复现)
  • 只读智能体(Explore、Plan)上的 omitClaudeMd 节省可观 token(每周 5-15 GTok 是 redreamality 估算)
  • AsyncLocalStorage 提供隐式上下文隔离,无需显式参数传递

[PATTERN] 多智能体编排模式:Agent 不是单打独斗,而是通过文件系统的 Team 配置 + 异步 Mailbox 消息队列协调。model: 'inherit' 是缓存优化的关键,isolation: worktree 是修改隔离的关键。

工具系统:40+ 内置工具与权限模型

在 6 层 harness 视图下,本节对应"Execution"层;在 MBZUAI 7 组件视图下,对应"Tools Layer"和"Execution Environment"。

源码中的工具列表读起来像一个小型 IDE:

  • 文件操作BashTool, FileReadTool, FileEditTool, FileWriteTool, GlobTool, GrepTool
  • 网络能力WebFetchTool, WebSearchTool
  • 笔记本支持NotebookEditTool
  • 智能体调度AgentTool, SendMessageTool, TaskCreate/Get/List/Update
  • 团队协作TeamCreate/Delete
  • MCP 集成:通过标准协议接入外部服务

工具系统的设计原则:Agent 能做什么,完全由它拥有的工具集决定。系统中没有任何"后门"让模型绕过工具系统直接执行操作。

Agent SDK 的 9 个核心内置工具

Anthropic 把 Claude Code 内置工具中最核心的 9 个稳定 API 暴露给 Agent SDK 用户(jidonglab 整理)——这是 Agent SDK 与 LangGraph、CrewAI、OpenAI Agents SDK 拉开差距的关键:别的框架都从空工具集起步,每个工具都要从头实现;Agent SDK 直接把生产级工具塞给你。

工具 类型 默认权限
Read 只读 自动允许
Glob 只读 自动允许
Grep 只读 自动允许
WebSearch 只读 自动允许
WebFetch 只读 自动允许
AskUserQuestion 交互 自动允许
Write 需审批
Edit 需审批
Bash 副作用 需审批

"扫描所有 TODO 注释并汇总"这种任务在 Agent SDK 里只要 5 行——allowed_tools=["Read", "Glob", "Grep"] 就开干。在 LangGraph 里同样的任务要 80 行:实现 file reading function、写 shell wrapper、集成 glob 库——这是 Agent SDK 的直接竞争优势。

Tool annotations 与并行执行

工具定义里有一组 annotation 字段,决定 harness 如何调度(jidonglab):

1
2
3
4
5
6
7
8
9
10
11
@tool(
"search_db",
"Search internal database",
{"query": str},
annotations={
"readOnlyHint": True, # 可与其他只读工具并行
"destructiveHint": False, # 非破坏性
"idempotentHint": True, # 多次调用结果一致
},
)
async def search_db(args): ...

readOnlyHint: True 让 Claude Code 把多个只读工具调用打包成并行批次——典型场景下从 5 串行 × 200ms = 1 秒缩短到 200ms。这是 Claude Code 看起来"敏捷"的另一个隐藏机制。

自定义工具:内嵌 MCP Server

Agent SDK 的自定义工具实现机制揭示了 MCP 的设计深度:当你用 @tool 装饰器定义一个工具时,SDK 在内部把它包成一个 in-process MCP server——同一个进程,零网络开销,但走 MCP 协议(jidonglab):

1
2
3
4
5
6
7
8
9
10
11
from claude_agent_sdk import tool, create_sdk_mcp_server

@tool("get_temperature", "Get current temperature",
{"latitude": float, "longitude": float})
async def get_temperature(args):
return {"content": [{"type": "text", "text": "72°F"}]}

server = create_sdk_mcp_server(
name="weather", version="1.0.0",
tools=[get_temperature],
)

工具命名遵循 mcp__{server}__{tool} 模式(如 mcp__weather__get_temperature)——这是为什么 hook matcher 用 ^mcp__ 一行就能拦截所有 MCP 工具:内置工具和自定义工具走同一个命名空间。

错误处理上有一个关键工程设计:handler 抛未捕获异常会导致整个 agent loop 崩溃,但如果 catch 后返回 {"is_error": True, ...},Claude 会把错误当数据,可以重试或换路径。生产代码必须永远 catch-and-return,永远不要 throw。

权限是双层防御

权限有两层独立机制:

  1. Claude Code 客户端规则deny → ask → allowdeny 永远胜出
  2. OS-level Sandboxing:filesystem / network 隔离(仅作用于 Bash 及其子进程)

Permission 模式有六个:default(首用提示)、acceptEdits(自动接受编辑、mkdir/touch/mv/cp)、plan(只读探索)、auto(背景 classifier 审核)、dontAsk(除预批准外全拒)、bypassPermissions(全跳过,rm -rf /rm -rf ~ 仍提示作 circuit breaker——bypass 不是无防护)。

命令解析的两个隐藏陷阱

陷阱一:复合命令分治授权Bash(safe-cmd *) 不会授权 safe-cmd && other-cmd,因为 &&|/||/;/|/|&/&/换行 都被拆开独立判规则。Process wrapper(timeout/time/nice/nohup/stdbuf + 无 flag 的 xargs)会被预先剥离,所以 Bash(npm test *) 能匹配 timeout 30 npm test。但环境运行器(devbox run / npx / docker exec)不在剥离名单——这是文档专门警告的语义陷阱。

陷阱二:/path 路径锚定。这是新手 deny rule 写错最高频的点:

写法 含义
//path 文件系统绝对路径
~/path 用户 home 目录
/path 项目根(不是文件系统根!)
path 当前工作目录

Symlink 走双路径检查:allow 要两端都过,deny 任一命中即拦——这就是项目内 symlink 仍能拦住 ~/.ssh/id_rsa 的机制。

URL 过滤极其脆弱:Bash(curl http://github.com/ *) 挡不住 -X 前置选项、HTTPS 协议、-L 重定向、变量、多空格等变体——文档明确建议改用 PreToolUse hook。

权限评估管线:deny > ask > allow

[PATTERN] 工具即能力边界模式:Agentic AI 的能力不取决于模型智能,而取决于工具集的设计。注册-校验-执行-聚合的流水线决定了 Agent 的能力上限。

[PATTERN] 权限路径锚定模式:在 Agentic 系统的 allowlist/denylist 设计中,//abs / ~/home / /project-root / relative 四类锚定语义必须在文档与实现里同时表达,否则用户写出的规则与意图不一致——这是"deny 不生效"类 bug 的高频源。

MCP 集成:开放协议把外部世界接进来

在 6 层 harness 视图下,本节对应"Integration"层。MCP 是 Claude Code 与"外部世界"的接口——也是 Anthropic 在 2026 年 AI 工程生态里下的最大注。

MCP(Model Context Protocol)是 Anthropic 牵头的开放标准,把"模型 ↔ 工具"的连接抽象为三类原语:tools(可调用函数)、resources(可读上下文)、prompts(可复用模板)。MCP 服务器与本地 BashTool/Edit 等内置工具是平级关系——所有暴露的工具最终都进同一个 tool registry,模型在 tool selection 时不区分来源。

MCP Tool Search:按需加载工具 schema

一个常被忽略的工程细节:MCP 工具的 schema 默认不全量加载。如果 Claude Code 启动时把所有注册的 MCP 服务器(GitHub、Slack、Notion、Linear、PayPal……)的所有工具 schema 全塞进 system prompt,每个会话的初始 token 就会爆炸——10 个 MCP 服务器轻松吃掉 30k token。

解决方案是 MCP Tool Search(mer.vin):harness 只在 system prompt 里注入 MCP 服务器的"目录"(每个服务器一段简短描述),具体工具 schema 在模型决定调用某个服务器时才按需加载。这跟 Skill 的三层渐进披露是同一思路——把"能做什么"和"怎么做"分到两个加载时机

Claude Code 通过三种 transport 连接 MCP:

Transport 状态 适用场景
HTTP(也叫 streamable-http) 推荐 远程服务的主流方式
SSE 已 deprecated 仅向后兼容
stdio 当前 本地子进程

配置存三个作用域,按精度递减:local(~/.claude.json 内当前项目段)→ project(.mcp.json,可入版本库)→ user(~/.claude.json 全局段)。HTTP/SSE 服务器掉线后自动指数退避重连(最多 5 次,初始 1 秒翻倍);stdio 不自动重连(本地进程语义)。

1
2
3
4
5
# 远程 HTTP 服务器(推荐)
claude mcp add --transport http notion https://mcp.notion.com/mcp

# 项目级,团队共享
claude mcp add --transport http paypal --scope project https://mcp.paypal.com/mcp
1
2
3
4
5
6
7
8
9
10
// .mcp.json,支持 ${VAR} 与 ${VAR:-default} 展开
{
"mcpServers": {
"api-server": {
"type": "http",
"url": "${API_BASE_URL:-https://api.example.com}/mcp",
"headers": { "Authorization": "Bearer ${API_KEY}" }
}
}
}

环境变量 MCP_TIMEOUT 控启动超时,MAX_MCP_OUTPUT_TOKENS 控单次工具输出上限(默认 10k token 警告阈值)。

Skills:渐进式披露的三层加载

在 6 层 harness 视图下,本节对应"Knowledge"层;Skill 是 CLAUDE.md 的进化版——把"程序性知识"(procedural knowledge)从内联 prompt 中分离出来。

Anthropic 在 2026 年初把 Skills 推为开放标准 Agent Skills,目标是让 PDF 处理、Excel 编辑、品牌规范这类"专家技能"在 Claude Code、Claude Desktop、第三方 Agent 之间通用。MIT 许可,规范公开——这是 Anthropic 在生态层面对 LangChain Tool 抽象的反击:Tool 是函数,Skill 是带文档的工程包

Skills 是文件系统目录,至少包含一个 SKILL.md,前置 YAML frontmatter 提供 metadata。三级渐进披露是核心设计:

flowchart TD
    L1["Level 1 · Metadata<br/>仅 name + description(合计 1,536 字符上限)<br/>启动时全量加载到 system prompt<br/>每个 Skill 约 100 token"]
    L2["Level 2 · Instructions<br/>Claude 根据 description 判断匹配后<br/>通过 Read 加载 SKILL.md 正文<br/>建议 5k token 以内"]
    L3["Level 3 · Resources<br/>references/*.md / scripts/*.py<br/>仅在 SKILL.md 显式引用时按需加载<br/>scripts 通过 bash 执行,源码不进 context"]
    L1 -- 触发 --> L2
    L2 -- 按需加载 --> L3
1
2
3
4
5
6
7
8
9
10
---
name: pdf-processing
description: Extract text and tables from PDF files, fill forms, merge documents.
Use when working with PDF files or when the user mentions PDFs, forms, or document extraction.
---

# PDF Processing
## Quick start
...
For advanced form filling, see [FORMS.md](FORMS.md).

字段约束:name 必填,≤64 字符,仅小写字母/数字/连字符,禁用 “anthropic” “claude”。description 必填,≤1024 字符。其它字段(allowed-tools / when_to_use / hooks 等)是 Claude Code 与社区生态的扩展约定。

Skills 与其它机制的边界:

谁触发 何时加载 例子
Hook harness 强制 / 事件触发 autoformat / permission 拦截
Skill Claude 选择 / 调用时全量 pdf-processing / superpowers
Sub Agent Claude 委派 / 独立 context code-reviewer / explore
CLAUDE.md 用户写 / 启动时全量 项目编码规范

Custom commands have been merged into skills.(官方表态)

.claude/commands/foo.md.claude/skills/foo/SKILL.md 都创建 /foo,老路径仍然有效。

记忆系统:跨会话持久化

在 6 层 harness 视图下,本节对应"Knowledge"层中"context survival"和"persistent instructions"的部分。

源码暴露了跨会话记忆机制:

记忆类型 存储位置 用途
短期记忆 会话内对话状态数组 当前对话上下文
中期记忆 ~/.claude/memory/ 用户偏好、常用命令模式
长期记忆 DreamTask 后台整合 跨项目经验积累

记忆系统让 Agent 在后续会话中自动加载历史偏好,表现出"越来越懂你"的行为。/memory 命令读写 MEMORY.md,约定首 200 行或 25KB 加载——和 CLAUDE.md 同样以 user message(不是 system prompt)形式注入。

Auto Dream:REM-Sleep for Agents

claudefa.st 在 2026 年 5 月详细记录了 Anthropic 在 v2.1 系列推出的 Auto Dream 功能——这是 Claude Code 第一次有"睡眠时整理记忆"这样的拟人化机制。

工作流程:

flowchart LR
    A[会话结束 / Stop hook] --> B[DreamTask 入队]
    B --> C[后台异步执行]
    C --> D[读取所有 memory/*.md]
    D --> E[LLM 摘要 + 去重]
    E --> F[合并相关条目]
    F --> G[剪掉 >7 天的过时条目]
    G --> H[更新 MEMORY.md 索引]

设计哲学是直接拟人:“像 REM 睡眠一样合并洞察、剪枝陈旧笔记”——这不是修辞,而是工程目标。如果记忆只能 append,1 年后会有 5000 条互相冲突的 note;Auto Dream 让记忆系统自我修复,避免老化。

Anthropic 把 Dream 实现为独立的 sub agent taskDreamTask),而非主 agent 的扩展——这意味着:

  • 主 agent 上下文不被占用(dream 是后台进程)
  • 主 agent 不知道 dream 在跑,避免"我应该启动 dream 吗"的元思考
  • 用户可以独立控制 OMC_DREAM_ENABLED 这类开关

KAIROS:从被动响应到主动协作的实验

源码中还发现了代号 KAIROS 的模块(多源确认存在,但定位社区描述分歧):claudefa.st 描述为 “autonomous daemon”,CSDN 描述为 “long-term assistant mode + AutoDream”,redreamality 描述为 “append-only logs”。比较一致的部分是:源码注释将其描述为长期/守护态助手模式(custom system prompt, brief responses, persistent),具备主动心跳、睡眠机制和追加式记忆,能够以 24/7 守护进程的方式监控代码库变化——类似贾维斯的角色。

KAIROS 的关键差异:

普通 Claude Code KAIROS
用户问,Claude 答 系统主动 ping 用户
会话结束即释放 会话从不真正结束
上下文是对话历史 上下文是"代码库当前状态"
计费 = token 用量 计费 = 守护进程时长

这代表了 Claude Code 从"被动响应"向"主动协作"演进的实验性方向。是否会成为产品形态尚未公开——但源码里已经为这种形态准备了基础设施(心跳、睡眠、追加式日志),意味着 Anthropic 在押注这条路径。

隐藏功能:源码中的工程文化彩蛋

BUDDY:终端虚拟宠物系统

BUDDY 模块是 Claude Code 在 2026 年 4 月 1 日(约在 v2.1.x 系列)正式发布的终端虚拟宠物系统。用户在终端输入 /buddy 命令即可孵化一个 ASCII 像素宠物,系统基于用户 ID 的 hash 确定性分配 18 种物种(CSDN 列出全名:duck / goose / blob / cat / dragon / octopus / owl / penguin / turtle / snail / ghost / axolotl / capybara / cactus / robot / rabbit / mushroom / chonk),每种拥有 5 个稀有度分级(Common 到 Legendary)、属性系统(SNARK、WISDOM 等 stats)和装饰系统(帽子等)。宠物会根据任务执行进度表现出不同的情绪状态动画。

技术彩蛋:claudefa.st 提到 capybara 物种代号在源码中用 hex 编码绕过 build scanner——这是个工程文化的小细节,说明发布前曾被严格的命名审查检查过。

训练数据投毒防护:ANTI_DISTILLATION_CC

源码显示 Claude Code 在工具定义中注入了虚假的工具签名和参数描述——由 ANTI_DISTILLATION_CC 标志控制(多源确认)。但有作用域限制:源码中的逻辑仅在 entrypoint === 'official-cli'USER_TYPE === 'ant'(Anthropic 自家员工)以外的场景注入。当该标志激活时,虚假工具定义会被注入到系统提示词中。目的是污染从公开代码库爬取的训练数据:如果竞争对手录制 Claude Code 的 API 流量来蒸馏训练自己的模型,这些虚假工具会污染其训练集。

Undercover 模式:USER_TYPE === ‘ant’

CSDN 与 claudefa.st 双源确认:Anthropic 自家员工(USER_TYPE === 'ant')向公开仓库提 commit 时自动剥离署名和模型代号,注释明确写着"There is NO force-OFF"。这是反向蒸馏外的另一条数据卫生策略——避免内部使用 pattern 通过 git blame 泄漏给外部观察者。

Wizard rules:源码注释中的工程师吐槽

CSDN 提到源码里有大量带有"工程踩坑笔记"性质的注释,譬如警告违反某些 prompt 工程约束的人将面对"a full day of debugging and hair-pulling",又譬如 DIR_EXISTS_GUIDANCE 字符串解释为什么 Claude 在写文件前会反复 ls/mkdir -p 浪费回合——这些直接揭示了 Anthropic 工程师踩过的真实 prompt 工程坑。把"为什么这样写"写在源码注释里而不是单独的 ADR 文档里,是有意识地降低阅读成本的工程文化。

启动延迟优化:apiPreconnect 与 earlyInput

CLI 启动延迟工程化:apiPreconnect.ts 在 module load 同时就开始 TCP+TLS 预握手,earlyInput.ts 让用户在启动过程中就能开始输入。两个机制重叠节省约 100–200ms(CSDN)。--version 短路命中也是同类优化。这是把"工具响应感"做到极致的小细节——LLM 应用通常忽视的边角,Anthropic 把它当成产品体验的一部分。

实践启示

给"构建 Agentic AI 系统"的开发者

  • 静态/动态提示词分界:用边界标记明确划分可缓存和不可缓存的内容,最大化 prompt cache 命中率;价格 0.1× vs 1.25× / 2× 的比例决定了"写一次省一万次"的工程口诀
  • 采用 AsyncGenerator 模式:替代状态机,获得原生流式、中断和预算控制;Anthropic 把它做成 SDK 的对外 API 抽象就是认可
  • Hook 不是模型选择:要让某件事一定发生(autoformat、permission、telemetry),写 Hook 而不是 prompt 规则——但记住"非 0 即失败"是错的,只有 exit 2 真正阻塞
  • 权限路径锚定要文档化/path 是项目根而不是文件系统根这种语义差异必须在 README 中显式说明
  • 复合命令分治授权&& / || / ; / | 拆开评估,allowlist 不能假设整行命令是原子的
  • 五级覆盖优先级:让不同来源的上下文可组合、可覆盖,而非简单拼接
  • 受控降级压缩流水线:从单 tool_result 裁剪到全对话摘要分级处理,优雅降级而非粗暴截断;为压缩失败加 MAX_FAILURES 熔断常量
  • 文件系统的 Team 协调:轻量、持久、可调试的多智能体通信
  • model: 'inherit' 缓存优化:子智能体通过 byte-identical copies 共享父智能体提示词缓存
  • isolation: worktree:副作用密集的 sub agent 给一个独立 git worktree,完成自动清理
  • Skill / Sub agent / CLAUDE.md / Hook 边界要清晰:谁触发 × 何时加载这两个维度决定了选哪一个,不要让一个机制承担所有事

给"使用 Claude Code 的工程师"

  • 投资 harness 而不是 prompt:mer.vin 总结的金句——“Tool descriptions, permissions, CLAUDE.md, and hooks often beat prompt tweaking”。改 prompt 是一次性收益,改 harness 是结构性收益
  • 能用 workflow 就别用 agent:Anthropic 自己的 effective agents 文章建议——“Default to workflows when steps are known; use full agent loops only when exploration is required”。Agent 看似聪明但不可预测,工作流可控但需要你写 DAG
  • 设预算上限max_turnsmax_budget_usd 是两个核武器,生产环境必须设。事故案例:1,279 sessions 跑 50+ 轮 autocompact 失败,每天烧 25 万 API 调用——不是模型出错,是没设上限
  • 把规则写在 CLAUDE.md,不要写在 chat 里:压缩会丢 chat,CLAUDE.md 自动重读。所有"以后都这样做"的指令必须 commit 到 CLAUDE.md
  • 想象自己只看到上下文窗口的最后一屏:mer.vin 的建议——“if you would be lost with only the last screenshot and tool output, the model will be too”。Agent 也是只能看到上下文,预测它能否完成任务的最简方法就是站在它的位置上看

给"评估竞品 AI 编码工具"的人

MBZUAI 论文的发现是关键工具:四支独立团队(Anthropic / OpenAI / Cursor / Devin)在没有相互参考的前提下,agent harness 的 7 个组件几乎完全重合——这意味着:

  • 比较 harness 完整度,而不是模型聪明度:模型在快速商品化,每 6 个月排名洗牌;harness 是结构性工程,差距更稳定
  • 看 hook 系统:是否覆盖十余类生命周期事件(会话/输入/工具/权限/子智能体/压缩/文件等节奏)、是否支持 deny-wins 链、是否能拦截 LLM-as-judge——这是工程化深度的标尺
  • 看上下文管理:是否有多级压缩、是否暴露 PreCompact 钩子、是否有 cache write/read 比例的可观测性
  • 看权限模型:deny > ask > allow 的三层链是基线,复合命令分治、symlink 双路径检查是工程化标志
  • 看 SDK 是否对外稳定:Anthropic 把 Agent SDK 做成 Python/TypeScript 双 SDK,意味着 harness 设计已经成熟到可以承诺兼容性。还在频繁重命名内部抽象的工具说明设计还没收敛

看完这一篇之后

如果你只能记住三句话:

  1. Claude Code 不是 LLM CLI,是个 harness 系统——模型外面那一圈(permission / hook / compaction / sub-agent / cache)才是真正决定它好不好用的地方
  2. AsyncGenerator + mutableMessages 是核心抽象——一个生成器吐 token,一个数组累积历史,所有复杂功能都是绕着这两个抽象长出来的
  3. harness engineering 是 2026 年 AI 工程的主战场——MBZUAI 论文已经把"四个团队独立收敛到同一架构"的现象量化了。如果你在做 Agent 产品,应该把投资从 prompt 转向 harness

参考资料

官方文档(2026 年 docs.anthropic.com 已迁移到 platform.claude.com / code.claude.com

社区源码逆向工程

2026 年 5 月后的二次分析与学术化