第9章 深度剖析 Claude Code 架构原理

《长程任务 Agent 开发实战:Harness 工程原理与应用实践》
——深度剖析 Claude Code、OpenClaw、Codex 架构原理与应用


章首金句

命令行是工程师最诚实的界面:它不粉饰、不隐藏、不替你做决定。Claude Code 选择住进终端而不是躲进 IDE,恰恰因为它要做的不是"替你写代码",而是"和你一起扛活"——一个愿意跟你一起待在战壕里的搭档,永远比一个坐在办公室里替你审批的人有用。


9.1 开场隐喻:从"驻场总管"到"命令行搭档"

想象你是一家施工队的总工程师,手底下管着几十号人。你不需要一个替你画图纸的绘图员——你自己会画。你需要的是一个"驻场总管":他住在工地,能随时翻图纸、量尺寸、查规范、跑腿去仓库领料、发现管线冲突时立刻喊停。他不是你的替代者,他是你的外延。

传统 IDE 里的 AI 助手,更像是一个"办公室绘图员":你把需求口述给他,他在自己的工位上画好图,递给你看;你要改,他把图拿回去再改。他从不踏进工地,也从不管施工队的活怎么排。他体面、干净,但隔着一层。

Claude Code 是另一种存在。它不住办公室,它住在你每天待的那个最朴素的地方——命令行终端。它像一个拎着工具箱、蹲在你旁边的搭档:你说"把这片的 var 都换成 let,测试别挂",它就自己翻文件、自己改、自己跑测试、挂了自己回滚、最后给你一张清单。你不放心的地方,它会先问你要不要动手(权限确认);你忙不过来的活,它能再拉几个工友来并行干(子代理派发)。

这个"命令行搭档"的定位,不是技术上的随手选择,而是一整套设计哲学的起点。住进终端意味着:它必须接受工程师最朴素的工作环境(文件系统 + Shell),必须接受工程师最朴素的交互方式(文本对话),必须接受工程师最朴素的信任模型(你来审批每一步危险操作)。这种"朴素",恰恰是长程任务可靠性的根基。

学习目标(读完本章你将能够)

  1. 用一张总架构图讲清 Claude Code 从 CLI 入口到子代理派发的完整数据通路,并能把每一块映射到第1章的七层 Harness 模型。
  2. 复述 Agent SDK 的四个核心抽象(Session、Loop、Tool 注册、Message 流),并解释为什么"SDK 化"是 Agent 从产品走向平台的关键一步。
  3. 说清 Claude Code 的上下文与记忆策略:Read 按需读取、TodoList 作为外部规划记忆、子代理上下文隔离、compaction 压缩——并理解它们与第4章上下文工程的呼应关系。
  4. 区分 Skills、Hooks、Tools 三者的层次关系与扩展边界,能用它们组合出可复用的自动化行为。
  5. 复述权限模型的三态决策(allow/deny/ask)与沙箱机制,并能用它给一个真实项目配置最小权限。
  6. 设计一个基于 Skills + Hooks 的代码审查 Agent,并写出配置骨架与伪代码。

9.2 整体架构:CLI 入口、Agent SDK 内核、工具层与子代理派发

要把 Claude Code 讲透,最好的办法是跟着一条用户指令从入口走到出口,把沿途经过的每一个模块都点一遍名。

9.2.1 从一行命令说起

当你在终端敲下 claude 并输入一句任务时,背后发生的事情远比看上去复杂。据公开资料与业界观察,Claude Code 的运行时大致经过以下通路:

  1. CLI 入口层:解析命令行参数、加载配置(settings.json、CLAUDE.md 等)、初始化会话环境。
  2. Agent SDK 内核:创建 Session、启动 Agent Loop、注册可用工具集、管理消息流。
  3. 模型推理:将组装好的上下文发送给 Claude 模型,接收返回的文本与工具调用意图。
  4. 权限检查:在工具真正执行前,走一遍权限闸门(allow/deny/ask)。
  5. 工具执行:调用对应工具(Read、Bash、Edit、Grep 等),在本地文件系统或 Shell 中执行。
  6. 结果回灌:将工具执行结果作为新的 message 注入消息流,进入下一轮 Loop。
  7. 子代理派发(按需):当主代理判断需要并行或隔离探索时,派发子代理,子代理独立运行后汇报结果。
  8. 可观测记录:全过程被日志、审计追踪记录。

这条通路看着线性,实则是一个循环——Agent Loop 会不断重复 3→4→5→6→3,直到模型判断任务完成或需要用户介入。

9.2.2 总架构图

下面这张图把 Claude Code 的整体架构按七层 Harness 的视角展开,同时标注每一块在系统中的位置。

