Claude Code 是怎么运转的:一篇讲清主循环、上下文、工具、记忆与安全

很多人第一次接触 Claude Code,会把它理解成“一个会写代码的大模型”。但如果你真的拆开看它的内部实现,就会发现它的核心竞争力并不只是模型本身,而是一整套围绕模型构建出来的 Agent 运行时。

它不是回答一次问题就结束的聊天机器人,而是一个能持续思考、调用工具、压缩上下文、恢复错误、管理权限、协调子 Agent 的执行系统。换句话说,Claude Code 的本质不是一次回答,而是一套受控的工具循环

这篇文章把原始草稿里的关键内容重新整理成一篇完整博客,按“主循环 → 上下文工程 → 工具系统 → 记忆系统 → Hooks → 多 Agent → 权限与安全”的顺序,讲清楚 Claude Code 为什么能工作,为什么能扩展,以及为什么它在真实机器上运行时还能尽量保持安全。


一、Claude Code 的本质:受控工具循环 Agent

从用户输入,到最终输出,中间并不是一次模型调用,而是一轮又一轮的循环:

  1. 接收用户输入
  2. 组装上下文
  3. 调用模型
  4. 解析响应
  5. 如果模型要求调用工具,就执行工具
  6. 把工具结果再塞回上下文
  7. 继续下一轮,直到任务完成

这就是 Claude Code 的核心范式:Controlled Tool-Loop Agent

在实现上,它把这套流程拆成两层:

  • QueryEngine:负责整个会话生命周期
  • query():负责单次查询中的主循环

这两层分离非常重要。QueryEngine 管的是“这一场对话如何持续、花费多少、权限如何处理、结果如何保存”;query() 管的是“这一轮模型该怎么跑、上下文是否需要压缩、工具调用是否成功、异常是否需要恢复”。前者偏会话管理,后者偏执行编排。

这种拆分让 Claude Code 具备了很多聊天产品没有的能力:

  • 会话生命周期管理
  • 工具调用编排
  • 流式结果输出
  • 错误恢复与续写
  • Token 预算控制
  • 记忆与技能注入
  • Stop Hooks 与回合结束处理

所以从工程角度看,Claude Code 最有价值的不是“提示词写得好”,而是它把模型放进了一个可持续运行的执行框架里。


二、上下文工程:Agent 能力的真正分水岭

大模型的上下文窗口再大,也会在多轮对话中被逐渐撑满。Claude Code 的能力,很大一部分就体现在它如何管理上下文。

简单说,上下文工程做两件事:

  • 组合:把系统提示词、工具定义、用户上下文、历史消息拼成一次完整请求
  • 压缩:当上下文变长时,尽可能低成本地释放空间

Claude Code 每次调用 API 时,核心会发送三部分:

  • system
  • tools
  • messages

这三部分并不是随便拼接的,而是精细设计过的。

1. 系统提示词不是一整块,而是分层构建的

Claude Code 的系统提示词有严格优先级。不同模式下,可能由默认提示词、自定义提示词、协调器提示词、Agent 专用提示词等叠加或替换而成。

这里最关键的设计,是 SYSTEM_PROMPT_DYNAMIC_BOUNDARY

它把系统提示词切成两段:

  • 边界之前:核心规则、工具说明、安全约束等静态内容
  • 边界之后:输出风格、语言偏好、MCP 指令等动态内容

这个切分的意义不是“写得整齐”,而是为了缓存

静态部分对所有用户几乎一致,可以做更高层级的共享缓存;动态部分跟用户和会话相关,就不参与这种共享。这样 Claude Code 才能在多轮请求里尽可能命中 Prompt Cache,降低成本和延迟。

2. 系统上下文和用户上下文被故意放在不同位置

Claude Code 里有两个容易混淆的概念:

  • 系统上下文:平台、Git 状态、环境信息
  • 用户上下文:CLAUDE.md、当前日期、项目级提醒

它们都属于“会话级快照”,但放置位置不同:

  • 系统上下文放在 system 的动态部分
  • 用户上下文放在 messages[0],以 <system-reminder> 的形式出现

这样做是为了让缓存前缀尽量稳定。尤其是用户上下文被固定在最前面的消息里,可以随着历史消息一起参与前缀缓存。

3. 五级压缩流水线是 Claude Code 的关键工程设计

Claude Code 并不是等上下文爆掉后再“总结一下”,而是准备了一条渐进式压缩流水线。原则很明确:先用便宜的方法,必要时再用昂贵的方法

