Harness Engineering,把 Claude Code 变成你的自动化引擎

2026-03-28

Harness Engineering,把 Claude Code 变成你的自动化引擎

Claude Code 的官方文档里有一句话让我停了很久。在 How Claude Code Works 那个页面,Anthropic 写道"Claude Code serves as the agentic harness around Claude: it provides the tools, context management, and execution environment that turn a language model into a capable coding agent."

Harness。缰绳,马具,或者更准确地说,驾驭装置。

这个隐喻非常精准。Claude 模型本身是一匹能力很强但方向感模糊的马,Claude Code 就是你套在它身上的那套装备,让它能按照你的意图去完成具体的工作。而 Harness Engineering,就是关于如何设计、调试和优化这套装备的工程实践。

大多数人用 Claude Code 的方式是在终端里打开一个交互式会话,让它帮忙写代码、改 bug、做重构。这当然没问题,但这只是最浅层的使用方式。Claude Code 实际上提供了一个相当完整的 Agent 运行时,你可以通过 Hooks 做自动化约束,通过 Skills 做知识注入,通过 Subagents 做任务编排,通过 Agent SDK 做程序化调用,通过 GitHub Actions 做持续集成。把这些能力组合在一起,你就拥有了一个可编程的自动化引擎。

我想聊聊怎么用好这个引擎。

先理解 Harness 的结构

Claude Code 的 Harness 由几个层次组成,每一层解决不同的问题。

最底层是 Agentic Loop,也就是那个"收集上下文、执行动作、验证结果"的循环。这是 Claude Code 的心脏,每一次你给它一个任务,它都在这个循环里运转,读文件、改代码、跑测试,直到任务完成或者你叫停它。你不需要控制这个循环,但你需要理解它的节奏。Claude 在循环中会自主决定下一步做什么,但它的决策质量取决于你给它的上下文质量。

往上是一层工具系统,包括文件操作(Read、Write、Edit)、搜索(Glob、Grep)、执行(Bash)、网络(WebSearch、WebFetch)和代码智能(类型检查、跳转定义)。Claude 通过这些工具与你的项目交互。这个系统是可扩展的,MCP(Model Context Protocol)允许你接入外部服务,比如数据库、Figma、Slack,任何提供了 MCP 适配器的东西都可以变成 Claude 的工具。

再往上是知识层。CLAUDE.md 提供持久的项目指令,Skills 提供按需加载的领域知识和可复用工作流,Auto Memory 让 Claude 自己积累对项目的理解。这三者共同构成了 Claude 的"长期记忆",让它不只依赖当前对话中的信息。

最上层是自动化层。Hooks 在特定生命周期节点执行确定性操作(比如每次文件编辑后自动格式化),Subagents 在隔离的上下文中执行专业化任务(比如让一个只读 agent 做安全审查),Agent SDK 允许你从 Python 或 TypeScript 程序中调用 Claude Code 的全部能力,GitHub Actions 让 Claude Code 直接融入你的 CI/CD 流程。

理解了这个结构,你就能针对不同的需求选择不同的层次来构建你的 Harness。

Hooks 是 Harness 的骨骼

Hooks 是我在 Claude Code 中最喜欢的特性,也是我认为被严重低估的一个。

简单来说,Hooks 是在 Claude Code 生命周期特定节点自动触发的 shell 命令。它和 CLAUDE.md 的区别在于,CLAUDE.md 是"建议"性的,Claude 可能会忽略你的指令;而 Hooks 是"确定性"的,只要触发条件满足,脚本一定会执行。

Claude Code 提供了丰富的 Hook 事件,覆盖了从会话开始到工具使用的整个生命周期。

我实际用下来觉得最有价值的几个场景,第一个是自动格式化。你在项目根目录的 .claude/settings.json 里配一个 PostToolUse Hook,matcher 设为 Edit 和 Write,然后在 Hook 脚本里调用 Prettier。这样 Claude 每次编辑文件后,Prettier 会自动把代码格式化。你不需要在 CLAUDE.md 里写"请用 Prettier 格式化代码",也不需要每次提醒 Claude,格式化这件事就这么悄无声息地发生了。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
          }
        ]
      }
    ]
  }
}