图 9-1:Claude Code 整体架构总览(七层 Harness 视角)

        ┌──────────────────────────────────────────────────────────────┐
        │                     用户(工程师)                           │
        │              在终端输入任务 / 审批权限 / 读输出               │
        └──────────────────────────┬───────────────────────────────────┘
                                   │ 文本对话
                                   ▼
        ┌──────────────────────────────────────────────────────────────┐
        │  L7 可观测层    │ 日志 / 审计追踪 / Token 计量 / 错误上报     │
        ├──────────────────────────────────────────────────────────────┤
        │  L6 治理安全层  │ 权限三态(allow/deny/ask) │ 沙箱 │ Hooks 拦截│
        ├──────────────────────────────────────────────────────────────┤
        │  L4 任务编排层  │  Agent SDK 内核                            │
        │                 │  ┌──────────────────────────────────────┐  │
        │                 │  │ Session(会话状态)                     │  │
        │                 │  │ Agent Loop(Thought→Action→Observe)   │  │
        │                 │  │ Tool Registry(工具注册表)             │  │
        │                 │  │ Message Stream(消息流)                │  │
        │                 │  └──────────────────────────────────────┘  │
        ├──────────────────────────────────────────────────────────────┤
        │  L3 上下文记忆  │ Read按需读取 │ TodoList外部记忆 │ compaction│
        │                 │ CLAUDE.md │ 子代理上下文隔离                │
        ├──────────────────────────────────────────────────────────────┤
        │  L2 工具接口层  │ 内置工具: Read Write Edit Bash Grep Glob   │
        │                 │           Agent(Task) TodoWrite ...        │
        │                 │ MCP工具:  外部MCP Server注册的工具         │
        │                 │ Skills:   按需加载的技能定义              │
        ├──────────────────────────────────────────────────────────────┤
        │  L1 执行环境层  │ 本地文件系统 │ Shell进程 │ 沙箱容器        │
        │                 │ Git仓库 │ 工作目录(workspace)              │
        └──────────────────────────────────────────────────────────────┘
                                   │
                                   ▼ 模型推理
        ┌──────────────────────────────────────────────────────────────┐
        │              Claude 模型(认知核 / 大脑)                     │
        │         接收上下文 → 产出文本 + 工具调用意图                  │
        └──────────────────────────────────────────────────────────────┘
        ```

图注:Claude Code 的架构不是"模型 + 几个工具"的扁平结构,而是七层同心包裹大脑的立体结构。用户在终端与系统交互,指令从 L7(可观测)经 L6(治理)到达 L4(Agent SDK 内核),内核驱动 Loop 并经由 L3(上下文记忆)组装消息、L2(工具接口)执行动作、L1(执行环境)落地到文件系统与 Shell。模型(大脑)在最底层被七层包裹。

9.2.3 三大子系统

把上面的总图压缩一下,Claude Code 的运行时可以归纳为三大子系统:

(一)入口与配置子系统:负责 CLI 参数解析、settings.json / CLAUDE.md 加载、环境变量注入、会话初始化。这是整个系统的"前台",决定了 Agent 启动时"知道什么、能做什么、被允许做什么"。

(二)Agent SDK 内核子系统:负责 Session 管理、Agent Loop 驱动、Tool 注册与调度、Message 流编排。这是系统的"中枢神经",是第 9.3 节的主角。

(三)工具与执行子系统:包括内置工具(Read/Write/Edit/Bash/Grep/Glob/Agent/TodoWrite 等)、MCP 外部工具、Skills 技能、Hooks 钩子、文件系统与 Shell 执行环境。这是系统的"手脚与神经末梢"。

金句

三大子系统——前台(入口配置)、中枢(Agent SDK)、手脚(工具执行)——恰好对应一个驻场总管的三件套:知道规矩、能调度、会动手。少了任何一件,都只是半个搭档。

9.2.4 配置文件的层次

Claude Code 的配置不是单一文件,而是一组有层次的文件。据公开资料,配置层次大致如下(从全局到局部):

  • 用户级配置(如 ~/.claude/settings.json):跨所有项目生效的默认设置。
  • 项目级配置(如项目根目录的 .claude/settings.json):随项目走、可提交到版本控制的团队共享设置。
  • 本地级配置(如 .claude/settings.local.json):项目级但不提交,用于个人偏好覆盖。
  • CLAUDE.md:项目根目录的 Markdown 文件,内容会被注入到 Agent 的系统上下文,相当于"给搭档的项目交接文档"。

这种层次设计的好处是:团队可以把"所有人都该遵守的规矩"放进项目级配置并提交到 Git,而"我个人喜欢的工作方式"放进本地级配置不提交。权限规则、Hooks 定义、环境变量都可以按这个层次覆盖。

配置的覆盖规则遵循"局部优先"原则——当一个配置项在多个层级都有定义时,更局部的层级优先。这意味着:项目级配置可以覆盖用户级的默认值,本地级配置可以覆盖项目级的值。这种设计让团队规矩和个人偏好可以共存而不冲突——团队说"所有 Bash 命令都要 ask",个人可以在本地配置里对特定安全命令放行 allow,不需要修改团队共享的配置文件。

金句

配置的层次,本质是信任的层次。全局配置是你对"所有搭档"的底线要求,项目配置是你对"这个项目"的特别交代,本地配置是你对"此刻的自己"的个人偏好。层次清晰,才不会把团队规矩和个人习惯搅成一锅粥。

9.2.5 文件系统视图:Agent 看到的世界

理解 Claude Code 的另一个重要视角是"Agent 看到的文件系统是什么样的"。Agent 不是通过 API 访问一个抽象的"项目",它是直接活在文件系统里的——它的 Read、Write、Edit、Bash 工具都是对真实文件系统的操作。

Agent 启动时,它被"放在"一个工作目录(workspace)里。这个工作目录通常是用户运行 claude 命令时所在的目录——也就是项目的根目录。从这时起,Agent 的文件系统操作(Read、Write、Bash 里的 ls/cat 等)都以这个目录为基准。

这个设计有两个工程含义:

第一,Agent 的"世界"就是文件系统。它不需要一个专门的"项目模型"或"代码索引"——它通过 Read 看代码、通过 Grep 搜代码、通过 Bash 跑命令、通过 Write/Edit 改代码。这与传统 IDE 插件不同——IDE 插件通常有自己的项目模型(语法树、符号索引、引用关系),Agent 直接和"裸文件系统"打交道。这看起来更"原始",但其实更灵活——Agent 不受限于任何特定的项目模型或语言生态,它能处理任何可以用文件和文本表示的工程任务。

第二,Agent 的文件系统视图是"真实"的——它看到的就是磁盘上的实际文件,不是某种缓存或抽象。这意味着 Agent 的操作是"真刀真枪"的:它改的文件就是磁盘上的文件,它跑的命令就是真实的 Shell 命令。这带来了能力(Agent 可以做任何文件系统能做的事),也带来了风险(Agent 也能搞乱任何文件系统能搞乱的东西)。这就是为什么权限模型(9.7 节)和沙箱如此重要——能力越大,治理越要跟上。

金句

Agent 不需要一个抽象的项目模型,它的"世界"就是文件系统本身。这看似原始,实则最强——文件系统是所有工程工作的公共底座,能操作文件系统就能做任何工程任务。但能力与风险成正比:能改一切文件,也意味着能搞乱一切文件。治理必须跟上能力,否则搭档就变成了闯祸精。


9.3 Agent SDK 抽象:Session、Loop、Tool 注册、Message 流

Agent SDK 是 Claude Code 的中枢神经。如果说 CLI 是面孔,工具是手脚,那 Agent SDK 就是大脑和手脚之间的那套神经传导与协调系统。理解了 Agent SDK 的四个核心抽象,就理解了 Claude Code 为什么能从"一个产品"变成"一个可编程平台"。

9.3.1 为什么要 SDK 化

在 Claude Code 早期,Agent 的运行逻辑是和 CLI 紧耦合的——你想跑一个 Agent,就得装整个 CLI。这有两个问题:一是无法被其他程序嵌入调用(比如你想在自己的 CI/CD 里跑一个 Agent 做代码审查,你得把整个交互式 CLI 搬进去);二是运行逻辑无法被复用和定制(你想改 Loop 行为、换工具集,得 fork 整个代码库)。

SDK 化的本质,是把"Agent 的运行内核"从"CLI 的交互外壳"中剥离出来,变成一个可编程的库。这意味着:任何程序都可以 import 这个 SDK,创建一个 Agent 实例,配置它的工具和权限,然后驱动它完成任务。Claude Code CLI 本身,也变成了"SDK 的一个消费者"——它只是 SDK 的一个交互式前端。

金句

产品是 SDK 的一个消费者,SDK 是产品的内核。这个关系一旦成立,Agent 就从"一个能用的东西"变成了"一个能被编程的东西"——而能被编程的东西,才能被组合、被嵌入、被定制成千变万化的长程任务系统。

9.3.2 四个核心抽象

据公开资料与业界观察,Agent SDK 的核心抽象可以归纳为四个:Session、Loop、Tool Registry、Message Stream

(一)Session(会话)

Session 是一次 Agent 运行的"生命周期容器"。它持有:会话标识、当前消息历史、工具注册表、权限配置、工作目录、环境变量、以及可跨轮次持久化的状态。可以把 Session 理解为"搭档这次上岗的全套装备和工作记录"。

Session 的关键设计是:它是有状态的、可持久化的。这意味着一次长程任务可以跨多次调用延续——今天干到一半,明天可以接着干,因为 Session 的状态可以被保存和恢复。这与第1章讲的"模型本身无状态"形成互补:模型无状态,但 Session 有状态,Session 就是那个"把状态喂回去"的外部记忆。

(二)Loop(认知循环)

Loop 是 Agent 的心跳。它的最小形态就是第3章讲的 ReAct 循环——Thought → Action → Observe → Thought → …。但在 Claude Code 的工程实现里,Loop 比裸 ReAct 复杂得多,它至少要处理:

  • 终止判断:模型说"任务完成"时退出循环,但也要防止模型陷入"永远觉得自己没做完"的死循环。
  • 错误处理:工具调用失败时,决定是重试、换方案、还是上报用户。
  • 权限交互:遇到 ask 态操作时,暂停循环等待用户审批。
  • 上下文管理:每轮循环前检查消息流是否过长,触发 compaction 压缩。
  • 子代理调度:当模型发起 Agent 工具调用时,派发子代理并在子代理完成后回收结果。

图 9-2:Agent SDK 消息流时序图(一次完整 Loop 迭代)

   用户输入                  Agent SDK 内核                    工具层            模型
      │                          │                             │                │
      │── "把测试跑通" ─────────▶│                             │                │
      │                          │                             │                │
      │                          │── 组装 Message(系统提示+     │                │
      │                          │    上下文+用户输入+历史)     │                │
      │                          │                             │                │
      │                          │─────────────────────────────────────────────▶│
      │                          │                             │   模型推理     │
      │                          │◀─────────────────────────────────────────────│
      │                          │   返回: 文本 + 工具调用意图  │                │
      │                          │   (如: 调用Bash, 参数:npm test)│               │
      │                          │                             │                │
      │                          │── 权限检查(ask?) ───────────│                │
      │◀── "允许执行 npm test?" ─│                             │                │
      │── "允许" ───────────────▶│                             │                │
      │                          │                             │                │
      │                          │── 调用 Bash 工具 ──────────▶│                │
      │                          │                             │── 执行 npm test│
      │                          │                             │◀── stdout/stderr│
      │                          │◀── 工具结果 ────────────────│                │
      │                          │                             │                │
      │                          │── 结果作为新Message注入流   │                │
      │                          │── 检查是否需要compaction    │                │
      │                          │                             │                │
      │                          │───────── 下一轮 Loop ──────────────────────▶│
      │                          │             ...             │                │
      │                          │                             │                │
      │                          │◀── 模型: "任务完成" ─────────────────────────│
      │◀── 输出最终结果 ─────────│                             │                │

图注:一次 Loop 迭代包含"组装消息→模型推理→权限检查→工具执行→结果回灌→下一轮"六个阶段。权限检查和结果回灌是裸 ReAct 不具备的工程环节,正是它们把"会跑的循环"变成了"可控的循环"。

(三)Tool Registry(工具注册表)

Tool Registry 是 Agent "知道自己有哪些手"的来源。在 Session 初始化时,所有可用工具被注册到一个注册表里——包括内置工具(Read、Write、Edit、Bash、Grep、Glob、Agent、TodoWrite 等)、MCP 外部工具(通过 MCP Server 动态注册)、以及 Skills 暴露的工具入口。

注册表的核心职责是:

  • 声明工具契约:每个工具注册时声明自己的名称、参数 schema、描述。模型通过这些声明知道"有什么手能用、每只手怎么用"。
  • 路由调用:当模型返回一个工具调用意图时,注册表负责找到对应的工具实现并执行。
  • 权限映射:每个工具可以有独立的权限规则,注册表在路由时联动权限层做检查。

Tool Registry 的设计使得"给 Agent 加一只手"变成了一件声明式的事情——你只需要写一个工具定义(名称 + 参数 schema + 执行函数),注册进去,模型就自动知道能用它。这比硬编码在 Loop 里灵活得多。

(四)Message Stream(消息流)

Message Stream 是 Agent 的"对话流水"。它是一个有序的消息列表,每条消息有角色(user / assistant / tool_result 等)和内容。整个 Agent 的运行过程,本质上就是 Message Stream 不断增长、被读取、被压缩、被发送给模型的过程。

Message Stream 的关键工程问题有三个:

  1. 增长管理:每轮 Loop 都会往流里加消息(模型输出、工具结果),流会越来越长。当总 token 数逼近模型上下文窗口上限时,必须触发 compaction(见 9.4 节)。
  2. 相关性筛选:不是所有历史消息都对当前步骤有用。Read 工具返回的大段文件内容,在用过之后可能就不需要留在流里了。如何在不丢失关键信息的前提下精简流,是上下文工程的核心难题。
  3. 子代理隔离:子代理有自己的独立 Message Stream,它的中间过程不污染主代理的流,只有最终汇报结果被注入主流。

金句

Session 管生命周期,Loop 管认知循环,Tool Registry 管能力边界,Message Stream 管信息流动。四个抽象各司其职,一个 Agent 就立住了。缺了 Session 活不久,缺了 Loop 不会走,缺了 Registry 没有手,缺了 Stream 没有记忆。

9.3.3 SDK 的工程模型:可编程、可嵌入、可组合

Agent SDK 的工程价值不止于"把 Agent 逻辑模块化",更在于它带来了三种新的工程模式:

可编程:你可以用 SDK 写脚本,程序化地创建 Agent、配置工具、注入上下文、驱动 Loop。这意味着 Agent 可以被嵌入到任何工作流里——CI/CD pipeline、Git hook、定时任务、Web 后端。

可嵌入:SDK 可以被任何 Node.js/TypeScript 程序 import(据公开资料,Claude Agent SDK 提供 TypeScript SDK)。这意味着你不需要启动一个交互式 CLI,就能在你的程序里跑一个 Agent。

可组合:多个 Agent 实例可以组合——主代理派发子代理、子代理再派发孙代理,形成多层级编排。每个层级的 Agent 可以有不同的工具集、权限、上下文。这就是第7章 Multi-Agent 协作的工程基础。

图 9-3:Agent SDK 的三种工程模式

   ┌─────────────────────────────────────────────────────────────┐
   │                    Agent SDK (内核)                         │
   │                                                             │
   │   Session  │  Loop  │  Tool Registry  │  Message Stream    │
   └──────┬──────────┬────────────────┬──────────────────────────┘
          │          │                │
     可编程│     可嵌入│           可组合│
          │          │                │
          ▼          ▼                ▼
   ┌──────────┐  ┌──────────┐   ┌──────────────┐
   │ 脚本驱动  │  │ 程序嵌入  │   │ 多Agent编排  │
   │ CI/CD    │  │ Web后端  │   │ 主→子→孙    │
   │ Git Hook │  │ 定时任务  │   │ 并行派发     │
   └──────────┘  └──────────┘   └──────────────┘

图注:SDK 把 Agent 从"一个交互式产品"变成"一个可编程组件"。三种工程模式分别对应:用脚本驱动(可编程)、嵌入到其他程序(可嵌入)、多个 Agent 组合编排(可组合)。这是 Agent 从产品走向平台的关键跃迁。

9.3.4 Session 的生命周期与持久化

Session 作为"一次 Agent 运行的生命周期容器",它的生命周期管理是工程实现中容易被忽视但极其重要的部分。一个设计良好的 Session 生命周期应该包含以下几个阶段:

创建阶段:Session 创建时,SDK 初始化消息历史(空或从持久化恢复)、加载工具注册表、应用权限配置、设置工作目录。这个阶段决定了 Agent "上岗时"的初始状态——它知道什么规矩、有什么工具、能碰哪些文件。

运行阶段:Agent Loop 在 Session 内运行,不断产生新的消息、调用工具、更新 TodoList。这个阶段是 Session 的"活"态——状态持续变化,消息流持续增长。

暂停阶段:长程任务可能需要中途暂停——等待用户审批(ask 态)、等待外部条件满足(如等待 CI 跑完)、或者用户主动中断。暂停时 Session 的状态需要被保持,以便恢复时能从中断点继续。

恢复阶段:从暂停中恢复,Session 的状态被重新加载,Agent Loop 从中断点继续运行。这要求 Session 的状态是可持久化的——消息历史、TodoList、工具状态等都需要能被保存和恢复。

终止阶段:任务完成或被取消,Session 进入终止。终止时可以进行收尾工作——生成最终报告、保存审计日志、清理临时资源。Session 终止后,其状态可以被归档以备事后复盘。

Session 的持久化能力是长程任务 Agent 区别于"一次性聊天"的关键。一个能跨时间恢复的 Session,意味着 Agent 可以处理"今天干到一半,明天接着干"的长程任务——这正是第8章"持久化与检查点"在 Agent SDK 层面的落地。

金句

模型是无状态的,但 Session 是有状态的。Session 的持久化能力,就是把"无状态的大脑"变成"有记忆的搭档"的那层工程魔法。没有 Session 持久化,Agent 每次都是从零开始;有了它,Agent 才真正能"接着上次继续干"。

9.3.5 Loop 的工程化:不止是 ReAct

第3章讲了 ReAct 循环的理论模型——Thought → Action → Observe。Claude Code 的 Agent Loop 在此基础上做了大量工程化增强,使得裸 ReAct 的"会跑"变成了"跑得稳、跑得久、跑得可控"。这些增强至少包括:

终止条件的工程化。裸 ReAct 的终止依赖模型自己说"完成了",但这不可靠——模型可能永远不说"完成"(死循环),也可能过早说"完成"(偷懒)。Claude Code 的 Loop 增加了多重终止保护:最大轮次限制(防死循环)、最大 token 消耗(防烧钱)、用户主动中断(Ctrl+C)、以及 Stop 钩子(可以在 Agent 想停时做二次校验,比如"测试还没全绿,不许停")。

错误恢复的工程化。裸 ReAct 遇到工具失败时,行为不确定——可能重试、可能放弃、可能换个完全无关的思路。Claude Code 的 Loop 增加了结构化的错误处理:工具失败时,错误信息被结构化地注入消息流(而不是简单的"出错了"),让模型能基于具体错误信息决定下一步。这呼应了第8章的"错误恢复三式"——重试、换方案、上报。

上下文管理的集成。每轮 Loop 迭代前,Loop 会检查 Message Stream 的 token 占用,必要时触发 compaction。这意味着 Loop 不只管"推理 → 执行",还管"上下文健康度"——这是一个裸 ReAct 完全不考虑的工程维度。

子代理调度的集成。当模型返回 Agent 工具调用(派发子代理)时,Loop 需要管理子代理的生命周期——创建子 Session、启动子 Loop、等待子代理完成、回收结果。这使得主 Loop 不再是单线程的线性循环,而是可以嵌套的层级结构。

这些工程化增强,把 ReAct 从一个"理论上能跑的循环"变成了一个"生产环境能扛活的循环"。这也正是第1章讲的"6.7% → 68.3%"差距的来源——裸 ReAct 就是那个 6.7%,工程化 Loop 就是那个 68.3%。

金句

ReAct 论文告诉你 Thought-Action-Observe 循环能让 Agent 动起来,工程实践告诉你光有循环还不够——你得管终止、管错误、管上下文、管子代理。循环是心跳,工程化是免疫系统。有心跳不等于活着,有免疫系统才算活着。


9.4 上下文与记忆策略:Read 按需、TodoList、子代理隔离、Compaction

上下文工程是第4章的主题。这一节不重复理论,而是聚焦 Claude Code 在工程上如何落地第4章的原则。Claude Code 的上下文策略可以概括为四个字:按需、外化、隔离、压缩

9.4.1 Read 按需读取:不预加载,用到才读

一个朴素的问题:Agent 要改一个项目里的代码,它需要先"读"多少文件?

最笨的做法是启动时把整个项目目录读进上下文。这有两个致命问题:一是大多数文件跟当前任务无关,白白占用宝贵的 token 窗口;二是项目可能很大(几十万个文件),根本读不完。

Claude Code 的做法是"按需读取"——Agent 启动时只有系统提示、CLAUDE.md 内容、用户输入,不预加载任何代码文件。当模型在推理过程中判断"我需要看某个文件"时,它发起 Read 工具调用,读取指定文件(或指定行范围),结果作为 tool_result 注入消息流。用完了,这个文件的内容就"在流里了",但后续如果上下文需要精简,它可以被 compaction 摘要化或移除。

这个设计呼应了第4章的核心原则:上下文窗口是稀缺资源,只塞"此刻正好够用"的信息。按需读取把"加载什么"的决策权交给了模型自身的推理——模型比任何预定义规则都更清楚"我现在需要看哪个文件"。

但按需读取也带来一个工程挑战:模型可能反复读同一个文件(因为它忘了自己读过,或者不确定之前读的内容是否还在上下文里)。Claude Code 通过 Read 工具的行范围参数来缓解这个问题——你可以只读文件的第 100-150 行,而不是整个文件,既省 token 又减少信息过载。

金句

按需读取的本质,是把"上下文窗口里放什么"的决策权,从工程师写的预加载规则,交给了模型自己的判断。模型比规则更懂"我现在需要看什么"——前提是,你给了它一只叫 Read 的手。

9.4.2 TodoList:外部规划记忆

长程任务有一个经典失败模式:遗忘中段。Agent 干到第五步时,忘了第三步的结论;或者干着干着,偏离了原始目标的优先级。这不是模型"笨",而是上下文窗口里的信息被新内容不断稀释,早期的规划意图被冲淡了。

Claude Code 用 TodoWrite 工具来对抗这个问题。TodoWrite 允许模型(或用户)把任务拆解写成一个结构化的待办清单,这个清单作为一条独立的消息存在消息流里。每当模型完成一项,它可以更新清单状态(pending → in_progress → completed)。这个清单就像一张"贴在显示器边上的便利贴"——它不会被中间的对话淹没,因为它是结构化的、可随时被模型重新读取的。

这呼应了第4章讲的"外部规划记忆"概念:把规划从"藏在对话流里的隐式记忆"变成"显式存储的外部结构"。人干活也这样——你不会把待办事项全记在脑子里,你会写在清单上,因为清单比记忆可靠。

TodoList 的工程价值不止于防遗忘,还在于:

  • 进度可观测:用户和可观测层(L7)都能看到当前完成了哪些、还剩哪些。
  • 中断恢复:如果 Session 被中断,下次恢复时 TodoList 还在,Agent 知道从哪继续。
  • 优先级纠偏:当模型跑偏时,重新读一遍 TodoList 能把它拉回正轨。

金句

记忆有两种:一种藏在对话流里,会被新内容稀释;一种写在便利贴上,随时可以重读。TodoList 就是 Agent 的便利贴——它不增加记忆容量,但它让记忆变得可靠。

9.4.3 子代理上下文隔离

当主代理派发一个子代理去"搜索整个代码库找到所有调用 foo() 的地方"时,子代理可能需要读几十个文件、跑几十次 Grep。这些中间过程如果全部灌回主代理的消息流,主代理的上下文会被瞬间淹没——这就是第7章讲的"上下文污染"问题。

Claude Code 的子代理机制通过上下文隔离来解决这个问题。每个子代理有自己独立的 Session 和 Message Stream。子代理在隔离的上下文里自由地读文件、跑搜索、做推理,所有中间过程留在自己的流里。当子代理完成任务后,只有它的最终汇报结果(一条摘要消息)被注入回主代理的消息流。

这个设计的美妙之处在于:主代理的上下文窗口只被"结论"占用,不被"探索过程"占用。主代理可以用 2000 token 的结论代替 50000 token 的中间过程,上下文利用率提升一个数量级。

图 9-4:子代理上下文隔离示意图

   ┌─────────────────────────────────────────────────┐
   │          主代理 Message Stream                   │
   │                                                  │
   │  [user] 找到所有调用 foo() 的地方并改成 bar()    │
   │  [assistant] 我先派一个子代理去搜索               │
   │  [tool_call] Agent(prompt="搜索foo()调用点")     │
   │                                                  │
   │    ┌───────────────────────────────────┐        │
   │    │    子代理 Message Stream (隔离)    │        │
   │    │                                   │        │
   │    │  [tool] Grep("foo\(") → 30个匹配  │        │
   │    │  [tool] Read(file1.js) → ...      │        │
   │    │  [tool] Read(file2.js) → ...      │        │
   │    │  [tool] Read(file3.js) → ...      │        │
   │    │  ... (大量中间过程)               │        │
   │    │  [assistant] 汇总: 找到30处,      │        │
   │    │    分布在12个文件中, 其中3处在     │        │
   │    │    测试文件里                     │        │
   │    └───────────────────────────────────┘        │
   │                                                  │
   │  [tool_result] 找到30处,分布在12个文件中,        │
   │    其中3处在测试文件里  ← 只有结论回到主流       │
   │  [assistant] 好的,我开始逐个修改...              │
   │  ...                                             │
   └─────────────────────────────────────────────────┘

图注:子代理在隔离的 Message Stream 中完成大量探索工作(读文件、跑 Grep),但只有最终摘要结果被注入主代理的流。主代理的上下文窗口只被结论占用,不被过程污染。这就是"用 2000 token 结论代替 50000 token 过程"的工程智慧。

9.4.4 Compaction:上下文压缩

即便有按需读取、TodoList 外化、子代理隔离,主代理的消息流在长程任务中仍然会持续增长——几十轮对话后,流里的历史消息可能已经超出模型上下文窗口的一半甚至更多。这时候就需要 compaction(压缩)。

Compaction 的核心思路是:当消息流的总 token 数逼近阈值时,把较早的历史消息摘要化——用一段精炼的摘要替代原来的大段详细消息,保留关键信息(做了什么决策、改了哪些文件、遇到了什么错误),丢弃冗余细节(某个文件的完整内容、某次工具调用的完整输出)。

据业界观察,Claude Code 的 compaction 行为大致遵循以下策略:

  • 触发时机:当消息流占用达到上下文窗口的某个比例(如 70-80%)时触发,而非等到溢出才压缩——留出余量给后续轮次使用。
  • 保留策略:系统提示、最近的几轮对话、TodoList 当前状态、CLAUDE.md 内容通常被保留;较早的工具调用结果、大段文件读取内容是被压缩的主要对象。
  • 摘要质量:摘要由模型自身生成(而非简单截断),因此能保留语义关键信息。
  • 不可逆性:压缩后原始详细消息被移除,无法恢复——因此 compaction 是有信息损失的,这也是为什么"关键信息应该外化到 TodoList 或文件中"而不是依赖流里的大段历史。

Compaction 与第4章讲的"记忆分层"直接呼应:工作记忆(当前上下文窗口)是有限的,长期记忆需要被外化(文件、TodoList、CLAUDE.md)或被压缩(compaction 摘要)。Claude Code 的做法是两者的结合——既外化关键状态,又对历史进行摘要压缩,双管齐管理窗口压力。

金句

上下文窗口就像一张不大的桌子。干活时工具和材料越堆越多,总有一刻你必须收拾——把用完的工具收进抽屉(外化),把不再需要的草稿纸扔掉(压缩),只留桌面上"此刻正在用"的东西。compaction 不是删记忆,是收拾桌子。

9.4.5 CLAUDE.md:持久化的项目记忆

除了上述四种动态策略,Claude Code 还有一个静态但重要的记忆机制:CLAUDE.md。这是一个放在项目根目录的 Markdown 文件,内容在 Agent 启动时被自动注入到系统上下文里。

CLAUDE.md 的典型内容包括:项目架构说明、编码规范、常见命令(怎么跑测试、怎么构建)、已知陷阱、文件结构说明等。它相当于给搭档的一份"项目交接文档"——搭档每次上岗都会先读一遍,确保它理解这个项目的规矩。

CLAUDE.md 的工程价值在于:它是跨 Session 持久化的项目记忆。动态记忆(消息流、TodoList)在一次 Session 结束后就消失了,但 CLAUDE.md 留在文件系统里,每次 Agent 启动都会重新加载。这解决了"同一个项目反复用 Agent,每次都要重新交代规矩"的痛点。

值得注意的是,CLAUDE.md 不宜过长。它被注入系统上下文,会持续占用 token。一个好的实践是:把"所有任务都需要知道的规矩"放进 CLAUDE.md(控制在几百行以内),把"特定任务需要的详细信息"留给 Agent 在运行时通过 Read 按需读取。

9.4.6 记忆策略与第4章的呼应

把 Claude Code 的四招记忆策略(按需读取、TodoList 外化、子代理隔离、compaction 压缩)放回到第4章的理论框架里,可以清晰地看到它们各自对应上下文工程的哪个原则:

  • 按需读取对应第4章的"相关性筛选"原则——上下文窗口只放此刻正好需要的信息,不预加载可能用不到的内容。
  • TodoList 外化对应第4章的"记忆分层"原则——把需要跨轮次稳定保持的状态从工作记忆(消息流)外化到更稳定的存储(结构化清单),工作记忆会膨胀、会被压缩,但外部清单不会。
  • 子代理隔离对应第4章的"上下文预算"原则——每个 Agent 实例有独立的上下文预算,子代理的探索过程不占用主代理的预算,只有结论才"付费"进入主代理的上下文。
  • compaction 压缩对应第4章的"摘要化"原则——当工作记忆超出预算时,用语义摘要替代原始内容,保留关键信息、丢弃冗余细节。

这四个策略不是孤立的技巧,而是第4章上下文工程理论在 Claude Code 产品层面的系统化落地。理解了第4章的原则,你就能理解 Claude Code 为什么这么做;看懂了 Claude Code 的做法,你就能把同样的模式迁移到自己的 Agent 设计里。

金句

上下文工程不是一门只存在于论文里的理论——它在 Claude Code 的每一个工具设计里都有落点。Read 是相关性筛选的工具化,TodoList 是记忆分层的工具化,子代理是上下文预算的工具化,compaction 是摘要化的工具化。理论告诉你"应该怎么做",产品告诉你"做了之后长什么样"。


9.5 Skills 机制:按需加载的能力扩展

Skills 是 Claude Code 里一个精巧的扩展机制。理解它,需要先理解一个矛盾:Agent 的能力来自它知道有哪些工具可用、怎么用,但把这些全塞进系统提示会撑爆上下文

9.5.1 矛盾:能力广度 vs 上下文深度

设想你给 Agent 配了 100 个工具——代码审查、安全扫描、数据库迁移、文档生成、部署……如果每个工具的名称、参数 schema、使用说明都写进系统提示,光工具声明可能就占了上下文窗口的 30%。而且这 100 个工具里,当前任务可能只需要 3 个。

这就是"能力广度 vs 上下文深度"的矛盾:你想让 Agent 什么都会,但把所有会的都摆在工作台上,桌子就放不下当前任务真正需要的东西了。

9.5.2 Skills 的解法:按需加载

Skills 的解法是"按需加载"。据公开资料与业界观察,Skills 的工作方式大致如下:

  • 技能定义:每个 Skill 是一个独立定义文件(通常在 .claude/skills/ 目录下),包含技能的名称、描述、触发条件、以及该技能对应的指令/工具/流程。Skills 可以是数据库技能、文件系统技能、或任何自定义技能。
  • 技能发现:Agent 启动时,只把"技能清单"(名称 + 一句话描述)放进系统上下文,而不是每个技能的完整内容。这就像书架上的书脊——你能看到书名,但不把每本书的内容都摊在桌上。
  • 按需激活:当模型在推理中判断"我需要用某个技能"时(可能是用户明确触发,也可能是模型根据任务自动匹配),它加载该技能的完整内容到上下文,然后按技能定义的流程执行。
  • 用后释放:技能用完后,其完整内容可以从上下文中移除(或被后续 compaction 摘要化),不持续占用窗口。

这就像工程师的工具箱:你不把所有工具都摊在工作台上,你把它们收在工具箱里,工作台上只贴一张"工具箱里有什么"的清单。需要哪件工具时,从箱子里取出来用,用完放回去。

图 9-5:Skills / Hooks / Tools 三层能力扩展体系

   ┌─────────────────────────────────────────────────────────────┐
   │                     能力扩展体系                             │
   │                                                             │
   │  ┌─────────────────────────────────────────────────────┐   │
   │  │  Skills 层(按需加载)                                 │   │
   │  │  ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐      │   │
   │  │  │技能A │ │技能B │ │技能C │ │技能D │ │技能E │      │   │
   │  │  └──────┘ └──────┘ └──────┘ └──────┘ └──────┘      │   │
   │  │  系统提示只含技能清单(名称+一句话描述)               │   │
   │  │  按需激活时才加载完整内容                            │   │
   │  └─────────────────────────────────────────────────────┘   │
   │                          │ 激活                             │
   │                          ▼                                  │
   │  ┌─────────────────────────────────────────────────────┐   │
   │  │  Hooks 层(自动拦截)                                  │   │
   │  │  PreToolUse ──▶ 工具调用 ──▶ PostToolUse             │   │
   │  │  在工具执行前后注入自动化行为                        │   │
   │  └─────────────────────────────────────────────────────┘   │
   │                          │ 驱动                             │
   │                          ▼                                  │
   │  ┌─────────────────────────────────────────────────────┐   │
   │  │  Tools 层(直接执行)                                  │   │
   │  │  Read │ Write │ Edit │ Bash │ Grep │ Glob │ Agent   │   │
   │  │  TodoWrite │ MCP工具 │ ...                          │   │
   │  │  声明式注册,模型直接调用                            │   │
   │  └─────────────────────────────────────────────────────┘   │
   └─────────────────────────────────────────────────────────────┘

图注:三层能力扩展体系自上而下:Skills 是"按需加载"的高级能力包(系统提示里只放清单),Hooks 是"自动拦截"的中间层(在工具执行前后注入行为),Tools 是"直接执行"的底层原子操作。三者层次分明:Skills 决定"会不会",Hooks 决定"怎么自动管",Tools 决定"怎么动手"。

9.5.3 Skills 与 Tools 的层次关系

Skills 和 Tools 不是同一层的东西,理解它们的层次关系至关重要:

  • Tools 是原子操作:Read、Write、Bash、Grep 是"手"——它们执行单一动作,不包含流程逻辑。模型直接调用它们。
  • Skills 是能力包:一个 Skill 可能组合多个 Tools,包含一套流程逻辑、上下文模板、甚至特定的工具配置。它是"一套打法"——不只是一只手。

类比:Tools 是锤子、螺丝刀、扳手;Skills 是"换轮胎"这个技能(它组合了扳手拧螺丝、千斤顶抬车、扭力检测等多个工具和步骤)。你不需要每次都把"换轮胎"的完整流程摆在桌上,你只需要知道"我会换轮胎",需要换的时候再把流程调出来。

这个层次关系带来一个重要的工程结论:Skills 扩展能力而不污染上下文。你可以给 Agent 配 100 个 Skills,系统提示只多 100 行清单(每行一个名称+一句话),而不是 100 套完整流程。Agent 的"会的东西"变多了,但"桌子上的东西"没变多。

金句

Tools 是手,Skills 是打法。手越多桌子越挤,打法越多脑子越富但桌子不挤——因为打法收在技能包里,用到才打开。这就是 Skills 的全部精妙。

9.5.4 Skills 如何被触发

据公开资料,Skills 的触发方式大致有两种:

显式触发:用户通过 /<skill-name> 的方式直接调用某个技能。这就像工程师从工具箱里指定取某件工具。

隐式匹配:模型根据任务描述自动匹配技能。系统提示里的技能清单包含每个技能的"适用场景描述",模型在推理时判断当前任务是否匹配某个技能的适用场景,匹配则自动激活。这就像工程师看到活儿就知道该用哪件工具,不需要翻清单。

隐式匹配是 Skills 机制最有价值的设计——它让 Agent 能"自动找到合适的能力",而不需要用户记住所有技能名称。但隐式匹配也依赖技能描述写得足够清晰,否则模型可能匹配错误或完全不匹配。写好技能的描述(一句话说清"这个技能在什么场景下用"),是 Skills 工程化的关键实践。

9.5.5 Skills 的工程化实践

把 Skills 从"概念"变成"可维护的工程资产",需要注意几个实践要点:

技能的粒度。一个 Skill 应该对应一个"完整的能力单元"——不要太大(一个技能管十件事,激活时全塞进上下文,浪费 token),也不要太小(每件小事都是一个技能,清单比上下文还长)。好的粒度是"一个技能解决一类问题"——比如"代码审查"是一个技能,而不是"检查类型安全"“检查错误处理”"检查命名规范"各做一个技能。后者应该是技能内部的步骤,而不是独立技能。

技能的内容组织。一个 Skill 的内容通常包含:触发条件(什么场景用)、执行流程(按什么步骤做)、检查清单(每个步骤要检查什么)、输出模板(结果长什么样)。把这些内容用 Markdown 结构化,让模型加载后能清晰地按步骤执行。避免写成大段自然语言散文——结构化的清单和步骤比散文更容易被模型准确执行。

技能的版本管理。Skills 是文件(在 .claude/skills/ 目录下),天然可以纳入 Git 版本控制。这意味着团队的 Skills 可以像代码一样被 review、被迭代、被回滚。一个好的实践是:把 Skills 当作团队的"工程知识库"来维护——每次团队学到新的工程实践,就更新或新增一个 Skill;每次发现 Skill 的流程有漏洞,就迭代修复。

技能与 CLAUDE.md 的分工。CLAUDE.md 放"所有任务都需要知道的通用规矩"(项目架构、编码规范、常用命令),Skills 放"特定类型任务需要的专项流程"(代码审查流程、安全扫描流程、数据库迁移流程)。CLAUDE.md 始终在上下文里(每次都占用 token),Skills 按需加载(用到才占用)。把信息放在正确的层——通用的进 CLAUDE.md,专项的进 Skills——是上下文经济性的关键。

金句

Skills 是团队的工程知识库——不是写在 wiki 里没人看的文档,而是 Agent 每次上岗都会加载并执行的活流程。把团队的工程经验沉淀成 Skills,让知识从"人的记忆"变成"Agent 的能力",这是 Agent 工程化最深层的价值之一。


9.6 Hooks(钩子):自动化行为注入

如果说 Skills 扩展的是 Agent 的"能力",Hooks 扩展的就是 Harness 的"自动化行为"。Hooks 让你能在 Agent 运行的特定节点上,自动执行一段逻辑——不需要模型主动决定,也不需要用户手动触发。

9.6.1 什么是 Hooks

Hooks 的概念来自经典的"钩子模式"(Hook Pattern):在系统的关键节点上预留"钩子点",外部代码可以挂载到这些点上,在节点被触发时自动执行。Git hooks、VS Code 的 lifecycle hooks、React 的 useEffect 都是这个模式的应用。

在 Claude Code 中,Hooks 让你能在 Agent 的特定运行节点上注入自动化行为。这些行为由 Harness(而非模型)执行——也就是说,Hooks 是"系统级行为",不经过模型推理,是确定性的、可审计的。

这里有一个重要的区分需要强调:Hooks 执行的是确定性代码,不是模型推理。当你写一个 PreToolUse Hook 来检查 Bash 命令是否包含 rm 时,这个检查是由一段 Shell 脚本或程序执行的——它不走模型,不受 prompt injection 影响,不会因为模型"心情不好"而跳过。每次工具调用前,这段代码都会被执行。这种确定性是 Hooks 相对于"在 prompt 里写规则"的根本优势。

但确定性也意味着局限性——Hooks 只能做模式匹配、条件判断、命令执行等确定性逻辑,不能做"理解语义"的判断。比如,你不能用 Hook 判断"这段代码改动是否合理"——那需要模型推理。Hooks 适合"规则可枚举"的自动化(格式化、日志、禁止特定命令),不适合"需要理解"的判断(代码质量、逻辑正确性)。理解这两者的边界,是用好 Hooks 的前提。

金句

Skills 教 Agent “会做什么”,Hooks 教 Harness “自动管什么”。前者是能力的扩展,后者是治理的扩展。一个让搭档更博学,一个让搭档更自律。

9.6.2 钩子点清单

据公开资料与业界观察,Claude Code 提供的主要钩子点包括:

表 9-1:Claude Code Hooks 钩子点清单

钩子点 触发时机 典型用途 返回值影响
PreToolUse 工具执行前 权限二次校验、参数预处理、阻止危险操作 可阻止工具执行
PostToolUse 工具执行后 结果后处理、日志记录、触发后续动作 可修改工具结果
Notification Agent 发出通知时 发送消息到外部渠道(Slack/邮件) 无直接影响
Stop Agent Loop 终止时 收尾工作、生成报告、清理临时文件 可阻止终止
SubagentStop 子代理终止时 子代理结果后处理、审计记录 可修改汇报内容
UserPromptSubmit 用户提交输入时 输入预处理、敏感信息过滤、注入上下文 可修改用户输入

这些钩子点覆盖了 Agent 运行的完整生命周期——从用户输入、到工具执行前后、到循环终止、到子代理终止。通过组合这些钩子点,你可以实现相当复杂的自动化行为,而不需要修改 Agent 的核心逻辑。

9.6.3 Hooks 与 settings.json 的关系

Hooks 的配置与 settings.json 紧密相关。据公开资料,Hooks 通常在 settings.json(或对应的配置文件)中声明,每个 Hook 定义包含:触发的钩子点、匹配条件(如只对特定工具生效)、以及要执行的命令或脚本。

一个典型的 Hooks 配置伪代码如下:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "command": "prettier --write $FILE_PATH && echo '已自动格式化'"
      }
    ],
    "PreToolUse": [
      {
        "matcher": "Bash",
        "command": "echo $COMMAND | grep -q 'rm -rf' && echo 'BLOCKED: 禁止rm -rf' && exit 1 || exit 0"
      }
    ]
  }
}

上面这个配置做了两件事:PostToolUse 钩子在 Write 或 Edit 工具执行后自动运行 prettier 格式化;PreToolUse 钩子在 Bash 工具执行前检查命令是否包含 rm -rf,如果包含则阻止执行。

注意这两个 Hook 都不需要模型参与——它们是 Harness 层面的自动化行为,是确定性的、每次必执行的。这就是 Hooks 相对于"让模型自己判断"的优势:确定性。你不需要祈祷模型每次都记得"改完文件要格式化",Harness 保证了它每次都会发生。

9.6.4 Hooks 作为 L6/L7 的扩展点

从七层 Harness 的视角看,Hooks 主要扩展的是 L6(治理安全层)和 L7(可观测层):

  • L6 扩展:PreToolUse 钩子可以做权限二次校验、危险操作拦截。比如"禁止 rm -rf"、"禁止向生产环境推送"等规则可以挂在 PreToolUse 上,比模型的"自觉"可靠得多。
  • L7 扩展:PostToolUse、Stop、SubagentStop 钩子可以做日志记录、审计追踪、结果上报。比如"每次工具执行后把操作记录写入审计日志"可以挂在 PostToolUse 上。

Hooks 的工程价值在于:它把"治理和可观测"的行为从"依赖模型自觉"变成了"由 Harness 强制执行"。模型可能会忘、可能会偷懒、可能会被 prompt injection 绕过,但 Hooks 不会——它是系统级代码,每次必执行。

金句

模型的自觉是概率性的,Hooks 的执行是确定性的。安全靠自觉是赌博,安全靠 Hooks 是工程。凡是"每次都必须发生"的行为,就该挂到 Hooks 上,而不是写进 prompt 里祈祷模型记得。


9.7 权限模型:allow/deny/ask 三态与沙箱

权限模型是 Claude Code 治理安全层(L6)的核心。一个能读写文件、执行 Shell 命令的 Agent,如果没有权限管控,就是一个随时可能闯祸的"无保险丝的电闸"。Claude Code 的权限模型设计,是长程任务 Agent 能被生产环境接受的关键前提。

9.7.1 三态权限模型

Claude Code 的权限不是简单的"允许/禁止"二态,而是 allow / deny / ask 三态。这个"第三态"(ask)是关键设计。

  • allow(允许):工具调用直接执行,不打断 Agent Loop。适用于只读操作、安全操作。
  • deny(禁止):工具调用被拒绝,不执行。适用于明确危险的操作。
  • ask(询问):工具调用暂停,向用户请求审批。用户批准则执行,拒绝则跳过。适用于有风险但有时需要的操作。

为什么需要 ask 态?因为有些操作不能简单归类为"安全"或"危险"。git push 在开发环境是安全的,在生产环境是危险的;rm 删临时文件是安全的,删源码是危险的。二态模型要么过度放行(危险),要么过度保守(每次都要人审批,Agent 根本跑不起来)。ask 态让"灰度操作"可以按需放行,兼顾了安全和效率。

图 9-6:权限三态决策状态机

                   工具调用请求
                         │
                         ▼
              ┌─────────────────────┐
              │  查询权限规则表      │
              │  (settings.json)    │
              └──────────┬──────────┘
                         │
          ┌──────────────┼──────────────┐
          │              │              │
          ▼              ▼              ▼
     ┌─────────┐   ┌─────────┐   ┌─────────┐
     │  allow  │   │  ask    │   │  deny   │
     │  (放行) │   │ (询问)  │   │ (拒绝)  │
     └────┬────┘   └────┬────┘   └────┬────┘
          │             │             │
          │             ▼             │
          │      ┌─────────────┐     │
          │      │  用户审批   │     │
          │      └──────┬──────┘     │
          │             │            │
          │      ┌──────┴──────┐     │
          │      ▼             ▼     │
          │   ┌──────┐   ┌──────┐   │
          │   │ 批准 │   │ 拒绝 │   │
          │   └──┬───┘   └──┬───┘   │
          │      │           │       │
          ▼      ▼           │       ▼
     ┌──────────────┐        │  ┌──────────────┐
     │  执行工具    │        │  │  不执行,     │
     │  返回结果    │        │  │  返回拒绝原因│
     └──────────────┘        │  └──────────────┘
          │                  │
          ▼                  ▼
     ┌─────────────────────────────┐
     │  结果注入 Message Stream    │
     │  进入下一轮 Loop            │
     └─────────────────────────────┘

图注:权限三态状态机。allow 直接放行执行,deny 直接拒绝,ask 暂停等待用户审批。用户批准则执行,拒绝则跳过。注意"ask→批准"路径和"allow"路径最终汇合到同一个"执行工具"节点,对 Agent Loop 而言,被批准的 ask 和 allow 没有区别。

9.7.2 权限粒度:工具级与参数级

Claude Code 的权限不只是"允许/禁止某个工具",还可以细化到参数级别。这意味着你可以配置"允许 Bash 工具,但只允许 npm testnpm run build,不允许 rmgit push"。

这种参数级权限粒度非常重要。以 Bash 工具为例——它本质上可以执行任何 Shell 命令,从安全的 ls 到危险的 rm -rf /。如果在工具级别只允许或禁止 Bash,你要么全放行(不安全),要么全禁止(Agent 没法跑测试)。参数级权限让你可以"放行安全的命令子集,禁止危险的命令子集,灰度的命令走 ask"。

据公开资料,权限规则的匹配方式通常支持通配符或正则表达式,可以按命令前缀、文件路径模式等维度匹配。例如:

  • Bash(npm test*) → allow(允许跑测试)
  • Bash(npm run build*) → allow(允许构建)
  • Bash(rm *) → deny(禁止删除)
  • Bash(git push*) → ask(推送前询问)
  • Bash(*) → ask(其他所有命令都问一下)

这种"白名单 + 黑名单 + 灰名单"的组合,使得权限模型既能保证安全,又不会把 Agent 卡死。

表 9-2:权限三态决策表(以常见操作为例)

操作 建议权限态 理由 典型规则
只读文件(Read) allow 只读不改变状态,安全 Read(*) → allow
写文件(Write/Edit) allow 或 ask 改代码是常态,但关键文件可加 ask Edit(src/*) → allow; Edit(.env*) → ask
跑测试(npm test) allow 只读+执行验证,安全 Bash(npm test*) → allow
构建(npm run build) allow 生成产物,安全 Bash(npm run build*) → allow
删除文件(rm) deny 不可逆,危险 Bash(rm *) → deny
Git 推送(git push) ask 影响远端,需人确认 Bash(git push*) → ask
安装依赖(npm install) ask 可能改变环境,需确认 Bash(npm install*) → ask
执行任意脚本 ask 不确定行为,灰度 Bash(*.sh) → ask
访问网络(curl/wget) ask 可能泄露数据或引入风险 `Bash(curl *
修改权限文件(chmod/chown) deny 安全风险高 `Bash(chmod *

9.7.3 危险操作确认与沙箱

除了三态权限,Claude Code 还有两道安全防线:

(一)危险操作确认

某些操作即便在 allow 列表里,也可能触发额外的确认机制。据业界观察,这通常针对"不可逆操作"——如删除文件、覆盖重要配置、向远端推送等。系统可能在执行前再次提示用户确认,或者要求更高级别的审批。这相当于在权限闸门之外,又加了一道"特别危险品检查站"。

(二)沙箱执行

Claude Code 的工具执行(尤其是 Bash 工具)可以在沙箱环境中进行。沙箱是一种隔离机制——限制工具能访问的文件路径、能执行的系统调用、能访问的网络。即使权限模型被绕过(比如模型构造了一个看起来安全但实际危险的命令),沙箱也能限制爆炸半径。

沙箱的工程意义在于"最小爆炸半径"原则:你不需要保证 Agent 永远不犯错,你需要保证它犯错时影响范围可控。一个在沙箱里跑的 Agent,最坏情况是搞乱了沙箱里的临时文件,不会影响到宿主系统。

沙箱的另一个工程价值是"可重建性"。一个设计良好的沙箱环境是可重建的——你可以随时销毁当前沙箱、创建一个干净的新沙箱。这意味着如果 Agent 把环境搞乱了(装了不该装的依赖、改了不该改的配置),你不需要费时清理,直接重建沙箱即可。这呼应了第8章"检查点与恢复"的理念——可重建的环境本身就是一种恢复机制。

据业界观察,沙箱的隔离粒度通常包括:文件系统隔离(限制可访问的路径范围)、网络隔离(限制可访问的网络地址)、进程隔离(限制可执行的系统调用类型)、以及资源限制(CPU、内存、执行时间上限)。不同场景需要不同粒度的隔离——开发环境可能只需要文件系统隔离,生产环境可能需要全粒度隔离。

金句

沙箱不是不信任 Agent,是承认 Agent 会犯错。信任靠权限模型保证,容错靠沙箱保证。前者防主动越界,后者防被动闯祸。一个成熟的长程任务 Agent 系统需要两者兼备——因为再聪明的 Agent,也会有"自信地搞砸"的时刻。

金句

权限三态(allow/deny/ask)管的是"能不能做",沙箱管的是"做错了能闯多大祸"。前者是门卫,后者是防火墙。一个长程任务 Agent 需要两者兼备——门卫防主动越界,防火墙防被动闯祸。

9.7.4 L6 治理在 Claude Code 的落地

把权限模型、危险操作确认、沙箱、Hooks 拼在一起,就是 L6(治理安全层)在 Claude Code 的完整落地:

  • 权限三态:决定每个工具调用能不能执行、要不要问人。
  • 参数级粒度:把权限细化到命令级别,避免"要么全放要么全禁"的粗粒度。
  • 危险操作确认:对不可逆操作加额外确认。
  • 沙箱:限制工具执行的爆炸半径。
  • PreToolUse Hooks:注入额外的权限校验逻辑(如禁止特定命令模式)。
  • 审计日志(L7 联动):所有权限决策和工具执行被记录,可事后追溯。

这套组合拳让 Claude Code 的治理安全层既不过度保守(Agent 能跑起来),又不过度放任(危险操作能被拦住)。这是长程任务 Agent 能被开发者信任、敢于交给它干活的基础。


9.8 Subagent 与并行:子代理派发、类型与回收

子代理(Subagent)是 Claude Code 编排能力(L4)的重要组成部分。第7章从原理层面讲了 Multi-Agent 协作,这一节聚焦 Claude Code 的工程实现。

9.8.1 为什么需要子代理

主代理在长程任务中面临一个困境:有些子任务需要大量"探索"(搜索代码库、读几十个文件、跑多轮试探),如果这些探索全在主代理的上下文里进行,主代理的消息流会被中间过程淹没(9.4.3 节已讨论过上下文污染问题)。

子代理的作用就是把这些"探索型子任务"外包出去。主代理派一个子代理去干,子代理在隔离的上下文里自由探索,干完后只汇报结论。主代理的上下文保持干净,可以继续做需要全局视野的决策。

除了上下文隔离,子代理还带来并行能力。主代理可以同时派多个子代理干不同的子任务——比如一个搜代码、一个查文档、一个跑测试——这些子任务并行执行,总耗时约等于最慢的那个,而不是所有子任务串行耗时之和。

9.8.2 子代理类型

据公开资料与业界观察,Claude Code 提供了几种预定义的子代理类型(通过 Agent 工具调用),也允许用户自定义子代理类型:

  • Explore(探索型):只读权限,用于代码库搜索、文件查找、信息收集。它不能修改文件,只能读和搜索。这种"只读"设计保证了探索过程不会意外改动代码。
  • general-purpose(通用型):拥有和主代理类似的工具集与权限,可以读写文件、执行命令。用于需要"动手"的子任务。
  • 自定义子代理:用户可以定义自己的子代理类型,指定它的工具集、权限、系统提示。比如定义一个"code-reviewer"子代理,只有只读权限+特定的审查指令。

子代理类型的设计体现了"最小权限"原则——探索型子代理不需要写权限,就只给它读权限。这样即使子代理"自作主张",它也没有能力造成破坏。

9.8.3 并发与回收

主代理派发子代理时,可以指定是串行(派一个、等结果、再派下一个)还是并行(同时派多个、等全部完成或部分完成后回收结果)。

并行的工程挑战在于回收策略

  • 全部等待:等所有子代理都完成后再汇总。简单但可能被最慢的子代理拖累。
  • 部分回收:谁先完成就先回收谁,不等慢的。适合"只要有一个找到答案就够了"的场景。
  • 超时终止:给每个子代理一个超时时间,超时了就终止并回收已有结果。防止某个子代理陷入死循环拖垮整体。

据业界观察,Claude Code 的子代理回收策略通常偏向"全部等待"模式——主代理发起 Agent 工具调用后,会等待子代理完成并返回结果,再进入下一轮推理。这种模式简单可靠,但在子代理数量多、任务差异大时可能不够高效。

金句

子代理不是"多雇了几个人",而是"把探索过程外包给隔离的上下文"。它解决的核心问题不是人手不够,而是主代理的桌子不够大——探索过程在别处发生,只有结论摆上桌。

9.8.4 子代理与主代理的权限继承

子代理的权限是一个需要特别注意的设计点。据业界观察,子代理通常继承主代理的权限配置,但可以被进一步收窄(不能放宽)。比如主代理有 Bash 权限,子代理可以被限制为只读(Explore 型);但主代理只有 Read 权限时,子代理不能凭空获得 Bash 权限。

这种"只收窄不放宽"的继承策略,保证了子代理不会成为权限逃逸的通道——你不能通过派一个子代理来绕过主代理的权限限制。这是治理安全层(L6)在子代理机制上的延伸。

9.8.5 子代理的典型派发场景

理解了子代理的机制,还需要理解"什么时候该派子代理"。不是所有子任务都值得派子代理——派发本身有开销(创建子 Session、初始化工具集、等待完成、回收结果),如果子任务太简单,这个开销可能比直接在主代理里做还大。据业界观察,子代理派发的典型场景包括:

场景一:广度探索。“找到项目中所有使用了 deprecated API 的地方”——这类任务需要搜索大量文件、读大量代码,中间过程很长但结论很短。派一个 Explore 型子代理去做,主代理的上下文只被结论占用。

场景二:并行验证。“同时跑单元测试、集成测试和 lint 检查”——这三个任务互相独立,可以并行执行。派三个子代理同时干,总耗时约等于最慢的那个,而不是三者串行之和。

场景三:深度调查。“这个 bug 的根因是什么”——需要追调用链、看日志、查历史变更,是一个深度调查过程。派一个子代理去做深度调查,调查过程的中间假设和推翻都不污染主代理的上下文。

场景四:独立子任务。“修改 A 模块、修改 B 模块、修改 C 模块”——如果三个模块互相独立,可以派三个子代理并行修改,最后主代理统一跑测试验证。但如果三个模块有依赖关系(改 B 依赖 A 的结果),就不能并行,得串行。

关键判断标准是:子代理适合"中间过程长但结论短"且"子任务之间低耦合"的场景。如果子任务的中间过程本身就很重要(需要主代理随时介入判断),或者子任务之间有强依赖(B 必须等 A 的结果才能开始),就不适合派子代理。

金句

子代理不是万能的并行加速器。它适合"过程长、结论短、低耦合"的子任务,不适合"过程即结论"或"强依赖"的子任务。滥用子代理不但不会加速,反而会增加编排复杂度和回收开销。好的编排不是"能并行就并行",是"该并行才并行"。


9.9 实战案例

理论讲到这里,我们用两个可复现的工程案例把前面所有概念串起来。

9.9.1 案例一:用 Skills + Hooks 定制一个代码审查 Agent

目标:给一个 TypeScript 项目配置一个自动化代码审查流程。每次开发者提交代码后,Claude Code 自动审查变更、运行 lint、检查类型、生成审查报告。

设计思路

  1. 定义一个 Skill:“code-review”——包含审查清单(类型安全、错误处理、代码风格、测试覆盖)、审查流程、报告模板。
  2. 配置 Hooks:PostToolUse 在 Edit/Write 后自动触发 lint;Stop 钩子在 Agent 终止时生成审查报告。
  3. 配置权限:子代理只读,不能修改代码(审查不改代码,只提意见)。

配置骨架

// .claude/settings.json
{
  "permissions": {
    "allow": [
      "Read(*)",
      "Grep(*)",
      "Glob(*)",
      "Bash(npm run lint*)",
      "Bash(npm run typecheck*)"
    ],
    "deny": [
      "Write(*)",
      "Edit(*)",
      "Bash(rm *)",
      "Bash(git push*)"
    ],
    "ask": [
      "Bash(*)"
    ]
  },
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "command": "npm run lint -- --fix $FILE_PATH 2>/dev/null; echo 'lint已运行'"
      }
    ],
    "Stop": [
      {
        "command": "echo '生成审查报告...' && node scripts/gen-review-report.js"
      }
    ]
  }
}
<!-- .claude/skills/code-review/SKILL.md -->
---
name: code-review
description: 对当前Git变更进行全面的代码审查,覆盖类型安全、错误处理、代码风格、测试覆盖四个维度
triggers:
  - "审查代码"
  - "code review"
  - "检查变更"
---

# 代码审查技能

## 审查流程

1. 用 `git diff` 获取当前未提交的变更
2. 逐文件分析变更内容
3. 对每个文件按以下四个维度审查:

### 维度一: 类型安全
- 是否有 `any` 类型? 能否收窄?
- 是否有隐式类型推断可能导致运行时错误?
- 函数返回值类型是否标注?

### 维度二: 错误处理
- async 函数是否有 try/catch?
- 外部调用(网络/文件/数据库)是否有错误处理?
- 是否有未处理的 Promise rejection?

### 维度三: 代码风格
- 命名是否清晰一致?
- 是否有过度嵌套(>3层)?
- 是否有重复代码可抽取?

### 维度四: 测试覆盖
- 新增逻辑是否有对应测试?
- 边界条件是否被测试?
- 是否有遗漏的测试场景?

4. 运行 `npm run lint` 和 `npm run typecheck` 验证
5. 生成审查报告,格式:

## 报告模板

代码审查报告

总览

  • 审查文件: N个
  • 发现问题: 严重X / 警告Y / 建议Z

逐文件审查

{文件名}
  • [严重] {问题描述} (行号)
  • [警告] {问题描述} (行号)
  • [建议] {问题描述}

工具检查结果

  • lint: 通过/失败(N个问题)
  • typecheck: 通过/失败(N个错误)

运行方式

# 开发者提交代码后,启动 Claude Code 并触发审查
claude
> 审查代码
# 或显式调用技能
> /code-review

预期行为

  1. Claude Code 加载 code-review 技能的完整流程到上下文。
  2. Agent 用 git diff 获取变更,逐文件审查。
  3. 每个 Edit/Write 操作后,PostToolUse 钩子自动运行 lint。
  4. 审查完成后,Stop 钩子生成审查报告。
  5. 由于权限配置为只读(deny Write/Edit),Agent 不会修改代码,只提意见。

复盘

这个案例展示了 Skills、Hooks、权限模型三者的协作——Skills 提供"怎么审查"的知识,Hooks 提供"每次改完自动 lint、结束时自动出报告"的自动化行为,权限模型保证"审查不改代码"的安全边界。三者各司其职,组合出一个可复用的代码审查 Agent。

更深层地看,这个案例展示了 Claude Code 架构的一个设计美学:能力(Skills)、治理(权限)、自动化(Hooks)三者正交。你可以换一个 Skill(比如换成"安全扫描"技能),但保持同样的权限和 Hooks 配置;你也可以换一组 Hooks(比如加一个"每次工具调用后发 Slack 通知"),但保持同样的 Skill 和权限。这种正交设计意味着你可以像搭积木一样组合不同的能力包、治理规则、自动化行为,而不需要修改任何一块的内部实现。这就是第1章讲的"可组合"在产品层面的具体体现。

金句

好的架构让你能换零件而不动骨架。Claude Code 的 Skills、Hooks、权限三者正交——换技能不改规矩,换规矩不改技能,换自动化不改能力。这种正交性,是"可组合"从口号变成工程的前提。

9.9.2 案例二:权限配置不当导致误删——最小权限修复

场景:一个团队给 Claude Code 配置了宽松的 Bash 权限(Bash(*) → allow),方便 Agent 跑各种命令。某天 Agent 在清理临时文件时执行了 rm -rf dist/,但因为工作目录配置错误,实际删除了 src/ 下的部分文件。源码丢失,不得不从 Git 恢复。

问题分析

根因是权限配置过于宽松——Bash(*) → allow 意味着 Agent 可以执行任何 Shell 命令,包括 rm -rf。Agent 并非恶意,它以为自己在删 dist/(构建产物),但路径搞错了。这恰好印证了第1章的教训:模型的置信度是盲区——它"自信地"执行了一个危险操作。

最小权限修复

// .claude/settings.json (修复后)
{
  "permissions": {
    "allow": [
      "Read(*)",
      "Grep(*)",
      "Glob(*)",
      "Bash(npm test*)",
      "Bash(npm run build*)",
      "Bash(npm run lint*)",
      "Bash(git status*)",
      "Bash(git diff*)",
      "Bash(git log*)",
      "Bash(ls *)",
      "Bash(cat *)",
      "Bash(mkdir *)"
    ],
    "deny": [
      "Bash(rm *)",
      "Bash(rmdir *)",
      "Bash(git push*)",
      "Bash(git reset --hard*)",
      "Bash(chmod *)",
      "Bash(chown *)",
      "Bash(sudo *)",
      "Bash(curl *)",
      "Bash(wget *)"
    ],
    "ask": [
      "Bash(*)"
    ]
  },
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "command": "echo \"$COMMAND\" | grep -qE 'rm|delete|drop|truncate' && echo 'BLOCKED: 检测到删除类操作,请人工确认' && exit 1 || exit 0"
      }
    ]
  }
}

修复要点

  1. 从"全 allow"改成"白名单 allow + 黑名单 deny + 灰度 ask":只有明确安全的命令(测试、构建、lint、只读 git)走 allow;明确危险的(rm、push、reset --hard、chmod)走 deny;其他一切走 ask。
  2. 删除类命令硬禁止rmrmdir 全部 deny,不让 Agent 删任何东西。需要清理时由人工执行。
  3. PreToolUse 钩子二次拦截:即使用权限模型遗漏了某种删除命令模式,Hooks 层面再用正则匹配兜底。
  4. Ask 作为安全网:所有未明确 allow 的命令都走 ask,让用户有机会审查。

复盘

这个案例的教训是:权限配置的最小化不是"小气",是"保险"。Agent 的能力越强(能执行任意 Shell),权限配置就越要精细。Bash(*) → allow 是一种"偷懒"配置——它省去了列白名单的麻烦,但也放弃了所有安全防线。最小权限原则(只给 Agent 完成当前任务必需的最小权限集)不是可选项,是长程任务 Agent 的安全底线。

更深层地看,这个案例揭示了"防御纵深"的重要性。修复方案不只有权限三态这一道防线,还加了 PreToolUse Hooks 做二次拦截。这是"纵深防御"原则在 Agent 安全上的体现——不要依赖单一防线,要用多层防线叠加。权限模型拦不住的(比如某种你没想到的删除命令模式),Hooks 用正则兜底;Hooks 也拦不住的(比如模型构造了一个看起来不像删除但实际会删除的命令),沙箱限制爆炸半径。三层防线叠加,即使某一层有漏洞,整体安全性仍然有保障。这与传统信息安全领域的"defense in depth"原则一脉相承——长程任务 Agent 的安全,本质上就是一个微缩版的信息安全工程。

金句

Agent 删错文件不是 Agent 的错,是给 Agent 权限的人的错。你给了它一把没保险的枪,它走火是迟早的事。最小权限不是限制 Agent 的能力,是限制 Agent 犯错的爆炸半径。


9.10 表格清单

表 9-3:Claude Code 七层落地映射表

表 9-3:Claude Code 在 Harness 七层模型上的落地映射

Harness 职责 Claude Code 落地机制 关键设计
L1 执行环境层 隔离、可重建的工作场地 本地文件系统 + Shell 进程 + 沙箱容器 工作目录(workspace)隔离;沙箱限制爆炸半径
L2 工具接口层 外部能力包装成可调用工具 内置工具(Read/Write/Edit/Bash/Grep/Glob/Agent/TodoWrite) + MCP 外部工具 声明式注册;参数 schema 契约;Tool Registry 路由
L3 上下文记忆层 有限窗口内的信息编排 Read 按需读取 + TodoList 外部记忆 + CLAUDE.md 持久记忆 + compaction 压缩 按需不预加载;关键状态外化;历史摘要化
L4 任务编排层 大目标拆解与循环驱动 Agent SDK 内核(Session + Loop + Tool Registry + Message Stream) ReAct 循环;TodoList 驱动规划;子代理派发
L5 反馈验证层 可执行检验接住输出 Bash 跑测试/编译/lint;模型解读结果决定回滚或继续 测试驱动验证;错误回滚重试
L6 治理安全层 权限闸门与危险操作确认 allow/deny/ask 三态权限 + 参数级粒度 + PreToolUse Hooks + 沙箱 白名单优先;最小权限;只收窄不放宽(子代理)
L7 可观测层 思考与行动可追溯 日志/审计追踪 + Token 计量 + PostToolUse/Stop Hooks 记录 全链路审计;可事后复盘

这张表把 Claude Code 的每个机制精确定位到七层模型上。值得注意的是,有些机制横跨多层——比如 Hooks 既在 L6(PreToolUse 做权限拦截)又在 L7(PostToolUse 做日志记录);TodoList 既在 L3(外部记忆)又在 L4(驱动规划)。这种"一机制多层复用"正是成熟系统的特征——好的抽象总是能在多个层面发挥作用。

表 9-1(前文已列):Hooks 钩子点清单

见表 9-1(9.6.2 节),列出了 PreToolUse、PostToolUse、Notification、Stop、SubagentStop、UserPromptSubmit 六个钩子点及其触发时机、典型用途、返回值影响。

表 9-2(前文已列):权限三态决策表

见表 9-2(9.7.2 节),以常见操作为例列出了 allow/deny/ask 的建议配置、理由和典型规则。

表 9-4:Claude Code 核心机制速查表

表 9-4:Claude Code 核心机制速查表

机制 定位 解决的核心问题 扩展方式 与七层的关系
Agent SDK 内核 Agent 逻辑可编程、可嵌入、可组合 SDK import L4
内置工具 能力 Agent 需要原子操作(读/写/执行) 声明式注册 L2
MCP 工具 能力扩展 接入外部系统(API/数据库/服务) MCP Server 注册 L2
Skills 能力包 能力广度不污染上下文 .claude/skills/ 定义 L2/L3
Hooks 自动化行为 确定性自动化(不依赖模型自觉) settings.json 配置 L6/L7
权限三态 治理 危险操作可控 settings.json 规则 L6
沙箱 治理 限制爆炸半径 运行时隔离 L1/L6
Subagent 编排 上下文隔离 + 并行 Agent 工具调用 L4/L3
TodoList 记忆 对抗遗忘中段 TodoWrite 工具 L3/L4
Compaction 记忆 上下文窗口压力管理 自动触发 L3
CLAUDE.md 记忆 跨 Session 项目记忆 项目根目录文件 L3

9.11 最佳实践 Tips

💡 Tip 1:权限配置永远从"全 deny"开始,按需放行。

新手常犯的错误是从"全 allow"开始,出了事再加 deny。正确做法反过来:默认 deny 一切,只对当前任务明确需要的操作放行 allow。这样即使你遗漏了某个危险操作的 deny 规则,它也会落在 ask 态(被拦截等待审批),而不是被静默放行。最小权限不是"配好了就不用管",是"每次开新任务都重新审视一遍"。

💡 Tip 2:CLAUDE.md 是性价比最高的工程投入。

花 30 分钟写一份好的 CLAUDE.md(项目架构、编码规范、常用命令、已知陷阱),能让 Agent 在每次 Session 里都少走大量弯路。这 30 分钟的投入,在项目生命周期里会被复用成百上千次。注意控制长度——几百行以内,把"所有任务都需要知道的"放进去,"特定任务需要的"让 Agent 按需 Read。

💡 Tip 3:凡是"每次都必须发生"的行为,挂 Hooks,不写 prompt。

“改完文件要格式化”、“工具执行后要记日志”、“禁止 rm -rf”——这些行为你期望每次都发生。如果你把它们写进 prompt 让模型"记得做",模型有概率忘记或被绕过。挂到 Hooks 上,Harness 保证每次必执行。确定性安全靠 Hooks,概率性判断靠模型,不要搞反。

💡 Tip 4:探索型子任务用只读子代理,动手型子任务才给写权限。

派子代理时,默认用 Explore 型(只读)。只有在子代理明确需要修改文件时,才用 general-purpose 或自定义型。这遵循最小权限原则——探索不需要写权限,不给它写权限就消除了"探索过程中手滑改了代码"的风险。

💡 Tip 5:Skills 的描述要写得像"搜索关键词",精准且简短。

隐式匹配依赖模型根据技能描述判断"要不要激活这个技能"。描述写得模糊,模型匹配不上,技能就白定义了。好的描述像搜索关键词:精准(说清什么场景用)、简短(一句话)、包含触发词(如"代码审查"、“安全扫描”)。不要写成段落,写成"场景 + 能力"的短语。

💡 Tip 6:用 TodoList 把长程任务"可视化",不只是给 Agent 看,也是给你看。

TodoList 不只帮助 Agent 对抗遗忘,它也让你(用户)能实时看到 Agent 的进度和计划。当你看到 TodoList 里某项卡在 in_progress 很久,你就知道 Agent 在那里可能遇到了困难,可以主动介入。把 TodoList 当作 Agent 的"工作看板",主动监控而非被动等待。

💡 Tip 7:权限规则用通配符分层,别一个 Bash(*) 走天下。

Bash 是最需要参数级权限粒度的工具。用通配符分层:Bash(npm test*) → allow(测试安全)、Bash(npm run *) → allow(构建脚本安全)、Bash(git push*) → ask(推送需确认)、Bash(rm *) → deny(删除禁止)、Bash(*) → ask(其他都问)。这比一个 Bash(*) → allow 安全得多,也比逐条枚举每个命令省事。

💡 Tip 8:定期检查 compaction 后的上下文是否丢失了关键信息。

Compaction 是有信息损失的。如果你发现 Agent 在长程任务的后段"忘了"前段的关键决策,可能是 compaction 把它摘要化时丢掉了。解决方案:把真正关键的信息(如"为什么保留这几处 var 不改")主动写进 TodoList 或文件中,而不是依赖消息流里的大段历史——TodoList 不会被 compaction 摘要化,文件在磁盘上更不会丢。


9.12 避坑红线

⚠️ 红线 1:不要用 Bash(*) → allow 偷懒配置。

这是本书反复强调的安全红线。Bash(*) → allow 等于把 Shell 的完整权限交给 Agent,包括 rm -rfgit push --forcecurl 恶意脚本 | bash。Agent 不需要"故意"搞破坏,它只需要"自信地"执行一个路径搞错的 rm 就够你喝一壶。永远用白名单 + ask 兜底,不要用全 allow 偷懒。

⚠️ 红线 2:不要把所有能力都塞进系统提示,指望模型"记住所有事"。

系统提示不是越丰富越好。你塞进去 50 条规则、20 个工具说明、10 个流程文档,模型确实"都看到了",但在实际推理中它能有效利用的信息是有限的——上下文窗口越大,注意力越分散。把"偶尔需要"的能力收进 Skills(按需加载),把"每次都需要但很长"的内容收进文件(按需 Read),系统提示只留"每次都需要且简短"的核心信息。

⚠️ 红线 3:不要让子代理的权限宽于主代理。

权限继承只应收窄不应放宽。如果你发现需要给子代理比主代理更大的权限才能完成任务,这说明主代理的权限配错了——应该调整主代理的权限,而不是通过子代理绕过限制。子代理绕过权限限制是典型的"权限逃逸"漏洞,任何成熟的 Agent 系统都不应允许。

⚠️ 红线 4:不要依赖模型"自觉"来做安全检查。

“在 prompt 里写’请不要执行危险命令’”——这是一种虚假安全感。模型可能会遵守,也可能会在特定上下文下忽略这条指令(比如被 prompt injection 误导)。安全检查必须由 Harness 层面的确定性机制(权限模型 + PreToolUse Hooks)来执行,不能依赖模型的"自觉"。prompt 里的安全指令是"补充",不是"替代"。

⚠️ 红线 5:不要在 CLAUDE.md 里放敏感信息。

CLAUDE.md 会被注入系统上下文,如果它包含 API Key、数据库密码、内部 IP 地址,这些信息可能随着 prompt 发送给模型服务。CLAUDE.md 只放"项目规矩"(架构说明、编码规范、常用命令),敏感信息用环境变量或 secret manager 管理,Agent 需要时通过工具按需读取。


9.13 番外篇

番外一:命令行搭档的设计哲学——为什么 Claude Code 选择 CLI 而非 IDE

业界有人问过:为什么 Anthropic 把第一个代码 Agent 做成 CLI 而不是 IDE 插件?毕竟 IDE 有更好的可视化、更丰富的交互能力、更低的开发者上手门槛。做 CLI 看起来是"反用户体验"的选择。

但如果你理解了"命令行搭档"的定位,就会明白这个选择背后的哲学。

第一,CLI 是可编程的,IDE 不是。 CLI 天然适合被脚本调用、被嵌入 pipeline、被 CI/CD 调度。你可以写一个 Git pre-commit hook 调用 claude 做代码审查;你可以在 CI 里加一步 claude --task "跑测试并修复失败项"。IDE 插件做不到这些——它需要人打开 IDE、点按钮、看面板。长程任务 Agent 的核心价值是"被托管",CLI 天然支持托管,IDE 天然需要人在场。

第二,CLI 是可组合的。 Unix 哲学的精髓是"每个工具做一件事,通过管道组合"。Claude Code 作为 CLI,可以和 grepsedgitmake 等经典工具无缝组合。Agent 的输出可以 pipe 给下一个工具,下一个工具的输出可以 pipe 回 Agent。IDE 是一个封闭花园——它有自己的生态系统,但难以和外部工具自由组合。

第三,CLI 是透明的。 CLI 的输入输出都是文本,你可以看到 Agent 的每一步思考、每一个工具调用、每一次结果。IDE 可能把这些藏在漂亮的 UI 背后,看起来更舒服,但调试时更难看清"Agent 到底做了什么"。长程任务 Agent 的可观测性(L7)极其重要,CLI 的文本透明性是天然优势。

第四,CLI 是最小的信任边界。 IDE 插件通常需要广泛的文件系统访问权限、网络权限、甚至 GUI 交互权限。CLI 只需要:一个工作目录、一个 Shell、标准输入输出。权限粒度更小,安全边界更清晰。

当然,CLI 也有劣势——没有代码高亮、没有可视化 diff、没有项目管理面板。但这些可以通过配合其他工具解决(bat 做高亮、git diff --color-words 做可视化 diff)。而 IDE 的封闭性是内在的,难以通过外部工具弥补。

金句

Claude Code 选择 CLI,不是因为 CLI 更好用,而是因为 CLI 更可编程、更可组合、更透明、更可控。这些恰恰是长程任务 Agent 最需要的品质——它不需要长得好看,它需要能被信任地嵌入到工程流水线里。

番外二:Anthropic 的 Harness 指南如何反哺 Claude Code 设计

据业界观察,Anthropic 在发布 Claude Code 的同时,也发布了一系列关于"如何构建可靠的 Agent"的工程指南(如"Building Effective Agents"等文章)。这些指南不是事后总结,而是和 Claude Code 的设计相互反哺的——指南里的原则来自设计 Claude Code 时的工程实践,Claude Code 的实现又反过来验证和打磨这些原则。

一个典型的例子是"保持 Agent 简单"的原则。Anthropic 的指南反复强调:不要过早引入复杂的 Multi-Agent 编排,先用最简单的单 Agent + 工具 + 循环把任务跑通,只有当单 Agent 的上下文或能力确实不够时才引入子代理。Claude Code 的默认模式就是单 Agent——你启动 claude 后默认是一个 Agent 在跑,子代理是按需才派发的。这种"默认简单、按需复杂"的设计哲学,直接来自指南原则。

另一个例子是"工具设计要给模型足够的反馈"。指南强调:工具的返回值要包含足够的信息让模型判断"做对了没有",不要只返回"成功"或"失败"。Claude Code 的 Bash 工具返回完整的 stdout/stderr,Read 工具返回带行号的文件内容——这些设计让模型能从工具结果中自行判断成功与否,而不需要额外的验证步骤。

这种"指南 ↔ 产品"的反哺关系,是 Anthropic 在 Agent 工程领域的独特优势——它既做模型又做 Harness,既写指南又做产品,三者形成正循环。指南来自实践,实践验证指南,产品体现指南。这也是为什么 Claude Code 的设计感觉"自洽"——它的每个机制都能在指南里找到对应的原理阐述。

还有一个值得关注的反哺方向是"从用户的真实使用模式反哺设计"。据业界观察,Claude Code 的很多功能演进并非来自自上而下的规划,而是来自对用户真实使用模式的观察。比如,用户频繁在 Agent 运行过程中手动打断来纠正方向——这个行为模式催生了"更灵活的中断与恢复"机制的设计需求。再比如,用户在 CLAUDE.md 里越写越多内容导致上下文膨胀——这个痛点催生了 Skills 按需加载机制,把"所有任务都需要"和"特定任务需要"分层。这种"自下而上"的设计反哺,使得 Claude Code 的功能演进始终贴近真实工程场景,而非工程师的想象。

金句

最好的工程产品,是那些"设计和原则同源"的产品。Claude Code 不是先有产品再补文档,也不是先有原则再做产品——两者同根同源、相互反哺。这种自洽感,是"先想清楚再动手"的工程师才能给出来的。


9.14 本章小结

  • Claude Code 的整体架构是七层 Harness 的完整落地:CLI 入口(前台)、Agent SDK 内核(中枢神经)、工具与执行子系统(手脚),三大子系统包裹着 Claude 模型(大脑)构成一个能在终端里"扛活"的搭档。
  • Agent SDK 的四个核心抽象——Session(生命周期)、Loop(认知循环)、Tool Registry(能力边界)、Message Stream(信息流动)——把 Agent 从"一个产品"变成"一个可编程、可嵌入、可组合的平台"。SDK 化是 Agent 走向工程化的关键一步。
  • 上下文与记忆策略围绕"按需、外化、隔离、压缩"四个原则展开:Read 按需读取不预加载、TodoList 把规划外化为结构化清单、子代理在隔离上下文中探索只回灌结论、compaction 在窗口压力时摘要化历史。这四招组合管理有限的上下文窗口,与第4章的上下文工程理论直接呼应。
  • Skills 机制通过"按需加载"解决了"能力广度 vs 上下文深度"的矛盾——系统提示只放技能清单,用到时才加载完整内容。Skills 与 Tools 的层次关系是"打法 vs 手":Tools 是原子操作,Skills 是组合打法。
  • Hooks 在 Agent 运行的关键节点(PreToolUse、PostToolUse、Stop 等)注入确定性自动化行为,扩展 L6(治理)和 L7(可观测)。凡是"每次都必须发生"的行为,挂 Hooks 而不写 prompt。
  • 权限模型用 allow/deny/ask 三态 + 参数级粒度 + 沙箱,构成 L6 治理安全层的完整防线。最小权限原则不是可选项,是长程任务 Agent 的安全底线。
  • Subagent 通过上下文隔离解决探索过程污染主代理的问题,同时带来并行能力。子代理权限只收窄不放宽,防止权限逃逸。

章末金句

Claude Code 不只是一个会写代码的 AI,它是一套"如何把一颗大脑包成能扛活的搭档"的完整工程范式。它的每个机制——Session 管命、Loop 管步、Registry 管手、Stream 管忆、Skills 管博、Hooks 管律、权限管界、子代理管并行——都在回答同一个问题:怎么让可靠性不依赖大脑的聪明,而依赖躯体与神经的可靠。


9.15 延伸思考题

  1. Skills 与 MCP 的边界:Skills 和 MCP 工具都能扩展 Agent 的能力,但它们的扩展方式不同——Skills 是"按需加载的打法包",MCP 是"外部能力的标准化接口"。如果一个团队需要给 Agent 加"数据库查询"能力,应该做成 Skill 还是 MCP Server?什么情况下两者都可以,什么情况下只有一种合适?请从上下文成本、复用性、维护成本三个维度给出你的决策框架。

  2. 权限模型的极端场景:假设你要部署一个 Claude Code Agent 在 CI/CD pipeline 里全自动运行(无人工审批),但任务包括"修改代码并提交 PR"。在完全无人值守的情况下,ask 态失去了意义(没有人来回答)。你会如何重新设计权限模型来适配这种"全自动 Agent"场景?哪些原本走 ask 的操作可以放行?哪些必须改用 Hooks 做确定性校验来替代人工审批?

  3. 子代理与 Compaction 的交互:主代理派了一个子代理,子代理跑了很久、产生了大量中间结果,最终汇报了一段很长的结论。这段结论被注入主代理的消息流后,又经过了几轮对话,触发了 compaction。compaction 会如何处理这段子代理结论?如果结论里有关键信息被摘要丢失了,主代理后续可能做出错误决策。你会如何设计一个机制,确保"子代理的关键结论"在 compaction 后仍然不丢失?提示:结合 9.4 节的"外化"思路。

  4. 从产品到平台的跃迁:Agent SDK 把 Claude Code 从"一个交互式产品"变成了"一个可编程平台"。但这种跃迁也带来了新的工程挑战:当你用 SDK 嵌入一个 Agent 到 CI/CD pipeline 里全自动运行时,9.7 节的权限三态模型中"ask"态失去了意义(没有人来回答)。你会如何重新设计一个"无人值守 Agent"的权限与安全模型?哪些原本走 ask 的操作可以放行?哪些必须改用 Hooks 做确定性校验来替代人工审批?这个问题的答案,直接决定了长程任务 Agent 能否真正进入"全自动托管"的阶段。


本章是三大产品解剖的第一篇。我们拆解了 Claude Code——一个"工程稳健派"的代表。它不追求最激进的自动化,而是追求每一层都做扎实、每一层都可治理。它的设计哲学可以用一句话概括:让可靠性不依赖大脑的聪明,而依赖躯体与神经的可靠

第 10 章,我们将把解剖刀转向 OpenAI Codex——一个用"3 名工程师 + Agent,5 个月生成 100 万行代码"的激进编排派,看看 Harness 工程的另一种哲学如何把"全自动"推到极限。当 Claude Code 选择"和你一起扛活"时,Codex 选择了"替你扛活"——两种哲学的碰撞,将揭示 Harness 工程的设计光谱的另一个极端。


《长程任务 Agent 开发实战:Harness 工程原理与应用实践》

——深度剖析 Claude Code、OpenClaw、Codex 架构原理与应用

全书总纲与写作规范(Spine & Style Guide)
版本:v1.0 | 定位:世界顶级大师级 AI 大模型应用技术经典


一、全书定位与精神

本书不是 API 罗列,也不是框架速成。它是一部从第一性原理出发、把"长程任务 Agent"当作一类新型软件系统来拆解的工程哲学与技术实战著作。

一句话灵魂

模型是大脑,Harness 是大脑之外的躯体与神经系统。同一颗大脑,换一副更好的躯体与神经,基准通过率可从 6.7% 飙升至 68.3%——这就是 Harness 工程存在的全部理由。

核心命题(全书反复演绎、归纳、对照的三条主线):

  1. 认知核命题:Agent 的认知核是 LLM(语言作为认知核),但其"长程可靠性"不取决于模型智商,而取决于围绕它搭建的可控、可验证、可观测、可恢复的运行外壳。
  2. 七层命题:Harness 的本质是七层系统——执行环境层、工具接口层、上下文记忆层、任务编排层、反馈验证层、治理安全层、可观测层——将模型推理组织成可生产化的任务流程。
  3. 共进化命题:模型与 Harness 不是单向服务关系,而是双向共进化;随着模型内化吸收部分底层 Harness 能力,Harness 工程师的价值会持续上移到"认知架构"层。

写作手法要求(每章必须显著体现):

  • 类比 / 隐喻:把抽象机制映射到人体神经、城市交通、工厂流水线、航海导航、法庭审判等具象系统。
  • 对比 / 对照:Claude Code vs Codex vs OpenClaw 横向对照;有 Harness vs 无 Harness;ReAct vs Plan-then-Execute;短程 vs 长程。
  • 演绎:从第一性原理推出工程结论(如"为何长程任务必须 checkpoint")。
  • 归纳:从多个真实案例归纳出可复用模式(如"错误恢复三式")。
  • 金句:每节至少 1-2 句可被读者画线摘抄的精到见解,独立成段,用 > 引用块呈现。

二、全书章节地图(14 章)

章号 章名 核心主题 主线产品
第1章 导论:从语言模型到长程任务 Agent 的认知革命 历史坐标、范式变迁、为何需要 Harness 通览
第2章 Harness 的本质:模型之外的躯体与神经系统 七层模型、Harness 定义、优缺点 通览
第3章 Agent Loop 与 ReAct:认知循环的工程化 Thought-Action-Observe、循环稳定性 Claude Code/Codex
第4章 上下文工程与记忆管理:从 KV Cache 到长短期记忆 Context Engineering、压缩、记忆分层 Claude Code
第5章 工具集成与 MCP 协议:神经末梢与肌肉 Tool Use、MCP、Skills、参数契约 Claude Code/OpenClaw
第6章 任务规划与推理:Planning & Reasoning 的长程应用 拆解、调度、TodoWrite、回溯 Claude Code/Codex
第7章 Subagent 与 Multi-Agent 协作机制 子代理、并行、上下文隔离、调度 Claude Code/OpenClaw
第8章 长程任务的持久化、检查点与错误恢复 Persistence、Checkpoint、Human-in-loop Anthropic 指南
第9章 深度剖析 Claude Code 架构原理 Agent SDK、Skills、Hooks、权限模型 Claude Code
第10章 深度剖析 OpenAI Codex 架构原理 Codex Agent、百万行实验、Harness Engineering 体系 Codex
第11章 深度剖析 OpenClaw 及开源 Harness 生态 开源架构、可复用骨架、对比 OpenClaw
第12章 Agent Ops:全生命周期治理、可观测与安全 开发-调试-评估-部署-监控、SLA 通览
第13章 模型与 Harness 的协同进化:后训练与认知架构 SFT/RLHF、模型内化、Harness 上移 通览
第14章 企业级落地实战:2B 场景规模化交付与未来展望 金融/政务/制造、低代码、路线图 通览

三、每章统一结构模板(强制)

每章正文必须包含以下板块(顺序可微调,但不可缺):

  1. 章首引言:一段隐喻开场 + 本章学习目标(3-5 条)+ 一句金句。
  2. 核心原理理论:第一性原理推导 + 概念定义 + 与其他范式的对比。
  3. 架构与原理结构图(纯文本画图,见第四节规范)。
  4. 领域关系模型图 / 系统逻辑流程图(纯文本)。
  5. 深度剖析主产品:以 Claude Code / Codex / OpenClaw 之一为主例,讲透机制。
  6. 实战案例:至少 2 个可复现的工程案例(含目标、步骤、代码骨架、结果、复盘)。
  7. 表格清单:至少 3 张高质量对照表(参数表、模式对照表、决策清单等)。
  8. 最佳实践 Tips:以 > 💡 **Tip N:** 格式,至少 6 条。
  9. 避坑红线:以 > ⚠️ **红线:** 格式,列出常见错误。
  10. 番外篇:1-2 则,增加趣味性(历史掌故、思想实验、业界轶事、跨学科类比)。
  11. 本章小结:3-5 句归纳 + 一句金句收尾。
  12. 延伸思考题:3 道开放题。

四、纯文本画图规范(强制,禁止 mermaid)

  • 使用 ASCII/Unicode 线条字符:+ - | / \ > < v ^ * o @,可适当用 ┌ ┐ └ ┘ │ ─ ├ ┤ ┬ ┴ ┼ 框线字符。
  • 图必须有图题图 N-X:xxx)和图注(图下方一行说明)。
  • 图内文字简短,箭头方向清晰,分层明确。
  • 复杂系统用"嵌套框 + 箭头"表达;流程用"纵向或横向流水线"表达;关系模型用"实体—关系"表达。
  • 每章图不少于 6 张,覆盖:架构图、原理结构图、领域关系模型图、系统逻辑流程图、时序图、状态机图。

五、字数与质量红线

  • 每章正文 5 万字左右(中文计字),全书 50 万字以上。
  • 严禁注水:每一个小节都要有实质技术内容、可落地见解或可复现案例。
  • 严禁编造 API:涉及真实产品(Claude Code/Codex/OpenClaw)的机制描述,以公开文档与业界公认事实为准;不确定处用"据公开资料/业界观察"措辞,不得伪造函数签名或版本号。
  • 代码示例用伪代码或带注释骨架,强调"原理可懂",不追求可直接编译。

六、术语统一表

统一用词 不用
Harness(线束/驾驭框架) 脚手架/外壳(仅在类比时用"躯体/神经")
长程任务 长时任务/长周期任务(首次出现注明)
认知核 ——
上下文工程 Context Engineering 上下文管理(可并用)
检查点 Checkpoint 快照(首次注明)
子代理 Subagent 子智能体(首次注明)
多代理 Multi-Agent 多智能体(首次注明)
提示工程 Prompt Engineering ——
后训练 Post-training ——
可观测性 Observability 监控(可并用)

七、文件命名(强制)

第{N}章-{章名}《长程任务Agent开发实战:Harness工程原理与应用实践》.md

示例:第1章-导论:从语言模型到长程任务Agent的认知革命《长程任务Agent开发实战:Harness工程原理与应用实践》.md


本总纲为全书之脊。各章作者(含并行 Agent)必须以此为准绳,保证全书气脉贯通、画风统一、金句密度恒定。

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