五级压缩分别是:

  1. Tool Result Budget
  2. Snip
  3. Microcompact
  4. Context Collapse
  5. Autocompact

这五层从轻到重:

  • 最轻的是工具结果预算控制,直接把大结果落盘,只保留预览
  • 中间层会删除冗余消息、清理旧工具结果、构造只读折叠视图
  • 最重的是自动摘要压缩,要 fork 子 Agent 调用模型生成摘要,并用摘要替换原始消息

这里最值得注意的是 Context Collapse。它不是立刻改写历史,而是先构造“折叠视图”,尽量在不破坏原始上下文的前提下减少 token 占用。如果折叠后已经足够,就能避免更重的摘要压缩。

这是一种非常典型的 Agent 工程思维:先保真,再压缩;先可逆,再不可逆

4. 记忆预取也是上下文工程的一部分

Claude Code 会在模型生成响应的同时,并行搜索相关记忆文件。这样真正需要把记忆注入上下文时,结果通常已经准备好了。

本质上,这是一种“隐藏延迟”的优化:把本来要串行发生的磁盘 I/O,提前和模型推理并行掉。对用户来说,这部分延迟几乎像“免费的一样”。


三、工具系统:模型负责决策,工具负责行动

如果没有工具,再强的模型也只能“建议”你做什么。Claude Code 真正像 Agent 的地方,在于它能调用工具完成实际动作。

Claude Code 的工具系统可以分成三层:

  • 设计层:定义工具接口与安全语义
  • 组装层:把内置工具、MCP 工具等组合成统一工具池
  • 执行层:在流式响应期间调度工具执行

1. 工具不是简单函数,而是带安全语义的能力单元

每个工具都不仅仅有“执行逻辑”,还要声明很多元信息:

  • 输入 Schema
  • 是否只读
  • 是否并发安全
  • 是否可能破坏环境
  • 是否需要权限检查
  • 如何在 UI 中展示输入和结果

也就是说,工具在 Claude Code 里不仅是执行单元,还是权限系统、并发系统和 UI 系统的一部分。

2. 工具执行被设计成流式、并发、带恢复能力

Claude Code 在模型流式输出时,就可以开始处理工具调用。工具执行流程里有一个很关键的优化:Pre-Tool Hook 和 Bash 分类器并行启动

为什么要这么做?因为这两个步骤都可能带来几十到几百毫秒的延迟。如果串行执行,权限判断会变慢;如果并行跑,就能显著降低整体等待时间。

3. 并发不是“能一起跑就一起跑”

Claude Code 对并发很克制,核心规则是:

  • 只读工具可以并行
  • 写入工具必须串行
  • 特殊情况下再用 isConcurrencySafe() 做更细粒度控制

这背后反映的是一个很成熟的设计观念:并发能力不应该默认开放,而应该默认保守

4. 大结果不直接塞进上下文,而是落盘后按需读取

工具输出过大时,Claude Code 不会把整段内容直接喂回模型,而是:

  1. 把完整结果写到磁盘
  2. 在上下文里只保留路径、预览和截断提示
  3. 需要完整内容时,再用读取工具回读

MCP 工具返回的图片、PDF 等二进制内容也会做类似处理,超过阈值时写入 .claude/mcp-outputs/

这其实是一种典型的“按需读取”模式。它把一次性上下文爆炸,变成了可控的增量访问。

5. 工具延迟加载是降低系统提示词开销的重要手段

并不是所有工具都会在每次请求时都发送给模型。某些工具会被标记为延迟加载,只有模型通过 ToolSearch 搜索到时,才会动态加入。

这样做的收益很直接:

  • 减少工具 schema 带来的 token 开销
  • 缩短系统提示词长度
  • 提高缓存命中率

对于一个工具很多的 Agent 系统来说,这是非常关键的优化。

6. 工具还负责 UI 渲染

Claude Code 的一个很有意思的设计,是“渲染即工具”的思想。工具不仅定义怎么执行,还定义怎么展示。

例如模型调用文件编辑工具时,传给 API 的可能只是相对路径;但 UI 展示时,系统会补全成绝对路径,方便人类阅读。这个补全发生在展示层,不修改真正发给模型的消息内容,从而避免破坏 Prompt Cache 的稳定性。

这类细节很小,但体现了一个成熟系统的习惯:展示层需要的信息,不一定应该污染推理层的数据结构