第二个场景是文件保护。有些文件你绝对不希望 Claude 碰,比如 .envpackage-lock.jsonmigrations/ 目录下的迁移文件。你写一个 PreToolUse Hook 脚本,检查目标文件路径是否匹配保护模式,如果匹配就 exit 2(exit code 2 在 Claude Code 的 Hook 系统中表示阻止操作)。Claude 收到阻止信号后会收到一段错误信息,告诉它为什么被阻止,然后它自己会调整策略。

#!/bin/bash
# .claude/hooks/protect-files.sh
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

PROTECTED_PATTERNS=(".env" "package-lock.json" ".git/" "migrations/")

for pattern in "${PROTECTED_PATTERNS[@]}"; do
  if [[ "$FILE_PATH" == *"$pattern"* ]]; then
    echo "Blocked: $FILE_PATH matches protected pattern" >&2
    exit 2
  fi
done

exit 0

第三个场景是上下文恢复。当 Claude 的上下文窗口满了,它会自动做 compaction(压缩),把对话历史替换成结构化摘要。这虽然释放了空间,但可能会丢失一些重要细节。你可以配一个 SessionStart Hook,matcher 设为 compact,在每次压缩后重新注入关键上下文。比如当前 sprint 的重点、正在进行的重构方向、最近几个 commit 的摘要。这样即使对话被压缩了,最重要的信息不会丢。

Hooks 的输入是通过 stdin 传递的 JSON,包含了触发事件的所有上下文信息。你的脚本用 jq 解析这个 JSON,根据内容决定放行还是阻止。输出可以写到 stdout(会被注入到 Claude 的上下文中)或 stderr(错误信息,传递给 Claude 作为反馈)。这个设计非常灵活,你可以做几乎任何自动化逻辑。

而且 Claude Code 还支持 Prompt-based Hooks 和 Agent-based Hooks。前者让一个 Claude 模型来评估是否应该执行某个操作(适合需要判断力的场景),后者直接启动一个 Subagent 来处理 Hook 事件(适合复杂的多步自动化)。这意味着你甚至不需要写 shell 脚本,用自然语言描述你的规则就行。

Skills 是 Harness 的肌肉

如果说 Hooks 解决的是"不让 Claude 做错事",那 Skills 解决的就是"让 Claude 做对事"。

Skills 的本质是可复用的知识包。你在 .claude/skills/ 目录下创建一个目录,里面放一个 SKILL.md 文件,Claude 就会知道这件事。Skill 的触发方式有两种,一种是你手动输入 /skill-name 调用,另一种是 Claude 根据你的对话内容自动判断是否需要加载某个 Skill。

Claude Code 还内置了几个很有用的 Bundle Skills/batch 可以并行处理大规模代码变更,它会先把代码库研究一遍,然后把工作分解成 5 到 30 个独立单元,每个单元在隔离的 Git worktree 里由一个独立的 agent 执行,最后自动开 PR。/simplify 会启动三个并行审查 agent,从代码复用、质量和效率三个角度审查你最近改动的文件,然后汇总发现并自动修复。这些内置 Skills 展示了 Harness 能做到什么程度。

我自己在实践中的体会是,Skill 的描述写得好不好,直接决定了它会不会被正确触发。Claude 是根据 SKILL.md 的 frontmatter 中的 description 字段来决定什么时候加载这个 Skill 的。如果你的描述太模糊,它可能会在不需要的时候加载,浪费上下文;太具体了,又可能在需要的时候匹配不上。

官方建议 description 控制在 250 字符以内,因为超出部分会被截断,Claude 只看到前 250 个字符。而且要"front-load",也就是把最关键的使用场景放在最前面。

另一个容易踩的坑是 Skill 太长。官方建议 SKILL.md 控制在 500 行以内。超过这个长度的内容应该拆到 supporting files 里,比如 reference.md 放详细文档、examples.md 放使用示例。SKILL.md 里只需要放核心指令和指向这些文件的引用。Claude 在需要时会自己去读这些文件,不需要一开始就全部加载。

