第9章 深度剖析 Claude Code 架构原理 《长程任务 Agent 开发实战:Harness 工程原理与应用实践》
第9章 深度剖析 Claude Code 架构原理
《长程任务 Agent 开发实战:Harness 工程原理与应用实践》
——深度剖析 Claude Code、OpenClaw、Codex 架构原理与应用
章首金句
命令行是工程师最诚实的界面:它不粉饰、不隐藏、不替你做决定。Claude Code 选择住进终端而不是躲进 IDE,恰恰因为它要做的不是"替你写代码",而是"和你一起扛活"——一个愿意跟你一起待在战壕里的搭档,永远比一个坐在办公室里替你审批的人有用。
9.1 开场隐喻:从"驻场总管"到"命令行搭档"
想象你是一家施工队的总工程师,手底下管着几十号人。你不需要一个替你画图纸的绘图员——你自己会画。你需要的是一个"驻场总管":他住在工地,能随时翻图纸、量尺寸、查规范、跑腿去仓库领料、发现管线冲突时立刻喊停。他不是你的替代者,他是你的外延。
传统 IDE 里的 AI 助手,更像是一个"办公室绘图员":你把需求口述给他,他在自己的工位上画好图,递给你看;你要改,他把图拿回去再改。他从不踏进工地,也从不管施工队的活怎么排。他体面、干净,但隔着一层。
Claude Code 是另一种存在。它不住办公室,它住在你每天待的那个最朴素的地方——命令行终端。它像一个拎着工具箱、蹲在你旁边的搭档:你说"把这片的 var 都换成 let,测试别挂",它就自己翻文件、自己改、自己跑测试、挂了自己回滚、最后给你一张清单。你不放心的地方,它会先问你要不要动手(权限确认);你忙不过来的活,它能再拉几个工友来并行干(子代理派发)。
这个"命令行搭档"的定位,不是技术上的随手选择,而是一整套设计哲学的起点。住进终端意味着:它必须接受工程师最朴素的工作环境(文件系统 + Shell),必须接受工程师最朴素的交互方式(文本对话),必须接受工程师最朴素的信任模型(你来审批每一步危险操作)。这种"朴素",恰恰是长程任务可靠性的根基。
学习目标(读完本章你将能够)
- 用一张总架构图讲清 Claude Code 从 CLI 入口到子代理派发的完整数据通路,并能把每一块映射到第1章的七层 Harness 模型。
- 复述 Agent SDK 的四个核心抽象(Session、Loop、Tool 注册、Message 流),并解释为什么"SDK 化"是 Agent 从产品走向平台的关键一步。
- 说清 Claude Code 的上下文与记忆策略:Read 按需读取、TodoList 作为外部规划记忆、子代理上下文隔离、compaction 压缩——并理解它们与第4章上下文工程的呼应关系。
- 区分 Skills、Hooks、Tools 三者的层次关系与扩展边界,能用它们组合出可复用的自动化行为。
- 复述权限模型的三态决策(allow/deny/ask)与沙箱机制,并能用它给一个真实项目配置最小权限。
- 设计一个基于 Skills + Hooks 的代码审查 Agent,并写出配置骨架与伪代码。
9.2 整体架构:CLI 入口、Agent SDK 内核、工具层与子代理派发
要把 Claude Code 讲透,最好的办法是跟着一条用户指令从入口走到出口,把沿途经过的每一个模块都点一遍名。
9.2.1 从一行命令说起
当你在终端敲下 claude 并输入一句任务时,背后发生的事情远比看上去复杂。据公开资料与业界观察,Claude Code 的运行时大致经过以下通路:
- CLI 入口层:解析命令行参数、加载配置(settings.json、CLAUDE.md 等)、初始化会话环境。
- Agent SDK 内核:创建 Session、启动 Agent Loop、注册可用工具集、管理消息流。
- 模型推理:将组装好的上下文发送给 Claude 模型,接收返回的文本与工具调用意图。
- 权限检查:在工具真正执行前,走一遍权限闸门(allow/deny/ask)。
- 工具执行:调用对应工具(Read、Bash、Edit、Grep 等),在本地文件系统或 Shell 中执行。
- 结果回灌:将工具执行结果作为新的 message 注入消息流,进入下一轮 Loop。
- 子代理派发(按需):当主代理判断需要并行或隔离探索时,派发子代理,子代理独立运行后汇报结果。
- 可观测记录:全过程被日志、审计追踪记录。
这条通路看着线性,实则是一个循环——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 的关键工程问题有三个:
- 增长管理:每轮 Loop 都会往流里加消息(模型输出、工具结果),流会越来越长。当总 token 数逼近模型上下文窗口上限时,必须触发 compaction(见 9.4 节)。
- 相关性筛选:不是所有历史消息都对当前步骤有用。Read 工具返回的大段文件内容,在用过之后可能就不需要留在流里了。如何在不丢失关键信息的前提下精简流,是上下文工程的核心难题。
- 子代理隔离:子代理有自己的独立 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 test 和 npm run build,不允许 rm 和 git 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 *)→ denyGit 推送(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、检查类型、生成审查报告。
设计思路:
- 定义一个 Skill:“code-review”——包含审查清单(类型安全、错误处理、代码风格、测试覆盖)、审查流程、报告模板。
- 配置 Hooks:PostToolUse 在 Edit/Write 后自动触发 lint;Stop 钩子在 Agent 终止时生成审查报告。
- 配置权限:子代理只读,不能修改代码(审查不改代码,只提意见)。
配置骨架:
// .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
预期行为:
- Claude Code 加载 code-review 技能的完整流程到上下文。
- Agent 用
git diff获取变更,逐文件审查。 - 每个 Edit/Write 操作后,PostToolUse 钩子自动运行 lint。
- 审查完成后,Stop 钩子生成审查报告。
- 由于权限配置为只读(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"
}
]
}
}
修复要点:
- 从"全 allow"改成"白名单 allow + 黑名单 deny + 灰度 ask":只有明确安全的命令(测试、构建、lint、只读 git)走 allow;明确危险的(rm、push、reset --hard、chmod)走 deny;其他一切走 ask。
- 删除类命令硬禁止:
rm、rmdir全部 deny,不让 Agent 删任何东西。需要清理时由人工执行。 - PreToolUse 钩子二次拦截:即使用权限模型遗漏了某种删除命令模式,Hooks 层面再用正则匹配兜底。
- 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 -rf、git push --force、curl 恶意脚本 | 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,可以和 grep、sed、git、make 等经典工具无缝组合。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 延伸思考题
-
Skills 与 MCP 的边界:Skills 和 MCP 工具都能扩展 Agent 的能力,但它们的扩展方式不同——Skills 是"按需加载的打法包",MCP 是"外部能力的标准化接口"。如果一个团队需要给 Agent 加"数据库查询"能力,应该做成 Skill 还是 MCP Server?什么情况下两者都可以,什么情况下只有一种合适?请从上下文成本、复用性、维护成本三个维度给出你的决策框架。
-
权限模型的极端场景:假设你要部署一个 Claude Code Agent 在 CI/CD pipeline 里全自动运行(无人工审批),但任务包括"修改代码并提交 PR"。在完全无人值守的情况下,ask 态失去了意义(没有人来回答)。你会如何重新设计权限模型来适配这种"全自动 Agent"场景?哪些原本走 ask 的操作可以放行?哪些必须改用 Hooks 做确定性校验来替代人工审批?
-
子代理与 Compaction 的交互:主代理派了一个子代理,子代理跑了很久、产生了大量中间结果,最终汇报了一段很长的结论。这段结论被注入主代理的消息流后,又经过了几轮对话,触发了 compaction。compaction 会如何处理这段子代理结论?如果结论里有关键信息被摘要丢失了,主代理后续可能做出错误决策。你会如何设计一个机制,确保"子代理的关键结论"在 compaction 后仍然不丢失?提示:结合 9.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 工程存在的全部理由。
核心命题(全书反复演绎、归纳、对照的三条主线):
- 认知核命题:Agent 的认知核是 LLM(语言作为认知核),但其"长程可靠性"不取决于模型智商,而取决于围绕它搭建的可控、可验证、可观测、可恢复的运行外壳。
- 七层命题:Harness 的本质是七层系统——执行环境层、工具接口层、上下文记忆层、任务编排层、反馈验证层、治理安全层、可观测层——将模型推理组织成可生产化的任务流程。
- 共进化命题:模型与 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 场景规模化交付与未来展望 | 金融/政务/制造、低代码、路线图 | 通览 |
三、每章统一结构模板(强制)
每章正文必须包含以下板块(顺序可微调,但不可缺):
- 章首引言:一段隐喻开场 + 本章学习目标(3-5 条)+ 一句金句。
- 核心原理理论:第一性原理推导 + 概念定义 + 与其他范式的对比。
- 架构与原理结构图(纯文本画图,见第四节规范)。
- 领域关系模型图 / 系统逻辑流程图(纯文本)。
- 深度剖析主产品:以 Claude Code / Codex / OpenClaw 之一为主例,讲透机制。
- 实战案例:至少 2 个可复现的工程案例(含目标、步骤、代码骨架、结果、复盘)。
- 表格清单:至少 3 张高质量对照表(参数表、模式对照表、决策清单等)。
- 最佳实践 Tips:以
> 💡 **Tip N:**格式,至少 6 条。 - 避坑红线:以
> ⚠️ **红线:**格式,列出常见错误。 - 番外篇:1-2 则,增加趣味性(历史掌故、思想实验、业界轶事、跨学科类比)。
- 本章小结:3-5 句归纳 + 一句金句收尾。
- 延伸思考题: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)必须以此为准绳,保证全书气脉贯通、画风统一、金句密度恒定。
更多推荐
所有评论(0)