四、记忆系统:只记“当前状态推不出来”的东西

Claude Code 的记忆系统有一句非常核心的原则:

只记忆那些不能从当前项目状态推导出来的信息。

这意味着它不会把一切都存下来,而是有选择地记。

1. 四类主记忆

Claude Code 的主记忆分四类:

  • user:用户身份、偏好、知识背景
  • feedback:用户对 Agent 行为方式的纠正
  • project:项目进展、决策、截止日期
  • reference:外部系统定位信息

这四类记忆的共同点是:它们对长期协作有价值,但未必能从代码仓库里直接推断出来。

2. 记忆存储不是一个大文件,而是一组独立 Markdown

记忆目录通常类似这样:

~/.claude/projects/{project-hash}/memory/
├── MEMORY.md
├── user_role.md
├── feedback_terse.md
├── project_freeze.md
└── reference_linear.md

MEMORY.md 是入口索引,不承担全部记忆内容。每条记忆独立成文件,并带 frontmatter。这样做的好处是:

  • 可单独读取和更新
  • 易于检索
  • 不会因为一个大文件不断膨胀而失控

同时它还做了双重截断保护:

  • 行数截断
  • 字节截断

目的是防止异常长内容污染入口索引。

3. 记忆召回是异步预取的

当用户发来新请求时,系统会扫描、评估、过滤潜在相关记忆。这个过程借助 pendingMemoryPrefetch 和后台 sideQuery() 并发进行,通常在模型真正需要记忆前就完成了。

这意味着 Claude Code 的记忆系统并不是“先停下来搜记忆,再回答你”,而是尽量把这一步藏到后台。

4. 记忆会过时,所以必须带新鲜度判断

这是 Claude Code 记忆系统最成熟的地方之一。

系统明确承认一件事:记忆记录的是写入时的事实,不一定是现在的事实

所以它会把时间转换成模型更容易理解的形式,比如“47 days ago”,帮助模型推断这条记忆是否可能过期。对于超过一定时效的记忆,还会注入额外提醒。

更重要的是,Claude Code 不把记忆当真理,而是要求验证:

  • 记忆里说某个文件存在,要用 GlobRead 再确认
  • 记忆里说某个函数还在,要用 Grep 再确认

这是一条很重要的经验:记忆是线索,不是事实源

5. 记忆写入不只靠显式命令,还有后台提取 Agent

除了用户手动保存和主 Agent 主动写入,Claude Code 还会在回合结束后运行一个后台记忆提取 Agent。

这个提取 Agent 有三层控制:

  • 触发时机控制
  • 提取频率节流
  • 并发防护与 trailing run

它不会和主 Agent 重复写同一段内容,也不会并发写坏记忆目录。如果提取没来得及完成,新上下文会先暂存,等上一轮提取结束后再补做。

这背后体现的是另一种成熟工程意识:自动化提取不能以制造重复和冲突为代价

6. 记忆还有私有、团队、子 Agent 多个作用域

启用团队记忆后,Claude Code 会区分私有记忆和团队记忆。对子 Agent,它还额外提供三种作用域:

  • user
  • project
  • local

这说明 Claude Code 的记忆系统不是简单的“记住点东西”,而是在尝试构建一套真正面向长期协作的知识层。


五、Hooks:把 Agent 生命周期开放给外部系统

Hooks 是 Claude Code 可扩展性非常强的一部分。它让很多事件节点不再只是内部实现细节,而可以变成外部脚本、外部服务、甚至另一个 LLM 的接入点。

Claude Code 支持的事件覆盖非常广,包括:

  • 工具生命周期
  • 权限请求与拒绝
  • 会话开始与结束
  • Stop 与 StopFailure
  • 子 Agent 启停
  • 任务创建与完成
  • 压缩前后
  • MCP 交互
  • 工作区变化
  • 通知事件

1. Hook 不止一种

它支持 4 类可配置 Hook 和 2 类编程式 Hook:

  • Command Hook
  • Prompt Hook
  • Agent Hook
  • HTTP Hook
  • Callback Hook
  • Function Hook

这意味着 Hook 能力横跨本地脚本、外部 API、结构化 LLM 调用和进程内函数。

2. Hook 的执行不是直接“命中就跑”

Claude Code 为 Hook 设计了一条完整的执行流水线:

  1. 快速检查当前事件是否可能命中 Hook
  2. 信任检查,防止不可信工作区触发恶意逻辑
  3. 基于 matcher 和条件表达式过滤
  4. 去重
  5. 并行执行
  6. 解析输出并聚合结果