Skill 还有一个很实用的特性是 context: fork。设置这个字段后,Skill 会在一个隔离的 Subagent 上下文中执行,不会占用你的主会话上下文。对于那些会读取大量文件或者执行多步操作的工作流来说,这个特性非常重要。

Subagents 是 Harness 的分工系统

Subagents 可能是 Claude Code 中最有"工程感"的特性。

每个 Subagent 运行在自己的上下文窗口中,有独立的系统提示、工具权限和模型选择。Claude 遇到匹配的任务时会自动委派给对应的 Subagent,Subagent 独立工作完成后返回摘要。只有摘要进入你的主会话,中间大量的文件读取和代码分析都被隔离在外。

这意味着你可以针对不同类型的任务设计专门化的 Subagent,让每个都成为某个领域的专家。

Claude Code 内置了三个 Subagent。Explore 用 Haiku 模型做快速只读搜索,便宜且延迟低。Plan 在规划模式下做研究,继承主会话的模型。General-purpose 处理需要读写兼备的复杂任务。

但真正有意思的是自定义 Subagent。它的定义就是一个带 YAML frontmatter 的 Markdown 文件。

我最近在实际项目中用了一个安全审查的 Subagent。它的配置很简单,tools 只给了 Read、Grep、Glob、Bash(不允许 Write 和 Edit),model 设为 sonnet(不需要 opus 那么贵),memory 设为 project(让它积累对项目的安全认知)。用了几天后我发现它确实越来越"懂"这个项目了,它会记得上次审查时发现的一些模式,这次会优先检查类似的地方。

memory: project 这个功能让我印象深刻。它会自动在 .claude/agent-memory/security-reviewer/ 目录下维护记忆文件。随着使用次数增加,Subagent 会积累对项目的理解。你可以在 SKILL.md 里写指令让它主动更新记忆,比如"每次审查后,把你发现的新的安全模式写入记忆"。这本质上是一种自动化的知识积累机制。

还有一个我觉得设计得很好的特性是 isolation: worktree。启用后,Subagent 会在一个临时的 Git worktree 里工作。如果它没有做任何修改,worktree 自动清理。如果做了修改,修改会保留在 worktree 里,你可以选择合并还是丢弃。这意味着多个 Subagent 可以同时修改同一个仓库的不同部分,不会互相冲突。

关于模型选择,我建议遵循一个简单的原则。只读研究任务用 Haiku,成本大概是 Sonnet 的十分之一,速度也快得多。需要写代码或做复杂推理的任务用 Sonnet。特别重要、需要最强推理能力的任务才用 Opus。Claude Code 的 Subagent 模型选择非常灵活,你可以精确到每个 Subagent 用什么模型。

maxTurns 字段也非常实用。它限制了 Subagent 的最大执行轮数。我建议所有自定义 Subagent 都设置这个值,比如研究类设 15 到 20,代码修改类设 30。这能防止 Claude 陷入无限循环,也能控制 token 消耗。

Agent SDK 是 Harness 的程序化接口

如果你需要把 Claude Code 的能力嵌入到更大的系统中,Agent SDK 是你的入口。

它提供了 Python 和 TypeScript 两个包,支持流式输出、结构化输出、工具审批回调等完整的程序化控制能力。你不需要手动实现 agentic loop,SDK 已经把 Claude Code 的工具系统、上下文管理和权限控制全部封装好了。

但在大多数工程实践中,你可能不需要完整的 SDK。CLI 的 headless 模式(claude -p)其实已经覆盖了大部分自动化场景。特别是加上了 --bare 参数后,它的行为完全可预测,适合在脚本和 CI/CD 中使用。

--bare 模式跳过所有自动发现(hooks、skills、MCP 服务器、auto memory、CLAUDE.md),只执行你显式传递的指令。你需要什么就通过 CLI 参数传什么。比如你需要加载 CLAUDE.md 就用 --append-system-prompt-file,需要连接 MCP 服务器就用 --mcp-config,需要自定义 Subagent 就用 --agents