这里的信任检查尤其重要。它把 Hook 系统放进了整个安全模型里,而不是把它当成“用户自己配的脚本,就不管了”。

3. 退出码 0/1/2 是非常漂亮的语义设计

Claude Code 的 Hook 退出码不是传统的“0 成功,其它失败”。

它定义了三层语义:

  • 0:静默成功
  • 1:向用户报问题,但不告诉模型
  • 2:阻塞性问题,必须注入模型上下文

这个设计很妙,因为它把“是否影响模型决策”单独建模了出来。

尤其是 asyncRewake 模式,非常适合后台跑测试这类任务:测试成功不用打扰模型,测试失败再把错误注入对话流,让模型继续处理。

换句话说,Claude Code 的 Hook 不是普通回调,而是一个和 Agent 执行循环深度耦合的控制面。


六、多 Agent 架构:不是“多开几个窗口”那么简单

Claude Code 的多 Agent 系统,解决的是一个实际问题:单 Agent 太慢,但多个 Agent 一起跑,又会带来上下文冲突、权限泄漏和成本膨胀。

它主要有三种模式:

  • 子 Agent
  • 协调器模式
  • Swarm 模式

1. 子 Agent:默认隔离,显式共享

最基础的形式是父 Agent 调用 AgentTool,把一个自包含任务交给新子 Agent 执行。

子 Agent 默认看不到父级完整历史。这个约束并不是功能缺失,而是刻意设计:

  • 保持隔离,避免无关上下文污染
  • 控制 token 成本
  • 避免并发共享可变历史导致竞态条件