claude --bare -p "Review this PR for security issues" \
  --append-system-prompt-file CLAUDE.md \
  --mcp-config .mcp.json \
  --agents '{"security-reviewer": {"description": "Security expert", "model": "sonnet"}}' \
  --allowedTools "Read,Grep,Glob,Bash" \
  --output-format json \
  --json-schema '{"type":"object","properties":{"issues":{"type":"array"},"severity":{"type":"string"}}}'

--json-schema 是一个被低估的参数。它让你可以约束 Claude 的输出格式,直接拿到结构化的 JSON。你可以用 jq 做后续处理,也可以把结果喂给其他程序。这在自动化流水线中非常有用。

GitHub Actions 是 Harness 的持续集成层

GitHub Actions 让 Claude Code 直接融入你的 CI/CD 流程,这可能是对团队协作影响最大的一个能力。

它的用法非常简单。你在 PR 或 Issue 的评论中 @claude,Claude Code Action 就会启动,在 GitHub 的 runner 上分析代码、创建 PR、修复 bug。它会自动读取你仓库中的 CLAUDE.md,遵循你定义的编码规范和项目约定。

最让我觉得有趣的是,Claude Code GitHub Actions 默认用 Sonnet 模型,但你可以在 workflow 文件中通过 claude_args: --model claude-opus-4-6 切换到 Opus。这意味着你可以根据任务的重要性选择不同的模型。日常的代码审查用 Sonnet 就够了,关键的安全审计才用 Opus。

Claude Code Action 的 v1.0 刚 GA 不久,从 Beta 升级需要做一些配置变更。主要的变化是把 mode 参数移除了(现在自动检测是交互模式还是自动化模式),把 direct_prompt 改为 prompt,把各种配置项统一收到 claude_args 里。升级过程不复杂,但需要注意这些 breaking changes。

上下文管理是 Harness 工程的核心难题

不管你的 Harness 设计得多好,如果你不管理上下文窗口,它迟早会出问题。

Claude 的上下文窗口是 200K token。CLAUDE.md 每次请求全量加载,Skill 描述在会话开始时加载(全文按需加载),MCP 工具名在会话开始时加载(Schema 按需加载),Auto Memory 的前 200 行或 25KB 在会话开始时加载。再加上你让 Claude 读的文件、你的对话、Claude 的回复和工具输出,这 200K token 的消耗速度比你想象的快。

官方在 Best Practices 页面说得很直接,"Most best practices are based on one constraint: Claude's context window fills up fast, and performance degrades as it fills."

我总结了一套上下文管理的实践原则。

CLAUDE.md 控制在 150 到 200 行以内。每次 CLAUDE.md 中的指令都会在上下文中占据一份空间,如果太长,Claude 可能会忽略写在后面的规则。对于不需要每次都加载的内容,移到 Skills 中。官方建议你问自己一个问题,"如果去掉这行,Claude 会犯错吗",如果不会,就删掉。

Skill 的 description 要精简,控制在 250 字符以内,把关键场景放在最前面。如果某个 Skill 不需要 Claude 自动触发,设置 disable-model-invocation: true,这样它的描述根本不会进入上下文。

需要大量文件读取的任务,用 Subagent 在隔离的上下文中完成。只有摘要返回主会话。

MCP 服务器的工具定义默认会通过 tool search 延迟加载,只有工具名占用上下文。但如果你有很多 MCP 服务器,工具名加起来也不少。定期用 /mcp 检查哪些服务器在消耗上下文,断开不用的。

养成定期运行 /context 的习惯。这个命令会显示当前上下文的详细使用情况,包括各类内容分别占了多少 token。如果你发现某类内容异常膨胀,就可以针对性地优化。

当上下文快满时,Claude 会自动做 compaction。你可以用 /compact 手动触发,还可以指定压缩时保留哪些信息,比如 /compact focus on the API changes。CLAUDE.md 中也可以加一个"Compact Instructions"段落,告诉 Claude 压缩时应该保留什么。

我搭建的一个实际 Harness

说了这么多理论,分享一个我实际搭建的 Harness 方案,你可以根据自己的项目调整。

项目根目录下的 .claude/settings.json 配置了四个 Hooks。

一个 PostToolUse Hook,matcher 是 Edit 和 Write,每次文件编辑后自动跑 Prettier 格式化。这保证所有 Claude 生成的代码都符合项目的格式规范。

一个 PreToolUse Hook,检查 Claude 是否试图修改 .envpackage-lock.jsonmigrations/ 目录等受保护文件。如果匹配就阻止,Claude 会收到反馈并调整策略。

一个 SessionStart Hook,matcher 是 compact,在每次上下文压缩后重新注入当前 sprint 的重点信息。我让它执行一个脚本,脚本会读取最近 5 个 git commit 的信息和 sprint 看板中的当前任务,拼接后输出到 stdout,Claude 会把这些信息接收到上下文中。

一个 ConfigChange Hook,记录所有配置变更到审计日志中。这对于团队协作场景很有用,你可以追踪谁在什么时候改了什么配置。

项目根目录的 CLAUDE.md 控制在 100 行左右,只包含每次都需要 Claude 知道的内容。代码风格用 ES modules、运行测试用 bun test、提交前必须通过类型检查。这些是高频规则,放在 CLAUDE.md 里是对的。

.claude/skills/ 下放了几个 Skill。api-conventions 包含 API 设计规范(URL 命名、错误格式、分页约定),deploy-checklist 包含部署前的检查清单(只在手动 /deploy-checklist 时触发,所以设了 disable-model-invocation: true),debug-patterns 包含这个项目中常见的问题模式和调试策略。

.claude/agents/ 下放了两个自定义 Subagent。security-reviewer 只读,用 Sonnet 模型,开启了 memory: project,有持久记忆。perf-analyzer 也是只读,用 Haiku 模型(性能分析不需要那么强的推理能力),加了 maxTurns: 15

.github/workflows/claude.yml 配置了 GitHub Actions,响应 PR 评论中的 @claude 提及,做自动代码审查。还有一个定时任务,每天早上 9 点自动运行依赖更新检查。

这套 Harness 搭建起来其实不需要太多时间,大部分配置都是复制粘贴然后微调。但它带来的效果是显著的。Claude 的代码质量更稳定了,因为格式化和规范检查由 Hooks 保证。安全审查更彻底了,因为专门的 Subagent 有持久记忆。团队协作更顺畅了,因为 CLAUDE.md 和 Skills 确保了所有团队成员(包括 Claude)都遵循相同的规范。

Harness Engineering 的思维方式

最后聊聊思维方式。

传统的编程思维是"我写代码,AI 帮我写或者帮我审查"。Harness Engineering 的思维是"我设计一套系统,AI 在这套系统里自主工作"。你不再是写代码的人,而是设计规则和约束的架构师。

这意味着你需要花更多时间在设计上,而不是在具体任务的执行上。你需要思考哪些规则应该是确定性的(用 Hooks),哪些应该是建议性的(用 CLAUDE.md),哪些知识应该按需加载(用 Skills),哪些任务应该隔离执行(用 Subagents)。

这种思维方式的核心是信任与控制。你信任 Claude 的推理能力,但同时通过 Hooks 和权限系统确保它不会越界。你给它足够的上下文和知识让它做好工作,但同时管理好上下文窗口确保它不会因为信息过载而退化。

Claude Code 官方在 How Claude Code Works 页面的最后给了几个建议,我觉得其中最有启发的是"Delegate, don't dictate"。不要试图通过长篇指令控制 Claude 的每一步,而是给它清晰的目标、充分的上下文和合适的工具,然后让它自己决定怎么达成目标。好的 Harness 不是枷锁,而是让 Claude 的能力得以充分发挥的框架。

如果你也在用 Claude Code 做比较复杂的事情,建议从 Hooks 开始搭建你的 Harness。它是投入产出比最高的起点。然后根据需要逐步加入 Skills 和 Subagents。等到你的工作流足够稳定时,再考虑用 Agent SDK 或 GitHub Actions 把它变成可复用的自动化系统。