Claude Code 内建了不同类型的子 Agent,比如只读探索型、规划型、通用型;也允许用户通过 .claude/agents/*.md 自定义。

真正精妙的地方在 createSubagentContext():它默认把大多数可变状态克隆或隔离,只有极少数字段显式共享。比如 UI 状态默认隔离,但进程注册相关状态必须共享,否则后台进程可能失去回收路径变成僵尸进程。

这是一种很成熟的并发设计哲学:共享不是默认值,隔离才是默认值

2. Fork 子 Agent 的真正目的,是复用 Prompt Cache

Claude Code 还有一种特殊的 fork 机制。表面上看,它像是“让子 Agent 继承父上下文”;但更准确地说,它的核心目的是让多个子 Agent 共享请求前缀,从而命中 Prompt Cache

做法是:

  • 继承父级已渲染的系统提示词字节
  • 克隆父级部分消息
  • 为 tool_use 构造统一占位结果
  • 只在尾部追加差异化指令

这样从同一父级 fork 出来的多个子 Agent,前缀字节几乎完全相同。第一个是冷启动,后面的成本就会大幅下降。

所以“Fork 是架构模式”只说对了一半。它更深层的真相是:Fork 是一种缓存优化策略

3. 多层工具过滤体现了纵深防御

子 Agent 能使用哪些工具,并不是简单继承父 Agent。

Claude Code 做了多层过滤:

  • 所有子 Agent 统一禁用某些元工具
  • 自定义 Agent 进一步受限
  • 异步 Agent 只能用后台安全工具白名单
  • 每种 Agent 类型还有自己的额外限制

这种设计比单个“allow/deny 开关”更稳健。一层失效,还有其他层兜底。

4. 协调器模式的关键不是“能派工”,而是“不能自己干活”

协调器模式里,主 Agent 被严格限制成编排者。它不能直接 ReadEditBash,只能派 Worker、给 Worker 发消息、终止 Worker。

为什么这么极端?因为如果协调器既能编排又能执行,它很快就会退化成普通单 Agent,失去强制分工的意义。

这和很多组织设计是一样的:想要真正分工,不能只靠提示词鼓励,而要靠能力边界约束

5. Swarm 模式提供对等协作

Swarm 模式更进一步,让多个 Worker 之间通过统一执行接口和命名信箱通信。在轻量实现里,它们甚至可以运行在同一个进程中,共享 API 客户端和 MCP 连接,减少额外通信开销。

6. Plan 模式是在“边想边做”之间插入审批关卡

Plan 模式不是简单只读,而是把 Agent 暂时限制为“只能读代码和写计划文件”。等用户批准后,再恢复原本权限。

这比“彻底禁写”更合理,因为 Agent 仍然可以产出一个可审阅的计划结果;同时又不会直接改动真实代码。


七、权限与安全:Claude Code 最硬核的工程基础

Claude Code 运行在用户真实机器上,能读文件、改代码、跑命令。这意味着安全不是附加项,而是底座。

它的核心理念是:纵深防御

也就是每一层都假设别的层可能会失败,因此要独立兜底。

1. 七层纵深防御架构

从上到下,Claude Code 大致有七层安全机制:

  1. 工作区信任
  2. 权限模式
  3. 权限规则
  4. Bash 多层安全分析
  5. 工具级权限检查
  6. 沙箱与隔离
  7. 最终用户确认

这套结构说明 Claude Code 并不相信任何单层机制能永远正确。

2. 权限模式不是一个开关,而是一组策略

它支持多种权限模式,比如:

  • 默认确认
  • 自动接受编辑但 Bash 仍需确认
  • Plan 审批模式
  • 全自动通过
  • 无人值守时自动拒绝

还有内部使用的自动分类和冒泡模式。

其中最关键的一点是:某些安全检查是 bypass-immune 的。也就是说,即使你开了极宽松的模式,危险路径和危险操作仍然要经过额外确认。

这说明 Claude Code 的“放权”不是绝对的,而是保留了硬边界。

3. 权限决策流程是分层判定的

Claude Code 在决定一个工具是否能执行时,会依次检查:

  • 是否命中 deny
  • 是否必须 ask
  • 工具自身的权限检查
  • 是否触发内容级 ask
  • 是否触发安全检查
  • 是否处于 bypass 模式
  • 是否命中 allow
  • 最后再回落到 ask

这不是一个简单 if-else,而是一条有严格优先级的决策链。

4. Bash 安全是整套系统里最复杂的一层

Claude Code 对 Bash 特别警惕,因为命令执行的攻击面最大。

它没有简单靠正则过滤,而是做了多层处理:

  • tree-sitter AST 解析
  • 语义层检查
  • 20 多项静态规则
  • 路径与符号链接约束
  • 环境变量黑白名单

尤其是 AST 解析采用 fail-closed 思路:看不懂的结构就默认不信任。这一点非常关键,因为 shell 的危险性很大程度上就来自那些难以用字符串规则覆盖的复杂语法结构。

5. 危险路径是特殊保护对象

.git.claude.bashrc.zshrc.mcp.json 这类路径,会被视为高风险目标。即使在宽松权限模式下,也不能轻易自动通过。

同时系统还会做路径标准化和符号链接解析,防止通过大小写差异或软链接绕过约束。

6. 三种权限处理器适配不同协作形态

Claude Code 不是所有场景都用同一种权限交互。

  • 交互式模式可以并行启动 UI、Hook、分类器
  • 协调器 Worker 采用更保守的顺序执行
  • Swarm Worker 直接继承父级权限,不自行弹框

这说明权限系统并不是孤立模块,而是和单 Agent、多 Agent、交互式、后台式等运行模型强耦合的。

7. 安全的本质不是“拦更多”,而是“每层都假设别的层会失误”

Claude Code 整套权限与安全体系最值得借鉴的地方,不是某条具体规则,而是它的设计态度:

  • AST 假设字符串规则会漏
  • 工具级检查假设上层模式可能过宽
  • 用户确认假设所有自动机制都可能判断错

它不是“先默认信任,再补几个例外”;而是从第一天起就按敌对环境来设计。


八、Claude Code 真正厉害的地方,不是模型,而是运行时

把上面这些系统放在一起,你就会发现 Claude Code 的核心能力并不是“回答得更聪明”,而是它把大模型放进了一个完整的工程框架:

  • 主循环保证它能持续行动,而不是一次性输出
  • 上下文工程保证它不会轻易失忆
  • 工具系统让它真正有执行能力
  • 记忆系统让它具备长期协作属性
  • Hooks 让它能和外部流程对接
  • 多 Agent 让它能分工、并行、节省成本
  • 权限与安全体系让它在真实环境里仍尽量可控

这也是为什么 Claude Code 不只是一个“聊天式编程助手”,而更像一个真正的 Agent 运行时。

如果只看模型回答,你看到的只是表层;如果看主循环、缓存、工具、记忆、权限和安全这些机制,你看到的才是 Claude Code 作为一个工程系统的真正价值。


参考资料

暂无评论

发送评论 编辑评论


				
上一篇